You've already forked joplin
mirror of
https://github.com/laurent22/joplin.git
synced 2025-07-16 00:14:34 +02:00
Download new items during sync
This commit is contained in:
@ -15,25 +15,13 @@ Application::Application(int &argc, char **argv) :
|
|||||||
QGuiApplication(argc, argv),
|
QGuiApplication(argc, argv),
|
||||||
db_(jop::db()),
|
db_(jop::db()),
|
||||||
api_("http://joplin.local"),
|
api_("http://joplin.local"),
|
||||||
synchronizer_(api_, db_),
|
synchronizer_(api_.baseUrl(), db_),
|
||||||
folderModel_(db_)
|
folderModel_(db_)
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|
||||||
jop::db().initialize("D:/Web/www/joplin/QtClient/data/notes.sqlite");
|
jop::db().initialize("D:/Web/www/joplin/QtClient/data/notes.sqlite");
|
||||||
|
|
||||||
// QVector<Change> changes = Change::all();
|
|
||||||
// foreach (Change change, changes) {
|
|
||||||
// qDebug() << change.value("item_id").toString() << change.value("type").toInt() << change.mergedFields();
|
|
||||||
// }
|
|
||||||
|
|
||||||
// qDebug() << "=====================================";
|
|
||||||
|
|
||||||
// changes = Change::mergedChanges(changes);
|
|
||||||
// foreach (Change change, changes) {
|
|
||||||
// qDebug() << change.value("item_id").toString() << change.value("type").toInt() << change.mergedFields();
|
|
||||||
// }
|
|
||||||
|
|
||||||
// This is linked to where the QSettings will be saved. In other words,
|
// This is linked to where the QSettings will be saved. In other words,
|
||||||
// if these values are changed, the settings will be reset and saved
|
// if these values are changed, the settings will be reset and saved
|
||||||
// somewhere else.
|
// somewhere else.
|
||||||
@ -57,25 +45,25 @@ Application::Application(int &argc, char **argv) :
|
|||||||
connect(rootObject, SIGNAL(currentNoteChanged()), this, SLOT(view_currentNoteChanged()));
|
connect(rootObject, SIGNAL(currentNoteChanged()), this, SLOT(view_currentNoteChanged()));
|
||||||
connect(rootObject, SIGNAL(addFolderButtonClicked()), this, SLOT(view_addFolderButtonClicked()));
|
connect(rootObject, SIGNAL(addFolderButtonClicked()), this, SLOT(view_addFolderButtonClicked()));
|
||||||
|
|
||||||
|
connect(&dispatcher(), SIGNAL(folderCreated(QString)), this, SLOT(dispatcher_folderCreated(QString)));
|
||||||
|
connect(&dispatcher(), SIGNAL(folderUpdated(QString)), this, SLOT(dispatcher_folderUpdated(QString)));
|
||||||
|
connect(&dispatcher(), SIGNAL(folderDeleted(QString)), this, SLOT(dispatcher_folderDeleted(QString)));
|
||||||
|
|
||||||
view_.show();
|
view_.show();
|
||||||
|
|
||||||
|
synchronizerTimer_.setInterval(1000 * 60);
|
||||||
|
synchronizerTimer_.start();
|
||||||
|
|
||||||
|
connect(&synchronizerTimer_, SIGNAL(timeout()), this, SLOT(synchronizerTimer_timeout()));
|
||||||
|
|
||||||
connect(&api_, SIGNAL(requestDone(const QJsonObject&, const QString&)), this, SLOT(api_requestDone(const QJsonObject&, const QString&)));
|
connect(&api_, SIGNAL(requestDone(const QJsonObject&, const QString&)), this, SLOT(api_requestDone(const QJsonObject&, const QString&)));
|
||||||
|
|
||||||
QString sessionId = settings.value("sessionId").toString();
|
QString sessionId = settings.value("sessionId").toString();
|
||||||
//if (sessionId == "") {
|
QUrlQuery postData;
|
||||||
QUrlQuery postData;
|
postData.addQueryItem("email", "laurent@cozic.net");
|
||||||
postData.addQueryItem("email", "laurent@cozic.net");
|
postData.addQueryItem("password", "12345678");
|
||||||
postData.addQueryItem("password", "12345678");
|
postData.addQueryItem("client_id", "B6E12222B6E12222");
|
||||||
postData.addQueryItem("client_id", "B6E12222B6E12222");
|
api_.post("sessions", QUrlQuery(), postData, "getSession");
|
||||||
api_.post("sessions", QUrlQuery(), postData, "getSession");
|
|
||||||
// } else {
|
|
||||||
// afterSessionInitialization();
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//emit jop::dispatcher().folderCreated("test");
|
|
||||||
//.folderCreated("tes");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Application::api_requestDone(const QJsonObject& response, const QString& tag) {
|
void Application::api_requestDone(const QJsonObject& response, const QString& tag) {
|
||||||
@ -90,6 +78,26 @@ void Application::api_requestDone(const QJsonObject& response, const QString& ta
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Application::dispatcher_folderCreated(const QString &folderId) {
|
||||||
|
qDebug() << "Folder created" << folderId;
|
||||||
|
synchronizerTimer_.start(1000 * 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::dispatcher_folderUpdated(const QString &folderId) {
|
||||||
|
qDebug() << "Folder udpated" << folderId;
|
||||||
|
synchronizerTimer_.start(1000 * 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::dispatcher_folderDeleted(const QString &folderId) {
|
||||||
|
qDebug() << "Folder deleted" << folderId;
|
||||||
|
synchronizerTimer_.start(1000 * 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::synchronizerTimer_timeout() {
|
||||||
|
synchronizerTimer_.start(1000 * 60);
|
||||||
|
synchronizer_.start();
|
||||||
|
}
|
||||||
|
|
||||||
QString Application::selectedFolderId() const {
|
QString Application::selectedFolderId() const {
|
||||||
QObject* rootObject = (QObject*)view_.rootObject();
|
QObject* rootObject = (QObject*)view_.rootObject();
|
||||||
|
|
||||||
@ -114,6 +122,7 @@ void Application::afterSessionInitialization() {
|
|||||||
QString sessionId = settings.value("sessionId").toString();
|
QString sessionId = settings.value("sessionId").toString();
|
||||||
qDebug() << "Session:" << sessionId;
|
qDebug() << "Session:" << sessionId;
|
||||||
api_.setSessionId(sessionId);
|
api_.setSessionId(sessionId);
|
||||||
|
synchronizer_.setSessionId(sessionId);
|
||||||
synchronizer_.start();
|
synchronizer_.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,6 +33,7 @@ private:
|
|||||||
QmlNote selectedQmlNote_;
|
QmlNote selectedQmlNote_;
|
||||||
WebApi api_;
|
WebApi api_;
|
||||||
Synchronizer synchronizer_;
|
Synchronizer synchronizer_;
|
||||||
|
QTimer synchronizerTimer_;
|
||||||
|
|
||||||
void afterSessionInitialization();
|
void afterSessionInitialization();
|
||||||
|
|
||||||
@ -44,6 +45,12 @@ public slots:
|
|||||||
void view_addFolderButtonClicked();
|
void view_addFolderButtonClicked();
|
||||||
void api_requestDone(const QJsonObject& response, const QString& tag);
|
void api_requestDone(const QJsonObject& response, const QString& tag);
|
||||||
|
|
||||||
|
void dispatcher_folderCreated(const QString& folderId);
|
||||||
|
void dispatcher_folderUpdated(const QString& folderId);
|
||||||
|
void dispatcher_folderDeleted(const QString& folderId);
|
||||||
|
|
||||||
|
void synchronizerTimer_timeout();
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -2,16 +2,22 @@
|
|||||||
|
|
||||||
using namespace jop;
|
using namespace jop;
|
||||||
|
|
||||||
Dispatcher::Dispatcher() {
|
Dispatcher::Dispatcher() {}
|
||||||
|
|
||||||
|
void Dispatcher::emitFolderCreated(const QString &folderId) {
|
||||||
|
emit folderCreated(folderId);
|
||||||
}
|
}
|
||||||
|
|
||||||
Dispatcher instance_;
|
void Dispatcher::emitFolderUpdated(const QString &folderId) {
|
||||||
|
emit folderUpdated(folderId);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Dispatcher::emitFolderDeleted(const QString &folderId) {
|
||||||
|
emit folderDeleted(folderId);
|
||||||
|
}
|
||||||
|
|
||||||
|
Dispatcher dispatcherInstance_;
|
||||||
|
|
||||||
Dispatcher& jop::dispatcher() {
|
Dispatcher& jop::dispatcher() {
|
||||||
return instance_;
|
return dispatcherInstance_;
|
||||||
}
|
}
|
||||||
|
|
||||||
//Dispatcher &Dispatcher::instance() {
|
|
||||||
// return instance_;
|
|
||||||
//}
|
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
#ifndef DISPATCHER_H
|
#ifndef DISPATCHER_H
|
||||||
#define DISPATCHER_H
|
#define DISPATCHER_H
|
||||||
|
|
||||||
|
#include <stable.h>
|
||||||
|
|
||||||
namespace jop {
|
namespace jop {
|
||||||
|
|
||||||
class Dispatcher : public QObject {
|
class Dispatcher : public QObject {
|
||||||
@ -10,15 +12,15 @@ class Dispatcher : public QObject {
|
|||||||
public:
|
public:
|
||||||
|
|
||||||
Dispatcher();
|
Dispatcher();
|
||||||
//static Dispatcher& instance();
|
void emitFolderCreated(const QString& folderId);
|
||||||
|
void emitFolderUpdated(const QString& folderId);
|
||||||
|
void emitFolderDeleted(const QString& folderId);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
|
|
||||||
void folderCreated(const QString& id);
|
void folderCreated(const QString& folderId);
|
||||||
|
void folderUpdated(const QString& folderId);
|
||||||
private:
|
void folderDeleted(const QString& folderId);
|
||||||
|
|
||||||
//static Dispatcher& instance_;
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
#include "basemodel.h"
|
#include "basemodel.h"
|
||||||
|
|
||||||
|
#include "dispatcher.h"
|
||||||
#include "models/change.h"
|
#include "models/change.h"
|
||||||
#include "database.h"
|
#include "database.h"
|
||||||
#include "uuid.h"
|
#include "uuid.h"
|
||||||
@ -9,10 +10,8 @@ using namespace jop;
|
|||||||
QMap<int, QVector<BaseModel::Field>> BaseModel::tableFields_;
|
QMap<int, QVector<BaseModel::Field>> BaseModel::tableFields_;
|
||||||
QHash<QString, QVariant> BaseModel::cache_;
|
QHash<QString, QVariant> BaseModel::cache_;
|
||||||
|
|
||||||
BaseModel::BaseModel() {}
|
BaseModel::BaseModel() {
|
||||||
|
isNew_ = -1;
|
||||||
BaseModel::BaseModel(const QSqlQuery &query) {
|
|
||||||
loadSqlQuery(query);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QStringList BaseModel::changedFields() const {
|
QStringList BaseModel::changedFields() const {
|
||||||
@ -92,22 +91,22 @@ bool BaseModel::save() {
|
|||||||
cacheDelete(QString("%1:count").arg(tableName));
|
cacheDelete(QString("%1:count").arg(tableName));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool output = false;
|
bool isSaved = false;
|
||||||
|
|
||||||
jop::db().transaction();
|
jop::db().transaction();
|
||||||
|
|
||||||
if (isNew) {
|
if (isNew) {
|
||||||
QSqlQuery q = jop::db().buildSqlQuery(Database::Insert, tableName, values);
|
QSqlQuery q = jop::db().buildSqlQuery(Database::Insert, tableName, values);
|
||||||
jop::db().execQuery(q);
|
jop::db().execQuery(q);
|
||||||
output = jop::db().errorCheck(q);
|
isSaved = jop::db().errorCheck(q);
|
||||||
if (output) setValue("id", values["id"]);
|
if (isSaved) setValue("id", values["id"]);
|
||||||
} else {
|
} else {
|
||||||
QSqlQuery q = jop::db().buildSqlQuery(Database::Update, tableName, values, QString("%1 = '%2'").arg(primaryKey()).arg(value("id").toString()));
|
QSqlQuery q = jop::db().buildSqlQuery(Database::Update, tableName, values, QString("%1 = '%2'").arg(primaryKey()).arg(value("id").toString()));
|
||||||
jop::db().execQuery(q);
|
jop::db().execQuery(q);
|
||||||
output = jop::db().errorCheck(q);
|
isSaved = jop::db().errorCheck(q);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (output && trackChanges()) {
|
if (isSaved && trackChanges()) {
|
||||||
if (isNew) {
|
if (isNew) {
|
||||||
Change change;
|
Change change;
|
||||||
change.setValue("item_id", id());
|
change.setValue("item_id", id());
|
||||||
@ -128,7 +127,17 @@ bool BaseModel::save() {
|
|||||||
|
|
||||||
jop::db().commit();
|
jop::db().commit();
|
||||||
|
|
||||||
return output;
|
if (isSaved && table() == jop::FoldersTable) {
|
||||||
|
if (isNew) {
|
||||||
|
dispatcher().emitFolderCreated(id().toString());
|
||||||
|
} else {
|
||||||
|
dispatcher().emitFolderUpdated(id().toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isNew_ = -1;
|
||||||
|
|
||||||
|
return isSaved;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool BaseModel::dispose() {
|
bool BaseModel::dispose() {
|
||||||
@ -138,11 +147,11 @@ bool BaseModel::dispose() {
|
|||||||
q.bindValue(":id", id().toString());
|
q.bindValue(":id", id().toString());
|
||||||
jop::db().execQuery(q);
|
jop::db().execQuery(q);
|
||||||
|
|
||||||
bool output = jop::db().errorCheck(q);
|
bool isDeleted = jop::db().errorCheck(q);
|
||||||
|
|
||||||
if (output) cacheDelete(QString("%1:count").arg(tableName));
|
if (isDeleted) cacheDelete(QString("%1:count").arg(tableName));
|
||||||
|
|
||||||
if (output && trackChanges()) {
|
if (isDeleted && trackChanges()) {
|
||||||
Change change;
|
Change change;
|
||||||
change.setValue("item_id", id());
|
change.setValue("item_id", id());
|
||||||
change.setValue("item_type", table());
|
change.setValue("item_type", table());
|
||||||
@ -150,7 +159,11 @@ bool BaseModel::dispose() {
|
|||||||
change.save();
|
change.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
return output;
|
if (isDeleted && table() == jop::FoldersTable) {
|
||||||
|
dispatcher().emitFolderDeleted(id().toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
return isDeleted;
|
||||||
}
|
}
|
||||||
|
|
||||||
Table BaseModel::table() const {
|
Table BaseModel::table() const {
|
||||||
@ -171,6 +184,8 @@ bool BaseModel::trackChanges() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool BaseModel::isNew() const {
|
bool BaseModel::isNew() const {
|
||||||
|
if (isNew_ == 0) return false;
|
||||||
|
if (isNew_ == 1) return true;
|
||||||
return !valueIsSet(primaryKey());
|
return !valueIsSet(primaryKey());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -228,6 +243,9 @@ bool BaseModel::isValidFieldName(Table table, const QString &name) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// When loading a QSqlQuery, all the values are cleared and replaced by those
|
||||||
|
// from the QSqlQuery. All the fields are marked as NOT changed as it's assumed
|
||||||
|
// the object is already in the database (since loaded from there).
|
||||||
void BaseModel::loadSqlQuery(const QSqlQuery &query) {
|
void BaseModel::loadSqlQuery(const QSqlQuery &query) {
|
||||||
values_.clear();
|
values_.clear();
|
||||||
QSqlRecord record = query.record();
|
QSqlRecord record = query.record();
|
||||||
@ -241,17 +259,45 @@ void BaseModel::loadSqlQuery(const QSqlQuery &query) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (field.type == QMetaType::QString) {
|
if (field.type == QMetaType::QString) {
|
||||||
values_.insert(field.name, Value(query.value(idx).toString()));
|
//values_.insert(field.name, Value(query.value(idx).toString()));
|
||||||
|
setValue(field.name, query.value(idx).toString());
|
||||||
} else if (field.type == QMetaType::Int) {
|
} else if (field.type == QMetaType::Int) {
|
||||||
values_.insert(field.name, Value(query.value(idx).toInt()));
|
//values_.insert(field.name, Value(query.value(idx).toInt()));
|
||||||
|
setValue(field.name, query.value(idx).toInt());
|
||||||
} else {
|
} else {
|
||||||
qCritical() << "Unsupported value type" << field.name;
|
qCritical() << "Unsupported value type" << field.name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isNew_ = -1;
|
||||||
|
|
||||||
changedFields_.clear();
|
changedFields_.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// When loading a QJsonObject, all the values are cleared and replaced by those
|
||||||
|
// from the QJsonObject. All the fields are marked as changed since it's
|
||||||
|
// assumed that the object comes from the web service.
|
||||||
|
void BaseModel::loadJsonObject(const QJsonObject &jsonObject) {
|
||||||
|
values_.clear();
|
||||||
|
changedFields_.clear();
|
||||||
|
|
||||||
|
QVector<BaseModel::Field> fields = BaseModel::tableFields(table());
|
||||||
|
|
||||||
|
foreach (BaseModel::Field field, fields) {
|
||||||
|
if (field.type == QMetaType::QString) {
|
||||||
|
//values_.insert(field.name, Value(jsonObject[field.name].toString()));
|
||||||
|
setValue(field.name, jsonObject[field.name].toString());
|
||||||
|
} else if (field.type == QMetaType::Int) {
|
||||||
|
//values_.insert(field.name, Value(jsonObject[field.name].toInt()));
|
||||||
|
setValue(field.name, jsonObject[field.name].toInt());
|
||||||
|
} else {
|
||||||
|
qCritical() << "Unsupported value type" << field.name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isNew_ = 1;
|
||||||
|
}
|
||||||
|
|
||||||
QHash<QString, BaseModel::Value> BaseModel::values() const {
|
QHash<QString, BaseModel::Value> BaseModel::values() const {
|
||||||
return values_;
|
return values_;
|
||||||
}
|
}
|
||||||
|
@ -40,12 +40,11 @@ public:
|
|||||||
};
|
};
|
||||||
|
|
||||||
BaseModel();
|
BaseModel();
|
||||||
BaseModel(const QSqlQuery& query);
|
|
||||||
QStringList changedFields() const;
|
QStringList changedFields() const;
|
||||||
static int count(jop::Table table);
|
static int count(jop::Table table);
|
||||||
bool load(const QString& id);
|
bool load(const QString& id);
|
||||||
bool save();
|
virtual bool save();
|
||||||
bool dispose();
|
virtual bool dispose();
|
||||||
|
|
||||||
virtual Table table() const;
|
virtual Table table() const;
|
||||||
virtual QString primaryKey() const;
|
virtual QString primaryKey() const;
|
||||||
@ -60,6 +59,7 @@ public:
|
|||||||
static bool isValidFieldName(Table table, const QString& name);
|
static bool isValidFieldName(Table table, const QString& name);
|
||||||
|
|
||||||
void loadSqlQuery(const QSqlQuery& query);
|
void loadSqlQuery(const QSqlQuery& query);
|
||||||
|
void loadJsonObject(const QJsonObject& jsonObject);
|
||||||
QHash<QString, Value> values() const;
|
QHash<QString, Value> values() const;
|
||||||
Value value(const QString& name) const;
|
Value value(const QString& name) const;
|
||||||
bool valueIsSet(const QString& name) const;
|
bool valueIsSet(const QString& name) const;
|
||||||
@ -84,6 +84,8 @@ protected:
|
|||||||
static QMap<int, QVector<BaseModel::Field>> tableFields_;
|
static QMap<int, QVector<BaseModel::Field>> tableFields_;
|
||||||
static QHash<QString, QVariant> cache_;
|
static QHash<QString, QVariant> cache_;
|
||||||
|
|
||||||
|
int isNew_;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -3,12 +3,6 @@
|
|||||||
|
|
||||||
using namespace jop;
|
using namespace jop;
|
||||||
|
|
||||||
Change::Change() {}
|
|
||||||
|
|
||||||
Change::Change(const QSqlQuery &query) {
|
|
||||||
loadSqlQuery(query);
|
|
||||||
}
|
|
||||||
|
|
||||||
Table Change::table() const {
|
Table Change::table() const {
|
||||||
return jop::ChangesTable;
|
return jop::ChangesTable;
|
||||||
}
|
}
|
||||||
@ -25,7 +19,8 @@ QVector<Change> Change::all(int limit) {
|
|||||||
QVector<Change> output;
|
QVector<Change> output;
|
||||||
|
|
||||||
while (q.next()) {
|
while (q.next()) {
|
||||||
Change change(q);
|
Change change;
|
||||||
|
change.loadSqlQuery(q);
|
||||||
output.push_back(change);
|
output.push_back(change);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,8 +13,6 @@ public:
|
|||||||
|
|
||||||
enum Type { Undefined, Create, Update, Delete };
|
enum Type { Undefined, Create, Update, Delete };
|
||||||
|
|
||||||
Change();
|
|
||||||
Change(const QSqlQuery& query);
|
|
||||||
Table table() const;
|
Table table() const;
|
||||||
|
|
||||||
static QVector<Change> all(int limit = 100);
|
static QVector<Change> all(int limit = 100);
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
#include "models/folder.h"
|
#include "models/folder.h"
|
||||||
|
|
||||||
|
#include "dispatcher.h"
|
||||||
#include "database.h"
|
#include "database.h"
|
||||||
#include "uuid.h"
|
#include "uuid.h"
|
||||||
|
|
||||||
|
@ -19,6 +19,9 @@ public:
|
|||||||
bool primaryKeyIsUuid() const;
|
bool primaryKeyIsUuid() const;
|
||||||
bool trackChanges() const;
|
bool trackChanges() const;
|
||||||
|
|
||||||
|
// bool save();
|
||||||
|
// bool dispose();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
};
|
};
|
||||||
|
@ -26,9 +26,7 @@
|
|||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
#include <QJsonParseError>
|
#include <QJsonParseError>
|
||||||
#include <QBuffer>
|
#include <QBuffer>
|
||||||
|
#include <QJsonArray>
|
||||||
|
|
||||||
|
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
|
|
||||||
#endif // __cplusplus
|
#endif // __cplusplus
|
||||||
|
@ -4,16 +4,29 @@
|
|||||||
|
|
||||||
using namespace jop;
|
using namespace jop;
|
||||||
|
|
||||||
Synchronizer::Synchronizer(WebApi& api, Database &database) : api_(api), db_(database) {
|
Synchronizer::Synchronizer(const QString &apiUrl, Database &database) : api_(apiUrl), db_(database) {
|
||||||
qDebug() << api_.baseUrl();
|
qDebug() << api_.baseUrl();
|
||||||
|
state_ = Idle;
|
||||||
|
uploadsRemaining_ = 0;
|
||||||
|
downloadsRemaining_ = 0;
|
||||||
connect(&api_, SIGNAL(requestDone(QJsonObject,QString)), this, SLOT(api_requestDone(QJsonObject,QString)));
|
connect(&api_, SIGNAL(requestDone(QJsonObject,QString)), this, SLOT(api_requestDone(QJsonObject,QString)));
|
||||||
}
|
}
|
||||||
|
|
||||||
void Synchronizer::start() {
|
void Synchronizer::start() {
|
||||||
|
if (state_ != Idle) {
|
||||||
|
qWarning() << "Cannot start synchronizer because synchronization already in progress. State: " << state_;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
qDebug() << "Starting synchronizer...";
|
qDebug() << "Starting synchronizer...";
|
||||||
|
|
||||||
|
state_ = UploadingChanges;
|
||||||
|
|
||||||
QVector<Change> changes = Change::all();
|
QVector<Change> changes = Change::all();
|
||||||
changes = Change::mergedChanges(changes);
|
changes = Change::mergedChanges(changes);
|
||||||
|
|
||||||
|
uploadsRemaining_ = changes.size();
|
||||||
|
|
||||||
foreach (Change change, changes) {
|
foreach (Change change, changes) {
|
||||||
jop::Table itemType = (jop::Table)change.value("item_type").toInt();
|
jop::Table itemType = (jop::Table)change.value("item_type").toInt();
|
||||||
QString itemId = change.value("item_id").toString();
|
QString itemId = change.value("item_id").toString();
|
||||||
@ -28,7 +41,7 @@ void Synchronizer::start() {
|
|||||||
Folder folder;
|
Folder folder;
|
||||||
folder.load(itemId);
|
folder.load(itemId);
|
||||||
QUrlQuery data = valuesToUrlQuery(folder.values());
|
QUrlQuery data = valuesToUrlQuery(folder.values());
|
||||||
api_.put("folders/" + folder.id().toString(), QUrlQuery(), data, "putFolder:" + folder.id().toString());
|
api_.put("folders/" + folder.id().toString(), QUrlQuery(), data, "upload:putFolder:" + folder.id().toString());
|
||||||
|
|
||||||
} else if (type == Change::Update) {
|
} else if (type == Change::Update) {
|
||||||
|
|
||||||
@ -39,15 +52,23 @@ void Synchronizer::start() {
|
|||||||
foreach (QString field, mergedFields) {
|
foreach (QString field, mergedFields) {
|
||||||
data.addQueryItem(field, folder.value(field).toString());
|
data.addQueryItem(field, folder.value(field).toString());
|
||||||
}
|
}
|
||||||
api_.patch("folders/" + folder.id().toString(), QUrlQuery(), data, "patchFolder:" + folder.id().toString());
|
api_.patch("folders/" + folder.id().toString(), QUrlQuery(), data, "upload:patchFolder:" + folder.id().toString());
|
||||||
|
|
||||||
} else if (type == Change::Delete) {
|
} else if (type == Change::Delete) {
|
||||||
|
|
||||||
api_.del("folders/" + itemId, QUrlQuery(), QUrlQuery(), "deleteFolder:" + itemId);
|
api_.del("folders/" + itemId, QUrlQuery(), QUrlQuery(), "upload:deleteFolder:" + itemId);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!uploadsRemaining_) {
|
||||||
|
downloadChanges();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Synchronizer::setSessionId(const QString &v) {
|
||||||
|
api_.setSessionId(v);
|
||||||
}
|
}
|
||||||
|
|
||||||
QUrlQuery Synchronizer::valuesToUrlQuery(const QHash<QString, Change::Value>& values) const {
|
QUrlQuery Synchronizer::valuesToUrlQuery(const QHash<QString, Change::Value>& values) const {
|
||||||
@ -58,31 +79,90 @@ QUrlQuery Synchronizer::valuesToUrlQuery(const QHash<QString, Change::Value>& va
|
|||||||
return query;
|
return query;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Synchronizer::api_requestDone(const QJsonObject& response, const QString& tag) {
|
void Synchronizer::downloadChanges() {
|
||||||
qDebug() << "WebApi: done" << tag;
|
state_ = DownloadingChanges;
|
||||||
|
//QUrlQuery data = valuesToUrlQuery(folder.values());
|
||||||
|
api_.get("synchronizer", QUrlQuery(), QUrlQuery(), "download:getSynchronizer");
|
||||||
|
}
|
||||||
|
|
||||||
|
void Synchronizer::api_requestDone(const QJsonObject& response, const QString& tag) {
|
||||||
QStringList parts = tag.split(':');
|
QStringList parts = tag.split(':');
|
||||||
QString action = tag;
|
QString category = parts[0];
|
||||||
|
QString action = parts[1];
|
||||||
QString id = "";
|
QString id = "";
|
||||||
|
|
||||||
if (parts.size() == 2) {
|
if (parts.size() == 3) {
|
||||||
action = parts[0];
|
id = parts[2];
|
||||||
id = parts[1];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
qDebug() << "WebApi: done" << category << action << id;
|
||||||
|
|
||||||
// TODO: check for error
|
// TODO: check for error
|
||||||
|
|
||||||
qDebug() << "Synced folder" << id;
|
if (category == "upload") {
|
||||||
|
uploadsRemaining_--;
|
||||||
|
|
||||||
if (action == "putFolder") {
|
qDebug() << "Synced folder" << id;
|
||||||
Change::disposeByItemId(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (action == "patchFolder") {
|
if (action == "putFolder") {
|
||||||
Change::disposeByItemId(id);
|
Change::disposeByItemId(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (action == "deleteFolder") {
|
if (action == "patchFolder") {
|
||||||
Change::disposeByItemId(id);
|
Change::disposeByItemId(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action == "deleteFolder") {
|
||||||
|
Change::disposeByItemId(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uploadsRemaining_ < 0) {
|
||||||
|
qWarning() << "Mismatch on operations done:" << uploadsRemaining_;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uploadsRemaining_ <= 0) {
|
||||||
|
uploadsRemaining_ = 0;
|
||||||
|
downloadChanges();
|
||||||
|
}
|
||||||
|
} else if (category == "download") {
|
||||||
|
if (action == "getSynchronizer") {
|
||||||
|
QJsonArray items = response["items"].toArray();
|
||||||
|
foreach (QJsonValue item, items) {
|
||||||
|
QJsonObject obj = item.toObject();
|
||||||
|
QString itemId = obj["item_id"].toString();
|
||||||
|
QString itemType = obj["item_type"].toString();
|
||||||
|
QString operationType = obj["type"].toString();
|
||||||
|
|
||||||
|
QString path = itemType + "s"; // That should remain true
|
||||||
|
|
||||||
|
if (operationType == "create") {
|
||||||
|
api_.get(path + "/" + itemId, QUrlQuery(), QUrlQuery(), "download:getFolder:" + itemId);
|
||||||
|
}
|
||||||
|
|
||||||
|
downloadsRemaining_++;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
downloadsRemaining_--;
|
||||||
|
|
||||||
|
if (action == "getFolder") {
|
||||||
|
Folder folder;
|
||||||
|
folder.loadJsonObject(response);
|
||||||
|
folder.save();
|
||||||
|
|
||||||
|
// TODO: save last rev ID
|
||||||
|
}
|
||||||
|
|
||||||
|
if (downloadsRemaining_ < 0) {
|
||||||
|
qCritical() << "Mismatch on download operations done" << downloadsRemaining_;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (downloadsRemaining_ <= 0) {
|
||||||
|
qDebug() << "All download operations complete";
|
||||||
|
downloadsRemaining_ = 0;
|
||||||
|
state_ = Idle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
qCritical() << "Invalid category" << category;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,14 +14,21 @@ class Synchronizer : public QObject {
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
Synchronizer(WebApi &api, Database& database);
|
enum SynchronizationState { Idle, UploadingChanges, DownloadingChanges };
|
||||||
|
|
||||||
|
Synchronizer(const QString& apiUrl, Database& database);
|
||||||
void start();
|
void start();
|
||||||
|
void setSessionId(const QString& v);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
QUrlQuery valuesToUrlQuery(const QHash<QString, BaseModel::Value> &values) const;
|
QUrlQuery valuesToUrlQuery(const QHash<QString, BaseModel::Value> &values) const;
|
||||||
WebApi& api_;
|
WebApi api_;
|
||||||
Database& db_;
|
Database& db_;
|
||||||
|
SynchronizationState state_;
|
||||||
|
int uploadsRemaining_;
|
||||||
|
int downloadsRemaining_;
|
||||||
|
void downloadChanges();
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
|
|
||||||
|
@ -99,7 +99,7 @@ void WebApi::processQueue() {
|
|||||||
if (r.method != jop::GET && r.method != jop::DEL) {
|
if (r.method != jop::GET && r.method != jop::DEL) {
|
||||||
cmd << "--data" << "'" + r.data.toString(QUrl::FullyEncoded) + "'";
|
cmd << "--data" << "'" + r.data.toString(QUrl::FullyEncoded) + "'";
|
||||||
}
|
}
|
||||||
cmd << url;
|
cmd << "'" + url + "'";
|
||||||
|
|
||||||
qDebug().noquote() << cmd.join(" ");
|
qDebug().noquote() << cmd.join(" ");
|
||||||
|
|
||||||
@ -116,7 +116,7 @@ void WebApi::request_finished(QNetworkReply *reply) {
|
|||||||
qWarning().noquote() << QString(responseBodyBA);
|
qWarning().noquote() << QString(responseBodyBA);
|
||||||
} else {
|
} else {
|
||||||
response = doc.object();
|
response = doc.object();
|
||||||
if (!response["error"].isNull()) {
|
if (response.contains("error") && !response["error"].isNull()) {
|
||||||
qWarning().noquote() << "API error:" << QString(responseBodyBA);
|
qWarning().noquote() << "API error:" << QString(responseBodyBA);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,7 @@ class FoldersController extends ApiController {
|
|||||||
if ($request->isMethod('POST')) {
|
if ($request->isMethod('POST')) {
|
||||||
$folder = new Folder();
|
$folder = new Folder();
|
||||||
$folder->fromPublicArray($request->request->all());
|
$folder->fromPublicArray($request->request->all());
|
||||||
|
$folder->owner_id = $this->user()->id;
|
||||||
$folder->save();
|
$folder->save();
|
||||||
return static::successResponse($folder);
|
return static::successResponse($folder);
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@ use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
|
|||||||
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
|
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
use AppBundle\Controller\ApiController;
|
use AppBundle\Controller\ApiController;
|
||||||
use AppBundle\Model\Action;
|
use AppBundle\Model\Change;
|
||||||
use AppBundle\Exception\UnauthorizedException;
|
use AppBundle\Exception\UnauthorizedException;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -103,11 +103,13 @@ class SynchronizerController extends ApiController {
|
|||||||
* @Route("/synchronizer")
|
* @Route("/synchronizer")
|
||||||
*/
|
*/
|
||||||
public function allAction(Request $request) {
|
public function allAction(Request $request) {
|
||||||
$id = (int)$request->query->get('last_id');
|
$lastChangeId = (int)$request->query->get('last_id');
|
||||||
|
|
||||||
if (!$this->user() || !$this->session()) throw new UnauthorizedException();
|
if (!$this->user() || !$this->session()) throw new UnauthorizedException();
|
||||||
|
|
||||||
$actions = Action::actionsDoneAfterId($this->user()->id, $this->session()->client_id, $id);
|
$actions = Change::changesDoneAfterId($this->user()->id, $this->session()->client_id, $lastChangeId);
|
||||||
|
// $actions['user_id'] = Change::hex($this->user()->id);
|
||||||
|
// $actions['client_id'] = Change::hex($this->session()->client_id);
|
||||||
return static::successResponse($actions);
|
return static::successResponse($actions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ class Change extends BaseModel {
|
|||||||
'type' => array('create', 'update', 'delete'),
|
'type' => array('create', 'update', 'delete'),
|
||||||
);
|
);
|
||||||
|
|
||||||
static public function changesDoneAfterId($userId, $clientId, $changeId) {
|
static public function changesDoneAfterId($userId, $clientId, $fromChangeId) {
|
||||||
// Simplification:
|
// Simplification:
|
||||||
//
|
//
|
||||||
// - If create, update, delete => return nothing
|
// - If create, update, delete => return nothing
|
||||||
@ -19,7 +19,7 @@ class Change extends BaseModel {
|
|||||||
// - If update, update, update => return last
|
// - If update, update, update => return last
|
||||||
|
|
||||||
$limit = 100;
|
$limit = 100;
|
||||||
$changes = self::where('id', '>', $changeId)
|
$changes = self::where('id', '>', $fromChangeId)
|
||||||
->where('user_id', '=', $userId)
|
->where('user_id', '=', $userId)
|
||||||
->where('client_id', '!=', $clientId)
|
->where('client_id', '!=', $clientId)
|
||||||
->orderBy('id')
|
->orderBy('id')
|
||||||
|
Reference in New Issue
Block a user