Skip to content

Commit

Permalink
Save state cache per-room
Browse files Browse the repository at this point in the history
Closes #257.
  • Loading branch information
KitsuneRal committed Nov 22, 2018
1 parent 0c3a453 commit 5fb74ca
Show file tree
Hide file tree
Showing 5 changed files with 70 additions and 49 deletions.
67 changes: 37 additions & 30 deletions lib/connection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1058,41 +1058,55 @@ void Connection::setHomeserver(const QUrl& url)
emit homeserverChanged(homeserver());
}

void Connection::saveState(const QUrl &toFile) const
void Connection::saveRoomState(Room* r) const
{
Q_ASSERT(r);
if (!d->cacheState)
return;

QElapsedTimer et; et.start();
QFile outRoomFile { stateCachePath() % SyncData::fileNameForRoom(r->id()) };
if (outRoomFile.open(QFile::WriteOnly))
{
QJsonDocument json { r->toJson() };
auto data = d->cacheToBinary ? json.toBinaryData()
: json.toJson(QJsonDocument::Compact);
outRoomFile.write(data.data(), data.size());
} else {
qCWarning(MAIN) << "Error opening" << outRoomFile.fileName()
<< ":" << outRoomFile.errorString();
}
}

QFileInfo stateFile {
toFile.isEmpty() ? stateCachePath() : toFile.toLocalFile()
};
if (!stateFile.dir().exists())
stateFile.dir().mkpath(".");
void Connection::saveState() const
{
if (!d->cacheState)
return;

QFile outfile { stateFile.absoluteFilePath() };
if (!outfile.open(QFile::WriteOnly))
QElapsedTimer et; et.start();

QFile outFile { stateCachePath() % "state.json" };
if (!outFile.open(QFile::WriteOnly))
{
qCWarning(MAIN) << "Error opening" << stateFile.absoluteFilePath()
<< ":" << outfile.errorString();
qCWarning(MAIN) << "Error opening" << outFile.fileName()
<< ":" << outFile.errorString();
qCWarning(MAIN) << "Caching the rooms state disabled";
d->cacheState = false;
return;
}

QJsonObject rootObj;
QJsonObject rootObj {
{ QStringLiteral("cache_version"), QJsonObject {
{ QStringLiteral("major"), SyncData::cacheVersion().first },
{ QStringLiteral("minor"), SyncData::cacheVersion().second }
}}};
{
QJsonObject rooms;
QJsonObject inviteRooms;
for (const auto* i : roomMap()) // Pass on rooms in Leave state
{
// TODO: instead of adding the room JSON add a file name and save
// the JSON to that file.
if (i->joinState() == JoinState::Invite)
inviteRooms.insert(i->id(), i->toJson());
else
rooms.insert(i->id(), i->toJson());
auto& targetArray = i->joinState() == JoinState::Invite
? inviteRooms : rooms;
targetArray.insert(i->id(), QJsonObject());
QElapsedTimer et1; et1.start();
QCoreApplication::processEvents();
if (et1.elapsed() > 1)
Expand Down Expand Up @@ -1120,29 +1134,23 @@ void Connection::saveState(const QUrl &toFile) const
QJsonObject {{ QStringLiteral("events"), accountDataEvents }});
}

QJsonObject versionObj;
versionObj.insert("major", SyncData::cacheVersion().first);
versionObj.insert("minor", SyncData::cacheVersion().second);
rootObj.insert("cache_version", versionObj);

QJsonDocument json { rootObj };
auto data = d->cacheToBinary ? json.toBinaryData() :
json.toJson(QJsonDocument::Compact);
qCDebug(PROFILER) << "Cache for" << userId() << "generated in" << et;

outfile.write(data.data(), data.size());
qCDebug(MAIN) << "State cache saved to" << outfile.fileName();
outFile.write(data.data(), data.size());
qCDebug(MAIN) << "State cache saved to" << outFile.fileName();
}

