<commit_before>/*
Dwarf Therapist
Copyright (c) 2009 Trey Stout (chmod)

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#include <QtCore>
#include <QtDebug>

#include "dfinstance.h"
#include "dwarfmodel.h"
#include "dwarf.h"
#include "skill.h"wh
#include "labor.h"
#include "profession.h"
#include "statetableview.h"
#include "defines.h"
#include "dwarftherapist.h"

#include "columntypes.h"
#include "gridview.h"
#include "viewcolumnset.h"
#include "viewcolumn.h"
#include "laborcolumn.h"
#include "skillcolumn.h"
#include "spacercolumn.h"

DwarfModel::DwarfModel(QObject *parent)
	: QStandardItemModel(parent)
	, m_df(0)
	, m_group_by(GB_NOTHING)
	, m_selected_col(-1)
{}

DwarfModel::~DwarfModel() {
	clear_all();
}

void DwarfModel::clear_all() {
	clear_pending();
	foreach(Dwarf *d, m_dwarves) {
		delete d;
	}
	m_dwarves.clear();
	m_grouped_dwarves.clear();
	clear();
}

void DwarfModel::section_right_clicked(int col) {
	if (col == m_selected_col) {
		m_selected_col = -1; // turn off the guides
	} else {
		m_selected_col = col;
	}
	emit dataChanged(index(0, col), index(rowCount()-1, col));
}

void DwarfModel::load_dwarves() {
	// clear id->dwarf map
	foreach(Dwarf *d, m_dwarves) {
		delete d;
	}
	m_dwarves.clear();
	if (rowCount())
		removeRows(0, rowCount());

	m_df->attach();
	foreach(Dwarf *d, m_df->load_dwarves()) {
		m_dwarves[d->id()] = d;
	}
	m_df->detach();
	/*! Let's try to guess the wave a dwarf arrived in based on ID.
	The game appears to assign ids to creates in a serial manner.
	Since at least 1 season seems to pass in between waves there is a
	high likelihood that dwarves arriving in a new wave will be folling
	several other creatures such as groundhogs and the like, thus
	producing a gap in IDs of more than 1
	*/
	int last_id = 0;
	int wave = 0;
	QList<int> ids = m_dwarves.uniqueKeys(); // get all ids
	qSort(ids); // now in order;
	for (int i = 0; i < ids.size(); ++i) {
		int id = ids.at(i);
		if ((id - last_id) > 5)
			wave++;
		last_id = id;
		m_dwarves[id]->set_migration_wave(wave);
	}

}

void DwarfModel::build_rows() {
	// don't need to go delete the dwarf pointers in here, since the earlier foreach should have
	// deleted them
	m_grouped_dwarves.clear();
	clear();

	// populate dwarf maps
	foreach(Dwarf *d, m_dwarves) {
		switch (m_group_by) {
			case GB_NOTHING:
				m_grouped_dwarves[QString::number(d->id())].append(d);
				break;
			case GB_PROFESSION:
				m_grouped_dwarves[d->profession()].append(d);
				break;
			case GB_LEGENDARY:
				{
					int legendary_skills = 0;
					foreach(Skill s, *d->get_skills()) {
						if (s.rating() >= 15)
							legendary_skills++;
					}
					if (legendary_skills)
						m_grouped_dwarves["Legends"].append(d);
					else
						m_grouped_dwarves["Losers"].append(d);
					break;
				}
			case GB_SEX:
				if (d->is_male())
					m_grouped_dwarves["Males"].append(d);
				else
					m_grouped_dwarves["Females"].append(d);
				break;
			case GB_HAPPINESS:
				m_grouped_dwarves[QString::number(d->get_happiness())].append(d);
				break;
			case GB_MIGRATION_WAVE:
				m_grouped_dwarves[QString("Wave %1").arg(d->migration_wave())].append(d);
				break;
		}
	}

	int start_col = 1;
	setHorizontalHeaderItem(0, new QStandardItem);
	emit clear_spacers();
	QSettings *s = DT->user_settings();
	int width = s->value("options/grid/cell_size", DEFAULT_CELL_SIZE).toInt();
	foreach(ViewColumnSet *set, m_gridview->sets()) {
		foreach(ViewColumn *col, set->columns()) {
			QStandardItem *header = new QStandardItem(col->title());
			header->setData(col->bg_color(), Qt::BackgroundColorRole);
			setHorizontalHeaderItem(start_col++, header);
			switch (col->type()) {
				case CT_SPACER:
					{
						SpacerColumn *c = static_cast<SpacerColumn*>(col);
						emit set_index_as_spacer(start_col - 1);
						emit preferred_header_size(start_col - 1, c->width());
					}
					break;
				default:
					emit preferred_header_size(start_col - 1, width);
			}
		}
	}

	//FIXME : jump to prof and jump to pending change
	if (m_group_by == GB_PROFESSION) {
		// sort professions by column labors
		QStringList group_order;
		foreach(ViewColumnSet *set, m_gridview->sets()) {
			foreach(ViewColumn *col, set->columns()) {
				int skill = -1;
				if (col->type() == CT_LABOR) {
					LaborColumn *lc = static_cast<LaborColumn*>(col);
					skill = lc->skill_id();
				} else if (col->type() == CT_SKILL) {
					SkillColumn *sc = static_cast<SkillColumn*>(col);
					skill = sc->skill_id();
				}
				if (skill != -1) {
					group_order << GameDataReader::ptr()->get_skill_name(skill);
				}
			}
		}
		foreach(QString prof_name, group_order) {
			if (m_grouped_dwarves.contains(prof_name)) {
				build_row(prof_name);
			}
		}
		foreach(QString key, m_grouped_dwarves.uniqueKeys()) {
			if (!group_order.contains(key)) {
				build_row(key);
			}
		}
	} else {
		foreach(QString key, m_grouped_dwarves.uniqueKeys()) {
			build_row(key);
		}
	}
}

void DwarfModel::build_row(const QString &key) {
	QIcon icn_f(":img/female.png");
	QIcon icn_m(":img/male.png");
	QStandardItem *root = 0;
	QList<QStandardItem*> root_row;

	if (m_group_by != GB_NOTHING) {
		// we need a root element to hold group members...
		QString title = QString("%1 (%2)").arg(key).arg(m_grouped_dwarves.value(key).size());
		root = new QStandardItem(title);
		root->setData(true, DR_IS_AGGREGATE);
		root->setData(0, DR_RATING);
		root_row << root;
	}

	if (root) { // we have a parent, so we should draw an aggregate row
		foreach(ViewColumnSet *set, m_gridview->sets()) {
			foreach(ViewColumn *col, set->columns()) {
				QStandardItem *item = col->build_aggregate(key, m_grouped_dwarves[key]);
				root_row << item;
			}
		}
	}

	foreach(Dwarf *d, m_grouped_dwarves.value(key)) {
        Profession *p = GameDataReader::ptr()->get_profession(d->raw_profession());
        QString name = d->nice_name();
        if (p && p->is_military())
            name = "[M]" + name;
		QStandardItem *i_name = new QStandardItem(name);

		i_name->setToolTip(d->tooltip_text());
		i_name->setStatusTip(d->nice_name());
		i_name->setData(false, DR_IS_AGGREGATE);
		i_name->setData(0, DR_RATING);
		i_name->setData(d->id(), DR_ID);
        i_name->setData(d->raw_profession(), DR_SORT_VALUE);

		if (d->is_male()) {
			i_name->setIcon(icn_m);
		} else {
			i_name->setIcon(icn_f);
		}

		QList<QStandardItem*> items;
		items << i_name;
		foreach(ViewColumnSet *set, m_gridview->sets()) {
			foreach(ViewColumn *col, set->columns()) {
				QStandardItem *item = col->build_cell(d);
				items << item;
			}
		}

		if (root) {
			root->appendRow(items);
		} else {
			appendRow(items);
		}
		d->m_name_idx = indexFromItem(i_name);
	}
	if (root) {
		appendRow(root_row);
	}
}

void DwarfModel::cell_activated(const QModelIndex &idx) {
    QStandardItem *item = itemFromIndex(idx);
    bool is_aggregate = item->data(DR_IS_AGGREGATE).toBool();
    if (idx.column() == 0) {
        if (is_aggregate)
            return; // no double clicking aggregate names
        int dwarf_id = item->data(DR_ID).toInt(); // TODO: handle no id
        if (!dwarf_id) {
            LOGW << "double clicked what should have been a dwarf name, but the ID wasn't set!";
            return;
        }
        Dwarf *d = get_dwarf_by_id(dwarf_id);
        d->show_details();
        return;
    }

	COLUMN_TYPE type = static_cast<COLUMN_TYPE>(idx.data(DwarfModel::DR_COL_TYPE).toInt());
	if (type != CT_LABOR)
		return;

	Q_ASSERT(item);

	int labor_id = item->data(DR_LABOR_ID).toInt();
	int dwarf_id = item->data(DR_ID).toInt(); // TODO: handle no id
	if (is_aggregate) {
		QModelIndex first_col = idx.sibling(idx.row(), 0);

		// first find out how many are enabled...
		int enabled_count = 0;
		QString group_name = idx.data(DwarfModel::DR_GROUP_NAME).toString();
		int children = rowCount(first_col);

		foreach(Dwarf *d, m_grouped_dwarves.value(group_name)) {
			if (d->is_labor_enabled(labor_id))
				enabled_count++;
		}

		// if none or some are enabled, enable all of them
		bool enabled = (enabled_count < children);
		foreach(Dwarf *d, m_grouped_dwarves.value(group_name)) {
			d->set_labor(labor_id, enabled);
		}

		// tell the view what we touched...
		setData(idx, idx.data(DR_DUMMY).toInt()+1, DR_DUMMY); // redraw the aggregate...
		for(int i = 0; i < rowCount(first_col); ++i) {
			QModelIndex tmp_index = index(i, idx.column(), first_col);
			setData(tmp_index, tmp_index.data(DR_DUMMY).toInt()+1, DR_DUMMY);
		}
	} else {
        if (!dwarf_id) {
            LOGW << "dwarf_id was 0 for cell at" << idx << "!";
        } else {
		    QModelIndex aggregate_col = index(idx.parent().row(), idx.column());
		    if (aggregate_col.isValid())
		    	setData(aggregate_col, aggregate_col.data(DR_DUMMY).toInt()+1, DR_DUMMY); // redraw the aggregate...
		    setData(idx, idx.data(DR_DUMMY).toInt()+1, DR_DUMMY); // redraw the aggregate...
		    m_dwarves[dwarf_id]->toggle_labor(labor_id);
        }
	}
	calculate_pending();
	TRACE << "toggling" << labor_id << "for dwarf:" << dwarf_id;
}

void DwarfModel::set_group_by(int group_by) {
	LOGD << "group_by now set to" << group_by;
	m_group_by = static_cast<GROUP_BY>(group_by);
	if (m_df)
		build_rows();
}

void DwarfModel::calculate_pending() {
	int changes = 0;
	foreach(Dwarf *d, m_dwarves) {
		changes += d->pending_changes();
	}
	emit new_pending_changes(changes);
}

void DwarfModel::clear_pending() {
	foreach(Dwarf *d, m_dwarves) {
		if (d->pending_changes()) {
			d->clear_pending();
		}
	}
	//reset();
	emit new_pending_changes(0);
	emit need_redraw();
}

void DwarfModel::commit_pending() {
	foreach(Dwarf *d, m_dwarves) {
		if (d->pending_changes()) {
			d->commit_pending();
		}
	}
	load_dwarves();
	emit new_pending_changes(0);
	emit need_redraw();
}

QVector<Dwarf*> DwarfModel::get_dirty_dwarves() {
	QVector<Dwarf*> dwarves;
	foreach(Dwarf *d, m_dwarves) {
		if (d->pending_changes())
			dwarves.append(d);
	}
	return dwarves;
}
<commit_msg>stupid typo<commit_after>/*
Dwarf Therapist
Copyright (c) 2009 Trey Stout (chmod)

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#include <QtCore>
#include <QtDebug>

#include "dfinstance.h"
#include "dwarfmodel.h"
#include "dwarf.h"
#include "skill.h"
#include "labor.h"
#include "profession.h"
#include "statetableview.h"
#include "defines.h"
#include "dwarftherapist.h"

#include "columntypes.h"
#include "gridview.h"
#include "viewcolumnset.h"
#include "viewcolumn.h"
#include "laborcolumn.h"
#include "skillcolumn.h"
#include "spacercolumn.h"

DwarfModel::DwarfModel(QObject *parent)
	: QStandardItemModel(parent)
	, m_df(0)
	, m_group_by(GB_NOTHING)
	, m_selected_col(-1)
{}

DwarfModel::~DwarfModel() {
	clear_all();
}

void DwarfModel::clear_all() {
	clear_pending();
	foreach(Dwarf *d, m_dwarves) {
		delete d;
	}
	m_dwarves.clear();
	m_grouped_dwarves.clear();
	clear();
}

void DwarfModel::section_right_clicked(int col) {
	if (col == m_selected_col) {
		m_selected_col = -1; // turn off the guides
	} else {
		m_selected_col = col;
	}
	emit dataChanged(index(0, col), index(rowCount()-1, col));
}

void DwarfModel::load_dwarves() {
	// clear id->dwarf map
	foreach(Dwarf *d, m_dwarves) {
		delete d;
	}
	m_dwarves.clear();
	if (rowCount())
		removeRows(0, rowCount());

	m_df->attach();
	foreach(Dwarf *d, m_df->load_dwarves()) {
		m_dwarves[d->id()] = d;
	}
	m_df->detach();
	/*! Let's try to guess the wave a dwarf arrived in based on ID.
	The game appears to assign ids to creates in a serial manner.
	Since at least 1 season seems to pass in between waves there is a
	high likelihood that dwarves arriving in a new wave will be folling
	several other creatures such as groundhogs and the like, thus
	producing a gap in IDs of more than 1
	*/
	int last_id = 0;
	int wave = 0;
	QList<int> ids = m_dwarves.uniqueKeys(); // get all ids
	qSort(ids); // now in order;
	for (int i = 0; i < ids.size(); ++i) {
		int id = ids.at(i);
		if ((id - last_id) > 5)
			wave++;
		last_id = id;
		m_dwarves[id]->set_migration_wave(wave);
	}

}

