1
0
Fork 0
mirror of https://git.jami.net/savoirfairelinux/jami-client-qt.git synced 2025-09-10 12:03:18 +02:00

misc: libclient: remove legacy account database migration mechanism

Removes all migration mechanisms and support for database versions that haven't been used for several years. Also cleans up some includes.

Change-Id: Iaf071a455f77dd4daa57f16f9924703961aa64e0
This commit is contained in:
Andreas Traczyk 2024-02-15 12:05:14 -05:00 committed by Adrien Béraud
parent a98f6ca4e3
commit 7650f45d6f
22 changed files with 80 additions and 1300 deletions

View file

@ -21,6 +21,8 @@
#include "lrcinstance.h"
#include <api/contact.h>
BannedListModel::BannedListModel(QObject* parent)
: AbstractListModelBase(parent)

View file

@ -19,6 +19,8 @@
#include "conversationlistmodelbase.h"
#include <api/contact.h>
ConversationListModelBase::ConversationListModelBase(LRCInstance* instance, QObject* parent)
: AbstractListModelBase(parent)
{

View file

@ -23,8 +23,9 @@
#include "systemtray.h"
#ifdef Q_OS_LINUX
#include "namedirectory.h"
#include <namedirectory.h>
#endif
#include <api/contact.h>
#include <QApplication>
#include <QJsonObject>

View file

@ -19,6 +19,7 @@
#include "currentconversation.h"
#include <api/conversationmodel.h>
#include <api/contact.h>
CurrentConversation::CurrentConversation(LRCInstance* lrcInstance, QObject* parent)
: QObject(parent)

View file

@ -28,13 +28,11 @@
#include <QRegularExpression>
#include <QtConcurrent/QtConcurrent>
LRCInstance::LRCInstance(migrateCallback willMigrateCb,
migrateCallback didMigrateCb,
const QString& updateUrl,
LRCInstance::LRCInstance(const QString& updateUrl,
ConnectivityMonitor* connectivityMonitor,
bool debugMode,
bool muteDaemon)
: lrc_(std::make_unique<Lrc>(willMigrateCb, didMigrateCb, !debugMode || muteDaemon))
: lrc_(std::make_unique<Lrc>(!debugMode || muteDaemon))
, updateManager_(std::make_unique<AppVersionManager>(updateUrl, connectivityMonitor, this))
, connectivityMonitor_(*connectivityMonitor)
, threadPool_(new QThreadPool(this))

View file

@ -28,16 +28,15 @@
#include "qtutils.h"
#include "utils.h"
#include "api/lrc.h"
#include "api/account.h"
#include "api/avmodel.h"
#include "api/behaviorcontroller.h"
#include "api/contact.h"
#include "api/contactmodel.h"
#include "api/conversation.h"
#include "api/conversationmodel.h"
#include "api/accountmodel.h"
#include "api/callmodel.h"
#include <api/lrc.h>
#include <api/account.h>
#include <api/avmodel.h>
#include <api/behaviorcontroller.h>
#include <api/contactmodel.h>
#include <api/conversation.h>
#include <api/conversationmodel.h>
#include <api/accountmodel.h>
#include <api/callmodel.h>
#include <QObject>
#include <QThreadPool>
@ -48,7 +47,6 @@ class ConnectivityMonitor;
using namespace lrc::api;
using migrateCallback = std::function<void()>;
using getConvPredicate = std::function<bool(const conversation::Info& conv)>;
class LRCInstance : public QObject
@ -61,9 +59,7 @@ class LRCInstance : public QObject
QML_PROPERTY(bool, currentAccountAvatarSet)
public:
explicit LRCInstance(migrateCallback willMigrateCb,
migrateCallback didMigrateCb,
const QString& updateUrl,
explicit LRCInstance(const QString& updateUrl,
ConnectivityMonitor* connectivityMonitor,
bool debugMode,
bool muteDaemon);

View file

@ -298,30 +298,7 @@ MainApplication::initLrc(const QString& downloadUrl,
bool debugMode,
bool muteDaemon)
{
/*
* Init mainwindow and finish splash when mainwindow shows up.
*/
std::atomic_bool isMigrating(false);
lrcInstance_.reset(new LRCInstance(
[this, &isMigrating] {
/*
* TODO: splash screen for account migration.
*/
isMigrating = true;
while (isMigrating) {
this->processEvents();
}
},
[&isMigrating] {
while (!isMigrating) {
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
isMigrating = false;
},
downloadUrl,
cm,
debugMode,
muteDaemon));
lrcInstance_.reset(new LRCInstance(downloadUrl, cm, debugMode, muteDaemon));
lrcInstance_->subscribeToDebugReceived();
}

View file

@ -29,6 +29,7 @@
#include "previewengine.h"
#include <api/datatransfermodel.h>
#include <api/contact.h>
#include <QApplication>
#include <QBuffer>

View file

@ -19,6 +19,8 @@
#include "lrcinstance.h"
#include <api/contact.h>
ModeratorListModel::ModeratorListModel(LRCInstance* instance, QObject* parent)
: AbstractListModelBase(parent)
{
@ -133,4 +135,4 @@ ModeratorListModel::connectAccount()
{
if (!lrcInstance_->get_currentAccountId().isEmpty())
reset();
}
}

View file

@ -25,6 +25,8 @@
#include "jamiavatartheme.h"
#include "lrcinstance.h"
#include <api/contact.h>
#include <qrencode.h>
#include <QApplication>

View file

@ -27,8 +27,8 @@
#include "utils.h"
#include "version.h"
#include "api/pluginmodel.h"
#include "api/datatransfermodel.h"
#include <api/datatransfermodel.h>
#include <api/contact.h>
#include <QApplication>
#include <QBuffer>

View file

@ -33,7 +33,6 @@
#include "authority/storagehelper.h"
#include "callbackshandler.h"
#include "database.h"
#include "vcard.h"
// old LRC
#include "api/profile.h"
@ -64,9 +63,7 @@ public:
AccountModelPimpl(AccountModel& linked,
Lrc& lrc,
const CallbacksHandler& callbackHandler,
const BehaviorController& behaviorController,
MigrationCb& willMigrateCb,
MigrationCb& didMigrateCb);
const BehaviorController& behaviorController);
~AccountModelPimpl();
using AccountInfoDbMap = std::map<QString, std::pair<account::Info, std::shared_ptr<Database>>>;
@ -84,10 +81,9 @@ public:
/**
* Add the profile information from an account to the db then add it to accounts.
* @param accountId
* @param db an optional migrated database object
* @note this method get details for an account from the daemon.
*/
void addToAccounts(const QString& accountId, std::shared_ptr<Database> db = nullptr);
void addToAccounts(const QString& accountId);
/**
* Remove account from accounts list. Emit accountRemoved.
@ -196,16 +192,9 @@ public Q_SLOTS:
AccountModel::AccountModel(Lrc& lrc,
const CallbacksHandler& callbacksHandler,
const BehaviorController& behaviorController,
MigrationCb& willMigrateCb,
MigrationCb& didMigrateCb)
const BehaviorController& behaviorController)
: QObject(nullptr)
, pimpl_(std::make_unique<AccountModelPimpl>(*this,
lrc,
callbacksHandler,
behaviorController,
willMigrateCb,
didMigrateCb))
, pimpl_(std::make_unique<AccountModelPimpl>(*this, lrc, callbacksHandler, behaviorController))
{}
AccountModel::~AccountModel() {}
@ -375,9 +364,7 @@ AccountModel::getAccountInfo(const QString& accountId) const
AccountModelPimpl::AccountModelPimpl(AccountModel& linked,
Lrc& lrc,
const CallbacksHandler& callbacksHandler,
const BehaviorController& behaviorController,
MigrationCb& willMigrateCb,
MigrationCb& didMigrateCb)
const BehaviorController& behaviorController)
: linked(linked)
, lrc {lrc}
, behaviorController(behaviorController)
@ -395,9 +382,8 @@ AccountModelPimpl::AccountModelPimpl(AccountModel& linked,
return;
}
auto accountDbs = authority::storage::migrateIfNeeded(accountIds, willMigrateCb, didMigrateCb);
for (const auto& id : accountIds) {
addToAccounts(id, accountDbs.at(accountIds.indexOf(id)));
addToAccounts(id);
}
connect(&callbacksHandler,
@ -728,38 +714,39 @@ AccountModelPimpl::slotNewPosition(const QString& accountId,
}
void
AccountModelPimpl::addToAccounts(const QString& accountId, std::shared_ptr<Database> db)
AccountModelPimpl::addToAccounts(const QString& accountId)
{
if (db == nullptr) {
try {
auto appPath = authority::storage::getPath();
auto dbName = accountId + "/history";
db = DatabaseFactory::create<Database>(dbName, appPath);
// create the profiles path if necessary
QDir profilesDir(appPath + accountId + "/profiles");
if (!profilesDir.exists()) {
profilesDir.mkpath(".");
}
} catch (const std::runtime_error& e) {
qWarning() << e.what();
return;
}
}
auto appPath = authority::storage::getPath();
auto dbName = accountId + "/history";
auto it = accounts.emplace(accountId, std::make_pair(account::Info(), db));
if (!it.second) {
qWarning("failed to add new account: id already present in map");
// Create and load the database.
auto db = std::make_shared<Database>(dbName, appPath);
try {
db->load();
} catch (const std::exception& e) {
LC_WARN << e.what();
return;
}
// Init profile
// Create the profiles path if necessary.
QDir profilesDir(appPath + accountId + "/profiles");
if (!profilesDir.exists()) {
profilesDir.mkpath(".");
}
auto it = accounts.emplace(accountId, std::make_pair(account::Info(), db));
if (!it.second) {
LC_WARN << "failed to add new account: id already present in map";
return;
}
// Initialize the profile.
account::Info& newAccInfo = (it.first)->second.first;
newAccInfo.id = accountId;
newAccInfo.profileInfo.avatar = authority::storage::getAccountAvatar(accountId);
updateAccountDetails(newAccInfo);
// Init models for this account
// Initialize models for this account.
newAccInfo.accountModel = &linked;
newAccInfo.callModel = std::make_unique<CallModel>(newAccInfo,
lrc,

View file

@ -52,9 +52,7 @@ class LIB_EXPORT AccountModel : public QObject
public:
AccountModel(Lrc& lrc,
const CallbacksHandler& callbackHandler,
const api::BehaviorController& behaviorController,
MigrationCb& willMigrateCb,
MigrationCb& didMigrateCb);
const api::BehaviorController& behaviorController);
~AccountModel();
/**

View file

@ -41,13 +41,10 @@ class LIB_EXPORT Lrc
{
public:
/**
* Construct an Lrc object and optionally invoke callbacks
* to control ui informing the user of a possibly lengthy
* migration process.
* @param willMigrateCb
* @param didMigrateCb
* Construct an Lrc object.
* @param muteDaemon
*/
Lrc(MigrationCb willMigrateCb = {}, MigrationCb didMigrateCb = {}, bool muteDaemon = false);
Lrc(bool muteDaemon = false);
~Lrc();
/**
* get a reference on account model.

View file

@ -23,7 +23,6 @@
#include "api/profile.h"
#include "api/conversation.h"
#include "api/datatransfer.h"
#include "api/lrc.h"
#include "uri.h"
#include "vcard.h"
@ -776,578 +775,6 @@ getLastTimestamp(Database& db)
return result;
}
//================================================================================
// This section provides migration helpers from ring.db
// to per-account databases yielding a file structure like:
//
// { local_storage } / jami
// └──{ account_id }
// ├── config.yml
// ├── contacts
// ├── export.gz
// ├── incomingTrustRequests
// ├── knownDevicesNames
// ├── history.db < --conversations and interactions database
// ├── profile.vcf < --account vcard
// ├── profiles < --account contact vcards
// │ │──{ contact_uri }.vcf
// │ └── ...
// ├── ring_device.crt
// └── ring_device.key
//================================================================================
namespace migration {
enum class msgFlag {
IS_INCOMING,
IS_OUTGOING,
IS_CONTACT_ADDED,
IS_INVITATION_RECEIVED,
IS_INVITATION_ACCEPTED,
IS_TEXT
};
QString profileToVcard(const lrc::api::profile::Info&, const QString&);
uint64_t getTimeFromTimeStr(const QString&) noexcept;
std::pair<msgFlag, uint64_t> migrateMessageBody(const QString&, const lrc::api::interaction::Type&);
VectorString getPeerParticipantsForConversationId(lrc::Database&, const QString&, const QString&);
void migrateAccountDb(const QString&,
std::shared_ptr<lrc::Database>,
std::shared_ptr<lrc::Database>);
namespace interaction {
static inline api::interaction::Type
to_type(const QString& type)
{
if (type == "TEXT")
return api::interaction::Type::TEXT;
else if (type == "CALL")
return api::interaction::Type::CALL;
else if (type == "CONTACT")
return api::interaction::Type::CONTACT;
else if (type == "OUTGOING_DATA_TRANSFER")
return api::interaction::Type::DATA_TRANSFER;
else if (type == "INCOMING_DATA_TRANSFER")
return api::interaction::Type::DATA_TRANSFER;
else
return api::interaction::Type::INVALID;
}
static inline QString
to_migrated_status_string(const QString& status)
{
if (status == "FAILED")
return "FAILURE";
else if (status == "SUCCEED")
return "SUCCESS";
else if (status == "READ")
return "SUCCESS";
else if (status == "UNREAD")
return "SUCCESS";
else
return status;
}
} // namespace interaction
QString
profileToVcard(const api::profile::Info& profileInfo, const QString& accountId = {})
{
using namespace api;
bool compressedImage = std::strncmp(profileInfo.avatar.toStdString().c_str(), "/9g=", 4) == 0;
;
QString vCardStr = vCard::Delimiter::BEGIN_TOKEN;
vCardStr += vCard::Delimiter::END_LINE_TOKEN;
vCardStr += vCard::Property::VERSION;
vCardStr += ":2.1";
vCardStr += vCard::Delimiter::END_LINE_TOKEN;
if (!accountId.isEmpty()) {
vCardStr += vCard::Property::UID;
vCardStr += ":";
vCardStr += accountId;
vCardStr += vCard::Delimiter::END_LINE_TOKEN;
}
vCardStr += vCard::Property::FORMATTED_NAME;
vCardStr += ":";
vCardStr += profileInfo.alias;
vCardStr += vCard::Delimiter::END_LINE_TOKEN;
if (profileInfo.type == profile::Type::JAMI) {
vCardStr += vCard::Property::TELEPHONE;
vCardStr += ":";
vCardStr += vCard::Delimiter::SEPARATOR_TOKEN;
vCardStr += "other:ring:";
vCardStr += profileInfo.uri;
vCardStr += vCard::Delimiter::END_LINE_TOKEN;
} else {
vCardStr += vCard::Property::TELEPHONE;
vCardStr += profileInfo.uri;
vCardStr += vCard::Delimiter::END_LINE_TOKEN;
}
vCardStr += vCard::Property::PHOTO;
vCardStr += vCard::Delimiter::SEPARATOR_TOKEN;
vCardStr += "ENCODING=BASE64";
vCardStr += vCard::Delimiter::SEPARATOR_TOKEN;
vCardStr += compressedImage ? "TYPE=JPEG:" : "TYPE=PNG:";
vCardStr += profileInfo.avatar;
vCardStr += vCard::Delimiter::END_LINE_TOKEN;
vCardStr += vCard::Delimiter::END_TOKEN;
return vCardStr;
}
uint64_t
getTimeFromTimeStr(const QString& str) noexcept
{
uint64_t minutes = 0, seconds = 0;
std::string timeStr = str.toStdString();
std::size_t delimiterPos = timeStr.find(":");
if (delimiterPos != std::string::npos) {
try {
minutes = std::stoull(timeStr.substr(0, delimiterPos));
seconds = std::stoull(timeStr.substr(delimiterPos + 1));
} catch (const std::exception&) {
return 0;
}
}
return minutes * 60 + seconds;
}
std::pair<msgFlag, uint64_t>
migrateMessageBody(const QString& body, const api::interaction::Type& type)
{
uint64_t duration {0};
// check in english and local to determine the direction of the call
static QString emo = "Missed outgoing call";
static QString lmo = QObject::tr("Missed outgoing call");
static QString eo = "Outgoing call";
static QString lo = QObject::tr("Outgoing call");
static QString eca = "Contact added";
static QString lca = QObject::tr("Contact added");
static QString eir = "Invitation received";
static QString lir = QObject::tr("Invitation received");
static QString eia = "Invitation accepted";
static QString lia = QObject::tr("Invitation accepted");
auto strBody = body.toStdString();
switch (type) {
case api::interaction::Type::CALL: {
bool en_missedOut = body.contains(emo);
bool en_out = body.contains(eo);
bool loc_missedOut = body.contains(lmo);
bool loc_out = body.contains(lo);
bool outgoingCall = en_missedOut || en_out || loc_missedOut || loc_out;
std::size_t dashPos = strBody.find("-");
if (dashPos != std::string::npos) {
duration = getTimeFromTimeStr(toQString(strBody.substr(dashPos + 2)));
}
return std::make_pair(msgFlag(outgoingCall), duration);
} break;
case api::interaction::Type::CONTACT:
if (body.contains(eca) || body.contains(lca)) {
return std::make_pair(msgFlag::IS_CONTACT_ADDED, 0);
} else if (body.contains(eir) || body.contains(lir)) {
return std::make_pair(msgFlag::IS_INVITATION_RECEIVED, 0);
} else if (body.contains(eia) || body.contains(lia)) {
return std::make_pair(msgFlag::IS_INVITATION_ACCEPTED, 0);
}
break;
case api::interaction::Type::INVALID:
case api::interaction::Type::TEXT:
case api::interaction::Type::DATA_TRANSFER:
case api::interaction::Type::COUNT__:
default:
return std::make_pair(msgFlag::IS_TEXT, 0);
}
return std::make_pair(msgFlag::IS_OUTGOING, 0);
}
VectorString
getPeerParticipantsForConversationId(Database& db,
const QString& profileId,
const QString& conversationId)
{
return db
.select("participant_id",
"conversations",
"id=:id AND participant_id!=:participant_id",
{{":id", conversationId}, {":participant_id", profileId}})
.payloads;
}
void
migrateAccountDb(const QString& accountId,
std::shared_ptr<Database> db,
std::shared_ptr<Database> legacyDb)
{
using namespace lrc::api;
using namespace migration;
auto accountLocalPath = getPath() + accountId + "/";
using namespace libjami::Account;
MapStringString accountDetails = ConfigurationManager::instance().getAccountDetails(
accountId.toStdString().c_str());
bool isRingAccount = accountDetails[ConfProperties::TYPE] == "RING";
std::map<QString, QString> profileIdUriMap;
std::map<QString, QString> convIdPeerUriMap;
QString accountProfileId;
// 1. profiles_accounts
// migrate account's avatar/alias from profiles table to {data_dir}/profile.vcf
QString accountUri;
if (isRingAccount) {
accountUri = accountDetails[libjami::Account::ConfProperties::USERNAME].contains("ring:")
? QString(accountDetails[libjami::Account::ConfProperties::USERNAME])
.remove(QString("ring:"))
: accountDetails[libjami::Account::ConfProperties::USERNAME];
} else {
accountUri = accountDetails[libjami::Account::ConfProperties::USERNAME];
}
auto accountProfileIds = legacyDb
->select("profile_id",
"profiles_accounts",
"account_id=:account_id AND is_account=:is_account",
{{":account_id", accountId}, {":is_account", "true"}})
.payloads;
if (accountProfileIds.size() != 1) {
return;
}
accountProfileId = accountProfileIds[0];
auto accountProfile
= legacyDb->select("photo, alias", "profiles", "id=:id", {{":id", accountProfileId}})
.payloads;
profile::Info accountProfileInfo;
// if we can not find the uri in the database
// (in the case of poorly kept SIP account uris),
// than we cannot migrate the conversations and vcard
if (!accountProfile.empty()) {
accountProfileInfo = {accountUri,
accountProfile[0],
accountProfile[1],
isRingAccount ? profile::Type::JAMI : profile::Type::SIP};
}
auto accountVcard = profileToVcard(accountProfileInfo, accountId);
QDir dir;
if (!dir.exists(accountLocalPath)) {
dir.mkpath(accountLocalPath);
}
auto profileFilePath = accountLocalPath + "profile.vcf";
QFile file(profileFilePath);
if (!file.open(QIODevice::WriteOnly)) {
throw std::runtime_error("Can't open file: " + profileFilePath.toStdString());
}
QTextStream(&file) << accountVcard;
// 2. profiles
// migrate profiles from profiles table to {data_dir}/{uri}.vcf
// - for JAMI, the scheme and the hostname is omitted
// - for SIP, the uri is must be stripped of prefix and port
// e.g. 3d1112ab2bb089370c0744a44bbbb0786418d40b.vcf
// username.vcf or username@hostname.vcf
// only select non-account profiles
auto profileIds = legacyDb
->select("profile_id",
"profiles_accounts",
"account_id=:account_id AND is_account=:is_account",
{{":account_id", accountId}, {":is_account", "false"}})
.payloads;
for (const auto& profileId : profileIds) {
auto profile = legacyDb
->select("uri, alias, photo, type",
"profiles",
"id=:id",
{{":id", profileId}})
.payloads;
if (profile.empty()) {
continue;
}
profile::Info profileInfo {profile[0], profile[2], profile[1]};
auto uri = URI(profile[0]);
auto profileUri = uri.userinfo();
if (!isRingAccount && uri.hasHostname()) {
profileUri += "@" + uri.hostname();
}
// insert into map for use during the conversations table migration
profileIdUriMap.insert(std::make_pair(profileId, profileUri));
auto vcard = profileToVcard(profileInfo);
// make sure the directory exists
QDir dir(accountLocalPath + "profiles");
if (!dir.exists())
dir.mkpath(".");
profileFilePath = accountLocalPath + "profiles/" + profileUri + ".vcf";
QFile file(profileFilePath);
// if we catch duplicates here, skip the profile because
// the previous db structure does not guarantee unique uris
if (file.exists()) {
qWarning() << "Profile file already exits: " << profileFilePath;
continue;
}
if (!file.open(QIODevice::WriteOnly)) {
qWarning() << "Can't open file: " << profileFilePath;
continue;
}
QTextStream(&file) << vcard;
}
// 3. conversations
// migrate old conversations table ==> new conversations table
// a) participant_id INTEGER becomes participant TEXT (the uri of the participant)
// use the selected non-account profiles
auto conversationIds = legacyDb
->select("id",
"conversations",
"participant_id=:participant_id",
{{":participant_id", accountProfileId}})
.payloads;
if (conversationIds.empty()) {
return;
}
for (auto conversationId : conversationIds) {
// only one peer pre-groupchat
auto peerProfileId = getPeerParticipantsForConversationId(*legacyDb,
accountProfileId,
conversationId);
if (peerProfileId.empty()) {
continue;
}
auto it = profileIdUriMap.find(peerProfileId.at(0));
// we cannot insert in the conversations table without a uri
if (it == profileIdUriMap.end()) {
continue;
}
convIdPeerUriMap.insert(std::make_pair(conversationId, it->second));
try {
db->insertInto("conversations",
{{":id", "id"}, {":participant", "participant"}},
{{":id", conversationId}, {":participant", it->second}});
} catch (const std::runtime_error& e) {
qWarning() << "Couldn't migrate conversation: " << e.what();
continue;
}
}
// 4. interactions
auto allInteractions = legacyDb->select("account_id, author_id, conversation_id, \
timestamp, body, type, status, daemon_id",
"interactions",
"account_id=:account_id",
{{":account_id", accountProfileId}});
auto interactionIt = allInteractions.payloads.begin();
while (interactionIt != allInteractions.payloads.end()) {
auto author_id = *(interactionIt + 1);
auto convId = *(interactionIt + 2);
auto timestamp = *(interactionIt + 3);
auto body = *(interactionIt + 4);
auto type = interaction::to_type(*(interactionIt + 5));
auto statusStr = *(interactionIt + 6);
auto daemonId = *(interactionIt + 7);
auto it = profileIdUriMap.find(author_id);
if (it == profileIdUriMap.end() && author_id != accountProfileId) {
std::advance(interactionIt, allInteractions.nbrOfCols);
continue;
}
// migrate body+type ==> msgFlag+duration
auto migratedMsg = migrateMessageBody(body, type);
QString profileUri = it == profileIdUriMap.end() ? "" : it->second;
// clear author uri if outgoing
switch (migratedMsg.first) {
case msgFlag::IS_OUTGOING:
case msgFlag::IS_CONTACT_ADDED:
profileUri.clear();
break;
case msgFlag::IS_INCOMING:
case msgFlag::IS_INVITATION_RECEIVED:
case msgFlag::IS_INVITATION_ACCEPTED: {
// try to set profile uri using the conversation id
auto it = convIdPeerUriMap.find(convId);
if (it == convIdPeerUriMap.end()) {
std::advance(interactionIt, allInteractions.nbrOfCols);
continue;
}
profileUri = it->second;
break;
}
case msgFlag::IS_TEXT:
default:
break;
}
// Set all read, call and datatransfer, and contact added
// interactions to a read state
bool is_read = statusStr != "UNREAD" || type == api::interaction::Type::CALL
|| type == api::interaction::Type::CONTACT;
// migrate status
if (migratedMsg.first == msgFlag::IS_INVITATION_RECEIVED) {
statusStr = "UNKNOWN";
}
QString extra_data = migratedMsg.second == 0
? ""
: JSONStringFromInitList(
{qMakePair(QString("duration"),
QJsonValue(QString::number(migratedMsg.second)))});
if (accountUri == profileUri)
profileUri.clear();
auto typeStr = api::interaction::to_string(type);
try {
db->insertInto("interactions",
{{":author", "author"},
{":conversation", "conversation"},
{":timestamp", "timestamp"},
{":body", "body"},
{":type", "type"},
{":status", "status"},
{":is_read", "is_read"},
{":daemon_id", "daemon_id"},
{":extra_data", "extra_data"}},
{{":author", profileUri},
{":conversation", convId},
{":timestamp", timestamp},
{migratedMsg.first != msgFlag::IS_TEXT ? "" : ":body", body},
{":type", api::interaction::to_string(type)},
{":status", interaction::to_migrated_status_string(statusStr)},
{":is_read", is_read ? "1" : "0"},
{daemonId.isEmpty() ? "" : ":daemon_id", daemonId},
{extra_data.isEmpty() ? "" : ":extra_data", extra_data}});
} catch (const std::runtime_error& e) {
qWarning() << e.what();
}
std::advance(interactionIt, allInteractions.nbrOfCols);
}
qDebug() << "Done";
}
} // namespace migration
std::vector<std::shared_ptr<Database>>
migrateIfNeeded(const QStringList& accountIds, MigrationCb& willMigrateCb, MigrationCb& didMigrateCb)
{
using namespace lrc::api;
using namespace migration;
std::vector<std::shared_ptr<Database>> dbs(accountIds.size());
if (!accountIds.size()) {
qDebug() << "No accounts to migrate";
return dbs;
}
auto appPath = getPath();
// ring -> jami path migration
QDir dataDir(appPath);
// create data directory if not created yet
dataDir.mkpath(appPath);
QDir oldDataDir(appPath);
oldDataDir.cdUp();
oldDataDir = oldDataDir.absolutePath()
#if defined(_WIN32)
+ "/Savoir-faire Linux/Ring";
#elif defined(__APPLE__)
+ "/ring";
#else
+ "/gnome-ring";
#endif
QStringList filesList = oldDataDir.entryList();
QString filename;
QDir dir;
bool success = true;
Q_FOREACH (filename, filesList) {
qDebug() << "Migrate " << oldDataDir.absolutePath() << "/" << filename << " to "
<< dataDir.absolutePath() + "/" + filename;
if (filename != "." && filename != "..") {
success &= dir.rename(oldDataDir.absolutePath() + "/" + filename,
dataDir.absolutePath() + "/" + filename);
}
}
if (success) {
// Remove old directory if the migration is successful.
#if defined(_WIN32)
oldDataDir.cdUp();
#endif
oldDataDir.removeRecursively();
}
bool needsMigration = false;
std::map<QString, bool> hasMigratedData;
for (const auto& accountId : accountIds) {
auto hasMigratedDb = QFile(appPath + accountId + "/history.db").exists()
&& !QFile(appPath + accountId + "/history.db-journal").exists();
hasMigratedData.insert(std::make_pair(accountId, hasMigratedDb));
needsMigration |= !hasMigratedDb;
}
if (!needsMigration) {
// if there's any lingering pre-migration data, remove it
QFile(dataDir.absoluteFilePath("ring.db")).remove();
QDir(dataDir.absoluteFilePath("text/")).removeRecursively();
QDir(dataDir.absoluteFilePath("profiles/")).removeRecursively();
QDir(dataDir.absoluteFilePath("peer_profiles/")).removeRecursively();
qDebug() << "No migration required";
return dbs;
}
// A fairly long migration may now occur
std::thread migrateThread(
[&appPath, &accountIds, &dbs, &didMigrateCb, &dataDir, &hasMigratedData] {
// 1. migrate old lrc -> new lrc if needed
// 2. migrate new lrc db version 1 -> db version 1.1 if needed
// the destructor of LegacyDatabase will remove 'ring.db' and clean out
// old lrc files
std::shared_ptr<Database> legacyDb;
try {
legacyDb = lrc::DatabaseFactory::create<LegacyDatabase>(appPath);
} catch (const std::runtime_error& e) {
qDebug() << "Exception while attempting to load legacy database: " << e.what();
if (didMigrateCb)
didMigrateCb();
return;
}
// attempt to make a backup of ring.db
{
QFile dbFile(dataDir.absoluteFilePath("ring.db"));
if (dbFile.open(QIODevice::ReadOnly)) {
dbFile.copy(appPath + "ring.db.bak");
}
}
// 3. migrate db version 1.1 -> per account dbs version 1
int index = 0;
for (const auto& accountId : accountIds) {
if (hasMigratedData.at(accountId)) {
index++;
continue;
}
qDebug() << "Migrating account: " << accountId << "...";
// try to remove the transaction journal from a failed migration
QFile(appPath + accountId + "/history.db-journal").remove();
try {
QSqlDatabase::database().transaction();
auto dbName = QString::fromStdString(accountId.toStdString() + "/history");
dbs.at(index) = lrc::DatabaseFactory::create<Database>(dbName, appPath);
auto& db = dbs.at(index++);
migration::migrateAccountDb(accountId, db, legacyDb);
QSqlDatabase::database().commit();
} catch (const std::runtime_error& e) {
qWarning().noquote() << "Could not migrate database for account: " << accountId
<< "\n " << e.what();
QSqlDatabase::database().rollback();
}
}
// done
if (didMigrateCb)
didMigrateCb();
});
// if willMigrateCb blocks, it must be unblocked by didMigrateCb
if (willMigrateCb)
willMigrateCb();
migrateThread.join();
return dbs;
}
} // namespace storage
} // namespace authority

View file

@ -346,9 +346,7 @@ uint64_t getLastTimestamp(Database& db);
* @param willMigrateCb to invoke when migration will occur
* @param didMigrateCb to invoke when migration has completed
*/
std::vector<std::shared_ptr<Database>> migrateIfNeeded(const QStringList& accountIds,
MigrationCb& willMigrateCb,
MigrationCb& didMigrateCb);
std::vector<std::shared_ptr<Database>> migrateIfNeeded(const QStringList& accountIds);
} // namespace storage

View file

@ -191,7 +191,9 @@ Database::migrateIfNeeded()
void
Database::migrateFromVersion(const QString& currentVersion)
{
(void) currentVersion;
// If we ever have a new version, we can migrate the database here.
LC_WARN << "Database migration from version " << currentVersion << " to " << version_
<< " not implemented";
}
void
@ -471,547 +473,4 @@ Database::QueryTruncateError::details()
return qts.readAll();
}
/*****************************************************************************
* *
* LegacyDatabase *
* *
****************************************************************************/
LegacyDatabase::LegacyDatabase(const QString& basePath)
: Database("ring", basePath)
{
version_ = LEGACY_DB_VERSION;
}
LegacyDatabase::~LegacyDatabase()
{
remove();
// remove old LRC files
QDir(basePath_ + "text/").removeRecursively();
QDir(basePath_ + "profiles/").removeRecursively();
QDir(basePath_ + "peer_profiles/").removeRecursively();
}
void
LegacyDatabase::load()
{
// open the database.
if (not db_.open()) {
std::stringstream ss;
ss << "cannot open database: " << connectionName_.toStdString();
throw std::runtime_error(ss.str());
}
// if db is empty we create them.
if (db_.tables().empty()) {
try {
QSqlDatabase::database(connectionName_).transaction();
createTables();
QSqlDatabase::database(connectionName_).commit();
} catch (QueryError& e) {
QSqlDatabase::database(connectionName_).rollback();
throw std::runtime_error("Could not correctly create the database");
}
migrateOldFiles();
} else {
migrateIfNeeded();
}
}
void
LegacyDatabase::createTables()
{
QSqlQuery query(db_);
auto tableProfiles = "CREATE TABLE profiles (id INTEGER PRIMARY KEY, \
uri TEXT NOT NULL, \
alias TEXT, \
photo TEXT, \
type TEXT, \
status TEXT)";
auto tableConversations = "CREATE TABLE conversations (id INTEGER,\
participant_id INTEGER, \
FOREIGN KEY(participant_id) REFERENCES profiles(id))";
auto tableInteractions = "CREATE TABLE interactions (id INTEGER PRIMARY KEY,\
account_id INTEGER, \
author_id INTEGER, \
conversation_id INTEGER, \
timestamp INTEGER, \
body TEXT, \
type TEXT, \
status TEXT, \
daemon_id TEXT, \
FOREIGN KEY(account_id) REFERENCES profiles(id), \
FOREIGN KEY(author_id) REFERENCES profiles(id), \
FOREIGN KEY(conversation_id) REFERENCES conversations(id))";
auto tableProfileAccounts
= "CREATE TABLE profiles_accounts (profile_id INTEGER NOT NULL, \
account_id TEXT NOT NULL, \
is_account TEXT, \
FOREIGN KEY(profile_id) REFERENCES profiles(id))";
// add profiles table
if (not db_.tables().contains("profiles", Qt::CaseInsensitive)
and not query.exec(tableProfiles)) {
throw QueryError(std::move(query));
}
// add conversations table
if (not db_.tables().contains("conversations", Qt::CaseInsensitive)
and not query.exec(tableConversations)) {
throw QueryError(std::move(query));
}
// add interactions table
if (not db_.tables().contains("interactions", Qt::CaseInsensitive)
and not query.exec(tableInteractions)) {
throw QueryError(std::move(query));
}
// add profiles accounts table
if (not db_.tables().contains("profiles_accounts", Qt::CaseInsensitive)
and not query.exec(tableProfileAccounts)) {
throw QueryError(std::move(query));
}
storeVersion(version_);
}
void
LegacyDatabase::migrateOldFiles()
{
migrateLocalProfiles();
migratePeerProfiles();
migrateTextHistory();
linkRingProfilesWithAccounts(true);
}
void
LegacyDatabase::migrateLocalProfiles()
{
const QDir profilesDir = basePath_ + "profiles/";
const QStringList entries = profilesDir.entryList({QStringLiteral("*.vcf")}, QDir::Files);
Q_FOREACH (const QString& item, entries) {
auto filePath = profilesDir.path() + '/' + item;
QString content;
QFile file(filePath);
if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
content = QString::fromUtf8(file.readAll());
} else {
qWarning() << "Could not open .vcf file";
continue;
}
const auto vCard = lrc::vCard::utils::toHashMap(content.toUtf8());
const auto alias = vCard[lrc::vCard::Property::FORMATTED_NAME];
const auto avatar = vCard["PHOTO;ENCODING=BASE64;TYPE=PNG"];
const QStringList accountIds = ConfigurationManager::instance().getAccountList();
for (auto accountId : accountIds) {
// NOTE: If the daemon is down, but dbus answered, id can contains
// "Remote peer disconnected", "The name is not activable", etc.
// So avoid to migrate useless directories.
for (auto& id : accountIds)
if (id.indexOf(" ") != -1) {
qWarning() << "Invalid dbus answer. Daemon not running";
return;
}
MapStringString account = ConfigurationManager::instance().getAccountDetails(
accountId.toStdString().c_str());
auto accountURI
= account[libjami::Account::ConfProperties::USERNAME].contains("ring:")
? account[libjami::Account::ConfProperties::USERNAME].toStdString().substr(
std::string("ring:").size())
: account[libjami::Account::ConfProperties::USERNAME].toStdString();
for (const auto& accountId : accountIds) {
MapStringString account = ConfigurationManager::instance().getAccountDetails(
accountId.toStdString().c_str());
auto type = account[libjami::Account::ConfProperties::TYPE] == "SIP" ? "SIP"
: "RING";
auto uri = account[libjami::Account::ConfProperties::USERNAME].contains("ring:")
? QString(account[libjami::Account::ConfProperties::USERNAME])
.remove(0, QString("ring:").size())
: account[libjami::Account::ConfProperties::USERNAME];
if (select("id", "profiles", "uri=:uri", {{":uri", uri}}).payloads.empty()) {
insertInto("profiles",
{{":uri", "uri"},
{":alias", "alias"},
{":photo", "photo"},
{":type", "type"},
{":status", "status"}},
{{":uri", uri},
{":alias", alias},
{":photo", avatar},
{":type", type},
{":status", "TRUSTED"}});
auto profileIds = select("id", "profiles", "uri=:uri", {{":uri", uri}}).payloads;
if (!profileIds.empty()
&& select("profile_id",
"profiles_accounts",
"account_id=:account_id AND is_account=:is_account",
{{":account_id", accountId}, {":is_account", "true"}})
.payloads.empty()) {
insertInto("profiles_accounts",
{{":profile_id", "profile_id"},
{":account_id", "account_id"},
{":is_account", "is_account"}},
{{":profile_id", profileIds[0]},
{":account_id", accountId},
{":is_account", "true"}});
}
}
}
}
}
}
void
LegacyDatabase::migratePeerProfiles()
{
const QDir profilesDir = basePath_ + "peer_profiles/";
const QStringList entries = profilesDir.entryList({QStringLiteral("*.vcf")}, QDir::Files);
Q_FOREACH (const QString& item, entries) {
auto filePath = profilesDir.path() + '/' + item;
QString content;
QFile file(filePath);
if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
content = QString::fromUtf8(file.readAll());
} else {
qWarning() << "Could not open vcf file";
continue;
}
const auto vCard = lrc::vCard::utils::toHashMap(content.toUtf8());
auto uri = vCard["TEL;other"];
const auto alias = vCard["FN"];
const auto avatar = vCard["PHOTO;ENCODING=BASE64;TYPE=PNG"];
const QString type = uri.startsWith("ring:") ? "RING" : "SIP";
if (uri.startsWith("ring:")) {
uri = uri.mid(QString("ring:").size());
}
if (select("id", "profiles", "uri=:uri", {{":uri", uri}}).payloads.empty()) {
insertInto("profiles",
{{":uri", "uri"},
{":alias", "alias"},
{":photo", "photo"},
{":type", "type"},
{":status", "status"}},
{{":uri", uri},
{":alias", alias},
{":photo", avatar},
{":type", type},
{":status", "TRUSTED"}});
}
}
}
void
LegacyDatabase::migrateTextHistory()
{
// load all text recordings so we can recover CMs that are not in the call history
QDir dir(basePath_ + "text/");
if (dir.exists()) {
// get .json files, sorted by time, latest first
QStringList filters;
filters << "*.json";
auto list = dir.entryInfoList(filters,
QDir::Files | QDir::NoSymLinks | QDir::Readable,
QDir::Time);
for (int i = 0; i < list.size(); ++i) {
QFileInfo fileInfo = list.at(i);
QString content;
QFile file(fileInfo.absoluteFilePath());
if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
content = QString::fromUtf8(file.readAll());
} else {
qWarning() << "Could not open text recording json file";
continue;
}
if (!content.isEmpty()) {
QJsonParseError err;
auto loadDoc = QJsonDocument::fromJson(content.toUtf8(), &err).object();
if (loadDoc.find("peers") == loadDoc.end())
continue;
if (loadDoc.find("groups") == loadDoc.end())
continue;
// Load account
auto peersObject = loadDoc["peers"].toArray()[0].toObject();
MapStringString details = ConfigurationManager::instance().getAccountDetails(
peersObject["accountId"].toString());
if (!details.contains(libjami::Account::ConfProperties::USERNAME))
continue;
auto accountUri = details[libjami::Account::ConfProperties::USERNAME];
auto isARingContact = accountUri.startsWith("ring:");
if (isARingContact) {
accountUri = accountUri.mid(QString("ring:").length());
}
auto accountIds = select("id", "profiles", "uri=:uri", {{":uri", accountUri}})
.payloads;
auto contactIds = select("id",
"profiles",
"uri=:uri",
{{":uri", peersObject["uri"].toString()}})
.payloads;
if (contactIds.empty()) {
insertInto("profiles",
{{":uri", "uri"},
{":alias", "alias"},
{":photo", "photo"},
{":type", "type"},
{":status", "status"}},
{{":uri", peersObject["uri"].toString()},
{":alias", ""},
{":photo", ""},
{":type", "RING"},
{":status", "TRUSTED"}});
// NOTE: this profile is in a case where it's not a contact for the daemon but a
// conversation with an account. So we choose to add the profile to daemon's contacts
if (isARingContact) {
ConfigurationManager::instance()
.addContact(peersObject["accountId"].toString(),
peersObject["uri"].toString());
}
contactIds = select("id",
"profiles",
"uri=:uri",
{{":uri", peersObject["uri"].toString()}})
.payloads;
}
if (accountIds.empty()) {
qDebug() << "Can't find profile for URI: "
<< peersObject["accountId"].toString() << ". Ignore this file.";
} else if (contactIds.empty()) {
qDebug() << "Can't find profile for URI: " << peersObject["uri"].toString()
<< ". Ignore this file.";
} else {
auto contactId = contactIds[0];
// link profile id to account id
auto profiles = select("profile_id",
"profiles_accounts",
"profile_id=:profile_id AND \
account_id=:account_id AND \
is_account=:is_account",
{{":profile_id", contactId},
{":account_id", peersObject["accountId"].toString()},
{":is_account", "false"}})
.payloads;
if (profiles.empty()) {
insertInto("profiles_accounts",
{{":profile_id", "profile_id"},
{":account_id", "account_id"},
{":is_account", "is_account"}},
{{":profile_id", contactId},
{":account_id", peersObject["accountId"].toString()},
{":is_account", "false"}});
}
auto accountId = accountIds[0];
auto newConversationsId
= select("IFNULL(MAX(id), 0) + 1", "conversations", "1=1", {}).payloads[0];
try {
QSqlDatabase::database().transaction();
insertInto("conversations",
{{":id", "id"}, {":participant_id", "participant_id"}},
{{":id", newConversationsId}, {":participant_id", accountId}});
insertInto("conversations",
{{":id", "id"}, {":participant_id", "participant_id"}},
{{":id", newConversationsId}, {":participant_id", contactId}});
QSqlDatabase::database().commit();
} catch (QueryInsertError& e) {
qDebug() << e.details();
QSqlDatabase::database().rollback();
}
// Load interactions
auto groupsArray = loadDoc["groups"].toArray();
for (const auto& groupObject : groupsArray) {
auto messagesArray = groupObject.toObject()["messages"].toArray();
for (const auto& messageRef : messagesArray) {
auto messageObject = messageRef.toObject();
auto direction = messageObject["direction"].toInt();
auto body = messageObject["payloads"]
.toArray()[0]
.toObject()["payload"]
.toString();
insertInto("interactions",
{{":account_id", "account_id"},
{":author_id", "author_id"},
{":conversation_id", "conversation_id"},
{":timestamp", "timestamp"},
{":body", "body"},
{":type", "type"},
{":status", "status"}},
{{":account_id", accountId},
{":author_id", direction ? accountId : contactId},
{":conversation_id", newConversationsId},
{":timestamp", messageObject["timestamp"].toString()},
{":body", body},
{":type", "TEXT"},
{":status", direction ? "SUCCEED" : "READ"}});
}
}
}
} else {
qWarning() << "Text recording file is empty";
}
}
}
}
void
LegacyDatabase::migrateFromVersion(const QString& currentVersion)
{
if (currentVersion == "1") {
migrateSchemaFromVersion1();
}
}
void
LegacyDatabase::migrateSchemaFromVersion1()
{
QSqlQuery query(db_);
auto tableProfileAccounts
= "CREATE TABLE profiles_accounts (profile_id INTEGER NOT NULL, \
account_id TEXT NOT NULL, \
is_account TEXT, \
FOREIGN KEY(profile_id) REFERENCES profiles(id))";
// add profiles accounts table
if (not db_.tables().contains("profiles_accounts", Qt::CaseInsensitive)
and not query.exec(tableProfileAccounts)) {
throw QueryError(std::move(query));
}
linkRingProfilesWithAccounts(false);
}
void
LegacyDatabase::linkRingProfilesWithAccounts(bool contactsOnly)
{
const QStringList accountIds = ConfigurationManager::instance().getAccountList();
for (auto accountId : accountIds) {
// NOTE: If the daemon is down, but dbus answered, id can contains
// "Remote peer disconnected", "The name is not activable", etc.
// So avoid to migrate useless directories.
for (auto& id : accountIds)
if (id.indexOf(" ") != -1) {
qWarning() << "Invalid dbus answer. Daemon not running";
return;
}
MapStringString account = ConfigurationManager::instance().getAccountDetails(
accountId.toStdString().c_str());
auto accountURI = account[libjami::Account::ConfProperties::USERNAME].contains("ring:")
? QString(account[libjami::Account::ConfProperties::USERNAME])
.remove(0, QString("ring:").size())
: account[libjami::Account::ConfProperties::USERNAME];
auto profileIds = select("id", "profiles", "uri=:uri", {{":uri", accountURI}}).payloads;
if (profileIds.empty()) {
continue;
}
if (!contactsOnly) {
// if is_account is true we should have only one profile id for account id
if (select("profile_id",
"profiles_accounts",
"account_id=:account_id AND is_account=:is_account",
{{":account_id", accountId}, {":is_account", "true"}})
.payloads.empty()) {
insertInto("profiles_accounts",
{{":profile_id", "profile_id"},
{":account_id", "account_id"},
{":is_account", "is_account"}},
{{":profile_id", profileIds[0]},
{":account_id", accountId},
{":is_account", "true"}});
}
}
if (account[libjami::Account::ConfProperties::TYPE]
== libjami::Account::ProtocolNames::RING) {
// update RING contacts
const VectorMapStringString& contacts_vector
= ConfigurationManager::instance().getContacts(accountId.toStdString().c_str());
// update contacts profiles
for (auto contact_info : contacts_vector) {
auto contactURI = contact_info["id"];
updateProfileAccountForContact(contactURI, accountId);
}
// update pending contacts profiles
const VectorMapStringString& pending_tr
= ConfigurationManager::instance().getTrustRequests(accountId.toStdString().c_str());
for (auto tr_info : pending_tr) {
auto contactURI = tr_info[libjami::Account::TrustRequest::FROM];
updateProfileAccountForContact(contactURI, accountId);
}
} else if (account[libjami::Account::ConfProperties::TYPE]
== libjami::Account::ProtocolNames::SIP) {
// update SIP contacts
auto conversations = select("id",
"conversations",
"participant_id=:participant_id",
{{":participant_id", profileIds[0]}})
.payloads;
for (const auto& c : conversations) {
auto otherParticipants = select("participant_id",
"conversations",
"id=:id AND participant_id!=:participant_id",
{{":id", c}, {":participant_id", profileIds[0]}})
.payloads;
for (const auto& participant : otherParticipants) {
auto rows = select("profile_id",
"profiles_accounts",
"profile_id=:profile_id AND \
account_id=:account_id AND \
is_account=:is_account",
{{":profile_id", participant},
{":account_id", accountId},
{":is_account", "false"}})
.payloads;
if (rows.empty()) {
insertInto("profiles_accounts",
{{":profile_id", "profile_id"},
{":account_id", "account_id"},
{":is_account", "is_account"}},
{{":profile_id", participant},
{":account_id", accountId},
{":is_account", "false"}});
}
}
}
}
}
}
void
LegacyDatabase::updateProfileAccountForContact(const QString& contactURI, const QString& accountId)
{
auto profileIds = select("id", "profiles", "uri=:uri", {{":uri", contactURI}}).payloads;
if (profileIds.empty()) {
return;
}
auto rows = select("profile_id",
"profiles_accounts",
"account_id=:account_id AND is_account=:is_account",
{{":account_id", accountId}, {":is_account", "false"}})
.payloads;
if (std::find(rows.begin(), rows.end(), profileIds[0]) == rows.end()) {
insertInto("profiles_accounts",
{{":profile_id", "profile_id"},
{":account_id", "account_id"},
{":is_account", "is_account"}},
{{":profile_id", profileIds[0]},
{":account_id", accountId},
{":is_account", "false"}});
}
}
} // namespace lrc

