From 629fe42c5918b5aaf82adcc8476c6ed82db36ff2 Mon Sep 17 00:00:00 2001 From: Laurent Cozic Date: Mon, 2 Jan 2017 15:04:27 +0100 Subject: [PATCH] Record changes --- QtClient/JoplinQtClient/database.cpp | 27 +++++- QtClient/JoplinQtClient/database.h | 3 + QtClient/JoplinQtClient/enum.h | 4 +- QtClient/JoplinQtClient/models/basemodel.cpp | 86 ++++++++++++++++++-- QtClient/JoplinQtClient/models/basemodel.h | 3 +- QtClient/JoplinQtClient/models/change.cpp | 4 +- QtClient/JoplinQtClient/models/change.h | 11 ++- QtClient/JoplinQtClient/models/folder.cpp | 62 ++------------ QtClient/JoplinQtClient/models/folder.h | 1 + QtClient/JoplinQtClient/schema.sql | 3 +- 10 files changed, 127 insertions(+), 77 deletions(-) diff --git a/QtClient/JoplinQtClient/database.cpp b/QtClient/JoplinQtClient/database.cpp index b6b1450c5..335076b72 100755 --- a/QtClient/JoplinQtClient/database.cpp +++ b/QtClient/JoplinQtClient/database.cpp @@ -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:"<& 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_; }; diff --git a/QtClient/JoplinQtClient/enum.h b/QtClient/JoplinQtClient/enum.h index b707ed651..b6dc51527 100755 --- a/QtClient/JoplinQtClient/enum.h +++ b/QtClient/JoplinQtClient/enum.h @@ -5,9 +5,7 @@ namespace jop { -enum ColType { UndefinedType, TextColType, IntColType }; - -enum Table { UndefinedTable, FoldersTable, NotesTable }; +enum Table { UndefinedTable, FoldersTable, NotesTable, ChangesTable }; } diff --git a/QtClient/JoplinQtClient/models/basemodel.cpp b/QtClient/JoplinQtClient/models/basemodel.cpp index b65955e33..a5cbcd4fd 100755 --- a/QtClient/JoplinQtClient/models/basemodel.cpp +++ b/QtClient/JoplinQtClient/models/basemodel.cpp @@ -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> BaseModel::tableFields_; QHash 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::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::tableFields(jop::Table table) { return output; } +bool BaseModel::hasField(jop::Table table, const QString &name) { + QVector fields = tableFields(table); + foreach (Field field, fields) { + if (field.name == name) return true; + } + return false; +} + QStringList BaseModel::tableFieldNames(Table table) { QVector 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"; } diff --git a/QtClient/JoplinQtClient/models/basemodel.h b/QtClient/JoplinQtClient/models/basemodel.h index 2033b2df6..ec0e96653 100755 --- a/QtClient/JoplinQtClient/models/basemodel.h +++ b/QtClient/JoplinQtClient/models/basemodel.h @@ -3,7 +3,6 @@ #include -#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 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); diff --git a/QtClient/JoplinQtClient/models/change.cpp b/QtClient/JoplinQtClient/models/change.cpp index e89943a48..2181515a4 100755 --- a/QtClient/JoplinQtClient/models/change.cpp +++ b/QtClient/JoplinQtClient/models/change.cpp @@ -2,6 +2,8 @@ using namespace jop; -Change::Change(Database &database) : database_(database) { +Change::Change() {} +Table Change::table() const { + return jop::ChangesTable; } diff --git a/QtClient/JoplinQtClient/models/change.h b/QtClient/JoplinQtClient/models/change.h index dd00f59ef..c8adee327 100755 --- a/QtClient/JoplinQtClient/models/change.h +++ b/QtClient/JoplinQtClient/models/change.h @@ -3,19 +3,18 @@ #include -#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; }; diff --git a/QtClient/JoplinQtClient/models/folder.cpp b/QtClient/JoplinQtClient/models/folder.cpp index a73e32d4e..5aec95aed 100755 --- a/QtClient/JoplinQtClient/models/folder.cpp +++ b/QtClient/JoplinQtClient/models/folder.cpp @@ -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::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::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; -} diff --git a/QtClient/JoplinQtClient/models/folder.h b/QtClient/JoplinQtClient/models/folder.h index 50e6d2b1b..1bc67cb48 100755 --- a/QtClient/JoplinQtClient/models/folder.h +++ b/QtClient/JoplinQtClient/models/folder.h @@ -17,6 +17,7 @@ public: Table table() const; bool primaryKeyIsUuid() const; + bool trackChanges() const; private: diff --git a/QtClient/JoplinQtClient/schema.sql b/QtClient/JoplinQtClient/schema.sql index 629829ceb..27a878bc6 100755 --- a/QtClient/JoplinQtClient/schema.sql +++ b/QtClient/JoplinQtClient/schema.sql @@ -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 (