void DwarfModel::build_rows() {
	// don't need to go delete the dwarf pointers in here, since the earlier foreach should have
	// deleted them
	m_grouped_dwarves.clear();
	clear();

	// populate dwarf maps
	foreach(Dwarf *d, m_dwarves) {
		switch (m_group_by) {
			case GB_NOTHING:
				m_grouped_dwarves[QString::number(d->id())].append(d);
				break;
			case GB_PROFESSION:
				m_grouped_dwarves[d->profession()].append(d);
				break;
			case GB_LEGENDARY:
				{
					int legendary_skills = 0;
					foreach(Skill s, *d->get_skills()) {
						if (s.rating() >= 15)
							legendary_skills++;
					}
					if (legendary_skills)
						m_grouped_dwarves["Legends"].append(d);
					else
						m_grouped_dwarves["Losers"].append(d);
					break;
				}
			case GB_SEX:
				if (d->is_male())
					m_grouped_dwarves["Males"].append(d);
				else
					m_grouped_dwarves["Females"].append(d);
				break;
			case GB_HAPPINESS:
				m_grouped_dwarves[QString::number(d->get_happiness())].append(d);
				break;
			case GB_MIGRATION_WAVE:
				m_grouped_dwarves[QString("Wave %1").arg(d->migration_wave())].append(d);
				break;
		}
	}

	int start_col = 1;
	setHorizontalHeaderItem(0, new QStandardItem);
	emit clear_spacers();
	QSettings *s = DT->user_settings();
	int width = s->value("options/grid/cell_size", DEFAULT_CELL_SIZE).toInt();
	foreach(ViewColumnSet *set, m_gridview->sets()) {
		foreach(ViewColumn *col, set->columns()) {
			QStandardItem *header = new QStandardItem(col->title());
			header->setData(col->bg_color(), Qt::BackgroundColorRole);
			setHorizontalHeaderItem(start_col++, header);
			switch (col->type()) {
				case CT_SPACER:
					{
						SpacerColumn *c = static_cast<SpacerColumn*>(col);
						emit set_index_as_spacer(start_col - 1);
						emit preferred_header_size(start_col - 1, c->width());
					}
					break;
				default:
					emit preferred_header_size(start_col - 1, width);
			}
		}
	}

	//FIXME : jump to prof and jump to pending change
	if (m_group_by == GB_PROFESSION) {
		// sort professions by column labors
		QStringList group_order;
		foreach(ViewColumnSet *set, m_gridview->sets()) {
			foreach(ViewColumn *col, set->columns()) {
				int skill = -1;
				if (col->type() == CT_LABOR) {
					LaborColumn *lc = static_cast<LaborColumn*>(col);
					skill = lc->skill_id();
				} else if (col->type() == CT_SKILL) {
					SkillColumn *sc = static_cast<SkillColumn*>(col);
					skill = sc->skill_id();
				}
				if (skill != -1) {
					group_order << GameDataReader::ptr()->get_skill_name(skill);
				}
			}
		}
		foreach(QString prof_name, group_order) {
			if (m_grouped_dwarves.contains(prof_name)) {
				build_row(prof_name);
			}
		}
		foreach(QString key, m_grouped_dwarves.uniqueKeys()) {
			if (!group_order.contains(key)) {
				build_row(key);
			}
		}
	} else {
		foreach(QString key, m_grouped_dwarves.uniqueKeys()) {
			build_row(key);
		}
	}
}

void DwarfModel::build_row(const QString &key) {
	QIcon icn_f(":img/female.png");
	QIcon icn_m(":img/male.png");
	QStandardItem *root = 0;
	QList<QStandardItem*> root_row;

	if (m_group_by != GB_NOTHING) {
		// we need a root element to hold group members...
		QString title = QString("%1 (%2)").arg(key).arg(m_grouped_dwarves.value(key).size());
		root = new QStandardItem(title);
		root->setData(true, DR_IS_AGGREGATE);
		root->setData(0, DR_RATING);
		root_row << root;
	}

	if (root) { // we have a parent, so we should draw an aggregate row
		foreach(ViewColumnSet *set, m_gridview->sets()) {
			foreach(ViewColumn *col, set->columns()) {
				QStandardItem *item = col->build_aggregate(key, m_grouped_dwarves[key]);
				root_row << item;
			}
		}
	}

	foreach(Dwarf *d, m_grouped_dwarves.value(key)) {
        Profession *p = GameDataReader::ptr()->get_profession(d->raw_profession());
        QString name = d->nice_name();
        if (p && p->is_military())
            name = "[M]" + name;
		QStandardItem *i_name = new QStandardItem(name);

		i_name->setToolTip(d->tooltip_text());
		i_name->setStatusTip(d->nice_name());
		i_name->setData(false, DR_IS_AGGREGATE);
		i_name->setData(0, DR_RATING);
		i_name->setData(d->id(), DR_ID);
        i_name->setData(d->raw_profession(), DR_SORT_VALUE);

		if (d->is_male()) {
			i_name->setIcon(icn_m);
		} else {
			i_name->setIcon(icn_f);
		}

		QList<QStandardItem*> items;
		items << i_name;
		foreach(ViewColumnSet *set, m_gridview->sets()) {
			foreach(ViewColumn *col, set->columns()) {
				QStandardItem *item = col->build_cell(d);
				items << item;
			}
		}

		if (root) {
			root->appendRow(items);
		} else {
			appendRow(items);
		}
		d->m_name_idx = indexFromItem(i_name);
	}
	if (root) {
		appendRow(root_row);
	}
}

void DwarfModel::cell_activated(const QModelIndex &idx) {
    QStandardItem *item = itemFromIndex(idx);
    bool is_aggregate = item->data(DR_IS_AGGREGATE).toBool();
    if (idx.column() == 0) {
        if (is_aggregate)
            return; // no double clicking aggregate names
        int dwarf_id = item->data(DR_ID).toInt(); // TODO: handle no id
        if (!dwarf_id) {
            LOGW << "double clicked what should have been a dwarf name, but the ID wasn't set!";
            return;
        }
        Dwarf *d = get_dwarf_by_id(dwarf_id);
        d->show_details();
        return;
    }

	COLUMN_TYPE type = static_cast<COLUMN_TYPE>(idx.data(DwarfModel::DR_COL_TYPE).toInt());
	if (type != CT_LABOR)
		return;

	Q_ASSERT(item);

	int labor_id = item->data(DR_LABOR_ID).toInt();
	int dwarf_id = item->data(DR_ID).toInt(); // TODO: handle no id
	if (is_aggregate) {
		QModelIndex first_col = idx.sibling(idx.row(), 0);

		// first find out how many are enabled...
		int enabled_count = 0;
		QString group_name = idx.data(DwarfModel::DR_GROUP_NAME).toString();
		int children = rowCount(first_col);

		foreach(Dwarf *d, m_grouped_dwarves.value(group_name)) {
			if (d->is_labor_enabled(labor_id))
				enabled_count++;
		}

		// if none or some are enabled, enable all of them
		bool enabled = (enabled_count < children);
		foreach(Dwarf *d, m_grouped_dwarves.value(group_name)) {
			d->set_labor(labor_id, enabled);
		}

		// tell the view what we touched...
		setData(idx, idx.data(DR_DUMMY).toInt()+1, DR_DUMMY); // redraw the aggregate...
		for(int i = 0; i < rowCount(first_col); ++i) {
			QModelIndex tmp_index = index(i, idx.column(), first_col);
			setData(tmp_index, tmp_index.data(DR_DUMMY).toInt()+1, DR_DUMMY);
		}
	} else {
        if (!dwarf_id) {
            LOGW << "dwarf_id was 0 for cell at" << idx << "!";
        } else {
		    QModelIndex aggregate_col = index(idx.parent().row(), idx.column());
		    if (aggregate_col.isValid())
		    	setData(aggregate_col, aggregate_col.data(DR_DUMMY).toInt()+1, DR_DUMMY); // redraw the aggregate...
		    setData(idx, idx.data(DR_DUMMY).toInt()+1, DR_DUMMY); // redraw the aggregate...
		    m_dwarves[dwarf_id]->toggle_labor(labor_id);
        }
	}
	calculate_pending();
	TRACE << "toggling" << labor_id << "for dwarf:" << dwarf_id;
}

void DwarfModel::set_group_by(int group_by) {
	LOGD << "group_by now set to" << group_by;
	m_group_by = static_cast<GROUP_BY>(group_by);
	if (m_df)
		build_rows();
}

void DwarfModel::calculate_pending() {
	int changes = 0;
	foreach(Dwarf *d, m_dwarves) {
		changes += d->pending_changes();
	}
	emit new_pending_changes(changes);
}

void DwarfModel::clear_pending() {
	foreach(Dwarf *d, m_dwarves) {
		if (d->pending_changes()) {
			d->clear_pending();
		}
	}
	//reset();
	emit new_pending_changes(0);
	emit need_redraw();
}

void DwarfModel::commit_pending() {
	foreach(Dwarf *d, m_dwarves) {
		if (d->pending_changes()) {
			d->commit_pending();
		}
	}
	load_dwarves();
	emit new_pending_changes(0);
	emit need_redraw();
}

QVector<Dwarf*> DwarfModel::get_dirty_dwarves() {
	QVector<Dwarf*> dwarves;
	foreach(Dwarf *d, m_dwarves) {
		if (d->pending_changes())
			dwarves.append(d);
	}
	return dwarves;
}
<|endoftext|>


<commit_before>/*
 *  Phusion Passenger - https://www.phusionpassenger.com/
 *  Copyright (c) 2011-2017 Phusion Holding B.V.
 *
 *  "Passenger", "Phusion Passenger" and "Union Station" are registered
 *  trademarks of Phusion Holding B.V.
 *
 *  Permission is hereby granted, free of charge, to any person obtaining a copy
 *  of this software and associated documentation files (the "Software"), to deal
 *  in the Software without restriction, including without limitation the rights
 *  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 *  copies of the Software, and to permit persons to whom the Software is
 *  furnished to do so, subject to the following conditions:
 *
 *  The above copyright notice and this permission notice shall be included in
 *  all copies or substantial portions of the Software.
 *
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 *  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 *  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 *  THE SOFTWARE.
 */
#include <Core/ApplicationPool/Group.h>
#include <cassert>
#include <modp_b64.h>

/*************************************************************************
 *
 * Session management functions for ApplicationPool2::Group
 *
 *************************************************************************/