View file

@ -277,61 +277,4 @@ protected:
QSqlDatabase db_;
};
/**
* @brief A legacy database to help migrate from the single db epoch.
* @note not thread safe.
*/
class LegacyDatabase final : public Database
{
Q_OBJECT
public:
/**
* Create a migratory legacy database.
* @exception QueryError database query error.
*/
LegacyDatabase(const QString& basePath);
~LegacyDatabase();
void load() override;
protected:
void createTables() override;
private:
/**
* Migration helpers from old LRC. Parse JSON for history and VCards and add it into the database.
*/
void migrateOldFiles();
void migrateLocalProfiles();
void migratePeerProfiles();
void migrateTextHistory();
void migrateFromVersion(const QString& version) override;
/**
* Migration helpers from version 1
*/
void migrateSchemaFromVersion1();
void linkRingProfilesWithAccounts(bool contactsOnly);
void updateProfileAccountForContact(const QString& contactURI, const QString& accountID);
};
namespace DatabaseFactory {
template<typename T, class... Args>
std::enable_if_t<std::is_constructible<T, Args...>::value, std::shared_ptr<Database>>
create(Args&&... args)
{
auto pdb = std::static_pointer_cast<Database>(std::make_shared<T>(std::forward<Args>(args)...));
// To allow override of the db load method we don't
// call it from the constructor.
try {
pdb->load();
} catch (const std::runtime_error& e) {
throw std::runtime_error(e);
}
return pdb;
}
} // namespace DatabaseFactory
} // namespace lrc

