From 857416294999fa62204d7117deb3daf5c5d73621 Mon Sep 17 00:00:00 2001 From: sudden6 Date: Tue, 26 Jun 2018 15:55:21 +0200 Subject: [PATCH] refactor(core): cleanup Core class - use a factory method to create it - make it handle its own thread - remove dependency on GUI --- src/core/core.cpp | 425 +++++++++++++++++------------------- src/core/core.h | 32 +-- src/core/corefile.cpp | 12 +- src/core/toxoptions.cpp | 11 + src/core/toxoptions.h | 2 + src/nexus.cpp | 2 + src/persistence/profile.cpp | 49 ++--- src/persistence/profile.h | 7 +- 8 files changed, 259 insertions(+), 281 deletions(-) diff --git a/src/core/core.cpp b/src/core/core.cpp index f22bb4316..4c30b0bfb 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -28,7 +28,6 @@ #include "src/model/groupinvite.h" #include "src/nexus.h" #include "src/persistence/profile.h" -#include "src/widget/gui.h" #include #include @@ -40,209 +39,38 @@ #include const QString Core::TOX_EXT = ".tox"; -QThread* Core::coreThread{nullptr}; #define MAX_GROUP_MESSAGE_LEN 1024 -Core::Core(QThread* CoreThread, Profile& profile, const ICoreSettings* const settings) +Core::Core(QThread* coreThread) : tox(nullptr) , av(nullptr) - , profile(profile) - , ready(false) - , s{settings} + , coreThread{coreThread} { - coreThread = CoreThread; - toxTimer = new QTimer(this); - toxTimer->setSingleShot(true); - connect(toxTimer, &QTimer::timeout, this, &Core::process); - s->connectTo_dhtServerListChanged([=](const QList& servers){ - process(); - }); -} - -void Core::deadifyTox() -{ - if (av) { - delete av; - av = nullptr; - } - - if (tox) { - tox_kill(tox); - tox = nullptr; - } + toxTimer.setSingleShot(true); + connect(&this->toxTimer, &QTimer::timeout, this, &Core::process); } Core::~Core() { - if (coreThread->isRunning()) { - if (QThread::currentThread() == coreThread) { - killTimers(false); - } else { - QMetaObject::invokeMethod(this, "killTimers", Qt::BlockingQueuedConnection, - Q_ARG(bool, false)); - } + if (QThread::currentThread() == coreThread) { + killTimers(); + } else { + // ensure the timer is stopped, even if not called from this thread + QMetaObject::invokeMethod(this, "killTimers", Qt::BlockingQueuedConnection); } coreThread->exit(0); - if (QThread::currentThread() != coreThread) { - while (coreThread->isRunning()) { - qApp->processEvents(); - coreThread->wait(500); - } - } - deadifyTox(); + delete av; + tox_kill(tox); } /** - * @brief Returns the global widget's Core instance + * @brief Registers all toxcore callbacks + * @param tox Tox instance to register the callbacks on */ -Core* Core::getInstance() -{ - return Nexus::getCore(); -} - -const CoreAV* Core::getAv() const -{ - return av; -} - -CoreAV* Core::getAv() -{ - return av; -} - -/** - * @brief Creates Tox instance from previously saved data - * @param savedata Previously saved Tox data - null, if new profile was created - */ -void Core::makeTox(QByteArray savedata) -{ - auto toxOptions = ToxOptions::makeToxOptions(savedata, s); - if (toxOptions == nullptr) { - qCritical() << "could not allocate Tox Options data structure"; - emit failedToStart(); - return; - } - - TOX_ERR_NEW tox_err; - tox = tox_new(*toxOptions, &tox_err); - - switch (tox_err) { - case TOX_ERR_NEW_OK: - break; - - case TOX_ERR_NEW_LOAD_BAD_FORMAT: - qCritical() << "failed to parse Tox save data"; - emit failedToStart(); - return; - - case TOX_ERR_NEW_PORT_ALLOC: - if (s->getEnableIPv6()) { - tox_options_set_ipv6_enabled(*toxOptions, false); - tox = tox_new(*toxOptions, &tox_err); - if (tox_err == TOX_ERR_NEW_OK) { - qWarning() << "Core failed to start with IPv6, falling back to IPv4. LAN discovery " - "may not work properly."; - break; - } - } - - qCritical() << "can't to bind the port"; - emit failedToStart(); - return; - - case TOX_ERR_NEW_PROXY_BAD_HOST: - case TOX_ERR_NEW_PROXY_BAD_PORT: - case TOX_ERR_NEW_PROXY_BAD_TYPE: - qCritical() << "bad proxy, error code:" << tox_err; - emit badProxy(); - return; - - case TOX_ERR_NEW_PROXY_NOT_FOUND: - qCritical() << "proxy not found"; - emit badProxy(); - return; - - case TOX_ERR_NEW_LOAD_ENCRYPTED: - qCritical() << "attempted to load encrypted Tox save data"; - emit failedToStart(); - return; - - case TOX_ERR_NEW_MALLOC: - qCritical() << "memory allocation failed"; - emit failedToStart(); - return; - - case TOX_ERR_NEW_NULL: - qCritical() << "a parameter was null"; - emit failedToStart(); - return; - - default: - qCritical() << "Tox core failed to start, unknown error code:" << tox_err; - emit failedToStart(); - return; - } -} - -/** - * @brief Initializes the core, must be called before anything else - */ -void Core::start(const QByteArray& savedata) -{ - bool isNewProfile = profile.isNewProfile(); - if (isNewProfile) { - qDebug() << "Creating a new profile"; - makeTox(QByteArray()); - setStatusMessage(tr("Toxing on qTox")); - setUsername(profile.getName()); - } else { - qDebug() << "Loading user profile"; - if (savedata.isEmpty()) { - emit failedToStart(); - return; - } - - makeTox(savedata); - } - - qsrand(time(nullptr)); - if (!tox) { - ready = true; - GUI::setEnabled(true); - return; - } - - // toxcore is successfully created, create toxav - av = new CoreAV(tox); - if (!av->getToxAv()) { - qCritical() << "Toxav failed to start"; - emit failedToStart(); - deadifyTox(); - return; - } - - // set GUI with user and statusmsg - QString name = getUsername(); - if (!name.isEmpty()) { - emit usernameSet(name); - } - - QString msg = getStatusMessage(); - if (!msg.isEmpty()) { - emit statusMessageSet(msg); - } - - ToxId id = getSelfId(); - // TODO: probably useless check, comes basically directly from toxcore - if (id.isValid()) { - emit idSet(id); - } - - loadFriends(); - +void Core::registerCallbacks(Tox * tox) { tox_callback_friend_request(tox, onFriendRequest); tox_callback_friend_message(tox, onFriendMessage); tox_callback_friend_name(tox, onFriendNameChange); @@ -264,22 +92,176 @@ void Core::start(const QByteArray& savedata) tox_callback_file_recv(tox, CoreFile::onFileReceiveCallback); tox_callback_file_recv_chunk(tox, CoreFile::onFileRecvChunkCallback); tox_callback_file_recv_control(tox, CoreFile::onFileControlCallback); +} - ready = true; +/** + * @brief Factory method for the Core object + * @param savedata empty if new profile or saved data else + * @param settings Settings specific to Core + * @return nullptr or a Core object ready to start + */ +ToxCorePtr Core::makeToxCore(const QByteArray &savedata, const ICoreSettings * const settings) +{ + QThread* thread = new QThread(); + if (thread == nullptr) { + qCritical() << "could not allocate Core thread"; + return {}; + } + thread->setObjectName("qTox Core"); - if (isNewProfile) { - profile.saveToxSave(); + auto toxOptions = ToxOptions::makeToxOptions(savedata, settings); + if (toxOptions == nullptr) { + qCritical() << "could not allocate Tox Options data structure"; + return {}; } - if (isReady()) { - GUI::setEnabled(true); + ToxCorePtr core(new Core(thread)); + if(core == nullptr) { + return {}; } + TOX_ERR_NEW tox_err; + core->tox = tox_new(*toxOptions, &tox_err); + + switch (tox_err) { + case TOX_ERR_NEW_OK: + break; + + case TOX_ERR_NEW_LOAD_BAD_FORMAT: + qCritical() << "failed to parse Tox save data"; + return {}; + + case TOX_ERR_NEW_PORT_ALLOC: + if (toxOptions->getIPv6Enabled()) { + toxOptions->setIPv6Enabled(false); + core->tox = tox_new(*toxOptions, &tox_err); + if (tox_err == TOX_ERR_NEW_OK) { + qWarning() << "Core failed to start with IPv6, falling back to IPv4. LAN discovery " + "may not work properly."; + break; + } + } + + qCritical() << "can't to bind the port"; + return {}; + + case TOX_ERR_NEW_PROXY_BAD_HOST: + case TOX_ERR_NEW_PROXY_BAD_PORT: + case TOX_ERR_NEW_PROXY_BAD_TYPE: + qCritical() << "bad proxy, error code:" << tox_err; + //emit badProxy(); + return {}; + + case TOX_ERR_NEW_PROXY_NOT_FOUND: + qCritical() << "proxy not found"; + //emit badProxy(); + return {}; + + case TOX_ERR_NEW_LOAD_ENCRYPTED: + qCritical() << "attempted to load encrypted Tox save data"; + //emit failedToStart(); + return {}; + + case TOX_ERR_NEW_MALLOC: + qCritical() << "memory allocation failed"; + //emit failedToStart(); + return {}; + + case TOX_ERR_NEW_NULL: + qCritical() << "a parameter was null"; + //emit failedToStart(); + return {}; + + default: + qCritical() << "Tox core failed to start, unknown error code:" << tox_err; + //emit failedToStart(); + return {}; + } + + qsrand(time(nullptr)); // TODO(sudden6): needed? + // tox should be valid by now + assert(core->tox != nullptr); + + // toxcore is successfully created, create toxav + core->av = new CoreAV(core->tox); + if (!core->av || !core->av->getToxAv()) { + qCritical() << "Toxav failed to start"; + //emit failedToStart(); + //deadifyTox(); + return {}; + } + + registerCallbacks(core->tox); + + // connect the thread with the Core + connect(thread, &QThread::started, core.get(), &Core::onStarted); + core->moveToThread(thread); + // since this is allocated in the constructor move it to the other thread too + core->toxTimer.moveToThread(thread); + + // when leaving this function 'core' should be ready for it's start() action or + // a nullptr + return core; +} + +void Core::onStarted() +{ + // TODO(sudden6): this assert should hold? + //assert(QThread::currentThread() == coreThread); + // One time initialization stuff + // set GUI with user and statusmsg + QString name = getUsername(); + if (!name.isEmpty()) { + emit usernameSet(name); + } + + QString msg = getStatusMessage(); + if (!msg.isEmpty()) { + emit statusMessageSet(msg); + } + + ToxId id = getSelfId(); + // TODO: probably useless check, comes basically directly from toxcore + if (id.isValid()) { + emit idSet(id); + } + + loadFriends(); + process(); // starts its own timer av->start(); emit avReady(); } +/** + * @brief Starts toxcore and it's event loop, must be run from the Core thread + * @return true on success, false otherwise + */ +bool Core::start() +{ + coreThread->start(); + return true; +} + + +/** + * @brief Returns the global widget's Core instance + */ +Core* Core::getInstance() +{ + return Nexus::getCore(); +} + +const CoreAV* Core::getAv() const +{ + return av; +} + +CoreAV* Core::getAv() +{ + return av; +} + /* Using the now commented out statements in checkConnection(), I watched how * many ticks disconnects-after-initial-connect lasted. Out of roughly 15 trials, * 5 disconnected; 4 were DCd for less than 20 ticks, while the 5th was ~50 ticks. @@ -294,19 +276,21 @@ void Core::start(const QByteArray& savedata) */ void Core::process() { + assert(QThread::currentThread() == coreThread); if (!isReady()) { av->stop(); return; } static int tolerance = CORE_DISCONNECT_TOLERANCE; - tox_iterate(tox, getInstance()); + tox_iterate(tox, this); #ifdef DEBUG // we want to see the debug messages immediately fflush(stdout); #endif + // TODO(sudden6): recheck if this is still necessary if (checkConnection()) { tolerance = CORE_DISCONNECT_TOLERANCE; } else if (!(--tolerance)) { @@ -315,7 +299,7 @@ void Core::process() } unsigned sleeptime = qMin(tox_iteration_interval(tox), CoreFile::corefileIterationInterval()); - toxTimer->start(sleeptime); + toxTimer.start(sleeptime); } bool Core::checkConnection() @@ -339,7 +323,8 @@ bool Core::checkConnection() */ void Core::bootstrapDht() { - QList dhtServerList = s->getDhtServerList(); + // TODO(sudden6): fix bootstrapping + QList dhtServerList{};// = s->getDhtServerList(); int listSize = dhtServerList.size(); if (!listSize) { qWarning() << "no bootstrap list?!?"; @@ -540,7 +525,8 @@ void Core::acceptFriendRequest(const ToxPk& friendPk) if (friendId == std::numeric_limits::max()) { emit failedToAddFriend(friendPk); } else { - profile.saveToxSave(); + // TODO(sudden6): emit save request + //profile.saveToxSave(); emit friendAdded(friendId, friendPk); } } @@ -579,7 +565,8 @@ void Core::requestFriendship(const ToxId& friendId, const QString& message) QString errorMessage = getFriendRequestErrorMessage(friendId, message); if (!errorMessage.isNull()) { emit failedToAddFriend(friendPk, errorMessage); - profile.saveToxSave(); + // TODO(sudden6): emit save request + // profile.saveToxSave(); return; } @@ -595,7 +582,8 @@ void Core::requestFriendship(const ToxId& friendId, const QString& message) emit requestSent(friendPk, message); } - profile.saveToxSave(); + // TODO(sudden6): emit save request + // profile.saveToxSave(); } int Core::sendMessage(uint32_t friendId, const QString& message) @@ -757,8 +745,8 @@ void Core::removeFriend(uint32_t friendId, bool fake) emit failedToRemoveFriend(friendId); return; } - - profile.saveToxSave(); + // TODO(sudden6): emit save request + // profile.saveToxSave(); emit friendRemoved(friendId); } @@ -817,9 +805,8 @@ void Core::setUsername(const QString& username) } emit usernameSet(username); - if (ready) { - profile.saveToxSave(); - } + + // TODO(sudden6): request saving } /** @@ -900,9 +887,7 @@ void Core::setStatusMessage(const QString& message) return; } - if (ready) { - profile.saveToxSave(); - } + // TODO(sudden6): request saving emit statusMessageSet(message); } @@ -929,7 +914,8 @@ void Core::setStatus(Status status) } tox_self_set_status(tox, userstatus); - profile.saveToxSave(); + // TODO(sudden6): emit save request + // profile.saveToxSave(); emit statusSet(status); } @@ -1369,7 +1355,7 @@ QString Core::getPeerName(const ToxPk& id) const */ bool Core::isReady() const { - return av && av->getToxAv() && tox && ready; + return av && av->getToxAv() && tox; } /** @@ -1383,32 +1369,13 @@ void Core::setNospam(uint32_t nospam) } /** - * @brief Returns the unencrypted tox save data + * @brief Stops the AV thread and the timer here */ -void Core::killTimers(bool onlyStop) +void Core::killTimers() { assert(QThread::currentThread() == coreThread); if (av) { av->stop(); } - toxTimer->stop(); - if (!onlyStop) { - delete toxTimer; - toxTimer = nullptr; - } -} - -/** - * @brief Reinitialized the core. - * @warning Must be called from the Core thread, with the GUI thread ready to process events. - */ -void Core::reset() -{ - assert(QThread::currentThread() == coreThread); - QByteArray toxsave = getToxSaveData(); - ready = false; - killTimers(true); - deadifyTox(); - GUI::clearContacts(); - start(toxsave); + toxTimer.stop(); } diff --git a/src/core/core.h b/src/core/core.h index 626d4bc41..930be4c78 100644 --- a/src/core/core.h +++ b/src/core/core.h @@ -28,8 +28,10 @@ #include #include +#include #include +#include class CoreAV; class ICoreSettings; @@ -45,11 +47,16 @@ enum class Status Offline }; +class Core; + +using ToxCorePtr = std::unique_ptr; + class Core : public QObject { Q_OBJECT public: - Core(QThread* coreThread, Profile& profile, const ICoreSettings* const settings); + + static ToxCorePtr makeToxCore(const QByteArray& savedata, const ICoreSettings* const settings); static Core* getInstance(); const CoreAV* getAv() const; CoreAV* getAv(); @@ -85,10 +92,7 @@ public: void sendFile(uint32_t friendId, QString filename, QString filePath, long long filesize); public slots: - void start(const QByteArray& savedata); - void reset(); - void process(); - void bootstrapDht(); + bool start(); QByteArray getToxSaveData(); @@ -189,6 +193,8 @@ signals: void fileSendFailed(uint32_t friendId, const QString& fname); private: + Core(QThread* coreThread); + static void onFriendRequest(Tox* tox, const uint8_t* cUserId, const uint8_t* cMessage, size_t cMessageSize, void* core); static void onFriendMessage(Tox* tox, uint32_t friendId, TOX_MESSAGE_TYPE type, @@ -224,28 +230,28 @@ private: bool checkConnection(); void checkEncryptedHistory(); - void makeTox(QByteArray savedata); + void makeTox(QByteArray savedata, ICoreSettings *s); void makeAv(); void loadFriends(); + void bootstrapDht(); void checkLastOnline(uint32_t friendId); - void deadifyTox(); QString getFriendRequestErrorMessage(const ToxId& friendId, const QString& message) const; + static void registerCallbacks(Tox * tox); private slots: - void killTimers(bool onlyStop); + void killTimers(); + void process(); + void onStarted(); private: Tox* tox; CoreAV* av; - QTimer* toxTimer; - Profile& profile; + QTimer toxTimer; QMutex messageSendMutex; - bool ready; - const ICoreSettings* const s; - static QThread* coreThread; + QThread* coreThread = nullptr; friend class Audio; ///< Audio can access our calls directly to reduce latency friend class CoreFile; ///< CoreFile can access tox* and emit our signals diff --git a/src/core/corefile.cpp b/src/core/corefile.cpp index 17b56ff9e..9ffc14907 100644 --- a/src/core/corefile.cpp +++ b/src/core/corefile.cpp @@ -296,15 +296,17 @@ void CoreFile::onFileReceiveCallback(Tox*, uint32_t friendId, uint32_t fileId, u qDebug() << QString("Received empty avatar request %1:%2").arg(friendId).arg(fileId); // Avatars of size 0 means explicitely no avatar emit core->friendAvatarRemoved(friendId); - core->profile.removeAvatar(friendPk); + // TODO(sudden6): use signal from above for that action + //core->profile.removeAvatar(friendPk); return; } else { static_assert(TOX_HASH_LENGTH <= TOX_FILE_ID_LENGTH, "TOX_HASH_LENGTH > TOX_FILE_ID_LENGTH!"); uint8_t avatarHash[TOX_FILE_ID_LENGTH]; tox_file_get_file_id(core->tox, friendId, fileId, avatarHash, nullptr); - if (core->profile.getAvatarHash(friendPk) - == QByteArray((char*)avatarHash, TOX_HASH_LENGTH)) { + // TODO(sudden6): fix that condition below + if (/*core->profile.getAvatarHash(friendPk) + == QByteArray((char*)avatarHash, TOX_HASH_LENGTH)*/ false) { // If it's an avatar but we already have it cached, cancel qDebug() << QString( "Received avatar request %1:%2, reject, since we have it in cache.") @@ -446,8 +448,10 @@ void CoreFile::onFileRecvChunkCallback(Tox* tox, uint32_t friendId, uint32_t fil pic.loadFromData(file->avatarData); if (!pic.isNull()) { qDebug() << "Got" << file->avatarData.size() << "bytes of avatar data from" << friendId; + // TODO(sudden6): handle the action below with the signal + /* core->profile.saveAvatar(file->avatarData, - core->getFriendPublicKey(friendId)); + core->getFriendPublicKey(friendId));*/ emit core->friendAvatarChanged(friendId, pic); } } else { diff --git a/src/core/toxoptions.cpp b/src/core/toxoptions.cpp index 9c48dc748..c41a205d3 100644 --- a/src/core/toxoptions.cpp +++ b/src/core/toxoptions.cpp @@ -115,3 +115,14 @@ std::unique_ptr ToxOptions::makeToxOptions(const QByteArray& savedat return toxOptions; } + +bool ToxOptions::getIPv6Enabled() const +{ + return tox_options_get_ipv6_enabled(options); +} + +void ToxOptions::setIPv6Enabled(bool enabled) +{ + tox_options_set_ipv6_enabled(options, enabled); +} + diff --git a/src/core/toxoptions.h b/src/core/toxoptions.h index 844adefff..63f91c256 100644 --- a/src/core/toxoptions.h +++ b/src/core/toxoptions.h @@ -16,6 +16,8 @@ public: operator Tox_Options* (); const char* getProxyAddrData() const; static std::unique_ptr makeToxOptions(const QByteArray &savedata, const ICoreSettings *s); + bool getIPv6Enabled() const; + void setIPv6Enabled(bool enabled); private: ToxOptions(Tox_Options *options, const QByteArray& proxyAddrData); diff --git a/src/nexus.cpp b/src/nexus.cpp index 446162618..413d383aa 100644 --- a/src/nexus.cpp +++ b/src/nexus.cpp @@ -224,6 +224,8 @@ void Nexus::showMainGUI() connect(widget, &Widget::friendRequestAccepted, core, &Core::acceptFriendRequest); profile->startCore(); + + GUI::setEnabled(true); } /** diff --git a/src/persistence/profile.cpp b/src/persistence/profile.cpp index 0b4a53fed..9b0f2f417 100644 --- a/src/persistence/profile.cpp +++ b/src/persistence/profile.cpp @@ -60,30 +60,22 @@ Profile::Profile(QString name, const QString& password, bool isNewProfile, const s.setCurrentProfile(name); s.saveGlobal(); - coreThread = new QThread(); - coreThread->setObjectName("qTox Core"); - core = new Core(coreThread, *this, &Settings::getInstance()); - QObject::connect(core, &Core::idSet, this, - [this, password](const ToxId& id) { loadDatabase(id, password); }, - Qt::QueuedConnection); - core->moveToThread(coreThread); - QObject::connect(coreThread, &QThread::started, core, [=]() { - core->start(toxsave); + core = Core::makeToxCore(toxsave, &s); + if(!core) { + qDebug() << "failed to start ToxCore"; + return; + } - // prevent segfault by checking if core started successfully - if(!core->isReady()) { - qWarning() << "Core not ready, aborting"; - return; - } + const ToxId& selfId = core->getSelfId(); + loadDatabase(selfId, password); + const ToxPk& selfPk = selfId.getPublicKey(); + QByteArray data = loadAvatarData(selfPk); + if (data.isEmpty()) { + qDebug() << "Self avatar not found, will broadcast empty avatar to friends"; + } - const ToxPk selfPk = core->getSelfPublicKey(); - QByteArray data = loadAvatarData(selfPk); - if (data.isEmpty()) { - qDebug() << "Self avatar not found, will broadcast empty avatar to friends"; - } - - setAvatar(data, selfPk); - }); + // TODO(sudden6): check if needed + //setAvatar(data, selfPk); } /** @@ -223,11 +215,6 @@ Profile::~Profile() saveToxSave(); } - core->deleteLater(); - while (coreThread->isRunning()) - qApp->processEvents(); - - delete coreThread; if (!isRemoved) { Settings::getInstance().savePersonal(this); Settings::getInstance().sync(); @@ -281,7 +268,8 @@ QStringList Profile::getProfiles() Core* Profile::getCore() { - return core; + // TODO(sudden6): this is evil + return core.get(); } QString Profile::getName() const @@ -294,7 +282,7 @@ QString Profile::getName() const */ void Profile::startCore() { - coreThread->start(); + core->start(); } bool Profile::isNewProfile() @@ -721,12 +709,13 @@ const ToxEncrypt* Profile::getPasskey() const */ void Profile::restartCore() { + /* TODO(sudden6): rethink this GUI::setEnabled(false); // Core::reset re-enables it if (!isRemoved && core->isReady()) { saveToxSave(); } - QMetaObject::invokeMethod(core, "reset"); + */ } /** diff --git a/src/persistence/profile.h b/src/persistence/profile.h index d0feda86e..8a72b4ceb 100644 --- a/src/persistence/profile.h +++ b/src/persistence/profile.h @@ -21,6 +21,7 @@ #ifndef PROFILE_H #define PROFILE_H +#include "src/core/core.h" #include "src/core/toxencrypt.h" #include "src/core/toxid.h" @@ -33,9 +34,6 @@ #include #include -class Core; -class QThread; - class Profile : public QObject { Q_OBJECT @@ -96,8 +94,7 @@ private: QString avatarPath(const ToxPk& owner, bool forceUnencrypted = false); private: - Core* core; - QThread* coreThread; + std::unique_ptr core = nullptr; QString name; std::unique_ptr passkey = nullptr; std::shared_ptr database;