namespace Passenger {
namespace ApplicationPool2 {

using namespace std;
using namespace boost;

/****************************
 *
 * Public methods
 *
 ****************************/

unsigned int
Group::getProcessCount() const {
	return enabledCount + disablingCount + disabledCount;
}

/**
 * Returns whether the lower bound of the group-specific process limits
 * have been satisfied. Note that even if the result is false, the pool limits
 * may not allow spawning, so you should check `pool->atFullCapacity()` too.
 */
bool
Group::processLowerLimitsSatisfied() const {
	return capacityUsed() >= options.minProcesses;
}

/**
 * Returns whether the upper bound of the group-specific process limits have
 * been reached, or surpassed. Does not check whether pool limits have been
 * reached. Use `pool->atFullCapacity()` to check for that.
 */
bool
Group::processUpperLimitsReached() const {
	return options.maxProcesses != 0 && capacityUsed() >= options.maxProcesses;
}

/**
 * Returns whether all enabled processes are totally busy. If so, another
 * process should be spawned, if allowed by the process limits.
 * Returns false if there are no enabled processes.
 */
bool
Group::allEnabledProcessesAreTotallyBusy() const {
	return nEnabledProcessesTotallyBusy == enabledCount && enabledCount > 0;
}

/**
 * Returns the number of processes in this group that should be part of the
 * ApplicationPool process limits calculations.
 */
unsigned int
Group::capacityUsed() const {
	return enabledCount + disablingCount + disabledCount + processesBeingSpawned;
}

/**
 * Checks whether this group is waiting for capacity on the pool to
 * become available before it can continue processing requests.
 */
bool
Group::isWaitingForCapacity() const {
	return enabledProcesses.empty()
		&& processesBeingSpawned == 0
		&& !m_restarting
		&& !getWaitlist.empty();
}

bool
Group::garbageCollectable(unsigned long long now) const {
	/* if (now == 0) {
		now = SystemTime::getUsec();
	}
	return busyness() == 0
		&& getWaitlist.empty()
		&& disabledProcesses.empty()
		&& options.getMaxPreloaderIdleTime() != 0
		&& now - spawner->lastUsed() >
			(unsigned long long) options.getMaxPreloaderIdleTime() * 1000000; */
	return false;
}

void
Group::inspectXml(std::ostream &stream, bool includeSecrets) const {
	ProcessList::const_iterator it;

	stream << "<name>" << escapeForXml(info.name) << "</name>";
	stream << "<component_name>" << escapeForXml(info.name) << "</component_name>";
	stream << "<app_root>" << escapeForXml(options.appRoot) << "</app_root>";
	stream << "<app_type>" << escapeForXml(options.appType) << "</app_type>";
	stream << "<environment>" << escapeForXml(options.environment) << "</environment>";
	stream << "<uuid>" << toString(uuid) << "</uuid>";
	stream << "<enabled_process_count>" << enabledCount << "</enabled_process_count>";
	stream << "<disabling_process_count>" << disablingCount << "</disabling_process_count>";
	stream << "<disabled_process_count>" << disabledCount << "</disabled_process_count>";
	stream << "<capacity_used>" << capacityUsed() << "</capacity_used>";
	stream << "<get_wait_list_size>" << getWaitlist.size() << "</get_wait_list_size>";
	stream << "<disable_wait_list_size>" << disableWaitlist.size() << "</disable_wait_list_size>";
	stream << "<processes_being_spawned>" << processesBeingSpawned << "</processes_being_spawned>";
	if (m_spawning) {
		stream << "<spawning/>";
	}
	if (restarting()) {
		stream << "<restarting/>";
	}
	if (includeSecrets) {
		stream << "<secret>" << escapeForXml(getApiKey().toStaticString()) << "</secret>";
		stream << "<api_key>" << escapeForXml(getApiKey().toStaticString()) << "</api_key>";
	}
	LifeStatus lifeStatus = (LifeStatus) this->lifeStatus.load(boost::memory_order_relaxed);
	switch (lifeStatus) {
	case ALIVE:
		stream << "<life_status>ALIVE</life_status>";
		break;
	case SHUTTING_DOWN:
		stream << "<life_status>SHUTTING_DOWN</life_status>";
		break;
	case SHUT_DOWN:
		stream << "<life_status>SHUT_DOWN</life_status>";
		break;
	default:
		P_BUG("Unknown 'lifeStatus' state " << lifeStatus);
	}

	SpawningKit::UserSwitchingInfo usInfo(SpawningKit::prepareUserSwitching(options));
	stream << "<user>" << escapeForXml(usInfo.username) << "</user>";
	stream << "<uid>" << usInfo.uid << "</uid>";
	stream << "<group>" << escapeForXml(usInfo.groupname) << "</group>";
	stream << "<gid>" << usInfo.gid << "</gid>";

	stream << "<options>";
	options.toXml(stream, getResourceLocator());
	stream << "</options>";

	stream << "<processes>";

	for (it = enabledProcesses.begin(); it != enabledProcesses.end(); it++) {
		stream << "<process>";
		(*it)->inspectXml(stream, includeSecrets);
		stream << "</process>";
	}
	for (it = disablingProcesses.begin(); it != disablingProcesses.end(); it++) {
		stream << "<process>";
		(*it)->inspectXml(stream, includeSecrets);
		stream << "</process>";
	}
	for (it = disabledProcesses.begin(); it != disabledProcesses.end(); it++) {
		stream << "<process>";
		(*it)->inspectXml(stream, includeSecrets);
		stream << "</process>";
	}
	for (it = detachedProcesses.begin(); it != detachedProcesses.end(); it++) {
		stream << "<process>";
		(*it)->inspectXml(stream, includeSecrets);
		stream << "</process>";
	}

	stream << "</processes>";
}

void
Group::inspectPropertiesInAdminPanelFormat(Json::Value &result) const {
	result["path"] = absolutizePath(options.appRoot);
	result["startup_file"] = absolutizePath(options.getStartupFile(), absolutizePath(options.appRoot));
	result["start_command"] = options.getStartCommand(getResourceLocator());

	if (options.appType == "rack") {
		result["type"] = "ruby";
	} else if (options.appType == "wsgi") {
		result["type"] = "python";
	} else if (options.appType == "node") {
		result["type"] = "nodejs";
	} else if (options.appType == "meteor") {
		result["type"] = "meteor";
	} else {
		result["type"] = "generic";
	}

	SpawningKit::UserSwitchingInfo usInfo(SpawningKit::prepareUserSwitching(options));
	result["user"]["username"] = usInfo.username;
	result["user"]["uid"] = (Json::Int) usInfo.uid;
	result["group"]["groupname"] = usInfo.groupname;
	result["group"]["gid"] = (Json::Int) usInfo.gid;

	/******************/
}

void
Group::inspectConfigInAdminPanelFormat(Json::Value &result) const {
	#define VAL Pool::makeSingleValueJsonConfigFormat
	#define SVAL Pool::makeSingleStrValueJsonConfigFormat
	#define NON_EMPTY_SVAL Pool::makeSingleNonEmptyStrValueJsonConfigFormat

	result["app_root"] = NON_EMPTY_SVAL(absolutizePath(options.appRoot));
	result["app_group_name"] = NON_EMPTY_SVAL(info.name);
	result["default_user"] = NON_EMPTY_SVAL(options.defaultUser);
	result["default_group"] = NON_EMPTY_SVAL(options.defaultGroup);
	result["enabled"] = VAL(true, false);

	result["type"] = NON_EMPTY_SVAL(options.appType);
	result["startup_file"] = NON_EMPTY_SVAL(options.startupFile);
	result["start_command"] = NON_EMPTY_SVAL(replaceAll(options.startCommand,
		P_STATIC_STRING("\t"), P_STATIC_STRING(" ")));
	result["ruby"] = SVAL(options.ruby, DEFAULT_RUBY);
	result["python"] = SVAL(options.python, DEFAULT_PYTHON);
	result["nodejs"] = SVAL(options.nodejs, DEFAULT_NODEJS);
	result["meteor_app_settings"] = NON_EMPTY_SVAL(options.meteorAppSettings);
	result["min_processes"] = VAL(options.minProcesses, 1u);
	result["max_processes"] = VAL(options.maxProcesses, 0u);
	result["environment"] = SVAL(options.environment); // TODO: default value depends on integration mode
	result["spawn_method"] = SVAL(options.spawnMethod, DEFAULT_SPAWN_METHOD);
	result["start_timeout"] = VAL(options.startTimeout / 1000.0, DEFAULT_START_TIMEOUT / 1000.0);
	result["max_preloader_idle_time"] = VAL((Json::UInt) options.maxPreloaderIdleTime,
		(Json::UInt) DEFAULT_MAX_PRELOADER_IDLE_TIME);
	result["max_out_of_band_work_instances"] = VAL(options.maxOutOfBandWorkInstances,
		(Json::UInt) 1);
	result["base_uri"] = SVAL(options.baseURI, P_STATIC_STRING("/"));
	result["user"] = SVAL(options.user, options.defaultUser);
	result["group"] = SVAL(options.group, options.defaultGroup);
	result["user_switching"] = VAL(options.userSwitching); // TODO: default value depends on integration mode and euid
	result["file_descriptor_ulimit"] = VAL(options.fileDescriptorUlimit, 0u);
	result["load_shell_envvars"] = VAL(options.loadShellEnvvars); // TODO: default value depends on integration mode
	result["max_request_queue_size"] = VAL(options.maxRequestQueueSize,
		(Json::UInt) DEFAULT_MAX_REQUEST_QUEUE_SIZE);
	result["max_requests"] = VAL((Json::UInt) options.maxRequests, 0u);
	result["abort_websockets_on_process_shutdown"] = VAL(options.abortWebsocketsOnProcessShutdown);
	result["force_max_concurrent_requests_per_process"] = VAL(options.forceMaxConcurrentRequestsPerProcess, -1);
	result["restart_dir"] = NON_EMPTY_SVAL(options.restartDir);

	if (!options.environmentVariables.empty()) {
		DynamicBuffer envvarsData(options.environmentVariables.size() * 3 / 4);
		size_t envvarsDataSize = modp_b64_decode(envvarsData.data,
			options.environmentVariables.data(), options.environmentVariables.size());
		if (envvarsDataSize == (size_t) -1) {
			P_WARN("Unable to decode environment variable data");
		} else {
			Json::Value envvars(Json::objectValue);
			vector<string> envvarsAry;
			unsigned int i;

			split(StaticString(envvarsData.data, envvarsDataSize), '\0', envvarsAry);
			if (!envvarsAry.empty() && envvarsAry.back().empty()) {
				envvarsAry.pop_back();
			}
			assert(envvars.size() % 2 == 0);

			for (i = 0; i < envvarsAry.size(); i += 2) {
				envvars[envvarsAry[i]] = envvarsAry[i + 1];
			}

			result["environment_variables"] = VAL(envvars, Json::objectValue);
		}
	} else {
		result["environment_variables"] = VAL(Json::objectValue, Json::objectValue);
	}

	// Missing: sticky_sessions, sticky_session_cookie_name, friendly_error_pages

	/******************/

	#undef VAL
	#undef SVAL
	#undef NON_EMPTY_SVAL
}

} // namespace ApplicationPool2
} // namespace Passenger
<commit_msg>AdminPanelConnector: send lve_min_uid<commit_after>/*
 *  Phusion Passenger - https://www.phusionpassenger.com/
 *  Copyright (c) 2011-2017 Phusion Holding B.V.
 *
 *  "Passenger", "Phusion Passenger" and "Union Station" are registered
 *  trademarks of Phusion Holding B.V.
 *
 *  Permission is hereby granted, free of charge, to any person obtaining a copy
 *  of this software and associated documentation files (the "Software"), to deal
 *  in the Software without restriction, including without limitation the rights
 *  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 *  copies of the Software, and to permit persons to whom the Software is
 *  furnished to do so, subject to the following conditions:
 *
 *  The above copyright notice and this permission notice shall be included in
 *  all copies or substantial portions of the Software.
 *
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 *  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 *  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 *  THE SOFTWARE.
 */
#include <Core/ApplicationPool/Group.h>
#include <cassert>
#include <modp_b64.h>

/*************************************************************************
 *
 * Session management functions for ApplicationPool2::Group
 *
 *************************************************************************/

