2017-07-29 11:49:00 +03:00
|
|
|
/*
|
|
|
|
* nheko Copyright (C) 2017 Konstantinos Sideris <siderisk@auth.gr>
|
|
|
|
*
|
|
|
|
* This program is free software: you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
|
|
* (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <stdexcept>
|
|
|
|
|
2017-12-22 01:00:48 +03:00
|
|
|
#include <QByteArray>
|
2017-07-29 11:49:00 +03:00
|
|
|
#include <QDebug>
|
|
|
|
#include <QFile>
|
2018-04-21 16:34:50 +03:00
|
|
|
#include <QHash>
|
2017-07-29 11:49:00 +03:00
|
|
|
#include <QStandardPaths>
|
|
|
|
|
2018-03-26 20:41:16 +03:00
|
|
|
#include <variant.hpp>
|
|
|
|
|
2017-07-29 11:49:00 +03:00
|
|
|
#include "Cache.h"
|
|
|
|
|
2018-04-21 16:34:50 +03:00
|
|
|
//! Should be changed when a breaking change occurs in the cache format.
|
|
|
|
//! This will reset client's data.
|
2018-04-21 17:27:38 +03:00
|
|
|
static const std::string CURRENT_CACHE_FORMAT_VERSION("2018.04.21");
|
2017-12-10 13:51:44 +03:00
|
|
|
|
2017-07-29 11:49:00 +03:00
|
|
|
static const lmdb::val NEXT_BATCH_KEY("next_batch");
|
2017-12-10 13:51:44 +03:00
|
|
|
static const lmdb::val CACHE_FORMAT_VERSION_KEY("cache_format_version");
|
2017-07-29 11:49:00 +03:00
|
|
|
|
2018-04-21 16:34:50 +03:00
|
|
|
//! Cache databases and their format.
|
|
|
|
//!
|
|
|
|
//! Contains UI information for the joined rooms. (i.e name, topic, avatar url etc).
|
|
|
|
//! Format: room_id -> RoomInfo
|
|
|
|
static constexpr const char *ROOMS_DB = "rooms";
|
2018-04-22 12:26:41 +03:00
|
|
|
static constexpr const char *INVITES_DB = "invites";
|
2018-04-21 16:34:50 +03:00
|
|
|
//! Keeps already downloaded media for reuse.
|
|
|
|
//! Format: matrix_url -> binary data.
|
|
|
|
static constexpr const char *MEDIA_DB = "media";
|
|
|
|
//! Information that must be kept between sync requests.
|
|
|
|
static constexpr const char *SYNC_STATE_DB = "sync_state";
|
|
|
|
//! Read receipts per room/event.
|
|
|
|
static constexpr const char *READ_RECEIPTS_DB = "read_receipts";
|
|
|
|
|
2018-01-15 00:33:12 +03:00
|
|
|
using CachedReceipts = std::multimap<uint64_t, std::string, std::greater<uint64_t>>;
|
|
|
|
using Receipts = std::map<std::string, std::map<std::string, uint64_t>>;
|
|
|
|
|
2018-01-21 22:43:21 +03:00
|
|
|
Cache::Cache(const QString &userId, QObject *parent)
|
|
|
|
: QObject{parent}
|
|
|
|
, env_{nullptr}
|
2018-04-21 16:34:50 +03:00
|
|
|
, syncStateDb_{0}
|
|
|
|
, roomsDb_{0}
|
2017-12-19 23:36:12 +03:00
|
|
|
, invitesDb_{0}
|
2018-04-21 16:34:50 +03:00
|
|
|
, mediaDb_{0}
|
2018-01-03 19:05:49 +03:00
|
|
|
, readReceiptsDb_{0}
|
2018-04-21 16:34:50 +03:00
|
|
|
, localUserId_{userId}
|
2017-10-15 22:08:51 +03:00
|
|
|
{}
|
2017-10-03 21:16:31 +03:00
|
|
|
|
|
|
|
void
|
|
|
|
Cache::setup()
|
|
|
|
{
|
|
|
|
qDebug() << "Setting up cache";
|
|
|
|
|
2017-08-26 13:49:16 +03:00
|
|
|
auto statePath = QString("%1/%2/state")
|
|
|
|
.arg(QStandardPaths::writableLocation(QStandardPaths::CacheLocation))
|
2018-04-21 16:34:50 +03:00
|
|
|
.arg(QString::fromUtf8(localUserId_.toUtf8().toHex()));
|
2017-07-29 11:49:00 +03:00
|
|
|
|
2017-10-03 21:16:31 +03:00
|
|
|
cacheDirectory_ = QString("%1/%2")
|
|
|
|
.arg(QStandardPaths::writableLocation(QStandardPaths::CacheLocation))
|
2018-04-21 16:34:50 +03:00
|
|
|
.arg(QString::fromUtf8(localUserId_.toUtf8().toHex()));
|
2017-10-03 21:16:31 +03:00
|
|
|
|
2017-08-26 13:49:16 +03:00
|
|
|
bool isInitial = !QFile::exists(statePath);
|
2017-07-29 11:49:00 +03:00
|
|
|
|
2017-08-26 13:49:16 +03:00
|
|
|
env_ = lmdb::env::create();
|
2017-12-22 01:00:48 +03:00
|
|
|
env_.set_mapsize(256UL * 1024UL * 1024UL); /* 256 MB */
|
2017-08-26 13:49:16 +03:00
|
|
|
env_.set_max_dbs(1024UL);
|
2017-07-29 11:49:00 +03:00
|
|
|
|
2017-08-26 13:49:16 +03:00
|
|
|
if (isInitial) {
|
2017-10-03 21:16:31 +03:00
|
|
|
qDebug() << "First time initializing LMDB";
|
2017-07-29 11:49:00 +03:00
|
|
|
|
2017-08-26 13:49:16 +03:00
|
|
|
if (!QDir().mkpath(statePath)) {
|
|
|
|
throw std::runtime_error(
|
|
|
|
("Unable to create state directory:" + statePath).toStdString().c_str());
|
|
|
|
}
|
|
|
|
}
|
2017-07-29 11:49:00 +03:00
|
|
|
|
2017-08-26 13:49:16 +03:00
|
|
|
try {
|
|
|
|
env_.open(statePath.toStdString().c_str());
|
|
|
|
} catch (const lmdb::error &e) {
|
|
|
|
if (e.code() != MDB_VERSION_MISMATCH && e.code() != MDB_INVALID) {
|
|
|
|
throw std::runtime_error("LMDB initialization failed" +
|
|
|
|
std::string(e.what()));
|
|
|
|
}
|
2017-07-29 11:49:00 +03:00
|
|
|
|
2017-08-26 13:49:16 +03:00
|
|
|
qWarning() << "Resetting cache due to LMDB version mismatch:" << e.what();
|
2017-07-29 11:49:00 +03:00
|
|
|
|
2017-08-26 13:49:16 +03:00
|
|
|
QDir stateDir(statePath);
|
2017-07-29 11:49:00 +03:00
|
|
|
|
2017-08-26 13:49:16 +03:00
|
|
|
for (const auto &file : stateDir.entryList(QDir::NoDotAndDotDot)) {
|
|
|
|
if (!stateDir.remove(file))
|
|
|
|
throw std::runtime_error(
|
|
|
|
("Unable to delete file " + file).toStdString().c_str());
|
|
|
|
}
|
2017-07-29 11:49:00 +03:00
|
|
|
|
2017-08-26 13:49:16 +03:00
|
|
|
env_.open(statePath.toStdString().c_str());
|
|
|
|
}
|
2017-07-29 11:49:00 +03:00
|
|
|
|
2018-01-03 19:05:49 +03:00
|
|
|
auto txn = lmdb::txn::begin(env_);
|
2018-04-21 16:34:50 +03:00
|
|
|
syncStateDb_ = lmdb::dbi::open(txn, SYNC_STATE_DB, MDB_CREATE);
|
|
|
|
roomsDb_ = lmdb::dbi::open(txn, ROOMS_DB, MDB_CREATE);
|
|
|
|
invitesDb_ = lmdb::dbi::open(txn, INVITES_DB, MDB_CREATE);
|
|
|
|
mediaDb_ = lmdb::dbi::open(txn, MEDIA_DB, MDB_CREATE);
|
|
|
|
readReceiptsDb_ = lmdb::dbi::open(txn, READ_RECEIPTS_DB, MDB_CREATE);
|
2017-08-26 13:49:16 +03:00
|
|
|
txn.commit();
|
2017-07-29 11:49:00 +03:00
|
|
|
|
2018-04-21 16:34:50 +03:00
|
|
|
qRegisterMetaType<RoomInfo>();
|
2017-07-29 11:49:00 +03:00
|
|
|
}
|
|
|
|
|
2017-12-22 01:00:48 +03:00
|
|
|
void
|
|
|
|
Cache::saveImage(const QString &url, const QByteArray &image)
|
|
|
|
{
|
|
|
|
auto key = url.toUtf8();
|
|
|
|
|
|
|
|
try {
|
|
|
|
auto txn = lmdb::txn::begin(env_);
|
|
|
|
|
|
|
|
lmdb::dbi_put(txn,
|
2018-04-21 16:34:50 +03:00
|
|
|
mediaDb_,
|
2017-12-22 01:00:48 +03:00
|
|
|
lmdb::val(key.data(), key.size()),
|
|
|
|
lmdb::val(image.data(), image.size()));
|
|
|
|
|
|
|
|
txn.commit();
|
|
|
|
} catch (const lmdb::error &e) {
|
|
|
|
qCritical() << "saveImage:" << e.what();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-04-27 01:57:46 +03:00
|
|
|
QByteArray
|
|
|
|
Cache::image(lmdb::txn &txn, const std::string &url) const
|
|
|
|
{
|
|
|
|
if (url.empty())
|
|
|
|
return QByteArray();
|
|
|
|
|
|
|
|
try {
|
|
|
|
lmdb::val image;
|
|
|
|
bool res = lmdb::dbi_get(txn, mediaDb_, lmdb::val(url), image);
|
|
|
|
|
|
|
|
if (!res)
|
|
|
|
return QByteArray();
|
|
|
|
|
|
|
|
return QByteArray(image.data(), image.size());
|
|
|
|
} catch (const lmdb::error &e) {
|
|
|
|
qCritical() << "image:" << e.what() << QString::fromStdString(url);
|
|
|
|
}
|
|
|
|
|
|
|
|
return QByteArray();
|
|
|
|
}
|
|
|
|
|
2017-12-22 01:00:48 +03:00
|
|
|
QByteArray
|
|
|
|
Cache::image(const QString &url) const
|
|
|
|
{
|
2018-04-21 21:18:57 +03:00
|
|
|
if (url.isEmpty())
|
|
|
|
return QByteArray();
|
|
|
|
|
2017-12-22 01:00:48 +03:00
|
|
|
auto key = url.toUtf8();
|
|
|
|
|
|
|
|
try {
|
|
|
|
auto txn = lmdb::txn::begin(env_, nullptr, MDB_RDONLY);
|
|
|
|
|
|
|
|
lmdb::val image;
|
|
|
|
|
2018-04-21 16:34:50 +03:00
|
|
|
bool res = lmdb::dbi_get(txn, mediaDb_, lmdb::val(key.data(), key.size()), image);
|
2017-12-22 01:00:48 +03:00
|
|
|
|
|
|
|
txn.commit();
|
|
|
|
|
|
|
|
if (!res)
|
|
|
|
return QByteArray();
|
|
|
|
|
|
|
|
return QByteArray(image.data(), image.size());
|
|
|
|
} catch (const lmdb::error &e) {
|
2018-04-21 21:18:57 +03:00
|
|
|
qCritical() << "image:" << e.what() << url;
|
2017-12-22 01:00:48 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
return QByteArray();
|
|
|
|
}
|
|
|
|
|
2018-04-22 12:26:41 +03:00
|
|
|
void
|
|
|
|
Cache::removeInvite(lmdb::txn &txn, const std::string &room_id)
|
|
|
|
{
|
|
|
|
lmdb::dbi_del(txn, invitesDb_, lmdb::val(room_id), nullptr);
|
|
|
|
lmdb::dbi_drop(txn, getInviteStatesDb(txn, room_id), true);
|
|
|
|
lmdb::dbi_drop(txn, getInviteMembersDb(txn, room_id), true);
|
|
|
|
}
|
|
|
|
|
2017-08-20 13:47:22 +03:00
|
|
|
void
|
2018-04-21 16:34:50 +03:00
|
|
|
Cache::removeInvite(const std::string &room_id)
|
2017-07-29 11:49:00 +03:00
|
|
|
{
|
2018-04-21 16:34:50 +03:00
|
|
|
auto txn = lmdb::txn::begin(env_);
|
2018-04-22 12:26:41 +03:00
|
|
|
removeInvite(txn, room_id);
|
2018-04-21 16:34:50 +03:00
|
|
|
txn.commit();
|
2017-07-29 11:49:00 +03:00
|
|
|
}
|
|
|
|
|
2017-10-01 19:49:36 +03:00
|
|
|
void
|
2018-04-21 16:34:50 +03:00
|
|
|
Cache::removeRoom(lmdb::txn &txn, const std::string &roomid)
|
2017-10-01 19:49:36 +03:00
|
|
|
{
|
2018-04-21 16:34:50 +03:00
|
|
|
lmdb::dbi_del(txn, roomsDb_, lmdb::val(roomid), nullptr);
|
2018-04-22 12:26:41 +03:00
|
|
|
lmdb::dbi_drop(txn, getStatesDb(txn, roomid), true);
|
|
|
|
lmdb::dbi_drop(txn, getMembersDb(txn, roomid), true);
|
2017-10-01 19:49:36 +03:00
|
|
|
}
|
|
|
|
|
2017-12-19 23:36:12 +03:00
|
|
|
void
|
2018-04-21 16:34:50 +03:00
|
|
|
Cache::removeRoom(const std::string &roomid)
|
2017-12-19 23:36:12 +03:00
|
|
|
{
|
|
|
|
auto txn = lmdb::txn::begin(env_, nullptr, 0);
|
2018-04-21 16:34:50 +03:00
|
|
|
lmdb::dbi_del(txn, roomsDb_, lmdb::val(roomid), nullptr);
|
2017-12-19 23:36:12 +03:00
|
|
|
txn.commit();
|
|
|
|
}
|
|
|
|
|
2018-01-21 22:43:21 +03:00
|
|
|
void
|
2018-04-21 16:34:50 +03:00
|
|
|
Cache::setNextBatchToken(lmdb::txn &txn, const std::string &token)
|
2017-07-29 11:49:00 +03:00
|
|
|
{
|
2018-04-21 16:34:50 +03:00
|
|
|
lmdb::dbi_put(txn, syncStateDb_, NEXT_BATCH_KEY, lmdb::val(token.data(), token.size()));
|
2017-07-29 11:49:00 +03:00
|
|
|
}
|
|
|
|
|
2017-08-20 13:47:22 +03:00
|
|
|
void
|
2017-08-26 13:49:16 +03:00
|
|
|
Cache::setNextBatchToken(lmdb::txn &txn, const QString &token)
|
2017-07-29 11:49:00 +03:00
|
|
|
{
|
2018-04-21 16:34:50 +03:00
|
|
|
setNextBatchToken(txn, token.toStdString());
|
2017-07-29 11:49:00 +03:00
|
|
|
}
|
|
|
|
|
2017-08-20 13:47:22 +03:00
|
|
|
bool
|
2017-08-26 13:49:16 +03:00
|
|
|
Cache::isInitialized() const
|
2017-07-29 11:49:00 +03:00
|
|
|
{
|
2017-08-26 13:49:16 +03:00
|
|
|
auto txn = lmdb::txn::begin(env_, nullptr, MDB_RDONLY);
|
|
|
|
lmdb::val token;
|
2017-07-29 11:49:00 +03:00
|
|
|
|
2018-04-21 16:34:50 +03:00
|
|
|
bool res = lmdb::dbi_get(txn, syncStateDb_, NEXT_BATCH_KEY, token);
|
2017-07-29 11:49:00 +03:00
|
|
|
|
2017-08-26 13:49:16 +03:00
|
|
|
txn.commit();
|
2017-07-29 11:49:00 +03:00
|
|
|
|
2017-08-26 13:49:16 +03:00
|
|
|
return res;
|
2017-07-29 11:49:00 +03:00
|
|
|
}
|
|
|
|
|
2017-08-20 13:47:22 +03:00
|
|
|
QString
|
2017-08-26 13:49:16 +03:00
|
|
|
Cache::nextBatchToken() const
|
2017-07-29 11:49:00 +03:00
|
|
|
{
|
2017-08-26 13:49:16 +03:00
|
|
|
auto txn = lmdb::txn::begin(env_, nullptr, MDB_RDONLY);
|
|
|
|
lmdb::val token;
|
2017-07-29 11:49:00 +03:00
|
|
|
|
2018-04-21 16:34:50 +03:00
|
|
|
lmdb::dbi_get(txn, syncStateDb_, NEXT_BATCH_KEY, token);
|
2017-07-29 11:49:00 +03:00
|
|
|
|
2017-08-26 13:49:16 +03:00
|
|
|
txn.commit();
|
2017-07-29 11:49:00 +03:00
|
|
|
|
2017-08-26 13:49:16 +03:00
|
|
|
return QString::fromUtf8(token.data(), token.size());
|
2017-07-29 11:49:00 +03:00
|
|
|
}
|
2017-10-22 19:03:55 +03:00
|
|
|
|
|
|
|
void
|
|
|
|
Cache::deleteData()
|
|
|
|
{
|
|
|
|
qInfo() << "Deleting cache data";
|
|
|
|
|
|
|
|
if (!cacheDirectory_.isEmpty())
|
|
|
|
QDir(cacheDirectory_).removeRecursively();
|
|
|
|
}
|
2017-12-10 13:51:44 +03:00
|
|
|
|
|
|
|
bool
|
|
|
|
Cache::isFormatValid()
|
|
|
|
{
|
|
|
|
auto txn = lmdb::txn::begin(env_, nullptr, MDB_RDONLY);
|
|
|
|
|
|
|
|
lmdb::val current_version;
|
2018-04-21 16:34:50 +03:00
|
|
|
bool res = lmdb::dbi_get(txn, syncStateDb_, CACHE_FORMAT_VERSION_KEY, current_version);
|
2017-12-10 13:51:44 +03:00
|
|
|
|
|
|
|
txn.commit();
|
|
|
|
|
|
|
|
if (!res)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
std::string stored_version(current_version.data(), current_version.size());
|
|
|
|
|
|
|
|
if (stored_version != CURRENT_CACHE_FORMAT_VERSION) {
|
|
|
|
qWarning() << "Stored format version" << QString::fromStdString(stored_version);
|
|
|
|
qWarning() << "There are breaking changes in the cache format.";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
Cache::setCurrentFormat()
|
|
|
|
{
|
|
|
|
auto txn = lmdb::txn::begin(env_);
|
|
|
|
|
|
|
|
lmdb::dbi_put(
|
|
|
|
txn,
|
2018-04-21 16:34:50 +03:00
|
|
|
syncStateDb_,
|
2017-12-10 13:51:44 +03:00
|
|
|
CACHE_FORMAT_VERSION_KEY,
|
|
|
|
lmdb::val(CURRENT_CACHE_FORMAT_VERSION.data(), CURRENT_CACHE_FORMAT_VERSION.size()));
|
|
|
|
|
|
|
|
txn.commit();
|
|
|
|
}
|
2017-12-19 23:36:12 +03:00
|
|
|
|
2018-01-15 00:33:12 +03:00
|
|
|
CachedReceipts
|
2018-01-03 19:05:49 +03:00
|
|
|
Cache::readReceipts(const QString &event_id, const QString &room_id)
|
|
|
|
{
|
2018-01-15 00:33:12 +03:00
|
|
|
CachedReceipts receipts;
|
2018-01-03 19:05:49 +03:00
|
|
|
|
|
|
|
ReadReceiptKey receipt_key{event_id.toStdString(), room_id.toStdString()};
|
|
|
|
nlohmann::json json_key = receipt_key;
|
|
|
|
|
|
|
|
try {
|
|
|
|
auto txn = lmdb::txn::begin(env_, nullptr, MDB_RDONLY);
|
|
|
|
auto key = json_key.dump();
|
|
|
|
|
|
|
|
lmdb::val value;
|
|
|
|
|
|
|
|
bool res =
|
|
|
|
lmdb::dbi_get(txn, readReceiptsDb_, lmdb::val(key.data(), key.size()), value);
|
|
|
|
|
|
|
|
txn.commit();
|
|
|
|
|
|
|
|
if (res) {
|
|
|
|
auto json_response = json::parse(std::string(value.data(), value.size()));
|
2018-01-15 00:33:12 +03:00
|
|
|
auto values = json_response.get<std::map<std::string, uint64_t>>();
|
2018-01-03 19:05:49 +03:00
|
|
|
|
2018-02-28 13:12:07 +03:00
|
|
|
for (const auto &v : values)
|
2018-01-15 00:33:12 +03:00
|
|
|
// timestamp, user_id
|
|
|
|
receipts.emplace(v.second, v.first);
|
2018-01-03 19:05:49 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
} catch (const lmdb::error &e) {
|
|
|
|
qCritical() << "readReceipts:" << e.what();
|
|
|
|
}
|
|
|
|
|
|
|
|
return receipts;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2018-04-21 17:14:16 +03:00
|
|
|
Cache::updateReadReceipt(lmdb::txn &txn, const std::string &room_id, const Receipts &receipts)
|
2018-01-03 19:05:49 +03:00
|
|
|
{
|
2018-02-28 13:12:07 +03:00
|
|
|
for (const auto &receipt : receipts) {
|
2018-01-03 19:05:49 +03:00
|
|
|
const auto event_id = receipt.first;
|
|
|
|
auto event_receipts = receipt.second;
|
|
|
|
|
|
|
|
ReadReceiptKey receipt_key{event_id, room_id};
|
|
|
|
nlohmann::json json_key = receipt_key;
|
|
|
|
|
|
|
|
try {
|
|
|
|
const auto key = json_key.dump();
|
|
|
|
|
|
|
|
lmdb::val prev_value;
|
|
|
|
|
|
|
|
bool exists = lmdb::dbi_get(
|
2018-04-21 17:14:16 +03:00
|
|
|
txn, readReceiptsDb_, lmdb::val(key.data(), key.size()), prev_value);
|
2018-01-03 19:05:49 +03:00
|
|
|
|
2018-01-15 00:33:12 +03:00
|
|
|
std::map<std::string, uint64_t> saved_receipts;
|
2018-01-03 19:05:49 +03:00
|
|
|
|
|
|
|
// If an entry for the event id already exists, we would
|
|
|
|
// merge the existing receipts with the new ones.
|
|
|
|
if (exists) {
|
|
|
|
auto json_value =
|
|
|
|
json::parse(std::string(prev_value.data(), prev_value.size()));
|
|
|
|
|
|
|
|
// Retrieve the saved receipts.
|
2018-01-15 00:33:12 +03:00
|
|
|
saved_receipts = json_value.get<std::map<std::string, uint64_t>>();
|
2018-01-03 19:05:49 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// Append the new ones.
|
2018-02-28 13:12:07 +03:00
|
|
|
for (const auto &event_receipt : event_receipts)
|
2018-01-15 00:33:12 +03:00
|
|
|
saved_receipts.emplace(event_receipt.first, event_receipt.second);
|
2018-01-03 19:05:49 +03:00
|
|
|
|
|
|
|
// Save back the merged (or only the new) receipts.
|
|
|
|
nlohmann::json json_updated_value = saved_receipts;
|
|
|
|
std::string merged_receipts = json_updated_value.dump();
|
|
|
|
|
|
|
|
lmdb::dbi_put(txn,
|
|
|
|
readReceiptsDb_,
|
|
|
|
lmdb::val(key.data(), key.size()),
|
|
|
|
lmdb::val(merged_receipts.data(), merged_receipts.size()));
|
|
|
|
|
|
|
|
} catch (const lmdb::error &e) {
|
|
|
|
qCritical() << "updateReadReceipts:" << e.what();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-04-21 16:34:50 +03:00
|
|
|
|
|
|
|
void
|
|
|
|
Cache::saveState(const mtx::responses::Sync &res)
|
|
|
|
{
|
|
|
|
auto txn = lmdb::txn::begin(env_);
|
|
|
|
|
|
|
|
setNextBatchToken(txn, res.next_batch);
|
|
|
|
|
|
|
|
// Save joined rooms
|
|
|
|
for (const auto &room : res.rooms.join) {
|
|
|
|
auto statesdb = getStatesDb(txn, room.first);
|
|
|
|
auto membersdb = getMembersDb(txn, room.first);
|
|
|
|
|
|
|
|
saveStateEvents(txn, statesdb, membersdb, room.first, room.second.state.events);
|
|
|
|
saveStateEvents(txn, statesdb, membersdb, room.first, room.second.timeline.events);
|
|
|
|
|
|
|
|
RoomInfo updatedInfo;
|
|
|
|
updatedInfo.name = getRoomName(txn, statesdb, membersdb).toStdString();
|
|
|
|
updatedInfo.topic = getRoomTopic(txn, statesdb).toStdString();
|
|
|
|
updatedInfo.avatar_url =
|
|
|
|
getRoomAvatarUrl(txn, statesdb, membersdb, QString::fromStdString(room.first))
|
|
|
|
.toStdString();
|
|
|
|
|
|
|
|
lmdb::dbi_put(
|
|
|
|
txn, roomsDb_, lmdb::val(room.first), lmdb::val(json(updatedInfo).dump()));
|
2018-04-21 17:14:16 +03:00
|
|
|
|
|
|
|
updateReadReceipt(txn, room.first, room.second.ephemeral.receipts);
|
2018-04-22 12:26:41 +03:00
|
|
|
|
|
|
|
// Clean up non-valid invites.
|
|
|
|
removeInvite(txn, room.first);
|
2018-04-21 16:34:50 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
saveInvites(txn, res.rooms.invite);
|
|
|
|
|
|
|
|
removeLeftRooms(txn, res.rooms.leave);
|
|
|
|
|
|
|
|
txn.commit();
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
Cache::saveInvites(lmdb::txn &txn, const std::map<std::string, mtx::responses::InvitedRoom> &rooms)
|
|
|
|
{
|
|
|
|
for (const auto &room : rooms) {
|
|
|
|
auto statesdb = getInviteStatesDb(txn, room.first);
|
|
|
|
auto membersdb = getInviteMembersDb(txn, room.first);
|
|
|
|
|
|
|
|
saveInvite(txn, statesdb, membersdb, room.second);
|
|
|
|
|
|
|
|
RoomInfo updatedInfo;
|
|
|
|
updatedInfo.name = getInviteRoomName(txn, statesdb, membersdb).toStdString();
|
|
|
|
updatedInfo.topic = getInviteRoomTopic(txn, statesdb).toStdString();
|
|
|
|
updatedInfo.avatar_url =
|
|
|
|
getInviteRoomAvatarUrl(txn, statesdb, membersdb).toStdString();
|
|
|
|
updatedInfo.is_invite = true;
|
|
|
|
|
|
|
|
lmdb::dbi_put(
|
|
|
|
txn, invitesDb_, lmdb::val(room.first), lmdb::val(json(updatedInfo).dump()));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
Cache::saveInvite(lmdb::txn &txn,
|
|
|
|
lmdb::dbi &statesdb,
|
|
|
|
lmdb::dbi &membersdb,
|
|
|
|
const mtx::responses::InvitedRoom &room)
|
|
|
|
{
|
|
|
|
using namespace mtx::events;
|
|
|
|
using namespace mtx::events::state;
|
|
|
|
|
|
|
|
for (const auto &e : room.invite_state) {
|
|
|
|
if (mpark::holds_alternative<StrippedEvent<Member>>(e)) {
|
|
|
|
auto msg = mpark::get<StrippedEvent<Member>>(e);
|
|
|
|
|
|
|
|
auto display_name = msg.content.display_name.empty()
|
|
|
|
? msg.state_key
|
|
|
|
: msg.content.display_name;
|
|
|
|
|
|
|
|
MemberInfo tmp{display_name, msg.content.avatar_url};
|
|
|
|
|
|
|
|
lmdb::dbi_put(
|
|
|
|
txn, membersdb, lmdb::val(msg.state_key), lmdb::val(json(tmp).dump()));
|
|
|
|
} else {
|
|
|
|
mpark::visit(
|
|
|
|
[&txn, &statesdb](auto msg) {
|
|
|
|
bool res = lmdb::dbi_put(txn,
|
|
|
|
statesdb,
|
|
|
|
lmdb::val(to_string(msg.type)),
|
|
|
|
lmdb::val(json(msg).dump()));
|
|
|
|
|
|
|
|
if (!res)
|
|
|
|
std::cout << "couldn't save data" << json(msg).dump()
|
|
|
|
<< '\n';
|
|
|
|
},
|
|
|
|
e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
std::vector<std::string>
|
|
|
|
Cache::roomsWithStateUpdates(const mtx::responses::Sync &res)
|
|
|
|
{
|
|
|
|
std::vector<std::string> rooms;
|
|
|
|
for (const auto &room : res.rooms.join) {
|
|
|
|
bool hasUpdates = false;
|
|
|
|
for (const auto &s : room.second.state.events) {
|
|
|
|
if (containsStateUpdates(s)) {
|
|
|
|
hasUpdates = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (const auto &s : room.second.timeline.events) {
|
|
|
|
if (containsStateUpdates(s)) {
|
|
|
|
hasUpdates = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (hasUpdates)
|
|
|
|
rooms.emplace_back(room.first);
|
|
|
|
}
|
|
|
|
|
|
|
|
for (const auto &room : res.rooms.invite) {
|
|
|
|
for (const auto &s : room.second.invite_state) {
|
|
|
|
if (containsStateUpdates(s)) {
|
|
|
|
rooms.emplace_back(room.first);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return rooms;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::map<QString, RoomInfo>
|
|
|
|
Cache::getRoomInfo(const std::vector<std::string> &rooms)
|
|
|
|
{
|
|
|
|
std::map<QString, RoomInfo> room_info;
|
|
|
|
|
|
|
|
auto txn = lmdb::txn::begin(env_, nullptr, MDB_RDONLY);
|
|
|
|
|
|
|
|
for (const auto &room : rooms) {
|
|
|
|
lmdb::val data;
|
|
|
|
|
|
|
|
// Check if the room is joined.
|
|
|
|
if (lmdb::dbi_get(txn, roomsDb_, lmdb::val(room), data)) {
|
|
|
|
try {
|
|
|
|
room_info.emplace(
|
|
|
|
QString::fromStdString(room),
|
|
|
|
json::parse(std::string(data.data(), data.size())));
|
|
|
|
} catch (const json::exception &e) {
|
|
|
|
qWarning()
|
|
|
|
<< "failed to parse room info:" << QString::fromStdString(room)
|
|
|
|
<< QString::fromStdString(std::string(data.data(), data.size()));
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// Check if the room is an invite.
|
|
|
|
if (lmdb::dbi_get(txn, invitesDb_, lmdb::val(room), data)) {
|
|
|
|
try {
|
|
|
|
room_info.emplace(
|
|
|
|
QString::fromStdString(room),
|
|
|
|
json::parse(std::string(data.data(), data.size())));
|
|
|
|
} catch (const json::exception &e) {
|
|
|
|
qWarning() << "failed to parse room info for invite:"
|
|
|
|
<< QString::fromStdString(room)
|
|
|
|
<< QString::fromStdString(
|
|
|
|
std::string(data.data(), data.size()));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
txn.commit();
|
|
|
|
|
|
|
|
return room_info;
|
|
|
|
}
|
|
|
|
|
|
|
|
QMap<QString, RoomInfo>
|
2018-04-22 11:27:00 +03:00
|
|
|
Cache::roomInfo(bool withInvites)
|
2018-04-21 16:34:50 +03:00
|
|
|
{
|
|
|
|
QMap<QString, RoomInfo> result;
|
|
|
|
|
2018-04-22 11:27:00 +03:00
|
|
|
auto txn = lmdb::txn::begin(env_, nullptr, MDB_RDONLY);
|
2018-04-21 16:34:50 +03:00
|
|
|
|
|
|
|
std::string room_id;
|
|
|
|
std::string room_data;
|
|
|
|
|
|
|
|
// Gather info about the joined rooms.
|
2018-04-22 11:27:00 +03:00
|
|
|
auto roomsCursor = lmdb::cursor::open(txn, roomsDb_);
|
2018-04-21 16:34:50 +03:00
|
|
|
while (roomsCursor.get(room_id, room_data, MDB_NEXT)) {
|
2018-04-22 11:27:00 +03:00
|
|
|
RoomInfo tmp = json::parse(std::move(room_data));
|
2018-04-21 16:34:50 +03:00
|
|
|
result.insert(QString::fromStdString(std::move(room_id)), std::move(tmp));
|
|
|
|
}
|
2018-04-22 11:27:00 +03:00
|
|
|
roomsCursor.close();
|
2018-04-21 16:34:50 +03:00
|
|
|
|
2018-04-22 11:27:00 +03:00
|
|
|
if (withInvites) {
|
|
|
|
// Gather info about the invites.
|
|
|
|
auto invitesCursor = lmdb::cursor::open(txn, invitesDb_);
|
|
|
|
while (invitesCursor.get(room_id, room_data, MDB_NEXT)) {
|
|
|
|
RoomInfo tmp = json::parse(room_data);
|
|
|
|
result.insert(QString::fromStdString(std::move(room_id)), std::move(tmp));
|
|
|
|
}
|
|
|
|
invitesCursor.close();
|
2018-04-21 16:34:50 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
txn.commit();
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2018-04-22 14:19:05 +03:00
|
|
|
std::map<QString, bool>
|
|
|
|
Cache::invites()
|
|
|
|
{
|
|
|
|
std::map<QString, bool> result;
|
|
|
|
|
|
|
|
auto txn = lmdb::txn::begin(env_, nullptr, MDB_RDONLY);
|
|
|
|
auto cursor = lmdb::cursor::open(txn, invitesDb_);
|
|
|
|
|
|
|
|
std::string room_id, unused;
|
|
|
|
|
|
|
|
while (cursor.get(room_id, unused, MDB_NEXT))
|
|
|
|
result.emplace(QString::fromStdString(std::move(room_id)), true);
|
|
|
|
|
|
|
|
cursor.close();
|
|
|
|
txn.commit();
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2018-04-21 16:34:50 +03:00
|
|
|
QString
|
|
|
|
Cache::getRoomAvatarUrl(lmdb::txn &txn,
|
|
|
|
lmdb::dbi &statesdb,
|
|
|
|
lmdb::dbi &membersdb,
|
|
|
|
const QString &room_id)
|
|
|
|
{
|
|
|
|
using namespace mtx::events;
|
|
|
|
using namespace mtx::events::state;
|
|
|
|
|
|
|
|
lmdb::val event;
|
|
|
|
bool res = lmdb::dbi_get(
|
|
|
|
txn, statesdb, lmdb::val(to_string(mtx::events::EventType::RoomAvatar)), event);
|
|
|
|
|
|
|
|
if (res) {
|
|
|
|
try {
|
|
|
|
StateEvent<Avatar> msg =
|
|
|
|
json::parse(std::string(event.data(), event.size()));
|
|
|
|
|
|
|
|
return QString::fromStdString(msg.content.url);
|
|
|
|
} catch (const json::exception &e) {
|
|
|
|
qWarning() << QString::fromStdString(e.what());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// We don't use an avatar for group chats.
|
|
|
|
if (membersdb.size(txn) > 2)
|
|
|
|
return QString();
|
|
|
|
|
|
|
|
auto cursor = lmdb::cursor::open(txn, membersdb);
|
|
|
|
std::string user_id;
|
|
|
|
std::string member_data;
|
|
|
|
|
|
|
|
// Resolve avatar for 1-1 chats.
|
|
|
|
while (cursor.get(user_id, member_data, MDB_NEXT)) {
|
|
|
|
if (user_id == localUserId_.toStdString())
|
|
|
|
continue;
|
|
|
|
|
|
|
|
try {
|
|
|
|
MemberInfo m = json::parse(member_data);
|
|
|
|
|
|
|
|
cursor.close();
|
|
|
|
return QString::fromStdString(m.avatar_url);
|
|
|
|
} catch (const json::exception &e) {
|
|
|
|
qWarning() << QString::fromStdString(e.what());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
cursor.close();
|
|
|
|
|
|
|
|
// Default case when there is only one member.
|
|
|
|
return avatarUrl(room_id, localUserId_);
|
|
|
|
}
|
|
|
|
|
|
|
|
QString
|
|
|
|
Cache::getRoomName(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb)
|
|
|
|
{
|
|
|
|
using namespace mtx::events;
|
|
|
|
using namespace mtx::events::state;
|
|
|
|
|
|
|
|
lmdb::val event;
|
|
|
|
bool res = lmdb::dbi_get(
|
|
|
|
txn, statesdb, lmdb::val(to_string(mtx::events::EventType::RoomName)), event);
|
|
|
|
|
|
|
|
if (res) {
|
|
|
|
try {
|
|
|
|
StateEvent<Name> msg = json::parse(std::string(event.data(), event.size()));
|
|
|
|
|
|
|
|
if (!msg.content.name.empty())
|
|
|
|
return QString::fromStdString(msg.content.name);
|
|
|
|
} catch (const json::exception &e) {
|
|
|
|
qWarning() << QString::fromStdString(e.what());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
res = lmdb::dbi_get(
|
|
|
|
txn, statesdb, lmdb::val(to_string(mtx::events::EventType::RoomCanonicalAlias)), event);
|
|
|
|
|
|
|
|
if (res) {
|
|
|
|
try {
|
|
|
|
StateEvent<CanonicalAlias> msg =
|
|
|
|
json::parse(std::string(event.data(), event.size()));
|
|
|
|
|
|
|
|
if (!msg.content.alias.empty())
|
|
|
|
return QString::fromStdString(msg.content.alias);
|
|
|
|
} catch (const json::exception &e) {
|
|
|
|
qWarning() << QString::fromStdString(e.what());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
auto cursor = lmdb::cursor::open(txn, membersdb);
|
|
|
|
const int total = membersdb.size(txn);
|
|
|
|
|
|
|
|
std::size_t ii = 0;
|
|
|
|
std::string user_id;
|
|
|
|
std::string member_data;
|
|
|
|
std::map<std::string, MemberInfo> members;
|
|
|
|
|
|
|
|
while (cursor.get(user_id, member_data, MDB_NEXT) && ii < 3) {
|
|
|
|
try {
|
|
|
|
members.emplace(user_id, json::parse(member_data));
|
|
|
|
} catch (const json::exception &e) {
|
|
|
|
qWarning() << QString::fromStdString(e.what());
|
|
|
|
}
|
|
|
|
|
|
|
|
ii++;
|
|
|
|
}
|
|
|
|
|
|
|
|
cursor.close();
|
|
|
|
|
|
|
|
if (total == 1 && !members.empty())
|
|
|
|
return QString::fromStdString(members.begin()->second.name);
|
|
|
|
|
|
|
|
auto first_member = [&members, this]() {
|
|
|
|
for (const auto &m : members) {
|
|
|
|
if (m.first != localUserId_.toStdString())
|
|
|
|
return QString::fromStdString(m.second.name);
|
|
|
|
}
|
|
|
|
|
|
|
|
return localUserId_;
|
|
|
|
}();
|
|
|
|
|
|
|
|
if (total == 2)
|
|
|
|
return first_member;
|
|
|
|
else if (total > 2)
|
|
|
|
return QString("%1 and %2 others").arg(first_member).arg(total);
|
|
|
|
|
|
|
|
return "Empty Room";
|
|
|
|
}
|
|
|
|
|
|
|
|
QString
|
|
|
|
Cache::getRoomTopic(lmdb::txn &txn, lmdb::dbi &statesdb)
|
|
|
|
{
|
|
|
|
using namespace mtx::events;
|
|
|
|
using namespace mtx::events::state;
|
|
|
|
|
|
|
|
lmdb::val event;
|
|
|
|
bool res = lmdb::dbi_get(
|
|
|
|
txn, statesdb, lmdb::val(to_string(mtx::events::EventType::RoomTopic)), event);
|
|
|
|
|
|
|
|
if (res) {
|
|
|
|
try {
|
|
|
|
StateEvent<Topic> msg =
|
|
|
|
json::parse(std::string(event.data(), event.size()));
|
|
|
|
|
|
|
|
if (!msg.content.topic.empty())
|
|
|
|
return QString::fromStdString(msg.content.topic);
|
|
|
|
} catch (const json::exception &e) {
|
|
|
|
qWarning() << QString::fromStdString(e.what());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return QString();
|
|
|
|
}
|
|
|
|
|
|
|
|
QString
|
|
|
|
Cache::getInviteRoomName(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb)
|
|
|
|
{
|
|
|
|
using namespace mtx::events;
|
|
|
|
using namespace mtx::events::state;
|
|
|
|
|
|
|
|
lmdb::val event;
|
|
|
|
bool res = lmdb::dbi_get(
|
|
|
|
txn, statesdb, lmdb::val(to_string(mtx::events::EventType::RoomName)), event);
|
|
|
|
|
|
|
|
if (res) {
|
|
|
|
try {
|
|
|
|
StrippedEvent<state::Name> msg =
|
|
|
|
json::parse(std::string(event.data(), event.size()));
|
|
|
|
return QString::fromStdString(msg.content.name);
|
|
|
|
} catch (const json::exception &e) {
|
|
|
|
qWarning() << QString::fromStdString(e.what());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
auto cursor = lmdb::cursor::open(txn, membersdb);
|
|
|
|
std::string user_id, member_data;
|
|
|
|
|
|
|
|
while (cursor.get(user_id, member_data, MDB_NEXT)) {
|
|
|
|
if (user_id == localUserId_.toStdString())
|
|
|
|
continue;
|
|
|
|
|
|
|
|
try {
|
|
|
|
MemberInfo tmp = json::parse(member_data);
|
|
|
|
cursor.close();
|
|
|
|
|
|
|
|
return QString::fromStdString(tmp.name);
|
|
|
|
} catch (const json::exception &e) {
|
|
|
|
qWarning() << QString::fromStdString(e.what());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
cursor.close();
|
|
|
|
|
|
|
|
return QString("Empty Room");
|
|
|
|
}
|
|
|
|
|
|
|
|
QString
|
|
|
|
Cache::getInviteRoomAvatarUrl(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb)
|
|
|
|
{
|
|
|
|
using namespace mtx::events;
|
|
|
|
using namespace mtx::events::state;
|
|
|
|
|
|
|
|
lmdb::val event;
|
|
|
|
bool res = lmdb::dbi_get(
|
|
|
|
txn, statesdb, lmdb::val(to_string(mtx::events::EventType::RoomAvatar)), event);
|
|
|
|
|
|
|
|
if (res) {
|
|
|
|
try {
|
|
|
|
StrippedEvent<state::Avatar> msg =
|
|
|
|
json::parse(std::string(event.data(), event.size()));
|
|
|
|
return QString::fromStdString(msg.content.url);
|
|
|
|
} catch (const json::exception &e) {
|
|
|
|
qWarning() << QString::fromStdString(e.what());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
auto cursor = lmdb::cursor::open(txn, membersdb);
|
|
|
|
std::string user_id, member_data;
|
|
|
|
|
|
|
|
while (cursor.get(user_id, member_data, MDB_NEXT)) {
|
|
|
|
if (user_id == localUserId_.toStdString())
|
|
|
|
continue;
|
|
|
|
|
|
|
|
try {
|
|
|
|
MemberInfo tmp = json::parse(member_data);
|
|
|
|
cursor.close();
|
|
|
|
|
|
|
|
return QString::fromStdString(tmp.avatar_url);
|
|
|
|
} catch (const json::exception &e) {
|
|
|
|
qWarning() << QString::fromStdString(e.what());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
cursor.close();
|
|
|
|
|
|
|
|
return QString();
|
|
|
|
}
|
|
|
|
|
|
|
|
QString
|
|
|
|
Cache::getInviteRoomTopic(lmdb::txn &txn, lmdb::dbi &db)
|
|
|
|
{
|
|
|
|
using namespace mtx::events;
|
|
|
|
using namespace mtx::events::state;
|
|
|
|
|
|
|
|
lmdb::val event;
|
|
|
|
bool res =
|
|
|
|
lmdb::dbi_get(txn, db, lmdb::val(to_string(mtx::events::EventType::RoomTopic)), event);
|
|
|
|
|
|
|
|
if (res) {
|
|
|
|
try {
|
|
|
|
StrippedEvent<Topic> msg =
|
|
|
|
json::parse(std::string(event.data(), event.size()));
|
|
|
|
return QString::fromStdString(msg.content.topic);
|
|
|
|
} catch (const json::exception &e) {
|
|
|
|
qWarning() << QString::fromStdString(e.what());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return QString();
|
|
|
|
}
|
|
|
|
|
2018-04-24 14:13:05 +03:00
|
|
|
QImage
|
|
|
|
Cache::getRoomAvatar(const QString &room_id)
|
|
|
|
{
|
|
|
|
auto txn = lmdb::txn::begin(env_, nullptr, MDB_RDONLY);
|
|
|
|
|
|
|
|
lmdb::val response;
|
|
|
|
|
|
|
|
if (!lmdb::dbi_get(txn, roomsDb_, lmdb::val(room_id.toStdString()), response)) {
|
|
|
|
txn.commit();
|
|
|
|
return QImage();
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string media_url;
|
|
|
|
|
|
|
|
try {
|
|
|
|
RoomInfo info = json::parse(std::string(response.data(), response.size()));
|
|
|
|
media_url = std::move(info.avatar_url);
|
|
|
|
|
|
|
|
if (media_url.empty()) {
|
|
|
|
txn.commit();
|
|
|
|
return QImage();
|
|
|
|
}
|
|
|
|
} catch (const json::exception &e) {
|
|
|
|
qWarning() << "failed to parse room info" << e.what()
|
|
|
|
<< QString::fromStdString(std::string(response.data(), response.size()));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!lmdb::dbi_get(txn, mediaDb_, lmdb::val(media_url), response)) {
|
|
|
|
txn.commit();
|
|
|
|
return QImage();
|
|
|
|
}
|
|
|
|
|
|
|
|
txn.commit();
|
|
|
|
|
|
|
|
return QImage::fromData(QByteArray(response.data(), response.size()));
|
|
|
|
}
|
|
|
|
|
2018-04-21 16:34:50 +03:00
|
|
|
std::vector<std::string>
|
|
|
|
Cache::joinedRooms()
|
|
|
|
{
|
|
|
|
auto txn = lmdb::txn::begin(env_, nullptr, MDB_RDONLY);
|
|
|
|
auto roomsCursor = lmdb::cursor::open(txn, roomsDb_);
|
|
|
|
|
|
|
|
std::string id, data;
|
|
|
|
std::vector<std::string> room_ids;
|
|
|
|
|
|
|
|
// Gather the room ids for the joined rooms.
|
|
|
|
while (roomsCursor.get(id, data, MDB_NEXT))
|
|
|
|
room_ids.emplace_back(id);
|
|
|
|
|
|
|
|
roomsCursor.close();
|
|
|
|
txn.commit();
|
|
|
|
|
|
|
|
return room_ids;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
Cache::populateMembers()
|
|
|
|
{
|
|
|
|
auto rooms = joinedRooms();
|
|
|
|
qDebug() << "loading" << rooms.size() << "rooms";
|
|
|
|
|
|
|
|
auto txn = lmdb::txn::begin(env_);
|
|
|
|
|
|
|
|
for (const auto &room : rooms) {
|
|
|
|
const auto roomid = QString::fromStdString(room);
|
|
|
|
|
|
|
|
auto membersdb = getMembersDb(txn, room);
|
|
|
|
auto cursor = lmdb::cursor::open(txn, membersdb);
|
|
|
|
|
|
|
|
std::string user_id, info;
|
|
|
|
while (cursor.get(user_id, info, MDB_NEXT)) {
|
|
|
|
MemberInfo m = json::parse(info);
|
|
|
|
|
|
|
|
const auto userid = QString::fromStdString(user_id);
|
|
|
|
|
|
|
|
insertDisplayName(roomid, userid, QString::fromStdString(m.name));
|
|
|
|
insertAvatarUrl(roomid, userid, QString::fromStdString(m.avatar_url));
|
|
|
|
}
|
|
|
|
|
|
|
|
cursor.close();
|
|
|
|
}
|
|
|
|
|
|
|
|
txn.commit();
|
|
|
|
}
|
|
|
|
|
2018-04-27 01:57:46 +03:00
|
|
|
std::vector<RoomSearchResult>
|
|
|
|
Cache::searchRooms(const std::string &query, std::uint8_t max_items)
|
|
|
|
{
|
|
|
|
std::multimap<int, std::pair<std::string, RoomInfo>> items;
|
|
|
|
|
|
|
|
auto txn = lmdb::txn::begin(env_, nullptr, MDB_RDONLY);
|
|
|
|
auto cursor = lmdb::cursor::open(txn, roomsDb_);
|
|
|
|
|
|
|
|
std::string room_id, room_data;
|
|
|
|
while (cursor.get(room_id, room_data, MDB_NEXT)) {
|
|
|
|
RoomInfo tmp = json::parse(std::move(room_data));
|
|
|
|
|
|
|
|
const int score = utils::levenshtein_distance(
|
|
|
|
query, QString::fromStdString(tmp.name).toLower().toStdString());
|
|
|
|
items.emplace(score, std::make_pair(room_id, tmp));
|
|
|
|
}
|
|
|
|
|
|
|
|
cursor.close();
|
|
|
|
|
|
|
|
auto end = items.begin();
|
|
|
|
|
|
|
|
if (items.size() >= max_items)
|
|
|
|
std::advance(end, max_items);
|
|
|
|
else if (items.size() > 0)
|
|
|
|
std::advance(end, items.size());
|
|
|
|
|
|
|
|
std::vector<RoomSearchResult> results;
|
|
|
|
for (auto it = items.begin(); it != end; it++) {
|
|
|
|
results.push_back(
|
|
|
|
RoomSearchResult{it->second.first,
|
|
|
|
it->second.second,
|
|
|
|
QImage::fromData(image(txn, it->second.second.avatar_url))});
|
|
|
|
}
|
|
|
|
|
|
|
|
txn.commit();
|
|
|
|
|
|
|
|
return results;
|
|
|
|
}
|
|
|
|
|
2018-04-21 16:34:50 +03:00
|
|
|
QVector<SearchResult>
|
2018-04-27 01:57:46 +03:00
|
|
|
Cache::searchUsers(const std::string &room_id, const std::string &query, std::uint8_t max_items)
|
2018-04-21 16:34:50 +03:00
|
|
|
{
|
|
|
|
std::multimap<int, std::pair<std::string, std::string>> items;
|
|
|
|
|
|
|
|
auto txn = lmdb::txn::begin(env_, nullptr, MDB_RDONLY);
|
|
|
|
auto cursor = lmdb::cursor::open(txn, getMembersDb(txn, room_id));
|
|
|
|
|
|
|
|
std::string user_id, user_data;
|
|
|
|
while (cursor.get(user_id, user_data, MDB_NEXT)) {
|
|
|
|
const auto display_name = displayName(room_id, user_id);
|
|
|
|
const int score = utils::levenshtein_distance(query, display_name);
|
|
|
|
|
|
|
|
items.emplace(score, std::make_pair(user_id, display_name));
|
|
|
|
}
|
|
|
|
|
|
|
|
auto end = items.begin();
|
|
|
|
|
|
|
|
if (items.size() >= max_items)
|
|
|
|
std::advance(end, max_items);
|
|
|
|
else if (items.size() > 0)
|
|
|
|
std::advance(end, items.size());
|
|
|
|
|
|
|
|
QVector<SearchResult> results;
|
|
|
|
for (auto it = items.begin(); it != end; it++) {
|
|
|
|
const auto user = it->second;
|
|
|
|
results.push_back(SearchResult{QString::fromStdString(user.first),
|
|
|
|
QString::fromStdString(user.second)});
|
|
|
|
}
|
|
|
|
|
|
|
|
return results;
|
|
|
|
}
|
|
|
|
|
|
|
|
QHash<QString, QString> Cache::DisplayNames;
|
|
|
|
QHash<QString, QString> Cache::AvatarUrls;
|
|
|
|
|
|
|
|
QString
|
|
|
|
Cache::displayName(const QString &room_id, const QString &user_id)
|
|
|
|
{
|
|
|
|
auto fmt = QString("%1 %2").arg(room_id).arg(user_id);
|
|
|
|
if (DisplayNames.contains(fmt))
|
|
|
|
return DisplayNames[fmt];
|
|
|
|
|
|
|
|
return user_id;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string
|
|
|
|
Cache::displayName(const std::string &room_id, const std::string &user_id)
|
|
|
|
{
|
|
|
|
auto fmt = QString::fromStdString(room_id + " " + user_id);
|
|
|
|
if (DisplayNames.contains(fmt))
|
|
|
|
return DisplayNames[fmt].toStdString();
|
|
|
|
|
|
|
|
return user_id;
|
|
|
|
}
|
|
|
|
|
|
|
|
QString
|
|
|
|
Cache::avatarUrl(const QString &room_id, const QString &user_id)
|
|
|
|
{
|
|
|
|
auto fmt = QString("%1 %2").arg(room_id).arg(user_id);
|
|
|
|
if (AvatarUrls.contains(fmt))
|
|
|
|
return AvatarUrls[fmt];
|
|
|
|
|
|
|
|
return QString();
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
Cache::insertDisplayName(const QString &room_id,
|
|
|
|
const QString &user_id,
|
|
|
|
const QString &display_name)
|
|
|
|
{
|
|
|
|
auto fmt = QString("%1 %2").arg(room_id).arg(user_id);
|
|
|
|
DisplayNames.insert(fmt, display_name);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
Cache::removeDisplayName(const QString &room_id, const QString &user_id)
|
|
|
|
{
|
|
|
|
auto fmt = QString("%1 %2").arg(room_id).arg(user_id);
|
|
|
|
DisplayNames.remove(fmt);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
Cache::insertAvatarUrl(const QString &room_id, const QString &user_id, const QString &avatar_url)
|
|
|
|
{
|
|
|
|
auto fmt = QString("%1 %2").arg(room_id).arg(user_id);
|
|
|
|
AvatarUrls.insert(fmt, avatar_url);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
Cache::removeAvatarUrl(const QString &room_id, const QString &user_id)
|
|
|
|
{
|
|
|
|
auto fmt = QString("%1 %2").arg(room_id).arg(user_id);
|
|
|
|
AvatarUrls.remove(fmt);
|
|
|
|
}
|