You've already forked joplin
							
							
				mirror of
				https://github.com/laurent22/joplin.git
				synced 2025-10-31 00:07:48 +02:00 
			
		
		
		
	Record changes
This commit is contained in:
		| @@ -6,10 +6,9 @@ Database::Database() {} | ||||
|  | ||||
| void Database::initialize(const QString &path) { | ||||
| 	version_ = -1; | ||||
| 	transactionCount_ = 0; | ||||
|  | ||||
| 	// QFile::remove(path); | ||||
|  | ||||
| 	//qDebug() << Select << Text; | ||||
| 	//QFile::remove(path); | ||||
|  | ||||
| 	db_ = QSqlDatabase::addDatabase("QSQLITE"); | ||||
| 	db_.setDatabaseName(path); | ||||
| @@ -123,6 +122,28 @@ bool Database::errorCheck(const QSqlQuery& query) { | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| bool Database::transaction() { | ||||
| 	transactionCount_++; | ||||
| 	if (transactionCount_ > 1) return true; | ||||
| 	return db_.transaction(); | ||||
| } | ||||
|  | ||||
| bool Database::commit() { | ||||
| 	transactionCount_--; | ||||
|  | ||||
| 	if (transactionCount_ < 0) { | ||||
| 		transactionCount_ = 0; | ||||
| 		qCritical() << "Attempting commit on a database that is not in transaction mode"; | ||||
| 		return false; | ||||
| 	} | ||||
|  | ||||
| 	if (transactionCount_ <= 0) { | ||||
| 		return db_.commit(); | ||||
| 	} | ||||
|  | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| void Database::log(const QString &sql, const QSqlQuery &query) const { | ||||
| 	qDebug() <<"SQL:"<<sql; | ||||
|  | ||||
|   | ||||
| @@ -20,6 +20,8 @@ public: | ||||
| 	QSqlQuery buildSqlQuery(Database::QueryType type, const QString& tableName, const QStringList& fields, const VariantVector& values, const QString& whereCondition = ""); | ||||
| 	QSqlQuery buildSqlQuery(Database::QueryType type, const QString& tableName, const QMap<QString, QVariant>& values, const QString& whereCondition = ""); | ||||
| 	bool errorCheck(const QSqlQuery& query); | ||||
| 	bool transaction(); | ||||
| 	bool commit(); | ||||
|  | ||||
| private: | ||||
|  | ||||
| @@ -30,6 +32,7 @@ private: | ||||
| 	int version() const; | ||||
| 	mutable int version_; | ||||
| 	QStringList sqlStringToLines(const QString& sql); | ||||
| 	int transactionCount_; | ||||
|  | ||||
| }; | ||||
|  | ||||
|   | ||||
| @@ -5,9 +5,7 @@ | ||||
|  | ||||
| namespace jop { | ||||
|  | ||||
| enum ColType { UndefinedType, TextColType, IntColType }; | ||||
|  | ||||
| enum Table { UndefinedTable, FoldersTable, NotesTable }; | ||||
| enum Table { UndefinedTable, FoldersTable, NotesTable, ChangesTable }; | ||||
|  | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| #include "basemodel.h" | ||||
|  | ||||
| #include "models/change.h" | ||||
| #include "database.h" | ||||
| #include "uuid.h" | ||||
|  | ||||
| @@ -8,9 +9,7 @@ using namespace jop; | ||||
| QMap<int, QVector<BaseModel::Field>> BaseModel::tableFields_; | ||||
| QHash<QString, QVariant> BaseModel::cache_; | ||||
|  | ||||
| BaseModel::BaseModel() { | ||||
|  | ||||
| } | ||||
| BaseModel::BaseModel() {} | ||||
|  | ||||
| QStringList BaseModel::changedFields() const { | ||||
| 	QStringList output; | ||||
| @@ -26,7 +25,7 @@ int BaseModel::count(Table table) { | ||||
| 	QVariant r = BaseModel::cacheGet(k); | ||||
| 	if (r.isValid()) return r.toInt(); | ||||
|  | ||||
| 	QSqlQuery q = jop::db().query("SELECT count(*) as row_count FROM " + t); | ||||
| 	QSqlQuery q = jop::db().query("SELECT count(*) AS row_count FROM " + t); | ||||
| 	q.exec(); | ||||
| 	q.next(); | ||||
| 	int output = q.value(0).toInt(); | ||||
| @@ -47,10 +46,28 @@ bool BaseModel::save() { | ||||
| 		values[field] = value(field).toQVariant(); | ||||
| 	} | ||||
|  | ||||
| 	// If it's a new entry and the ID is a UUID, we need to create this | ||||
| 	// ID now. If the ID is an INT, it will be automatically set by | ||||
| 	// SQLite. | ||||
| 	if (isNew && primaryKeyIsUuid()) { | ||||
| 		values[primaryKey()] = uuid::createUuid(); | ||||
| 	} | ||||
|  | ||||
| 	// Update created_time and updated_time if needed. If updated_time | ||||
| 	// has already been updated (maybe manually by the user), don't | ||||
| 	// automatically update it. | ||||
| 	if (isNew) { | ||||
| 		if (BaseModel::hasField(table(), "created_time")) { | ||||
| 			values["created_time"] = (int)(QDateTime::currentMSecsSinceEpoch() / 1000); | ||||
| 		} | ||||
| 	} else { | ||||
| 		if (!values.contains("updated_time")) { | ||||
| 			if (BaseModel::hasField(table(), "updated_time")) { | ||||
| 				values["updated_time"] = (int)(QDateTime::currentMSecsSinceEpoch() / 1000); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	changedFields_.clear(); | ||||
|  | ||||
| 	const QString& tableName = BaseModel::tableName(table()); | ||||
| @@ -59,17 +76,43 @@ bool BaseModel::save() { | ||||
| 		cacheDelete(QString("%1:count").arg(tableName)); | ||||
| 	} | ||||
|  | ||||
| 	bool output = false; | ||||
|  | ||||
| 	jop::db().transaction(); | ||||
|  | ||||
| 	if (isNew) { | ||||
| 		QSqlQuery q = jop::db().buildSqlQuery(Database::Insert, tableName, values); | ||||
| 		q.exec(); | ||||
| 		return jop::db().errorCheck(q); | ||||
| 		output = jop::db().errorCheck(q); | ||||
| 		if (output) setValue("id", values["id"]); | ||||
| 	} else { | ||||
| 		QSqlQuery q = jop::db().buildSqlQuery(Database::Update, tableName, values, QString("%1 = '%2'").arg(primaryKey()).arg(value("id").toString())); | ||||
| 		q.exec(); | ||||
| 		return jop::db().errorCheck(q); | ||||
| 		output = jop::db().errorCheck(q); | ||||
| 	} | ||||
|  | ||||
| 	return false; // Unreachable | ||||
| 	if (output && trackChanges()) { | ||||
| 		if (isNew) { | ||||
| 			Change change; | ||||
| 			change.setValue("item_id", id()); | ||||
| 			change.setValue("item_type", table()); | ||||
| 			change.setValue("type", Change::Create); | ||||
| 			change.save(); | ||||
| 		} else { | ||||
| 			for (QMap<QString, QVariant>::const_iterator it = values.begin(); it != values.end(); ++it) { | ||||
| 				Change change; | ||||
| 				change.setValue("item_id", id()); | ||||
| 				change.setValue("item_type", table()); | ||||
| 				change.setValue("type", Change::Update); | ||||
| 				change.setValue("item_field", it.key()); | ||||
| 				change.save(); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	jop::db().commit(); | ||||
|  | ||||
| 	return output; | ||||
| } | ||||
|  | ||||
| bool BaseModel::dispose() { | ||||
| @@ -78,8 +121,20 @@ bool BaseModel::dispose() { | ||||
| 	q.prepare("DELETE FROM " + tableName + " WHERE " + primaryKey() + " = :id"); | ||||
| 	q.bindValue(":id", id().toString()); | ||||
| 	q.exec(); | ||||
| 	cacheDelete(QString("%1:count").arg(tableName)); | ||||
| 	return jop::db().errorCheck(q); | ||||
|  | ||||
| 	bool output = jop::db().errorCheck(q); | ||||
|  | ||||
| 	if (output) cacheDelete(QString("%1:count").arg(tableName)); | ||||
|  | ||||
| 	if (output && trackChanges()) { | ||||
| 		Change change; | ||||
| 		change.setValue("item_id", id()); | ||||
| 		change.setValue("item_type", table()); | ||||
| 		change.setValue("type", Change::Delete); | ||||
| 		change.save(); | ||||
| 	} | ||||
|  | ||||
| 	return output; | ||||
| } | ||||
|  | ||||
| Table BaseModel::table() const { | ||||
| @@ -95,6 +150,10 @@ bool BaseModel::primaryKeyIsUuid() const { | ||||
| 	return false; | ||||
| } | ||||
|  | ||||
| bool BaseModel::trackChanges() const { | ||||
| 	return false; | ||||
| } | ||||
|  | ||||
| bool BaseModel::isNew() const { | ||||
| 	return !valueIsSet(primaryKey()); | ||||
| } | ||||
| @@ -121,6 +180,14 @@ QVector<BaseModel::Field> BaseModel::tableFields(jop::Table table) { | ||||
| 	return output; | ||||
| } | ||||
|  | ||||
| bool BaseModel::hasField(jop::Table table, const QString &name) { | ||||
| 	QVector<BaseModel::Field> fields = tableFields(table); | ||||
| 	foreach (Field field, fields) { | ||||
| 		if (field.name == name) return true; | ||||
| 	} | ||||
| 	return false; | ||||
| } | ||||
|  | ||||
| QStringList BaseModel::tableFieldNames(Table table) { | ||||
| 	QVector<BaseModel::Field> fields = BaseModel::tableFields(table); | ||||
| 	QStringList output; | ||||
| @@ -202,6 +269,7 @@ BaseModel::Value BaseModel::id() const { | ||||
| QString BaseModel::tableName(Table t) { | ||||
| 	if (t == jop::FoldersTable) return "folders"; | ||||
| 	if (t == jop::NotesTable) return "notes"; | ||||
| 	if (t == jop::ChangesTable) return "changes"; | ||||
| 	return "UNDEFINED"; | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -3,7 +3,6 @@ | ||||
|  | ||||
| #include <stable.h> | ||||
|  | ||||
| #include "database.h" | ||||
| #include "enum.h" | ||||
|  | ||||
| namespace jop { | ||||
| @@ -49,10 +48,12 @@ public: | ||||
| 	virtual Table table() const; | ||||
| 	virtual QString primaryKey() const; | ||||
| 	virtual bool primaryKeyIsUuid() const; | ||||
| 	virtual bool trackChanges() const; | ||||
|  | ||||
| 	bool isNew() const; | ||||
|  | ||||
| 	static QVector<BaseModel::Field> tableFields(Table table); | ||||
| 	static bool hasField(jop::Table table, const QString& name); | ||||
| 	static QStringList tableFieldNames(Table table); | ||||
| 	static bool isValidFieldName(Table table, const QString& name); | ||||
|  | ||||
|   | ||||
| @@ -2,6 +2,8 @@ | ||||
|  | ||||
| using namespace jop; | ||||
|  | ||||
| Change::Change(Database &database) : database_(database) { | ||||
| Change::Change() {} | ||||
|  | ||||
| Table Change::table() const { | ||||
| 	return jop::ChangesTable; | ||||
| } | ||||
|   | ||||
| @@ -3,19 +3,18 @@ | ||||
|  | ||||
| #include <stable.h> | ||||
|  | ||||
| #include "database.h" | ||||
| #include "models/basemodel.h" | ||||
|  | ||||
| namespace jop { | ||||
|  | ||||
| class Change { | ||||
| class Change : public BaseModel { | ||||
|  | ||||
| public: | ||||
|  | ||||
| 	Change(Database& database); | ||||
| 	enum Type { Undefined, Create, Update, Delete }; | ||||
|  | ||||
| private: | ||||
|  | ||||
| 	Database& database_; | ||||
| 	Change(); | ||||
| 	Table table() const; | ||||
|  | ||||
| }; | ||||
|  | ||||
|   | ||||
| @@ -5,53 +5,25 @@ | ||||
|  | ||||
| using namespace jop; | ||||
|  | ||||
| Folder::Folder() : Item() { | ||||
| Folder::Folder() : Item() {} | ||||
|  | ||||
| Table Folder::table() const { | ||||
| 	return jop::FoldersTable; | ||||
| } | ||||
|  | ||||
| //bool Folder::isNew() const { | ||||
| //	return id().isEmpty(); | ||||
| //} | ||||
| bool Folder::primaryKeyIsUuid() const { | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| //bool Folder::save() { | ||||
| //	bool isNew = this->isNew(); | ||||
|  | ||||
| //	QStringList fields; | ||||
| //	VariantVector values; | ||||
| //	if (isNew) { | ||||
| //		setId(uuid::createUuid()); | ||||
| //		fields << "id"; | ||||
| //		values << id(); | ||||
| //	} | ||||
| //	fields << "title" << "synced"; | ||||
| //	values << title() << QVariant(0); | ||||
|  | ||||
| //	if (isNew) { | ||||
| //		QSqlQuery q = jop::db().buildSqlQuery(Database::Insert, "folders", fields, values); | ||||
| //		q.exec(); | ||||
| //		return jop::db().errorCheck(q); | ||||
| //	} else { | ||||
| //		QSqlQuery q = jop::db().buildSqlQuery(Database::Update, "folders", fields, values, "id = \"" + id() + "\""); | ||||
| //		q.exec(); | ||||
| //		return jop::db().errorCheck(q); | ||||
| //	} | ||||
| //} | ||||
|  | ||||
| //bool Folder::dispose() { | ||||
| //	return false; | ||||
| ////	QSqlQuery q(jop::db().database()); | ||||
| ////	q.prepare("DELETE FROM folders WHERE id = :id"); | ||||
| ////	q.bindValue(":id", id()); | ||||
| ////	q.exec(); | ||||
| ////	return jop::db().errorCheck(q); | ||||
| //} | ||||
| bool Folder::trackChanges() const { | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| int Folder::count() { | ||||
| 	return BaseModel::count(jop::FoldersTable); | ||||
| } | ||||
|  | ||||
| QVector<Folder> Folder::all(const QString &orderBy) { | ||||
| 	//QSqlQuery q = jop::db().query("SELECT " + Folder::dbFields().join(",") + " FROM folders ORDER BY " + orderBy); | ||||
| 	QSqlQuery q = jop::db().query("SELECT " + BaseModel::tableFieldNames(jop::FoldersTable).join(",") + " FROM folders ORDER BY " + orderBy); | ||||
| 	q.exec(); | ||||
|  | ||||
| @@ -61,23 +33,7 @@ QVector<Folder> Folder::all(const QString &orderBy) { | ||||
| 		Folder folder; | ||||
| 		folder.loadSqlQuery(q); | ||||
| 		output.push_back(folder); | ||||
|  | ||||
| //		Folder folder; | ||||
| //		folder.fromSqlQuery(q); | ||||
| //		output.push_back(folder); | ||||
|  | ||||
| //		Folder f2; | ||||
| //		f2.loadSqlQuery(q); | ||||
| //		qDebug() << "xxx" << f2.value("title").toString(); | ||||
| 	} | ||||
|  | ||||
| 	return output; | ||||
| } | ||||
|  | ||||
| Table Folder::table() const { | ||||
| 	return jop::FoldersTable; | ||||
| } | ||||
|  | ||||
| bool Folder::primaryKeyIsUuid() const { | ||||
| 	return true; | ||||
| } | ||||
|   | ||||
| @@ -17,6 +17,7 @@ public: | ||||
|  | ||||
| 	Table table() const; | ||||
| 	bool primaryKeyIsUuid() const; | ||||
| 	bool trackChanges() const; | ||||
|  | ||||
| private: | ||||
|  | ||||
|   | ||||
| @@ -66,9 +66,10 @@ CREATE TABLE version ( | ||||
|  | ||||
| CREATE TABLE changes ( | ||||
|     id INTEGER PRIMARY KEY, | ||||
| 	`type` INT, | ||||
| 	item_id TEXT, | ||||
| 	item_type INT, | ||||
| 	item_property TEXT | ||||
| 	item_field TEXT | ||||
| ); | ||||
|  | ||||
| --CREATE TABLE mimetypes ( | ||||
|   | ||||
		Reference in New Issue
	
	Block a user