namespace Passenger {
namespace ApplicationPool2 {

using namespace std;
using namespace boost;

/****************************
 *
 * Public methods
 *
 ****************************/

unsigned int
Group::getProcessCount() const {
	return enabledCount + disablingCount + disabledCount;
}

/**
 * Returns whether the lower bound of the group-specific process limits
 * have been satisfied. Note that even if the result is false, the pool limits
 * may not allow spawning, so you should check `pool->atFullCapacity()` too.
 */
bool
Group::processLowerLimitsSatisfied() const {
	return capacityUsed() >= options.minProcesses;
}

/**
 * Returns whether the upper bound of the group-specific process limits have
 * been reached, or surpassed. Does not check whether pool limits have been
 * reached. Use `pool->atFullCapacity()` to check for that.
 */
bool
Group::processUpperLimitsReached() const {
	return options.maxProcesses != 0 && capacityUsed() >= options.maxProcesses;
}

/**
 * Returns whether all enabled processes are totally busy. If so, another
 * process should be spawned, if allowed by the process limits.
 * Returns false if there are no enabled processes.
 */
bool
Group::allEnabledProcessesAreTotallyBusy() const {
	return nEnabledProcessesTotallyBusy == enabledCount && enabledCount > 0;
}

/**
 * Returns the number of processes in this group that should be part of the
 * ApplicationPool process limits calculations.
 */
unsigned int
Group::capacityUsed() const {
	return enabledCount + disablingCount + disabledCount + processesBeingSpawned;
}

/**
 * Checks whether this group is waiting for capacity on the pool to
 * become available before it can continue processing requests.
 */
bool
Group::isWaitingForCapacity() const {
	return enabledProcesses.empty()
		&& processesBeingSpawned == 0
		&& !m_restarting
		&& !getWaitlist.empty();
}

bool
Group::garbageCollectable(unsigned long long now) const {
	/* if (now == 0) {
		now = SystemTime::getUsec();
	}
	return busyness() == 0
		&& getWaitlist.empty()
		&& disabledProcesses.empty()
		&& options.getMaxPreloaderIdleTime() != 0
		&& now - spawner->lastUsed() >
			(unsigned long long) options.getMaxPreloaderIdleTime() * 1000000; */
	return false;
}

void
Group::inspectXml(std::ostream &stream, bool includeSecrets) const {
	ProcessList::const_iterator it;

	stream << "<name>" << escapeForXml(info.name) << "</name>";
	stream << "<component_name>" << escapeForXml(info.name) << "</component_name>";
	stream << "<app_root>" << escapeForXml(options.appRoot) << "</app_root>";
	stream << "<app_type>" << escapeForXml(options.appType) << "</app_type>";
	stream << "<environment>" << escapeForXml(options.environment) << "</environment>";
	stream << "<uuid>" << toString(uuid) << "</uuid>";
	stream << "<enabled_process_count>" << enabledCount << "</enabled_process_count>";
	stream << "<disabling_process_count>" << disablingCount << "</disabling_process_count>";
	stream << "<disabled_process_count>" << disabledCount << "</disabled_process_count>";
	stream << "<capacity_used>" << capacityUsed() << "</capacity_used>";
	stream << "<get_wait_list_size>" << getWaitlist.size() << "</get_wait_list_size>";
	stream << "<disable_wait_list_size>" << disableWaitlist.size() << "</disable_wait_list_size>";
	stream << "<processes_being_spawned>" << processesBeingSpawned << "</processes_being_spawned>";
	if (m_spawning) {
		stream << "<spawning/>";
	}
	if (restarting()) {
		stream << "<restarting/>";
	}
	if (includeSecrets) {
		stream << "<secret>" << escapeForXml(getApiKey().toStaticString()) << "</secret>";
		stream << "<api_key>" << escapeForXml(getApiKey().toStaticString()) << "</api_key>";
	}
	LifeStatus lifeStatus = (LifeStatus) this->lifeStatus.load(boost::memory_order_relaxed);
	switch (lifeStatus) {
	case ALIVE:
		stream << "<life_status>ALIVE</life_status>";
		break;
	case SHUTTING_DOWN:
		stream << "<life_status>SHUTTING_DOWN</life_status>";
		break;
	case SHUT_DOWN:
		stream << "<life_status>SHUT_DOWN</life_status>";
		break;
	default:
		P_BUG("Unknown 'lifeStatus' state " << lifeStatus);
	}

	SpawningKit::UserSwitchingInfo usInfo(SpawningKit::prepareUserSwitching(options));
	stream << "<user>" << escapeForXml(usInfo.username) << "</user>";
	stream << "<uid>" << usInfo.uid << "</uid>";
	stream << "<group>" << escapeForXml(usInfo.groupname) << "</group>";
	stream << "<gid>" << usInfo.gid << "</gid>";

	stream << "<options>";
	options.toXml(stream, getResourceLocator());
	stream << "</options>";

	stream << "<processes>";

	for (it = enabledProcesses.begin(); it != enabledProcesses.end(); it++) {
		stream << "<process>";
		(*it)->inspectXml(stream, includeSecrets);
		stream << "</process>";
	}
	for (it = disablingProcesses.begin(); it != disablingProcesses.end(); it++) {
		stream << "<process>";
		(*it)->inspectXml(stream, includeSecrets);
		stream << "</process>";
	}
	for (it = disabledProcesses.begin(); it != disabledProcesses.end(); it++) {
		stream << "<process>";
		(*it)->inspectXml(stream, includeSecrets);
		stream << "</process>";
	}
	for (it = detachedProcesses.begin(); it != detachedProcesses.end(); it++) {
		stream << "<process>";
		(*it)->inspectXml(stream, includeSecrets);
		stream << "</process>";
	}

	stream << "</processes>";
}

void
Group::inspectPropertiesInAdminPanelFormat(Json::Value &result) const {
	result["path"] = absolutizePath(options.appRoot);
	result["startup_file"] = absolutizePath(options.getStartupFile(), absolutizePath(options.appRoot));
	result["start_command"] = options.getStartCommand(getResourceLocator());

	if (options.appType == "rack") {
		result["type"] = "ruby";
	} else if (options.appType == "wsgi") {
		result["type"] = "python";
	} else if (options.appType == "node") {
		result["type"] = "nodejs";
	} else if (options.appType == "meteor") {
		result["type"] = "meteor";
	} else {
		result["type"] = "generic";
	}

	SpawningKit::UserSwitchingInfo usInfo(SpawningKit::prepareUserSwitching(options));
	result["user"]["username"] = usInfo.username;
	result["user"]["uid"] = (Json::Int) usInfo.uid;
	result["group"]["groupname"] = usInfo.groupname;
	result["group"]["gid"] = (Json::Int) usInfo.gid;

	/******************/
}

void
Group::inspectConfigInAdminPanelFormat(Json::Value &result) const {
	#define VAL Pool::makeSingleValueJsonConfigFormat
	#define SVAL Pool::makeSingleStrValueJsonConfigFormat
	#define NON_EMPTY_SVAL Pool::makeSingleNonEmptyStrValueJsonConfigFormat

	result["app_root"] = NON_EMPTY_SVAL(absolutizePath(options.appRoot));
	result["app_group_name"] = NON_EMPTY_SVAL(info.name);
	result["default_user"] = NON_EMPTY_SVAL(options.defaultUser);
	result["default_group"] = NON_EMPTY_SVAL(options.defaultGroup);
	result["enabled"] = VAL(true, false);
	result["lve_min_uid"] = VAL(options.lveMinUid, DEFAULT_LVE_MIN_UID);

	result["type"] = NON_EMPTY_SVAL(options.appType);
	result["startup_file"] = NON_EMPTY_SVAL(options.startupFile);
	result["start_command"] = NON_EMPTY_SVAL(replaceAll(options.startCommand,
		P_STATIC_STRING("\t"), P_STATIC_STRING(" ")));
	result["ruby"] = SVAL(options.ruby, DEFAULT_RUBY);
	result["python"] = SVAL(options.python, DEFAULT_PYTHON);
	result["nodejs"] = SVAL(options.nodejs, DEFAULT_NODEJS);
	result["meteor_app_settings"] = NON_EMPTY_SVAL(options.meteorAppSettings);
	result["min_processes"] = VAL(options.minProcesses, 1u);
	result["max_processes"] = VAL(options.maxProcesses, 0u);
	result["environment"] = SVAL(options.environment); // TODO: default value depends on integration mode
	result["spawn_method"] = SVAL(options.spawnMethod, DEFAULT_SPAWN_METHOD);
	result["start_timeout"] = VAL(options.startTimeout / 1000.0, DEFAULT_START_TIMEOUT / 1000.0);
	result["max_preloader_idle_time"] = VAL((Json::UInt) options.maxPreloaderIdleTime,
		(Json::UInt) DEFAULT_MAX_PRELOADER_IDLE_TIME);
	result["max_out_of_band_work_instances"] = VAL(options.maxOutOfBandWorkInstances,
		(Json::UInt) 1);
	result["base_uri"] = SVAL(options.baseURI, P_STATIC_STRING("/"));
	result["user"] = SVAL(options.user, options.defaultUser);
	result["group"] = SVAL(options.group, options.defaultGroup);
	result["user_switching"] = VAL(options.userSwitching); // TODO: default value depends on integration mode and euid
	result["file_descriptor_ulimit"] = VAL(options.fileDescriptorUlimit, 0u);
	result["load_shell_envvars"] = VAL(options.loadShellEnvvars); // TODO: default value depends on integration mode
	result["max_request_queue_size"] = VAL(options.maxRequestQueueSize,
		(Json::UInt) DEFAULT_MAX_REQUEST_QUEUE_SIZE);
	result["max_requests"] = VAL((Json::UInt) options.maxRequests, 0u);
	result["abort_websockets_on_process_shutdown"] = VAL(options.abortWebsocketsOnProcessShutdown);
	result["force_max_concurrent_requests_per_process"] = VAL(options.forceMaxConcurrentRequestsPerProcess, -1);
	result["restart_dir"] = NON_EMPTY_SVAL(options.restartDir);

	if (!options.environmentVariables.empty()) {
		DynamicBuffer envvarsData(options.environmentVariables.size() * 3 / 4);
		size_t envvarsDataSize = modp_b64_decode(envvarsData.data,
			options.environmentVariables.data(), options.environmentVariables.size());
		if (envvarsDataSize == (size_t) -1) {
			P_WARN("Unable to decode environment variable data");
		} else {
			Json::Value envvars(Json::objectValue);
			vector<string> envvarsAry;
			unsigned int i;

			split(StaticString(envvarsData.data, envvarsDataSize), '\0', envvarsAry);
			if (!envvarsAry.empty() && envvarsAry.back().empty()) {
				envvarsAry.pop_back();
			}
			assert(envvars.size() % 2 == 0);

			for (i = 0; i < envvarsAry.size(); i += 2) {
				envvars[envvarsAry[i]] = envvarsAry[i + 1];
			}

			result["environment_variables"] = VAL(envvars, Json::objectValue);
		}
	} else {
		result["environment_variables"] = VAL(Json::objectValue, Json::objectValue);
	}

	// Missing: sticky_sessions, sticky_session_cookie_name, friendly_error_pages

	/******************/

	#undef VAL
	#undef SVAL
	#undef NON_EMPTY_SVAL
}

} // namespace ApplicationPool2
} // namespace Passenger
<|endoftext|>


<commit_before>#pragma GCC optimize ("-O3")

#include "libgame.h"
#include "sprite.h"
#include "binary.h"
#include <stdlib.h>

/* Встроенные цвета:
 *
 *  BLACK - Чёрный
 *  BLUE - Синий
 *  GREEN - Зелёный
 *  RED - Красный
 *  CYAN - Циановый
 *  PURPLE - Фиолетовый
 *  BROWN - Коричневый
 *  WHITE - Белый
 *
 *  Для использования 64-х цветной палитры, укажите в game.ino COLOR_6BIT = 1
 *
 * */

/* Кнопки:
 *
 * НА КОРПУСЕ:
 * BUTTON_SW, BUTTON_NW, BUTTON_SE, BUTTON_NE
 *
 * NW              NE
 *  SW            SE
 *
 *
 * НА ДЖОЙСТИКЕ:
 * BUTTON_UP, BUTTON_LEFT, BUTTON_RIGHT, BUTTON_DOWN, BUTTON_SELECT, BUTTON_START, BUTTON_A, BUTTON_B
 *
 *      UP
 *  LEFT+RIGHT     SELECT  START      B  A
 *     DOWN
 *
 * */

/* Спрайты
 *
 * максимальная высота - 16 пикселей

 определение спрайта

   x     x
    x   x
     x x
  xxxxxxxxx
 xxxxxxxxxxx
xxx  xxx  xxx
 xxxxxxxxxxx
  xxxxxxxxx
    x x x
   x     x

 --------------------------------
 */
const uint8_t YourSprite_lines[] PROGMEM = {
    B11110000,
	B11110000,
	B11110000,
	B11110000,
	B11110000,
	B11110000,
	B11110000,
	B11110000,
	B11110000,
	B11110000,
	B11110000,
	B11110000
};

const game_sprite YourSprite PROGMEM = {
    // ШИРИНА, ВЫСОТА, КОЛИЧЕСТВО БАЙТ НА СТРОКУ, ДАННЫЕ
    3, 12, 1, YourSprite_lines
};
const uint8_t BallData[] PROGMEM = {
    B01100000, //   xxxx
    B11110000, // xxxxxxxx
    B11110000, //  xxxxxxxx
    B01100000  //    xxxx
};

const game_sprite Ball PROGMEM = {
    // ШИРИНА, ВЫСОТА, КОЛИЧЕСТВО БАЙТ НА СТРОКУ, ДАННЫЕ
     4, 4, 1, BallData
};

/* Функции отрисовки
 *
 * game_draw_pixel(x, y, color) - Красит точку (x, y) в цвет color
 * game_draw_char(char, x, y, color) - Выводит символ char с левым верхним углом в точке (x, y) с цветом color
 * game_draw_text(string, x, y, color) - Выводит строку string с левым верхним углом в точке (x, y) с цветом color
 * game_draw_sprite(sprite, x, y, color) - Выводит спрайт sprite с левым верхним углом в точке (x, y) с цветом color
 *
 * */

/* Функции ввода
 *
 * game_is_button_pressed(button) - Нажата ли кнопка? Например: game_is_button_pressed(BUTTON_START)
 * game_is_any_button_pressed(mask) - Нажата ли хотя бы одна кнопка? Например: game_is_any_button_pressed(BITMASK(BUTTON_SW) | BITMASK(BUTTON_DOWN))
 *
 * */

struct BreakOutData
{
	int Board1X,Board2X, flag, k, ballX, ballY,speedy,speedx;
	bool ft;
    /* Объявляйте ваши переменные здесь */
    /* Чтобы потом обращаться к ним, пишите data->ПЕРЕМЕННАЯ */
};
static BreakOutData* data; /* Эта переменная - указатель на структуру, которая содержит ваши переменные */

static void BreakOut_prepare()
{
	//game_set_ups(30);
    data->ballX = 30;
	data->ballY = 30;
    data->speedy = 1;
	data->speedx = 1;
	data->Board1X = 26;
	data->Board2X = 26;
	data->ft = true;
    /* Здесь код, который будет исполнятся один раз */
    /* Здесь нужно инициализировать переменные */
}

static void BreakOut_render()
{
    /* Здесь код, который будет вывзваться для отрисовки кадра */
    /* Он не должен менять состояние игры, для этого есть функция update */
    game_draw_sprite(&YourSprite,0,data->Board2X,GREEN);
    game_draw_sprite(&Ball,data->ballX,data->ballY,RED);
	game_draw_sprite(&YourSprite,61,data->Board1X,BLUE);
    /* Здесь (и только здесь) нужно вызывать функции game_draw_??? */
}

