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:
parent
a98f6ca4e3
commit
7650f45d6f
22 changed files with 80 additions and 1300 deletions
|
@ -21,6 +21,8 @@
|
|||
|
||||
#include "lrcinstance.h"
|
||||
|
||||
#include <api/contact.h>
|
||||
|
||||
BannedListModel::BannedListModel(QObject* parent)
|
||||
: AbstractListModelBase(parent)
|
||||
|
||||
|
|
|
@ -19,6 +19,8 @@
|
|||
|
||||
#include "conversationlistmodelbase.h"
|
||||
|
||||
#include <api/contact.h>
|
||||
|
||||
ConversationListModelBase::ConversationListModelBase(LRCInstance* instance, QObject* parent)
|
||||
: AbstractListModelBase(parent)
|
||||
{
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
#include "currentconversation.h"
|
||||
|
||||
#include <api/conversationmodel.h>
|
||||
#include <api/contact.h>
|
||||
|
||||
CurrentConversation::CurrentConversation(LRCInstance* lrcInstance, QObject* parent)
|
||||
: QObject(parent)
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
#include "previewengine.h"
|
||||
|
||||
#include <api/datatransfermodel.h>
|
||||
#include <api/contact.h>
|
||||
|
||||
#include <QApplication>
|
||||
#include <QBuffer>
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,6 +25,8 @@
|
|||
#include "jamiavatartheme.h"
|
||||
#include "lrcinstance.h"
|
||||
|
||||
#include <api/contact.h>
|
||||
|
||||
#include <qrencode.h>
|
||||
|
||||
#include <QApplication>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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();
|
||||
/**
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>()}
|
||||
{}
|
||||
|
|
|
@ -267,6 +267,3 @@ private:
|
|||
return p | second; \
|
||||
} \
|
||||
DO_PRAGMA(GCC diagnostic pop)
|
||||
|
||||
#include <functional>
|
||||
typedef std::function<void()> MigrationCb;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Add table
Reference in a new issue