void Connection::loadState(const QUrl &fromFile)
void Connection::loadState()
{
if (!d->cacheState)
return;

QElapsedTimer et; et.start();

SyncData sync {
fromFile.isEmpty() ? stateCachePath() : fromFile.toLocalFile() };
SyncData sync { stateCachePath() % "state.json" };
if (sync.nextBatch().isEmpty()) // No token means no cache by definition
return;

Expand All @@ -1162,8 +1170,7 @@ QString Connection::stateCachePath() const
{
auto safeUserId = userId();
safeUserId.replace(':', '_');
return QStandardPaths::writableLocation(QStandardPaths::CacheLocation)
% '/' % safeUserId % "_state.json";
return cacheLocation(safeUserId);
}

bool Connection::cacheState() const
Expand Down
7 changes: 5 additions & 2 deletions lib/connection.h
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,7 @@ namespace QMatrixClient
* to be QML-friendly. Empty parameter means using a path
* defined by stateCachePath().
*/
Q_INVOKABLE void loadState(const QUrl &fromFile = {});
Q_INVOKABLE void loadState();
/**
* 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
Expand All @@ -290,7 +290,10 @@ namespace QMatrixClient
* QML-friendly. Empty parameter means using a path defined by
* stateCachePath().
*/
Q_INVOKABLE void saveState(const QUrl &toFile = {}) const;
Q_INVOKABLE void saveState() const;

/// This method saves the current state of a single room.
void saveRoomState(Room* r) const;

/**
* The default path to store the cached room state, defined as
Expand Down
3 changes: 3 additions & 0 deletions lib/room.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1170,7 +1170,10 @@ void Room::updateData(SyncRoomData&& data)
emit notificationCountChanged(this);
}
if (roomChanges != Change::NoChange)
{
emit changed(roomChanges);
connection()->saveRoomState(this);
}
}

QString Room::Private::sendEvent(RoomEventPtr&& event)
Expand Down
37 changes: 22 additions & 15 deletions lib/syncdata.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include "events/eventloader.h"

#include <QtCore/QFile>
#include <QtCore/QFileInfo>

using namespace QMatrixClient;

Expand Down Expand Up @@ -69,14 +70,30 @@ SyncRoomData::SyncRoomData(const QString& roomId_, JoinState joinState_,

SyncData::SyncData(const QString& cacheFileName)
{
parseJson(loadJson(cacheFileName));
QFileInfo cacheFileInfo { cacheFileName };
auto json = loadJson(cacheFileName);
auto requiredVersion = std::get<0>(cacheVersion());
auto actualVersion = json.value("cache_version").toObject()
.value("major").toInt();
if (actualVersion == requiredVersion)
parseJson(json, cacheFileInfo.absolutePath() + '/');
else
qCWarning(MAIN)
<< "Major version of the cache file is" << actualVersion << "but"
<< requiredVersion << "is required; discarding the cache";
}

SyncDataList&& SyncData::takeRoomData()
{
return move(roomData);
}

QString SyncData::fileNameForRoom(QString roomId)
{
roomId.replace(':', '_');
return roomId + ".json";
}

Events&& SyncData::takePresenceData()
{
return std::move(presenceData);
Expand Down Expand Up @@ -115,22 +132,11 @@ QJsonObject SyncData::loadJson(const QString& fileName)
{
qCWarning(MAIN) << "State cache in" << fileName
<< "is broken or empty, discarding";
return {};
}
auto requiredVersion = std::get<0>(cacheVersion());
auto actualVersion = json.value("cache_version").toObject()
.value("major").toInt();
if (actualVersion < requiredVersion)
{
qCWarning(MAIN)
<< "Major version of the cache file is" << actualVersion << "but"
<< requiredVersion << "is required; discarding the cache";
return {};
}
return json;
}

void SyncData::parseJson(const QJsonObject& json)
void SyncData::parseJson(const QJsonObject& json, const QString& baseDir)
{
QElapsedTimer et; et.start();

Expand All @@ -150,8 +156,9 @@ void SyncData::parseJson(const QJsonObject& json)
roomData.reserve(static_cast<size_t>(rs.size()));
for(auto roomIt = rs.begin(); roomIt != rs.end(); ++roomIt)
{
auto roomJson = roomIt->isString() ? loadJson(roomIt->toString())
: roomIt->toObject();
auto roomJson = roomIt->isString()
? loadJson(baseDir + fileNameForRoom(roomIt.key()))
: roomIt->toObject();
if (roomJson.isEmpty())
{
unresolvedRoomIds.push_back(roomIt.key());
Expand Down
5 changes: 3 additions & 2 deletions lib/syncdata.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ namespace QMatrixClient {
* \return the list of rooms with missing cache files; always
* empty when parsing response from /sync
*/
void parseJson(const QJsonObject& json);
void parseJson(const QJsonObject& json, const QString& baseDir = {});

Events&& takePresenceData();
Events&& takeAccountData();
Expand All @@ -70,7 +70,8 @@ namespace QMatrixClient {

QStringList unresolvedRooms() const { return unresolvedRoomIds; }

static std::pair<int, int> cacheVersion() { return { 8, 0 }; }
static std::pair<int, int> cacheVersion() { return { 9, 0 }; }
static QString fileNameForRoom(QString roomId);

private:
QString nextBatch_;
Expand Down

0 comments on commit 5fb74ca

Please sign in to comment.