static void BreakOut_update(unsigned long delta)
{
    /* Здесь код, который будет выполняться в цикле */
    /* Переменная delta содержит количество миллисекунд с последнего вызова */

    if(game_is_button_pressed(BUTTON_DOWN) && data->Board1X < 51)
    {
      data->Board1X = (data->Board1X + 1);
      }
      if(game_is_button_pressed(BUTTON_UP) && data->Board1X > 0)
    {
      data->Board1X = (data->Board1X - 1);

      }

	  if(game_is_button_pressed(BUTTON_A) && data->Board2X < 51)
    {
      data->Board2X = (data->Board2X + 1);
      }
      if(game_is_button_pressed(BUTTON_B) && data->Board2X > 0)
    {
      data->Board2X = (data->Board2X - 1);

      }

	  if((data->ballX >= (data->Board1X - 4)) && (data->ballX <= (data->Board1X + 16)) &&
		  ( (data->ballX == 57) || (data->ballX == 3) )) {

		  data->speedx = -data->speedx;

	  }

	  if((data->ballX >= (data->Board2X - 4)) && (data->ballX <= (data->Board2X + 16)) &&
		  ( (data->ballX == 57) || (data->ballX == 3) ) ) {

		  data->speedx = -data->speedx;

	  }

	  if( (data->ballY == 0) || (data->ballY == 60) ) data->speedy = -data->speedy;

	  if(1){
      data->ballX += data->speedx;

      data->ballY += data->speedy;
	  }
	  data->ft = !(data->ft);
    /* Здесь можно работать с кнопками и обновлять переменные */
}

game_instance BreakOut = {
    "BreakOut",         /* Имя, отображаемое в меню */
    BreakOut_prepare,
    BreakOut_render,
    BreakOut_update,
    sizeof(BreakOutData),
    (void**)(&data)
};

/* Не забудьте зарегистрировать игру в games.h */<commit_msg>lvls added<commit_after>#pragma GCC optimize ("-O3")

#include "libgame.h"
#include "sprite.h"
#include "binary.h"
#include "string"
#include <stdlib.h>

/* Встроенные цвета:
 *
 *  BLACK - Чёрный
 *  BLUE - Синий
 *  GREEN - Зелёный
 *  RED - Красный
 *  CYAN - Циановый
 *  PURPLE - Фиолетовый
 *  BROWN - Коричневый
 *  WHITE - Белый
 *
 *  Для использования 64-х цветной палитры, укажите в game.ino COLOR_6BIT = 1
 *
 * */

/* Кнопки:
 *
 * НА КОРПУСЕ:
 * BUTTON_SW, BUTTON_NW, BUTTON_SE, BUTTON_NE
 *
 * NW              NE
 *  SW            SE
 *
 *
 * НА ДЖОЙСТИКЕ:
 * BUTTON_UP, BUTTON_LEFT, BUTTON_RIGHT, BUTTON_DOWN, BUTTON_SELECT, BUTTON_START, BUTTON_A, BUTTON_B
 *
 *      UP
 *  LEFT+RIGHT     SELECT  START      B  A
 *     DOWN
 *
 * */

/* Спрайты
 *
 * максимальная высота - 16 пикселей

 определение спрайта

   x     x
    x   x
     x x
  xxxxxxxxx
 xxxxxxxxxxx
xxx  xxx  xxx
 xxxxxxxxxxx
  xxxxxxxxx
    x x x
   x     x

 --------------------------------
 */
const uint8_t YourSprite_lines[] PROGMEM = {
    B11110000,
	B11110000,
	B11110000,
	B11110000,
	B11110000,
	B11110000,
	B11110000,
	B11110000,
	B11110000,
	B11110000,
	B11110000,
	B11110000
};

const game_sprite YourSprite PROGMEM = {
    // ШИРИНА, ВЫСОТА, КОЛИЧЕСТВО БАЙТ НА СТРОКУ, ДАННЫЕ
    3, 12, 1, YourSprite_lines
};
const uint8_t BallData[] PROGMEM = {
    B01100000, //   xxxx
    B11110000, // xxxxxxxx
    B11110000, //  xxxxxxxx
    B01100000  //    xxxx
};

const game_sprite Ball PROGMEM = {
    // ШИРИНА, ВЫСОТА, КОЛИЧЕСТВО БАЙТ НА СТРОКУ, ДАННЫЕ
     4, 4, 1, BallData
};

/* Функции отрисовки
 *
 * game_draw_pixel(x, y, color) - Красит точку (x, y) в цвет color
 * game_draw_char(char, x, y, color) - Выводит символ char с левым верхним углом в точке (x, y) с цветом color
 * game_draw_text(string, x, y, color) - Выводит строку string с левым верхним углом в точке (x, y) с цветом color
 * game_draw_sprite(sprite, x, y, color) - Выводит спрайт sprite с левым верхним углом в точке (x, y) с цветом color
 *
 * */

/* Функции ввода
 *
 * game_is_button_pressed(button) - Нажата ли кнопка? Например: game_is_button_pressed(BUTTON_START)
 * game_is_any_button_pressed(mask) - Нажата ли хотя бы одна кнопка? Например: game_is_any_button_pressed(BITMASK(BUTTON_SW) | BITMASK(BUTTON_DOWN))
 *
 * */

struct BreakOutData
{
	int Board1Y, Board2Y, flag, k, ballX, ballY, speedy, speedx, lvlcount;
	bool ft;
    /* Объявляйте ваши переменные здесь */
    /* Чтобы потом обращаться к ним, пишите data->ПЕРЕМЕННАЯ */
};
static BreakOutData* data; /* Эта переменная - указатель на структуру, которая содержит ваши переменные */

static void BreakOut_prepare()
{
	game_set_ups(10);
	data->lvlcount = 0;
    data->ballX = 30;
	data->ballY = 30;
    data->speedy = 1;
	data->speedx = 1;
	data->Board1Y = 26;
	data->Board2Y = 26;
	data->ft = true;

	//while(!game_is_button_pressed(BUTTON_DOWN));
    /* Здесь код, который будет исполнятся один раз */
    /* Здесь нужно инициализировать переменные */
}

static void BreakOut_render()
{
    /* Здесь код, который будет вывзваться для отрисовки кадра */
    /* Он не должен менять состояние игры, для этого есть функция update */
    game_draw_sprite(&YourSprite,0,data->Board2Y,GREEN);
    game_draw_sprite(&Ball,data->ballX,data->ballY,RED);
	game_draw_sprite(&YourSprite,61,data->Board1Y,BLUE);
	switch(data->lvlcount)
	{
	case 2:
		game_draw_text((uint8_t*)"GO GO", 18, 26, WHITE);
		game_set_ups(25);
		break;
	case 4: //10
		game_draw_text((uint8_t*)"NORMA", 18, 26, GREEN);
		game_set_ups(35);
		break;
	case 6: //20
		game_draw_text((uint8_t*)"HARD", 18, 26, BROWN);
		game_set_ups(45);
		break;
	case 8:
		game_draw_text((uint8_t*)"HARDCORE", 12, 26, RED);
		game_set_ups(55);
		break;
	case 10:
		for(int i = 0; i < 1000; i++)
		game_draw_text((uint8_t*)"SUICIDE", 12, 26, RED);
		for(int i = 0; i < 1000; i++)
		game_draw_text((uint8_t*)"SUICIDE", 12, 26, BROWN);
		game_set_ups(65);
		break;
	}
    /* Здесь (и только здесь) нужно вызывать функции game_draw_??? */
}

static void BreakOut_update(unsigned long delta)
{
    /* Здесь код, который будет выполняться в цикле */
    /* Переменная delta содержит количество миллисекунд с последнего вызова */
    if(game_is_button_pressed(BUTTON_DOWN) && data->Board1Y < 52)
    {
      data->Board1Y = (data->Board1Y + 1);
      }
      if(game_is_button_pressed(BUTTON_UP) && data->Board1Y > 0)
    {
      data->Board1Y = (data->Board1Y - 1);

      }

	  if(game_is_button_pressed(BUTTON_SW) && data->Board2Y < 52)
    {
      data->Board2Y = (data->Board2Y + 1);
      }
      if(game_is_button_pressed(BUTTON_NW) && data->Board2Y > 0)
    {
      data->Board2Y = (data->Board2Y - 1);

      }

	  if((data->ballY >= (data->Board1Y - 4)) && (data->ballY <= (data->Board1Y + 16)) &&
		  ( (data->ballX == 57) )) {

		  data->speedx = -data->speedx;
			data->lvlcount++;
	  }

	  if((data->ballY >= (data->Board2Y - 6)) &&
		  (data->ballY <= (data->Board2Y + 16)) &&
		  ( (data->ballX == 3) ) ) {

		  data->speedx = -data->speedx;
	  }

	  if( (data->ballY == 0) || (data->ballY == 60) ) data->speedy = -data->speedy;

	  if(1){
      data->ballX += data->speedx;

      data->ballY += data->speedy;
	  }
	  data->ft = !(data->ft);
    /* Здесь можно работать с кнопками и обновлять переменные */
}

game_instance BreakOut = {
    "BreakOut",         /* Имя, отображаемое в меню */
    BreakOut_prepare,
    BreakOut_render,
    BreakOut_update,
    sizeof(BreakOutData),
    (void**)(&data)
};

/* Не забудьте зарегистрировать игру в games.h */<|endoftext|>


<commit_before>/*
Created:        --
Last Updated:   22 October    2017

Dan Marley
daniel.edison.marley@cernSPAMNOT.ch
Texas A&M University

-----

Basic steering macro for running CyMiniAna
 - Make new ntuples
 - Make histograms/efficiencies

*/
#include "TROOT.h"
#include "TFile.h"
#include "TTree.h"
#include "TChain.h"
#include "TH1.h"
#include "TSystem.h"
#include "TMath.h"
#include "TTreeReader.h"
#include "TTreeReaderValue.h"
#include "TTreeReaderArray.h"

#include <iostream>
#include <sstream>
#include <stdio.h>
#include <map>
#include <fstream>
#include <string>
#include <math.h>

#include "cms-ttbarAC/CyMiniAna/interface/configuration.h"
#include "cms-ttbarAC/CyMiniAna/interface/Event.h"
#include "cms-ttbarAC/CyMiniAna/interface/eventSelection.h"
#include "cms-ttbarAC/CyMiniAna/interface/miniTree.h"
#include "cms-ttbarAC/CyMiniAna/interface/tools.h"
#include "cms-ttbarAC/CyMiniAna/interface/histogrammer.h"
#include "cms-ttbarAC/CyMiniAna/interface/efficiency.h"

