diff options
-rw-r--r-- | connection.cpp | 109 | ||||
-rw-r--r-- | connection.h | 40 |
2 files changed, 120 insertions, 29 deletions
diff --git a/connection.cpp b/connection.cpp index 27f0a86f..785e0e43 100644 --- a/connection.cpp +++ b/connection.cpp @@ -35,6 +35,9 @@ #include <QtCore/QFile> #include <QtCore/QDir> #include <QtCore/QFileInfo> +#include <QtCore/QStandardPaths> +#include <QtCore/QStringBuilder> +#include <QtCore/QElapsedTimer> using namespace QMatrixClient; @@ -60,6 +63,8 @@ class Connection::Private QString userId; SyncJob* syncJob; + + bool cacheState = true; }; Connection::Connection(const QUrl& server, QObject* parent) @@ -329,47 +334,101 @@ QByteArray Connection::generateTxnId() return d->data->generateTxnId(); } -void Connection::saveState(const QUrl &toFile) { - QJsonObject rooms; +void Connection::saveState(const QUrl &toFile) const +{ + if (!d->cacheState) + return; + + QElapsedTimer et; et.start(); - for (auto i : this->roomMap()) { - rooms[i->id()] = i->toJson(); + QFileInfo stateFile { + toFile.isEmpty() ? stateCachePath() : toFile.toLocalFile() + }; + if (!stateFile.dir().exists()) + stateFile.dir().mkpath("."); + + QFile outfile { stateFile.absoluteFilePath() }; + if (!outfile.open(QFile::WriteOnly)) + { + qCWarning(MAIN) << "Error opening" << stateFile.absoluteFilePath() + << ":" << outfile.errorString(); + qCWarning(MAIN) << "Caching the rooms state disabled"; + d->cacheState = false; + return; } QJsonObject roomObj; - roomObj.insert("leave", QJsonObject()); - roomObj.insert("join", rooms); - roomObj.insert("invite", QJsonObject()); + { + QJsonObject rooms; + QJsonObject inviteRooms; + for (auto i : roomMap()) // Pass on rooms in Leave state + { + if (i->joinState() == JoinState::Invite) + inviteRooms.insert(i->id(), i->toJson()); + else + rooms.insert(i->id(), i->toJson()); + } + + if (!rooms.isEmpty()) + roomObj.insert("join", rooms); + if (!inviteRooms.isEmpty()) + roomObj.insert("invite", inviteRooms); + } QJsonObject rootObj; rootObj.insert("next_batch", d->data->lastEvent()); - rootObj.insert("presence", QJsonObject()); rootObj.insert("rooms", roomObj); - QJsonDocument doc { rootObj }; - QByteArray data = doc.toJson(); + QByteArray data = QJsonDocument(rootObj).toJson(QJsonDocument::Compact); - QFileInfo stateFile { toFile.toLocalFile() }; - QFile outfile { stateFile.absoluteFilePath() }; - if (!stateFile.dir().exists()) stateFile.dir().mkpath("."); + qCDebug(MAIN) << "Writing state to file" << outfile.fileName(); + outfile.write(data.data(), data.size()); + qCDebug(PROFILER) << "*** Cached state for" << userId() + << "saved in" << et.elapsed() << "ms"; +} - if (outfile.open(QFile::WriteOnly)) { - qCDebug(MAIN) << "Writing state to file=" << outfile.fileName(); - outfile.write(data.data(), data.size()); +void Connection::loadState(const QUrl &fromFile) +{ + if (!d->cacheState) + return; - } else { - qCWarning(MAIN) << outfile.errorString(); + QElapsedTimer et; et.start(); + QFile file { + fromFile.isEmpty() ? stateCachePath() : fromFile.toLocalFile() + }; + if (!file.exists()) + { + qCDebug(MAIN) << "No state cache file found"; + return; } -} - -void Connection::loadState(const QUrl &fromFile) { - QFile file { fromFile.toLocalFile() }; - if (!file.exists()) return; file.open(QFile::ReadOnly); QByteArray data = file.readAll(); - QJsonDocument doc { QJsonDocument::fromJson(data) }; SyncData sync; - sync.parseJson(doc); + sync.parseJson(QJsonDocument::fromJson(data)); onSyncSuccess(std::move(sync)); + qCDebug(PROFILER) << "*** Cached state for" << userId() + << "loaded in" << et.elapsed() << "ms"; +} + +QString Connection::stateCachePath() const +{ + auto safeUserId = userId(); + safeUserId.replace(':', '_'); + return QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + % '/' % safeUserId % "_state.json"; +} + +bool Connection::cacheState() const +{ + return d->cacheState; +} + +void Connection::setCacheState(bool newValue) +{ + if (d->cacheState != newValue) + { + d->cacheState = newValue; + emit cacheStateChanged(); + } } diff --git a/connection.h b/connection.h index ad161d7c..bf50d7d3 100644 --- a/connection.h +++ b/connection.h @@ -39,6 +39,11 @@ namespace QMatrixClient class Connection: public QObject { Q_OBJECT + + /** Whether or not the rooms state should be cached locally + * \sa loadState(), saveState() + */ + Q_PROPERTY(bool cacheState READ cacheState WRITE setCacheState NOTIFY cacheStateChanged) public: explicit Connection(const QUrl& server, QObject* parent = nullptr); Connection(); @@ -86,10 +91,35 @@ namespace QMatrixClient /** * Call this before first sync to load from previously saved file. - * Uses QUrl to be QML-friendly. - */ - Q_INVOKABLE void loadState(const QUrl &fromFile); - Q_INVOKABLE void saveState(const QUrl &toFile); + * + * \param fromFile A local path to read the state from. Uses QUrl + * to be QML-friendly. Empty parameter means using a path + * defined by stateCachePath(). + */ + Q_INVOKABLE void loadState(const QUrl &fromFile = {}); + /** + * This method saves the current state of rooms (but not messages + * in them) to a local cache file, so that it could be loaded by + * loadState() on a next run of the client. + * + * \param toFile A local path to save the state to. Uses QUrl to be + * QML-friendly. Empty parameter means using a path defined by + * stateCachePath(). + */ + Q_INVOKABLE void saveState(const QUrl &toFile = {}) const; + + /** + * The default path to store the cached room state, defined as + * follows: + * QStandardPaths::writeableLocation(QStandardPaths::CacheLocation) + _safeUserId + "_state.json" + * where `_safeUserId` is userId() with `:` (colon) replaced with + * `_` (underscore) + * /see loadState(), saveState() + */ + Q_INVOKABLE QString stateCachePath() const; + + bool cacheState() const; + void setCacheState(bool newValue); template <typename JobT, typename... JobArgTs> JobT* callApi(JobArgTs... jobArgs) const @@ -120,6 +150,8 @@ namespace QMatrixClient void syncError(QString error); //void jobError(BaseJob* job); + void cacheStateChanged(); + protected: /** * @brief Access the underlying ConnectionData class |