1
0
mirror of https://github.com/qTox/qTox.git synced 2024-03-22 14:00:36 +08:00

refactor(core): cleanup Core class

- use a factory method to create it
- make it handle its own thread
- remove dependency on GUI
This commit is contained in:
sudden6 2018-06-26 15:55:21 +02:00
parent 4921b8868f
commit 8574162949
No known key found for this signature in database
GPG Key ID: 279509B499E032B9
8 changed files with 259 additions and 281 deletions

View File

@ -28,7 +28,6 @@
#include "src/model/groupinvite.h"
#include "src/nexus.h"
#include "src/persistence/profile.h"
#include "src/widget/gui.h"
#include <QCoreApplication>
#include <QRegularExpression>
@ -40,209 +39,38 @@
#include <ctime>
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<DhtServer>& 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);
killTimers();
} else {
QMetaObject::invokeMethod(this, "killTimers", Qt::BlockingQueuedConnection,
Q_ARG(bool, false));
}
// 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<DhtServer> dhtServerList = s->getDhtServerList();
// TODO(sudden6): fix bootstrapping
QList<DhtServer> 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<uint32_t>::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();
}

View File

@ -28,8 +28,10 @@
#include <QMutex>
#include <QObject>
#include <QTimer>
#include <functional>
#include <memory>
class CoreAV;
class ICoreSettings;
@ -45,11 +47,16 @@ enum class Status
Offline
};
class Core;
using ToxCorePtr = std::unique_ptr<Core>;
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

View File

@ -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 {

View File

@ -115,3 +115,14 @@ std::unique_ptr<ToxOptions> 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);
}

View File

@ -16,6 +16,8 @@ public:
operator Tox_Options* ();
const char* getProxyAddrData() const;
static std::unique_ptr<ToxOptions> makeToxOptions(const QByteArray &savedata, const ICoreSettings *s);
bool getIPv6Enabled() const;
void setIPv6Enabled(bool enabled);
private:
ToxOptions(Tox_Options *options, const QByteArray& proxyAddrData);

View File

@ -224,6 +224,8 @@ void Nexus::showMainGUI()
connect(widget, &Widget::friendRequestAccepted, core, &Core::acceptFriendRequest);
profile->startCore();
GUI::setEnabled(true);
}
/**

View File

@ -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);
// prevent segfault by checking if core started successfully
if(!core->isReady()) {
qWarning() << "Core not ready, aborting";
core = Core::makeToxCore(toxsave, &s);
if(!core) {
qDebug() << "failed to start ToxCore";
return;
}
const ToxPk selfPk = core->getSelfPublicKey();
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";
}
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");
*/
}
/**

View File

@ -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 <QVector>
#include <memory>
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> core = nullptr;
QString name;
std::unique_ptr<ToxEncrypt> passkey = nullptr;
std::shared_ptr<RawDatabase> database;