int main(int argc, char** argv) {
    /* Steering macro for CyMiniAna */
    if (argc < 2) {
        cma::HELP();
        return -1;
    }

    unsigned long long maxEntriesToRun(0);     // maximum number of entries in TTree
    unsigned int numberOfEventsToRun(0); // number of events to run
    bool passEvent(false);   // event passed selection
    bool isMC(false);      // Running over MC file

    // configuration
    configuration config(argv[1]);                         // configuration file
    config.initialize();

    int nEvents         = config.nEventsToProcess(); // requested number of events to run
    std::string outpath = config.outputFilePath();   // directory for output files
    unsigned long long firstEvent      = config.firstEvent();       // first event to begin running over
    std::vector<std::string> filenames = config.filesToProcess();
    std::vector<std::string> treenames = config.treeNames();
    std::string selection(config.selection());

    bool makeNewFile      = config.makeNewFile();
    bool makeHistograms   = config.makeHistograms();
    bool makeEfficiencies = config.makeEfficiencies();
    bool doSystWeights    = config.calcWeightSystematics(); // systemaics associated with scale factors

    std::string customFileEnding( config.customFileEnding() );
    if (customFileEnding.length()>0  && customFileEnding.substr(0,1).compare("_")!=0){
        customFileEnding = "_"+customFileEnding; // add '_' to beginning of string, if needed
    }

    // event selection
    eventSelection evtSel( config );
    evtSel.initialize();
    unsigned int ncuts = evtSel.numberOfCuts();            // number of cuts in selection
    std::vector<std::string> cutNames = evtSel.cutNames(); // names of cuts

    // --------------- //
    // -- File loop -- //
    // --------------- //
    unsigned int numberOfFiles(filenames.size());
    unsigned int currentFileNumber(0);
    cma::INFO("RUN : *** Starting file loop *** ");
    for (const auto& filename : filenames) {

        ++currentFileNumber;
        cma::INFO("RUN :   Opening "+filename+"   ("+std::to_string(currentFileNumber)+"/"+std::to_string(numberOfFiles)+")");

        auto file = TFile::Open(filename.c_str());
        if (!file || file->IsZombie()){
            cma::WARNING("RUN :  -- File: "+filename);
            cma::WARNING("RUN :     does not exist or it is a Zombie. ");
            cma::WARNING("RUN :     Continuing to next file. ");
            continue;
        }

        // -- Output file -- //
        std::size_t pos   = filename.find_last_of(".");     // the last ".", i.e., ".root"
        std::size_t found = filename.find_last_of("/");     // the last "/"
        std::string outputFilename = filename.substr(found+1,pos-1-found); // betwee "/" and "."
        // hopefully this returns: "diboson_WW_361082" given something like:
        // "/some/path/to/file/diboson_WW_361082.root"

        std::string fullOutputFilename = outpath+"/"+outputFilename+"_"+selection+customFileEnding+".root";
        std::unique_ptr<TFile> outputFile(TFile::Open( fullOutputFilename.c_str(), "RECREATE"));
        cma::INFO("RUN :   >> Saving to "+fullOutputFilename);

        // check the file type
        isMC = config.isMC( *file );

        std::vector<std::string> fileKeys;
        cma::getListOfKeys(file,fileKeys); // keep track of ttrees in file

/*
        // -- Extra trees to just clone into new file
        TChain * truth_chain;
        TChain * sumWeights_chain;

        if (makeNewFile){
            if (isMC){
                truth_chain = new TChain("truth");
                truth_chain->Add( filename.c_str() );
                truth_chain->CloneTree(-1,"fast");      // clone the truth tree
            }

            sumWeights_chain = new TChain("sumWeights");
            sumWeights_chain->Add( filename.c_str() );
            sumWeights_chain->CloneTree(-1,"fast");    // clone the sumWeights tree
        }
        // -- End tree cloning
*/

        histogrammer histMaker(config);      // initialize histogrammer
        efficiency effMaker(config);         // initialize efficiency class
        if (makeHistograms)
            histMaker.initialize( *outputFile,doSystWeights );
        if (makeEfficiencies)
            effMaker.bookEffs( *outputFile );

        // -- Cutflow histograms
        std::map<std::string, TH1D*>  h_cutflows;            // map of cutflow histograms (weights applied)
        std::map<std::string, TH1D*>  h_cutflows_unweighted; // map of cutflow histograms (raw # of events)

        // -- Loop over treenames
        for (const auto& treename : treenames) {

            // check that the ttree exists in this file before proceeding
            if (std::find(fileKeys.begin(), fileKeys.end(), treename) == fileKeys.end()){
                cma::INFO("SKIM : TTree "+treename+" is not present in this file, continuing to next TTree");
                continue;
            }

            // -- Cutflow histogram [initialize and label bins]
            h_cutflows[treename] = new TH1D( (treename+"_cutflow").c_str(),(treename+"_cutflow").c_str(),ncuts+1,0,ncuts+1);
            h_cutflows_unweighted[treename] = new TH1D( (treename+"_cutflow_unweighted").c_str(),(treename+"_cutflow_unweighted").c_str(),ncuts+1,0,ncuts+1);

            h_cutflows[treename]->GetXaxis()->SetBinLabel(1,"INITIAL");
            h_cutflows_unweighted[treename]->GetXaxis()->SetBinLabel(1,"INITIAL");

            for (unsigned int c=1;c<=ncuts;++c){
                h_cutflows[treename]->GetXaxis()->SetBinLabel(c+1,cutNames.at(c-1).c_str());
                h_cutflows_unweighted[treename]->GetXaxis()->SetBinLabel(c+1,cutNames.at(c-1).c_str());
            }

            // -- Load TTree to loop over
            cma::INFO("RUN :      TTree "+treename);
            TTreeReader myReader(treename.c_str(), file);

            // -- Make new Tree in Root file

            miniTree miniTTree(config);          // initialize TTree for new file
            if (makeNewFile)
                miniTTree.initialize( myReader.GetTree(), *outputFile );

            // -- Number of Entries to Process -- //
            maxEntriesToRun = myReader.GetEntries(true);
            if (maxEntriesToRun<1) // skip files with no entries
                continue;

            if (nEvents < 0 || ((unsigned int)nEvents+firstEvent) > maxEntriesToRun)
                numberOfEventsToRun = maxEntriesToRun - firstEvent;
            else
                numberOfEventsToRun = nEvents;

            // -- Event Loop -- //
            Long64_t imod = 1;                     // print to the terminal
            Event event = Event(myReader, config);

            Long64_t eventCounter = 0;    // counting the events processed
            Long64_t entry = firstEvent;  // start at a different event!
            while (myReader.Next()) {

                if (eventCounter+1 > numberOfEventsToRun){
                    cma::INFO("RUN : Processed the desired number of events: "+std::to_string(eventCounter)+"/"+std::to_string(numberOfEventsToRun));
                    break;
                }

                if (entry%imod==0){
                    cma::INFO("RUN :       Processing event "+std::to_string(entry) );
                    if(imod<2e4) imod *=10;
                }

                // -- Build Event -- //
                cma::DEBUG("RUN : Execute event");
                event.execute(entry);
                // now we have event object that has the event-level objects in it
                // pass this to the selection tools

                // -- Event Selection -- //
                // can do separate cutflows for each lepton flavor (just add if statement)
                // can do separate event selections as well, e.g.,
                //   evtSel.applySelectionBoosted(...) || evtSel.applySelectionResolved(...)
                cma::DEBUG("RUN : Apply event selection");
                passEvent = evtSel.applySelection(event,*h_cutflows.at( treename.c_str() ),*h_cutflows_unweighted.at( treename.c_str() ));

                if (passEvent){
                    cma::DEBUG("RUN : Passed selection, now save information");
                    if (makeNewFile)       miniTTree.saveEvent(event);
                    if (makeHistograms)    histMaker.fill( event );
                    if (makeEfficiencies)  effMaker.fill( event );
                }

                // iterate the entry and number of events processed
                ++entry;
                ++eventCounter;
            } // end event loop

            event.finalize();
            miniTTree.finalize();
        } // end tree loop

        // put overflow/underflow content into the first and last bins
        histMaker.overUnderFlow();

        cma::INFO("RUN :   END Running  "+filename);
        cma::INFO("RUN :   >> Output at "+fullOutputFilename);

        outputFile->Write();
        outputFile->Close();

        // -- Clean-up stuff
        delete file;          // free up some memory
        file = ((TFile *)0);  // (no errors for too many root files open)
    } // end file loop

    cma::INFO("RUN : *** End of file loop *** ");
}

// THE END<commit_msg>Fix run.cxx output filename<commit_after>/*
Created:        --
Last Updated:   22 October    2017

Dan Marley
daniel.edison.marley@cernSPAMNOT.ch
Texas A&M University

-----

Basic steering macro for running CyMiniAna
 - Make new ntuples
 - Make histograms/efficiencies

*/
#include "TROOT.h"
#include "TFile.h"
#include "TTree.h"
#include "TChain.h"
#include "TH1.h"
#include "TSystem.h"
#include "TMath.h"
#include "TTreeReader.h"
#include "TTreeReaderValue.h"
#include "TTreeReaderArray.h"

#include <iostream>
#include <sstream>
#include <stdio.h>
#include <map>
#include <fstream>
#include <string>
#include <math.h>

#include "cms-ttbarAC/CyMiniAna/interface/configuration.h"
#include "cms-ttbarAC/CyMiniAna/interface/Event.h"
#include "cms-ttbarAC/CyMiniAna/interface/eventSelection.h"
#include "cms-ttbarAC/CyMiniAna/interface/miniTree.h"
#include "cms-ttbarAC/CyMiniAna/interface/tools.h"
#include "cms-ttbarAC/CyMiniAna/interface/histogrammer.h"
#include "cms-ttbarAC/CyMiniAna/interface/efficiency.h"

int main(int argc, char** argv) {
    /* Steering macro for CyMiniAna */
    if (argc < 2) {
        cma::HELP();
        return -1;
    }

    unsigned long long maxEntriesToRun(0);     // maximum number of entries in TTree
    unsigned int numberOfEventsToRun(0); // number of events to run
    bool passEvent(false);   // event passed selection
    bool isMC(false);      // Running over MC file

    // configuration
    configuration config(argv[1]);                         // configuration file
    config.initialize();

    int nEvents         = config.nEventsToProcess(); // requested number of events to run
    std::string outpath = config.outputFilePath();   // directory for output files
    unsigned long long firstEvent      = config.firstEvent();       // first event to begin running over
    std::vector<std::string> filenames = config.filesToProcess();
    std::vector<std::string> treenames = config.treeNames();
    std::string selection(config.selection());

    bool makeNewFile      = config.makeNewFile();
    bool makeHistograms   = config.makeHistograms();
    bool makeEfficiencies = config.makeEfficiencies();
    bool doSystWeights    = config.calcWeightSystematics(); // systemaics associated with scale factors

    std::string customFileEnding( config.customFileEnding() );
    if (customFileEnding.length()>0  && customFileEnding.substr(0,1).compare("_")!=0){
        customFileEnding = "_"+customFileEnding; // add '_' to beginning of string, if needed
    }

    // event selection
    eventSelection evtSel( config );
    evtSel.initialize();
    unsigned int ncuts = evtSel.numberOfCuts();            // number of cuts in selection
    std::vector<std::string> cutNames = evtSel.cutNames(); // names of cuts

    // --------------- //
    // -- File loop -- //
    // --------------- //
    unsigned int numberOfFiles(filenames.size());
    unsigned int currentFileNumber(0);
    cma::INFO("RUN : *** Starting file loop *** ");
    for (const auto& filename : filenames) {

        ++currentFileNumber;
        cma::INFO("RUN :   Opening "+filename+"   ("+std::to_string(currentFileNumber)+"/"+std::to_string(numberOfFiles)+")");

        auto file = TFile::Open(filename.c_str());
        if (!file || file->IsZombie()){
            cma::WARNING("RUN :  -- File: "+filename);
            cma::WARNING("RUN :     does not exist or it is a Zombie. ");
            cma::WARNING("RUN :     Continuing to next file. ");
            continue;
        }

        // -- Output file -- //
        // CMS doesn't use something like 'mcChannelNumber', need to keep the same file names
        // therefore, make new directories for the different selections
        struct stat dirBuffer;
        std::string outpath = outpathBase+"/"+selection+customFileEnding;
        if ( !(stat((outpath).c_str(),&dirBuffer)==0 && S_ISDIR(dirBuffer.st_mode)) ){
            cma::DEBUG("RUN : Creating directory for storing output: "+outpath);
            system( ("mkdir "+outpath).c_str() );  // make the directory so the files are grouped together
        }

        std::size_t pos   = filename.find_last_of(".");     // the last ".", i.e., ".root"
        std::size_t found = filename.find_last_of("/");     // the last "/"
        std::string outputFilename = filename.substr(found+1,pos-1-found); // betwee "/" and "."
        // hopefully this returns: "diboson_WW_361082" given something like:
        // "/some/path/to/file/diboson_WW_361082.root"

        std::string fullOutputFilename = outpath+"/"+outputFilename+".root";
        std::unique_ptr<TFile> outputFile(TFile::Open( fullOutputFilename.c_str(), "RECREATE"));
        cma::INFO("RUN :   >> Saving to "+fullOutputFilename);

        // check the file type
        isMC = config.isMC( *file );

        std::vector<std::string> fileKeys;
        cma::getListOfKeys(file,fileKeys); // keep track of ttrees in file

/*
        // -- Extra trees to just clone into new file
        TChain * truth_chain;
        TChain * sumWeights_chain;

        if (makeNewFile){
            if (isMC){
                truth_chain = new TChain("truth");
                truth_chain->Add( filename.c_str() );
                truth_chain->CloneTree(-1,"fast");      // clone the truth tree
            }

            sumWeights_chain = new TChain("sumWeights");
            sumWeights_chain->Add( filename.c_str() );
            sumWeights_chain->CloneTree(-1,"fast");    // clone the sumWeights tree
        }
        // -- End tree cloning
*/

        histogrammer histMaker(config);      // initialize histogrammer
        efficiency effMaker(config);         // initialize efficiency class
        if (makeHistograms)
            histMaker.initialize( *outputFile,doSystWeights );
        if (makeEfficiencies)
            effMaker.bookEffs( *outputFile );

        // -- Cutflow histograms
        std::map<std::string, TH1D*>  h_cutflows;            // map of cutflow histograms (weights applied)
        std::map<std::string, TH1D*>  h_cutflows_unweighted; // map of cutflow histograms (raw # of events)

        // -- Loop over treenames
        for (const auto& treename : treenames) {

            // check that the ttree exists in this file before proceeding
            if (std::find(fileKeys.begin(), fileKeys.end(), treename) == fileKeys.end()){
                cma::INFO("SKIM : TTree "+treename+" is not present in this file, continuing to next TTree");
                continue;
            }

            // -- Cutflow histogram [initialize and label bins]
            h_cutflows[treename] = new TH1D( (treename+"_cutflow").c_str(),(treename+"_cutflow").c_str(),ncuts+1,0,ncuts+1);
            h_cutflows_unweighted[treename] = new TH1D( (treename+"_cutflow_unweighted").c_str(),(treename+"_cutflow_unweighted").c_str(),ncuts+1,0,ncuts+1);

            h_cutflows[treename]->GetXaxis()->SetBinLabel(1,"INITIAL");
            h_cutflows_unweighted[treename]->GetXaxis()->SetBinLabel(1,"INITIAL");

            for (unsigned int c=1;c<=ncuts;++c){
                h_cutflows[treename]->GetXaxis()->SetBinLabel(c+1,cutNames.at(c-1).c_str());
                h_cutflows_unweighted[treename]->GetXaxis()->SetBinLabel(c+1,cutNames.at(c-1).c_str());
            }

            // -- Load TTree to loop over
            cma::INFO("RUN :      TTree "+treename);
            TTreeReader myReader(treename.c_str(), file);

            // -- Make new Tree in Root file

            miniTree miniTTree(config);          // initialize TTree for new file
            if (makeNewFile)
                miniTTree.initialize( myReader.GetTree(), *outputFile );

            // -- Number of Entries to Process -- //
            maxEntriesToRun = myReader.GetEntries(true);
            if (maxEntriesToRun<1) // skip files with no entries
                continue;

            if (nEvents < 0 || ((unsigned int)nEvents+firstEvent) > maxEntriesToRun)
                numberOfEventsToRun = maxEntriesToRun - firstEvent;
            else
                numberOfEventsToRun = nEvents;

            // -- Event Loop -- //
            Long64_t imod = 1;                     // print to the terminal
            Event event = Event(myReader, config);

            Long64_t eventCounter = 0;    // counting the events processed
            Long64_t entry = firstEvent;  // start at a different event!
            while (myReader.Next()) {

                if (eventCounter+1 > numberOfEventsToRun){
                    cma::INFO("RUN : Processed the desired number of events: "+std::to_string(eventCounter)+"/"+std::to_string(numberOfEventsToRun));
                    break;
                }

                if (entry%imod==0){
                    cma::INFO("RUN :       Processing event "+std::to_string(entry) );
                    if(imod<2e4) imod *=10;
                }

                // -- Build Event -- //
                cma::DEBUG("RUN : Execute event");
                event.execute(entry);
                // now we have event object that has the event-level objects in it
                // pass this to the selection tools

                // -- Event Selection -- //
                // can do separate cutflows for each lepton flavor (just add if statement)
                // can do separate event selections as well, e.g.,
                //   evtSel.applySelectionBoosted(...) || evtSel.applySelectionResolved(...)
                cma::DEBUG("RUN : Apply event selection");
                passEvent = evtSel.applySelection(event,*h_cutflows.at( treename.c_str() ),*h_cutflows_unweighted.at( treename.c_str() ));

                if (passEvent){
                    cma::DEBUG("RUN : Passed selection, now save information");
                    if (makeNewFile)       miniTTree.saveEvent(event);
                    if (makeHistograms)    histMaker.fill( event );
                    if (makeEfficiencies)  effMaker.fill( event );
                }

                // iterate the entry and number of events processed
                ++entry;
                ++eventCounter;
            } // end event loop

            event.finalize();
            miniTTree.finalize();
        } // end tree loop

        // put overflow/underflow content into the first and last bins
        histMaker.overUnderFlow();

        cma::INFO("RUN :   END Running  "+filename);
        cma::INFO("RUN :   >> Output at "+fullOutputFilename);

        outputFile->Write();
        outputFile->Close();

        // -- Clean-up stuff
        delete file;          // free up some memory
        file = ((TFile *)0);  // (no errors for too many root files open)
    } // end file loop

    cma::INFO("RUN : *** End of file loop *** ");
}