View file

@ -31,14 +31,12 @@
#include "api/avmodel.h"
#include "api/pluginmodel.h"
#include "api/behaviorcontroller.h"
#include "api/datatransfermodel.h"
#include "api/accountmodel.h"
#include "callbackshandler.h"
#include "dbus/callmanager.h"
#include "dbus/configurationmanager.h"
#include "dbus/instancemanager.h"
#include "dbus/configurationmanager.h"
#include "authority/storagehelper.h"
Q_LOGGING_CATEGORY(libclientLog, "libclient")
@ -54,7 +52,7 @@ bool isFinished(const QString& callState);
class LrcPimpl
{
public:
LrcPimpl(Lrc& linked, MigrationCb& willMigrateCb, MigrationCb& didMigrateCb);
LrcPimpl(Lrc& linked);
const Lrc& linked;
std::unique_ptr<BehaviorController> behaviorController;
@ -64,7 +62,7 @@ public:
std::unique_ptr<PluginModel> PluginModel_;
};
Lrc::Lrc(MigrationCb willDoMigrationCb, MigrationCb didDoMigrationCb, bool muteDaemon)
Lrc::Lrc(bool muteDaemon)
{
lrc::api::Lrc::holdConferences.store(true);
#ifndef ENABLE_LIBWRAP
@ -79,7 +77,7 @@ Lrc::Lrc(MigrationCb willDoMigrationCb, MigrationCb didDoMigrationCb, bool muteD
// Ensure Daemon is running/loaded (especially on non-DBus platforms)
// before instantiating LRC and its members
InstanceManager::instance(muteDaemon);
lrcPimpl_ = std::make_unique<LrcPimpl>(*this, willDoMigrationCb, didDoMigrationCb);
lrcPimpl_ = std::make_unique<LrcPimpl>(*this);
}
Lrc::~Lrc()
@ -245,15 +243,11 @@ Lrc::monitor(bool continuous)
ConfigurationManager::instance().monitor(continuous);
}
LrcPimpl::LrcPimpl(Lrc& linked, MigrationCb& willMigrateCb, MigrationCb& didMigrateCb)
LrcPimpl::LrcPimpl(Lrc& linked)
: linked(linked)
, behaviorController(std::make_unique<BehaviorController>())
, callbackHandler(std::make_unique<CallbacksHandler>(linked))
, accountModel(std::make_unique<AccountModel>(linked,
*callbackHandler,
*behaviorController,
willMigrateCb,
didMigrateCb))
, accountModel(std::make_unique<AccountModel>(linked, *callbackHandler, *behaviorController))
, AVModel_ {std::make_unique<AVModel>(*callbackHandler)}
, PluginModel_ {std::make_unique<PluginModel>()}
{}