// THE END
<|endoftext|>


<commit_before>#include "Nextion.h"
#include <stdarg.h>
#include <stdio.h>
#include <string.h>

bool Nextion::init()
{
    rxBuffer.reset();
    return hwa.init();
}

bool Nextion::setScreen(size_t screenID)
{
    return writeCommand("page %u", screenID);
}

bool Nextion::update(size_t& buttonID, bool& state)
{
    uint8_t data;

    if (hwa.read(data))
        rxBuffer.insert(data);

    if (data == 0xFF)
    {
        endCounter++;
    }
    else
    {
        if (endCounter)
            endCounter = 0;
    }

    if (endCounter == 3)
    {
        //new message received
        endCounter = 0;

        //handle only button messages for now
        bool messageStart = true;

        while (rxBuffer.count())
        {
            rxBuffer.remove(data);

            if ((data == 0x65) && messageStart)
            {
                if (rxBuffer.count() == 6)
                {
                    //first data is page, don't care about that
                    rxBuffer.remove(data);

                    //next byte is component id
                    rxBuffer.remove(data);

                    buttonID = data;

                    //1 - pressed, 0 - released
                    rxBuffer.remove(data);

                    state = data ? 1 : 0;

                    rxBuffer.reset();
                    return true;
                }
            }
            else
            {
                rxBuffer.reset();
            }

            messageStart = false;
        }
    }

    return false;
}

void Nextion::setIconState(IO::Touchscreen::icon_t& icon, bool state)
{
    writeCommand("picq %u,%u,%u,%u,%u", icon.xPos, icon.yPos, icon.width, icon.height, state ? 1 : 0);
}

bool Nextion::writeCommand(const char* line, ...)
{
    va_list args;
    va_start(args, line);

    size_t retVal = vsnprintf(commandBuffer, bufferSize, line, args);

    va_end(args);

    if (retVal < 0)
        return false;

    for (size_t i = 0; i < strlen(commandBuffer); i++)
    {
        if (!hwa.write(commandBuffer[i]))
            return false;
    }

    return endCommand();
}

bool Nextion::endCommand()
{
    for (int i = 0; i < 3; i++)
    {
        if (!hwa.write(0xFF))
            return false;
    }

    return true;
}<commit_msg>touchscreen: don't process data if no data has been received<commit_after>#include "Nextion.h"
#include <stdarg.h>
#include <stdio.h>
#include <string.h>

bool Nextion::init()
{
    rxBuffer.reset();
    return hwa.init();
}

bool Nextion::setScreen(size_t screenID)
{
    return writeCommand("page %u", screenID);
}

bool Nextion::update(size_t& buttonID, bool& state)
{
    uint8_t data;

    if (hwa.read(data))
        rxBuffer.insert(data);
    else
        return false;

    if (data == 0xFF)
    {
        endCounter++;
    }
    else
    {
        if (endCounter)
            endCounter = 0;
    }

    if (endCounter == 3)
    {
        //new message received
        endCounter = 0;

        //handle only button messages for now
        bool messageStart = true;

        while (rxBuffer.count())
        {
            rxBuffer.remove(data);

            if ((data == 0x65) && messageStart)
            {
                if (rxBuffer.count() == 6)
                {
                    //first data is page, don't care about that
                    rxBuffer.remove(data);

                    //next byte is component id
                    rxBuffer.remove(data);

                    buttonID = data;

                    //1 - pressed, 0 - released
                    rxBuffer.remove(data);

                    state = data ? 1 : 0;

                    rxBuffer.reset();
                    return true;
                }
            }
            else
            {
                rxBuffer.reset();
            }

            messageStart = false;
        }
    }

    return false;
}

void Nextion::setIconState(IO::Touchscreen::icon_t& icon, bool state)
{
    writeCommand("picq %u,%u,%u,%u,%u", icon.xPos, icon.yPos, icon.width, icon.height, state ? 1 : 0);
}

bool Nextion::writeCommand(const char* line, ...)
{
    va_list args;
    va_start(args, line);

    size_t retVal = vsnprintf(commandBuffer, bufferSize, line, args);

    va_end(args);

    if (retVal < 0)
        return false;

    for (size_t i = 0; i < strlen(commandBuffer); i++)
    {
        if (!hwa.write(commandBuffer[i]))
            return false;
    }

    return endCommand();
}

bool Nextion::endCommand()
{
    for (int i = 0; i < 3; i++)
    {
        if (!hwa.write(0xFF))
            return false;
    }

    return true;
}<|endoftext|>


<commit_before>#include "SQLiteQueryExecutor.h"
#include "Logger.h"
#include "sqlite_orm.h"

#include <sqlite3.h>
#include <memory>
#include <sstream>
#include <string>
#include <system_error>

#define ACCOUNT_ID 1

namespace comm {

using namespace sqlite_orm;

std::string SQLiteQueryExecutor::sqliteFilePath;

bool create_table(sqlite3 *db, std::string query, std::string tableName) {
  char *error;
  sqlite3_exec(db, query.c_str(), nullptr, nullptr, &error);
  if (!error) {
    return true;
  }

  std::ostringstream stringStream;
  stringStream << "Error creating '" << tableName << "' table: " << error;
  Logger::log(stringStream.str());

  sqlite3_free(error);
  return false;
}

bool create_drafts_table(sqlite3 *db) {
  std::string query =
      "CREATE TABLE IF NOT EXISTS drafts (threadID TEXT UNIQUE PRIMARY KEY, "
      "text TEXT);";
  return create_table(db, query, "drafts");
}

bool rename_threadID_to_key(sqlite3 *db) {
  sqlite3_stmt *key_column_stmt;
  sqlite3_prepare_v2(
      db,
      "SELECT name AS col_name FROM pragma_table_xinfo ('drafts') WHERE "
      "col_name='key';",
      -1,
      &key_column_stmt,
      nullptr);
  sqlite3_step(key_column_stmt);

  auto num_bytes = sqlite3_column_bytes(key_column_stmt, 0);
  sqlite3_finalize(key_column_stmt);
  if (num_bytes) {
    return true;
  }

  char *error;
  sqlite3_exec(
      db,
      "ALTER TABLE drafts RENAME COLUMN `threadID` TO `key`;",
      nullptr,
      nullptr,
      &error);
  if (error) {
    std::ostringstream stringStream;
    stringStream << "Error occurred renaming threadID column in drafts table "
                 << "to key: " << error;
    Logger::log(stringStream.str());
    sqlite3_free(error);
    return false;
  }
  return true;
}

bool create_persist_account_table(sqlite3 *db) {
  std::string query =
      "CREATE TABLE olm_persist_account("
      "id INTEGER UNIQUE PRIMARY KEY NOT NULL, "
      "account_data TEXT NOT NULL);";
  return create_table(db, query, "olm_persist_account");
}

bool create_persist_sessions_table(sqlite3 *db) {
  std::string query =
      "CREATE TABLE olm_persist_sessions("
      "target_user_id TEXT UNIQUE PRIMARY KEY NOT NULL, "
      "session_data TEXT NOT NULL);";
  return create_table(db, query, "olm_persist_sessions");
}

bool drop_messages_table(sqlite3 *db) {
  char *error;
  sqlite3_exec(db, "DROP TABLE IF EXISTS messages;", nullptr, nullptr, &error);

  if (!error) {
    return true;
  }

  std::ostringstream stringStream;
  stringStream << "Error dropping 'messages' table: " << error;
  Logger::log(stringStream.str());

  sqlite3_free(error);
  return false;
}

bool recreate_messages_table(sqlite3 *db) {
  std::string query =
      "CREATE TABLE IF NOT EXISTS messages ( "
      "id INTEGER UNIQUE PRIMARY KEY NOT NULL, "
      "thread INTEGER NOT NULL, "
      "user INTEGER NOT NULL, "
      "type INTEGER NOT NULL, "
      "future_type INTEGER, "
      "content TEXT, "
      "time INTEGER NOT NULL);";
  return create_table(db, query, "messages");
}

typedef std::function<bool(sqlite3 *)> MigrationFunction;
std::vector<std::pair<uint, MigrationFunction>> migrations{{
    {1, create_drafts_table},
    {2, rename_threadID_to_key},
    {4, create_persist_account_table},
    {5, create_persist_sessions_table},
    {6, drop_messages_table},
    {7, recreate_messages_table},
}};

void SQLiteQueryExecutor::migrate() {
  sqlite3 *db;
  sqlite3_open(SQLiteQueryExecutor::sqliteFilePath.c_str(), &db);

  sqlite3_stmt *user_version_stmt;
  sqlite3_prepare_v2(
      db, "PRAGMA user_version;", -1, &user_version_stmt, nullptr);
  sqlite3_step(user_version_stmt);

  int current_user_version = sqlite3_column_int(user_version_stmt, 0);
  sqlite3_finalize(user_version_stmt);

  std::stringstream version_msg;
  version_msg << "db version: " << current_user_version << std::endl;
  Logger::log(version_msg.str());

  for (const auto &[idx, migration] : migrations) {
    if (idx <= current_user_version) {
      continue;
    }

    std::stringstream migration_msg;
    sqlite3_exec(db, "BEGIN TRANSACTION;", nullptr, nullptr, nullptr);
    if (!migration(db)) {
      migration_msg << "migration " << idx << " failed." << std::endl;
      Logger::log(migration_msg.str());
      break;
    };

    std::stringstream update_version;
    update_version << "PRAGMA user_version=" << idx << ";";
    auto update_version_str = update_version.str();

    sqlite3_exec(db, update_version_str.c_str(), nullptr, nullptr, nullptr);
    sqlite3_exec(db, "END TRANSACTION;", nullptr, nullptr, nullptr);

    migration_msg << "migration " << idx << " succeeded." << std::endl;
    Logger::log(migration_msg.str());
  }

  sqlite3_close(db);
}

auto SQLiteQueryExecutor::getStorage() {
  static auto storage = make_storage(
      SQLiteQueryExecutor::sqliteFilePath,
      make_table(
          "drafts",
          make_column("key", &Draft::key, unique(), primary_key()),
          make_column("text", &Draft::text)),
      make_table(
          "messages",
          make_column("id", &Message::id, unique(), primary_key()),
          make_column("thread", &Message::thread),
          make_column("user", &Message::user),
          make_column("type", &Message::type),
          make_column("future_type", &Message::future_type),
          make_column("content", &Message::content),
          make_column("time", &Message::time)),
      make_table(
          "olm_persist_account",
          make_column("id", &OlmPersistAccount::id),
          make_column("account_data", &OlmPersistAccount::account_data)),
      make_table(
          "olm_persist_sessions",
          make_column("target_user_id", &OlmPersistSession::target_user_id),
          make_column("session_data", &OlmPersistSession::session_data)));
  return storage;
}

SQLiteQueryExecutor::SQLiteQueryExecutor() {
  this->migrate();
}

std::string SQLiteQueryExecutor::getDraft(std::string key) const {
  std::unique_ptr<Draft> draft =
      SQLiteQueryExecutor::getStorage().get_pointer<Draft>(key);
  return (draft == nullptr) ? "" : draft->text;
}

void SQLiteQueryExecutor::updateDraft(std::string key, std::string text) const {
  Draft draft = {key, text};
  SQLiteQueryExecutor::getStorage().replace(draft);
}

bool SQLiteQueryExecutor::moveDraft(std::string oldKey, std::string newKey)
    const {
  std::unique_ptr<Draft> draft =
      SQLiteQueryExecutor::getStorage().get_pointer<Draft>(oldKey);
  if (draft == nullptr) {
    return false;
  }
  draft->key = newKey;
  SQLiteQueryExecutor::getStorage().replace(*draft);
  SQLiteQueryExecutor::getStorage().remove<Draft>(oldKey);
  return true;
}

std::vector<Draft> SQLiteQueryExecutor::getAllDrafts() const {
  return SQLiteQueryExecutor::getStorage().get_all<Draft>();
}

void SQLiteQueryExecutor::removeAllDrafts() const {
  SQLiteQueryExecutor::getStorage().remove_all<Draft>();
}

void SQLiteQueryExecutor::removeAllMessages() const {
  SQLiteQueryExecutor::getStorage().remove_all<Message>();
}

std::vector<Message> SQLiteQueryExecutor::getAllMessages() const {
  return SQLiteQueryExecutor::getStorage().get_all<Message>();
}

void SQLiteQueryExecutor::removeMessages(std::vector<int> ids) const {
  SQLiteQueryExecutor::getStorage().remove_all<Message>(
      where(in(&Message::id, ids)));
}

void SQLiteQueryExecutor::replaceMessage(Message message) const {
  SQLiteQueryExecutor::getStorage().replace(message);
}

std::vector<OlmPersistSession>
SQLiteQueryExecutor::getOlmPersistSessionsData() const {
  return SQLiteQueryExecutor::getStorage().get_all<OlmPersistSession>();
}

folly::Optional<std::string>
SQLiteQueryExecutor::getOlmPersistAccountData() const {
  std::vector<OlmPersistAccount> result =
      SQLiteQueryExecutor::getStorage().get_all<OlmPersistAccount>();
  if (result.size() > 1) {
    throw std::system_error(
        ECANCELED,
        std::generic_category(),
        "Multiple records found for the olm_persist_account table");
  }
  return (result.size() == 0)
      ? folly::none
      : folly::Optional<std::string>(result[0].account_data);
}

void SQLiteQueryExecutor::storeOlmPersistData(crypto::Persist persist) const {
  OlmPersistAccount persistAccount = {
      ACCOUNT_ID, std::string(persist.account.begin(), persist.account.end())};
  SQLiteQueryExecutor::getStorage().replace(persistAccount);
  for (auto it = persist.sessions.begin(); it != persist.sessions.end(); it++) {
    OlmPersistSession persistSession = {
        it->first, std::string(it->second.begin(), it->second.end())};
    SQLiteQueryExecutor::getStorage().replace(persistSession);
  }
}

} // namespace comm
<commit_msg>[CommCoreModule] Create index on `thread` and `time` columns of messages table<commit_after>#include "SQLiteQueryExecutor.h"
#include "Logger.h"
#include "sqlite_orm.h"

#include <sqlite3.h>
#include <memory>
#include <sstream>
#include <string>
#include <system_error>

#define ACCOUNT_ID 1

namespace comm {

using namespace sqlite_orm;

std::string SQLiteQueryExecutor::sqliteFilePath;

bool create_table(sqlite3 *db, std::string query, std::string tableName) {
  char *error;
  sqlite3_exec(db, query.c_str(), nullptr, nullptr, &error);
  if (!error) {
    return true;
  }

  std::ostringstream stringStream;
  stringStream << "Error creating '" << tableName << "' table: " << error;
  Logger::log(stringStream.str());

  sqlite3_free(error);
  return false;
}

bool create_drafts_table(sqlite3 *db) {
  std::string query =
      "CREATE TABLE IF NOT EXISTS drafts (threadID TEXT UNIQUE PRIMARY KEY, "
      "text TEXT);";
  return create_table(db, query, "drafts");
}

bool rename_threadID_to_key(sqlite3 *db) {
  sqlite3_stmt *key_column_stmt;
  sqlite3_prepare_v2(
      db,
      "SELECT name AS col_name FROM pragma_table_xinfo ('drafts') WHERE "
      "col_name='key';",
      -1,
      &key_column_stmt,
      nullptr);
  sqlite3_step(key_column_stmt);

  auto num_bytes = sqlite3_column_bytes(key_column_stmt, 0);
  sqlite3_finalize(key_column_stmt);
  if (num_bytes) {
    return true;
  }

  char *error;
  sqlite3_exec(
      db,
      "ALTER TABLE drafts RENAME COLUMN `threadID` TO `key`;",
      nullptr,
      nullptr,
      &error);
  if (error) {
    std::ostringstream stringStream;
    stringStream << "Error occurred renaming threadID column in drafts table "
                 << "to key: " << error;
    Logger::log(stringStream.str());
    sqlite3_free(error);
    return false;
  }
  return true;
}

bool create_persist_account_table(sqlite3 *db) {
  std::string query =
      "CREATE TABLE olm_persist_account("
      "id INTEGER UNIQUE PRIMARY KEY NOT NULL, "
      "account_data TEXT NOT NULL);";
  return create_table(db, query, "olm_persist_account");
}

bool create_persist_sessions_table(sqlite3 *db) {
  std::string query =
      "CREATE TABLE olm_persist_sessions("
      "target_user_id TEXT UNIQUE PRIMARY KEY NOT NULL, "
      "session_data TEXT NOT NULL);";
  return create_table(db, query, "olm_persist_sessions");
}

bool drop_messages_table(sqlite3 *db) {
  char *error;
  sqlite3_exec(db, "DROP TABLE IF EXISTS messages;", nullptr, nullptr, &error);

  if (!error) {
    return true;
  }

  std::ostringstream stringStream;
  stringStream << "Error dropping 'messages' table: " << error;
  Logger::log(stringStream.str());

  sqlite3_free(error);
  return false;
}

bool recreate_messages_table(sqlite3 *db) {
  std::string query =
      "CREATE TABLE IF NOT EXISTS messages ( "
      "id INTEGER UNIQUE PRIMARY KEY NOT NULL, "
      "thread INTEGER NOT NULL, "
      "user INTEGER NOT NULL, "
      "type INTEGER NOT NULL, "
      "future_type INTEGER, "
      "content TEXT, "
      "time INTEGER NOT NULL);";
  return create_table(db, query, "messages");
}

bool create_messages_idx_thread_time(sqlite3 *db) {
  char *error;
  sqlite3_exec(
      db,
      "CREATE INDEX messages_idx_thread_time "
      "ON messages (thread, time);",
      nullptr,
      nullptr,
      &error);

  if (!error) {
    return true;
  }

  std::ostringstream stringStream;
  stringStream << "Error creating (thread, time) index on messages table: "
               << error;
  Logger::log(stringStream.str());

  sqlite3_free(error);
  return false;
}

typedef std::function<bool(sqlite3 *)> MigrationFunction;
std::vector<std::pair<uint, MigrationFunction>> migrations{{
    {1, create_drafts_table},
    {2, rename_threadID_to_key},
    {4, create_persist_account_table},
    {5, create_persist_sessions_table},
    {6, drop_messages_table},
    {7, recreate_messages_table},
    {8, create_messages_idx_thread_time},
}};

void SQLiteQueryExecutor::migrate() {
  sqlite3 *db;
  sqlite3_open(SQLiteQueryExecutor::sqliteFilePath.c_str(), &db);

  sqlite3_stmt *user_version_stmt;
  sqlite3_prepare_v2(
      db, "PRAGMA user_version;", -1, &user_version_stmt, nullptr);
  sqlite3_step(user_version_stmt);

  int current_user_version = sqlite3_column_int(user_version_stmt, 0);
  sqlite3_finalize(user_version_stmt);

  std::stringstream version_msg;
  version_msg << "db version: " << current_user_version << std::endl;
  Logger::log(version_msg.str());

  for (const auto &[idx, migration] : migrations) {
    if (idx <= current_user_version) {
      continue;
    }

    std::stringstream migration_msg;
    sqlite3_exec(db, "BEGIN TRANSACTION;", nullptr, nullptr, nullptr);
    if (!migration(db)) {
      migration_msg << "migration " << idx << " failed." << std::endl;
      Logger::log(migration_msg.str());
      break;
    };

    std::stringstream update_version;
    update_version << "PRAGMA user_version=" << idx << ";";
    auto update_version_str = update_version.str();

    sqlite3_exec(db, update_version_str.c_str(), nullptr, nullptr, nullptr);
    sqlite3_exec(db, "END TRANSACTION;", nullptr, nullptr, nullptr);

    migration_msg << "migration " << idx << " succeeded." << std::endl;
    Logger::log(migration_msg.str());
  }

  sqlite3_close(db);
}

auto SQLiteQueryExecutor::getStorage() {
  static auto storage = make_storage(
      SQLiteQueryExecutor::sqliteFilePath,
      make_table(
          "drafts",
          make_column("key", &Draft::key, unique(), primary_key()),
          make_column("text", &Draft::text)),
      make_table(
          "messages",
          make_column("id", &Message::id, unique(), primary_key()),
          make_column("thread", &Message::thread),
          make_column("user", &Message::user),
          make_column("type", &Message::type),
          make_column("future_type", &Message::future_type),
          make_column("content", &Message::content),
          make_column("time", &Message::time)),
      make_table(
          "olm_persist_account",
          make_column("id", &OlmPersistAccount::id),
          make_column("account_data", &OlmPersistAccount::account_data)),
      make_table(
          "olm_persist_sessions",
          make_column("target_user_id", &OlmPersistSession::target_user_id),
          make_column("session_data", &OlmPersistSession::session_data)));
  return storage;
}

SQLiteQueryExecutor::SQLiteQueryExecutor() {
  this->migrate();
}

std::string SQLiteQueryExecutor::getDraft(std::string key) const {
  std::unique_ptr<Draft> draft =
      SQLiteQueryExecutor::getStorage().get_pointer<Draft>(key);
  return (draft == nullptr) ? "" : draft->text;
}

void SQLiteQueryExecutor::updateDraft(std::string key, std::string text) const {
  Draft draft = {key, text};
  SQLiteQueryExecutor::getStorage().replace(draft);
}

bool SQLiteQueryExecutor::moveDraft(std::string oldKey, std::string newKey)
    const {
  std::unique_ptr<Draft> draft =
      SQLiteQueryExecutor::getStorage().get_pointer<Draft>(oldKey);
  if (draft == nullptr) {
    return false;
  }
  draft->key = newKey;
  SQLiteQueryExecutor::getStorage().replace(*draft);
  SQLiteQueryExecutor::getStorage().remove<Draft>(oldKey);
  return true;
}

std::vector<Draft> SQLiteQueryExecutor::getAllDrafts() const {
  return SQLiteQueryExecutor::getStorage().get_all<Draft>();
}

void SQLiteQueryExecutor::removeAllDrafts() const {
  SQLiteQueryExecutor::getStorage().remove_all<Draft>();
}

void SQLiteQueryExecutor::removeAllMessages() const {
  SQLiteQueryExecutor::getStorage().remove_all<Message>();
}

std::vector<Message> SQLiteQueryExecutor::getAllMessages() const {
  return SQLiteQueryExecutor::getStorage().get_all<Message>();
}

void SQLiteQueryExecutor::removeMessages(std::vector<int> ids) const {
  SQLiteQueryExecutor::getStorage().remove_all<Message>(
      where(in(&Message::id, ids)));
}

void SQLiteQueryExecutor::replaceMessage(Message message) const {
  SQLiteQueryExecutor::getStorage().replace(message);
}

std::vector<OlmPersistSession>
SQLiteQueryExecutor::getOlmPersistSessionsData() const {
  return SQLiteQueryExecutor::getStorage().get_all<OlmPersistSession>();
}

folly::Optional<std::string>
SQLiteQueryExecutor::getOlmPersistAccountData() const {
  std::vector<OlmPersistAccount> result =
      SQLiteQueryExecutor::getStorage().get_all<OlmPersistAccount>();
  if (result.size() > 1) {
    throw std::system_error(
        ECANCELED,
        std::generic_category(),
        "Multiple records found for the olm_persist_account table");
  }
  return (result.size() == 0)
      ? folly::none
      : folly::Optional<std::string>(result[0].account_data);
}

void SQLiteQueryExecutor::storeOlmPersistData(crypto::Persist persist) const {
  OlmPersistAccount persistAccount = {
      ACCOUNT_ID, std::string(persist.account.begin(), persist.account.end())};
  SQLiteQueryExecutor::getStorage().replace(persistAccount);
  for (auto it = persist.sessions.begin(); it != persist.sessions.end(); it++) {
    OlmPersistSession persistSession = {
        it->first, std::string(it->second.begin(), it->second.end())};
    SQLiteQueryExecutor::getStorage().replace(persistSession);
  }
}

} // namespace comm
<|endoftext|>