View file

@ -267,6 +267,3 @@ private:
return p | second; \
} \
DO_PRAGMA(GCC diagnostic pop)
#include <functional>
typedef std::function<void()> MigrationCb;

View file

@ -22,12 +22,11 @@
#include "qmlregister.h"
#include "systemtray.h"
#include "api/profile.h"
#include "api/account.h"
#include "api/conversationmodel.h"
#include "api/contactmodel.h"
#include <atomic>
#include <api/profile.h>
#include <api/account.h>
#include <api/conversationmodel.h>
#include <api/contactmodel.h>
#include <api/contact.h>
#include <QFontDatabase>
#include <QQmlContext>
@ -45,7 +44,9 @@
#include <windows.h>
#endif
#include <atomic>
#include <thread>
using namespace std::literals::chrono_literals;
class Setup : public QObject
@ -86,8 +87,7 @@ public Q_SLOTS:
QFontDatabase::addApplicationFont(":/images/FontAwesome.otf");
lrcInstance_.reset(
new LRCInstance(nullptr, nullptr, "", connectivityMonitor_.get(), true, muteDaemon_));
lrcInstance_.reset(new LRCInstance("", connectivityMonitor_.get(), true, muteDaemon_));
lrcInstance_->subscribeToDebugReceived();
auto downloadPath = settingsManager_->getValue(Settings::Key::DownloadPath);

View file

@ -16,8 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "mainapplication.h"
#include "qmlregister.h"
#include "lrcinstance.h"
#include "appsettingsmanager.h"
#include "connectivitymonitor.h"
#include "systemtray.h"
@ -47,8 +46,7 @@ public:
systemTray.reset(new SystemTray(settingsManager.get(), nullptr));
std::atomic_bool isMigrating(false);
lrcInstance.reset(
new LRCInstance(nullptr, nullptr, "", connectivityMonitor.get(), debugMode, muteDaemon));
lrcInstance.reset(new LRCInstance("", connectivityMonitor.get(), debugMode, muteDaemon));
lrcInstance->subscribeToDebugReceived();
// setup the adapters (their lifetimes are that of MainApplication)