Browse Source

refactor(history): Moved creaton of RawDatabase from History constuctor

pull/3823/head
Diadlo 9 years ago
parent
commit
b522da5bed
No known key found for this signature in database
GPG Key ID: 5AF9F2E29107C727
  1. 275
      src/persistence/history.cpp
  2. 61
      src/persistence/history.h
  3. 149
      src/persistence/profile.cpp
  4. 10
      src/persistence/profile.h

275
src/persistence/history.cpp

@ -1,12 +1,30 @@
#include "history.h" /*
#include "src/persistence/profile.h" Copyright © 2015-2016 by The qTox Project
#include "src/persistence/settings.h"
#include "src/persistence/db/rawdatabase.h" This file is part of qTox, a Qt-based graphical interface for Tox.
#include "src/persistence/historykeeper.h"
qTox is libre software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
qTox is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with qTox. If not, see <http://www.gnu.org/licenses/>.
*/
#include <QDebug> #include <QDebug>
#include <cassert> #include <cassert>
using namespace std; #include "db/rawdatabase.h"
#include "history.h"
#include "historykeeper.h"
#include "profile.h"
#include "settings.h"
/** /**
* @class History * @class History
@ -18,33 +36,43 @@ using namespace std;
*/ */
/** /**
* @brief Opens the profile database and prepares to work with the history. * @brief Prepares the database to work with the history.
* @param profileName Profile name to load. * @param db This database will be prepared for use with the history.
* @param password If empty, the database will be opened unencrypted.
*/ */
History::History(const QString &profileName, const QString &password) History::History(std::shared_ptr<RawDatabase> db)
: db{getDbPath(profileName), password} : db(db)
{ {
init(); if (!isValid())
} {
qWarning() << "Database not open, init failed";
return;
}
/** db->execLater("CREATE TABLE IF NOT EXISTS peers (id INTEGER PRIMARY KEY, public_key TEXT NOT NULL UNIQUE);"
* @brief Opens the profile database, and import from the old database. "CREATE TABLE IF NOT EXISTS aliases (id INTEGER PRIMARY KEY, owner INTEGER,"
* @param profileName Profile name to load. "display_name BLOB NOT NULL, UNIQUE(owner, display_name));"
* @param password If empty, the database will be opened unencrypted. "CREATE TABLE IF NOT EXISTS history (id INTEGER PRIMARY KEY, timestamp INTEGER NOT NULL, "
* @param oldHistory Old history to import. "chat_id INTEGER NOT NULL, sender_alias INTEGER NOT NULL, "
*/ "message BLOB NOT NULL);"
History::History(const QString &profileName, const QString &password, const HistoryKeeper &oldHistory) "CREATE TABLE IF NOT EXISTS faux_offline_pending (id INTEGER PRIMARY KEY);");
: History{profileName, password}
{ // Cache our current peers
import(oldHistory); db->execLater(RawDatabase::Query{"SELECT public_key, id FROM peers;", [this](const QVector<QVariant>& row)
{
peers[row[0].toString()] = row[1].toInt();
}});
} }
History::~History() History::~History()
{ {
if (!isValid())
{
return;
}
// We could have execLater requests pending with a lambda attached, // We could have execLater requests pending with a lambda attached,
// so clear the pending transactions first // so clear the pending transactions first
db.sync(); db->sync();
} }
/** /**
@ -53,34 +81,7 @@ History::~History()
*/ */
bool History::isValid() bool History::isValid()
{ {
return db.isOpen(); return db && db->isOpen();
}
/**
* @brief Changes the database password, will encrypt or decrypt if necessary.
* @param password Password to set.
*/
void History::setPassword(const QString& password)
{
db.setPassword(password);
}
/**
* @brief Moves the database file on disk to match the new name.
* @param newName New name.
*/
void History::rename(const QString &newName)
{
db.rename(getDbPath(newName));
}
/**
* @brief Deletes the on-disk database file.
* @return True if success, false otherwise.
*/
bool History::remove()
{
return db.remove();
} }
/** /**
@ -88,7 +89,12 @@ bool History::remove()
*/ */
void History::eraseHistory() void History::eraseHistory()
{ {
db.execNow("DELETE FROM faux_offline_pending;" if (!isValid())
{
return;
}
db->execNow("DELETE FROM faux_offline_pending;"
"DELETE FROM history;" "DELETE FROM history;"
"DELETE FROM aliases;" "DELETE FROM aliases;"
"DELETE FROM peers;" "DELETE FROM peers;"
@ -99,22 +105,33 @@ void History::eraseHistory()
* @brief Erases the chat history with one friend. * @brief Erases the chat history with one friend.
* @param friendPk Friend public key to erase. * @param friendPk Friend public key to erase.
*/ */
void History::removeFriendHistory(const QString &friendPk) void History::removeFriendHistory(const QString& friendPk)
{ {
if (!isValid())
{
return;
}
if (!peers.contains(friendPk)) if (!peers.contains(friendPk))
{
return; return;
}
int64_t id = peers[friendPk]; int64_t id = peers[friendPk];
if (db.execNow(QString("DELETE FROM faux_offline_pending " QString queryText = QString(
"WHERE faux_offline_pending.id IN ( " "DELETE FROM faux_offline_pending "
"SELECT faux_offline_pending.id FROM faux_offline_pending " "WHERE faux_offline_pending.id IN ( "
"LEFT JOIN history ON faux_offline_pending.id = history.id " " SELECT faux_offline_pending.id FROM faux_offline_pending "
"WHERE chat_id=%1 " " LEFT JOIN history ON faux_offline_pending.id = history.id "
"); " " WHERE chat_id=%1 "
"DELETE FROM history WHERE chat_id=%1; " "); "
"DELETE FROM aliases WHERE owner=%1; " "DELETE FROM history WHERE chat_id=%1; "
"DELETE FROM peers WHERE id=%1; " "DELETE FROM aliases WHERE owner=%1; "
"VACUUM;").arg(id))) "DELETE FROM peers WHERE id=%1; "
"VACUUM;").arg(id);
if (db->execNow(queryText))
{ {
peers.remove(friendPk); peers.remove(friendPk);
} }
@ -134,8 +151,8 @@ void History::removeFriendHistory(const QString &friendPk)
* @param dispName Name, which should be displayed. * @param dispName Name, which should be displayed.
* @param insertIdCallback Function, called after query execution. * @param insertIdCallback Function, called after query execution.
*/ */
QVector<RawDatabase::Query> History::generateNewMessageQueries(const QString &friendPk, const QString &message, QVector<RawDatabase::Query> History::generateNewMessageQueries(const QString& friendPk, const QString& message,
const QString &sender, const QDateTime &time, bool isSent, QString dispName, const QString& sender, const QDateTime& time, bool isSent, QString dispName,
std::function<void(int64_t)> insertIdCallback) std::function<void(int64_t)> insertIdCallback)
{ {
QVector<RawDatabase::Query> queries; QVector<RawDatabase::Query> queries;
@ -149,11 +166,18 @@ QVector<RawDatabase::Query> History::generateNewMessageQueries(const QString &fr
else else
{ {
if (peers.isEmpty()) if (peers.isEmpty())
{
peerId = 0; peerId = 0;
}
else else
peerId = *max_element(begin(peers), end(peers))+1; {
peerId = *std::max_element(peers.begin(), peers.end()) + 1;
}
peers[friendPk] = peerId; peers[friendPk] = peerId;
queries += RawDatabase::Query{("INSERT INTO peers (id, public_key) VALUES (%1, '"+friendPk+"');").arg(peerId)}; queries += RawDatabase::Query(("INSERT INTO peers (id, public_key) "
"VALUES (%1, '" + friendPk + "');")
.arg(peerId));
} }
// Get the db id of the sender of the message // Get the db id of the sender of the message
@ -165,11 +189,18 @@ QVector<RawDatabase::Query> History::generateNewMessageQueries(const QString &fr
else else
{ {
if (peers.isEmpty()) if (peers.isEmpty())
{
senderId = 0; senderId = 0;
}
else else
senderId = *max_element(begin(peers), end(peers))+1; {
senderId = *std::max_element(peers.begin(), peers.end()) + 1;
}
peers[sender] = senderId; peers[sender] = senderId;
queries += RawDatabase::Query{("INSERT INTO peers (id, public_key) VALUES (%1, '"+sender+"');").arg(senderId)}; queries += RawDatabase::Query{("INSERT INTO peers (id, public_key) "
"VALUES (%1, '" + sender + "');")
.arg(senderId)};
} }
queries += RawDatabase::Query(QString("INSERT OR IGNORE INTO aliases (owner, display_name) VALUES (%1, ?);") queries += RawDatabase::Query(QString("INSERT OR IGNORE INTO aliases (owner, display_name) VALUES (%1, ?);")
@ -187,7 +218,12 @@ QVector<RawDatabase::Query> History::generateNewMessageQueries(const QString &fr
{message.toUtf8(), dispName.toUtf8()}, insertIdCallback); {message.toUtf8(), dispName.toUtf8()}, insertIdCallback);
if (!isSent) if (!isSent)
queries += RawDatabase::Query{"INSERT INTO faux_offline_pending (id) VALUES (last_insert_rowid());"}; {
queries += RawDatabase::Query{
"INSERT INTO faux_offline_pending (id) VALUES ("
" last_insert_rowid()"
");"};
}
return queries; return queries;
} }
@ -202,10 +238,18 @@ QVector<RawDatabase::Query> History::generateNewMessageQueries(const QString &fr
* @param dispName Name, which should be displayed. * @param dispName Name, which should be displayed.
* @param insertIdCallback Function, called after query execution. * @param insertIdCallback Function, called after query execution.
*/ */
void History::addNewMessage(const QString &friendPk, const QString &message, const QString &sender, void History::addNewMessage(const QString& friendPk, const QString& message,
const QDateTime &time, bool isSent, QString dispName, std::function<void(int64_t)> insertIdCallback) const QString& sender, const QDateTime& time,
bool isSent, QString dispName,
std::function<void(int64_t)> insertIdCallback)
{ {
db.execLater(generateNewMessageQueries(friendPk, message, sender, time, isSent, dispName, insertIdCallback)); if (!isValid())
{
return;
}
db->execLater(generateNewMessageQueries(friendPk, message, sender, time,
isSent, dispName, insertIdCallback));
} }
/** /**
@ -215,13 +259,21 @@ void History::addNewMessage(const QString &friendPk, const QString &message, con
* @param to End of period to fetch. * @param to End of period to fetch.
* @return List of messages. * @return List of messages.
*/ */
QList<History::HistMessage> History::getChatHistory(const QString &friendPk, const QDateTime &from, const QDateTime &to) QList<History::HistMessage> History::getChatHistory(const QString& friendPk,
const QDateTime& from,
const QDateTime& to)
{ {
if (!isValid())
{
return {};
}
QList<HistMessage> messages; QList<HistMessage> messages;
auto rowCallback = [&messages](const QVector<QVariant>& row) auto rowCallback = [&messages](const QVector<QVariant>& row)
{ {
// dispName and message could have null bytes, QString::fromUtf8 truncates on null bytes so we strip them // dispName and message could have null bytes, QString::fromUtf8
// truncates on null bytes so we strip them
messages += {row[0].toLongLong(), messages += {row[0].toLongLong(),
row[1].isNull(), row[1].isNull(),
QDateTime::fromMSecsSinceEpoch(row[2].toLongLong()), QDateTime::fromMSecsSinceEpoch(row[2].toLongLong()),
@ -232,14 +284,18 @@ QList<History::HistMessage> History::getChatHistory(const QString &friendPk, con
}; };
// Don't forget to update the rowCallback if you change the selected columns! // Don't forget to update the rowCallback if you change the selected columns!
db.execNow({QString("SELECT history.id, faux_offline_pending.id, timestamp, chat.public_key, " QString queryText = QString(
"aliases.display_name, sender.public_key, message FROM history " "SELECT history.id, faux_offline_pending.id, timestamp, "
"LEFT JOIN faux_offline_pending ON history.id = faux_offline_pending.id " "chat.public_key, aliases.display_name, sender.public_key, "
"JOIN peers chat ON chat_id = chat.id " "message FROM history "
"JOIN aliases ON sender_alias = aliases.id " "LEFT JOIN faux_offline_pending ON history.id = faux_offline_pending.id "
"JOIN peers sender ON aliases.owner = sender.id " "JOIN peers chat ON chat_id = chat.id "
"WHERE timestamp BETWEEN %1 AND %2 AND chat.public_key='%3';") "JOIN aliases ON sender_alias = aliases.id "
.arg(from.toMSecsSinceEpoch()).arg(to.toMSecsSinceEpoch()).arg(friendPk), rowCallback}); "JOIN peers sender ON aliases.owner = sender.id "
"WHERE timestamp BETWEEN %1 AND %2 AND chat.public_key='%3';")
.arg(from.toMSecsSinceEpoch()).arg(to.toMSecsSinceEpoch()).arg(friendPk);
db->execNow({queryText, rowCallback});
return messages; return messages;
} }
@ -250,52 +306,22 @@ QList<History::HistMessage> History::getChatHistory(const QString &friendPk, con
* *
* @param id Message ID. * @param id Message ID.
*/ */
void History::markAsSent(qint64 id) void History::markAsSent(qint64 messageId)
{
db.execLater(QString("DELETE FROM faux_offline_pending WHERE id=%1;").arg(id));
}
/**
* @brief Retrieves the path to the database file for a given profile.
* @param profileName Profile name.
* @return Path to database.
*/
QString History::getDbPath(const QString &profileName)
{
return Settings::getInstance().getSettingsDirPath() + profileName + ".db";
}
/**
* @brief Makes sure the history tables are created
*/
void History::init()
{ {
if (!isValid()) if (!isValid())
{ {
qWarning() << "Database not open, init failed";
return; return;
} }
db.execLater("CREATE TABLE IF NOT EXISTS peers (id INTEGER PRIMARY KEY, public_key TEXT NOT NULL UNIQUE);" db->execLater(QString("DELETE FROM faux_offline_pending WHERE id=%1;")
"CREATE TABLE IF NOT EXISTS aliases (id INTEGER PRIMARY KEY, owner INTEGER," .arg(messageId));
"display_name BLOB NOT NULL, UNIQUE(owner, display_name));"
"CREATE TABLE IF NOT EXISTS history (id INTEGER PRIMARY KEY, timestamp INTEGER NOT NULL, "
"chat_id INTEGER NOT NULL, sender_alias INTEGER NOT NULL, "
"message BLOB NOT NULL);"
"CREATE TABLE IF NOT EXISTS faux_offline_pending (id INTEGER PRIMARY KEY);");
// Cache our current peers
db.execLater(RawDatabase::Query{"SELECT public_key, id FROM peers;", [this](const QVector<QVariant>& row)
{
peers[row[0].toString()] = row[1].toInt();
}});
} }
/** /**
* @brief Imports messages from the old history file. * @brief Imports messages from the old history file.
* @param oldHistory Old history to import. * @param oldHistory Old history to import.
*/ */
void History::import(const HistoryKeeper &oldHistory) void History::import(const HistoryKeeper& oldHistory)
{ {
if (!isValid()) if (!isValid())
{ {
@ -304,7 +330,7 @@ void History::import(const HistoryKeeper &oldHistory)
} }
qDebug() << "Importing old database..."; qDebug() << "Importing old database...";
QTime t=QTime::currentTime(); QTime t = QTime::currentTime();
t.start(); t.start();
QVector<RawDatabase::Query> queries; QVector<RawDatabase::Query> queries;
constexpr int batchSize = 1000; constexpr int batchSize = 1000;
@ -312,14 +338,15 @@ void History::import(const HistoryKeeper &oldHistory)
QList<HistoryKeeper::HistMessage> oldMessages = oldHistory.exportMessagesDeleteFile(); QList<HistoryKeeper::HistMessage> oldMessages = oldHistory.exportMessagesDeleteFile();
for (const HistoryKeeper::HistMessage& msg : oldMessages) for (const HistoryKeeper::HistMessage& msg : oldMessages)
{ {
queries += generateNewMessageQueries(msg.chat, msg.message, msg.sender, msg.timestamp, true, msg.dispName); queries += generateNewMessageQueries(msg.chat, msg.message, msg.sender,
msg.timestamp, true, msg.dispName);
if (queries.size() >= batchSize) if (queries.size() >= batchSize)
{ {
db.execLater(queries); db->execLater(queries);
queries.clear(); queries.clear();
} }
} }
db.execLater(queries); db->execLater(queries);
db.sync(); db->sync();
qDebug() << "Imported old database in"<<t.elapsed()<<"ms"; qDebug() << "Imported old database in" << t.elapsed() << "ms";
} }

61
src/persistence/history.h

@ -1,24 +1,48 @@
/*
Copyright © 2015-2016 by The qTox Project
This file is part of qTox, a Qt-based graphical interface for Tox.
qTox is libre software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
qTox is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with qTox. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef HISTORY_H #ifndef HISTORY_H
#define HISTORY_H #define HISTORY_H
#include <tox/toxencryptsave.h>
#include <QDateTime> #include <QDateTime>
#include <QVector> #include <QVector>
#include <QHash> #include <QHash>
#include <cstdint> #include <cstdint>
#include <tox/toxencryptsave.h>
#include "src/persistence/db/rawdatabase.h" #include "src/persistence/db/rawdatabase.h"
class Profile; class Profile;
class HistoryKeeper; class HistoryKeeper;
class RawDatabase;
class History class History
{ {
public: public:
struct HistMessage struct HistMessage
{ {
HistMessage(qint64 id, bool isSent, QDateTime timestamp, QString chat, QString dispName, QString sender, QString message) : HistMessage(qint64 id, bool isSent, QDateTime timestamp, QString chat,
chat{chat}, sender{sender}, message{message}, dispName{dispName}, timestamp{timestamp}, id{id}, isSent{isSent} {} QString dispName, QString sender, QString message)
: chat{chat}, sender{sender}, message{message}, dispName{dispName}
, timestamp{timestamp}, id{id}, isSent{isSent}
{
}
QString chat; QString chat;
QString sender; QString sender;
@ -30,33 +54,32 @@ public:
}; };
public: public:
History(const QString& profileName, const QString& password); History(std::shared_ptr<RawDatabase> db);
History(const QString& profileName, const QString& password, const HistoryKeeper& oldHistory);
~History(); ~History();
bool isValid(); bool isValid();
void import(const HistoryKeeper& oldHistory); void import(const HistoryKeeper& oldHistory);
void setPassword(const QString& password);
void rename(const QString& newName);
bool remove();
void eraseHistory(); void eraseHistory();
void removeFriendHistory(const QString& friendPk); void removeFriendHistory(const QString& friendPk);
void addNewMessage(const QString& friendPk, const QString& message, const QString& sender, void addNewMessage(const QString& friendPk, const QString& message,
const QDateTime &time, bool isSent, QString dispName, const QString& sender, const QDateTime& time,
bool isSent, QString dispName,
std::function<void(int64_t)> insertIdCallback={}); std::function<void(int64_t)> insertIdCallback={});
QList<HistMessage> getChatHistory(const QString& friendPk, const QDateTime &from, const QDateTime &to); QList<HistMessage> getChatHistory(const QString& friendPk,
void markAsSent(qint64 id); const QDateTime& from,
static QString getDbPath(const QString& profileName); const QDateTime& to);
void markAsSent(qint64 messageId);
protected: protected:
void init(); QVector<RawDatabase::Query> generateNewMessageQueries(
QVector<RawDatabase::Query> generateNewMessageQueries(const QString& friendPk, const QString& message, const QString& friendPk, const QString& message,
const QString& sender, const QDateTime &time, bool isSent, QString dispName, const QString& sender, const QDateTime& time,
std::function<void(int64_t)> insertIdCallback={}); bool isSent, QString dispName,
std::function<void(int64_t)> insertIdCallback={});
private: private:
RawDatabase db; std::shared_ptr<RawDatabase> db;
QHash<QString, int64_t> peers; QHash<QString, int64_t> peers;
}; };

149
src/persistence/profile.cpp

@ -1,5 +1,5 @@
/* /*
Copyright © 2015 by The qTox Project Copyright © 2015-2016 by The qTox Project
This file is part of qTox, a Qt-based graphical interface for Tox. This file is part of qTox, a Qt-based graphical interface for Tox.
@ -18,23 +18,25 @@
*/ */
#include "profile.h" #include <QDebug>
#include "profilelocker.h"
#include "src/persistence/settings.h"
#include "src/persistence/historykeeper.h"
#include "src/core/core.h"
#include "src/widget/gui.h"
#include "src/widget/widget.h"
#include "src/nexus.h"
#include <cassert>
#include <QDir> #include <QDir>
#include <QFileInfo> #include <QFileInfo>
#include <QObject>
#include <QSaveFile> #include <QSaveFile>
#include <QThread> #include <QThread>
#include <QObject>
#include <QDebug> #include <cassert>
#include <sodium.h> #include <sodium.h>
#include "profile.h"
#include "profilelocker.h"
#include "settings.h"
#include "historykeeper.h"
#include "src/core/core.h"
#include "src/nexus.h"
#include "src/widget/gui.h"
#include "src/widget/widget.h"
/** /**
* @class Profile * @class Profile
* @brief Manages user profiles. * @brief Manages user profiles.
@ -52,25 +54,31 @@
QVector<QString> Profile::profiles; QVector<QString> Profile::profiles;
Profile::Profile(QString name, const QString &password, bool isNewProfile) Profile::Profile(QString name, const QString& password, bool isNewProfile)
: name{name}, password{password}, : name{name}, password{password}
newProfile{isNewProfile}, isRemoved{false} , database(std::make_shared<RawDatabase>(getDbPath(name), password))
, newProfile{isNewProfile}, isRemoved{false}
{ {
if (!password.isEmpty()) if (!password.isEmpty())
{
passkey = *core->createPasskey(password); passkey = *core->createPasskey(password);
}
Settings& s = Settings::getInstance(); Settings& s = Settings::getInstance();
s.setCurrentProfile(name); s.setCurrentProfile(name);
s.saveGlobal(); s.saveGlobal();
// At this point it's too early to load the personal settings (Nexus will do it), so we always load // At this point it's too early to load the personal settings (Nexus will do
// the history, and if it fails we can't change the setting now, but we keep a nullptr // it), so we always load the history, and if it fails we can't change the
history.reset(new History{name, password}); // setting now, but we keep a nullptr
if (!history->isValid()) if (database->isOpen())
{ {
qWarning() << "Failed to open history for profile"<<name; history.reset(new History(database));
}
else
{
qWarning() << "Failed to open history for profile" << name;
GUI::showError(QObject::tr("Error"), QObject::tr("qTox couldn't open your chat logs, they will be disabled.")); GUI::showError(QObject::tr("Error"), QObject::tr("qTox couldn't open your chat logs, they will be disabled."));
history.release();
} }
coreThread = new QThread(); coreThread = new QThread();
@ -88,7 +96,7 @@ Profile::Profile(QString name, const QString &password, bool isNewProfile)
* *
* @example If the profile is already in use return nullptr. * @example If the profile is already in use return nullptr.
*/ */
Profile* Profile::loadProfile(QString name, const QString &password) Profile* Profile::loadProfile(QString name, const QString& password)
{ {
if (ProfileLocker::hasLock()) if (ProfileLocker::hasLock())
{ {
@ -141,7 +149,7 @@ Profile* Profile::loadProfile(QString name, const QString &password)
} }
uint8_t salt[TOX_PASS_SALT_LENGTH]; uint8_t salt[TOX_PASS_SALT_LENGTH];
tox_get_salt(reinterpret_cast<uint8_t *>(data.data()), salt); tox_get_salt(reinterpret_cast<uint8_t*>(data.data()), salt);
auto tmpkey = *Core::createPasskey(password, salt); auto tmpkey = *Core::createPasskey(password, salt);
data = Core::decryptData(data, tmpkey); data = Core::decryptData(data, tmpkey);
@ -155,13 +163,18 @@ Profile* Profile::loadProfile(QString name, const QString &password)
else else
{ {
if (!password.isEmpty()) if (!password.isEmpty())
{
qWarning() << "We have a password, but the tox save file is not encrypted"; qWarning() << "We have a password, but the tox save file is not encrypted";
}
} }
} }
Profile* p = new Profile(name, password, false); Profile* p = new Profile(name, password, false);
if (p->history && HistoryKeeper::isFileExist(!password.isEmpty())) if (p->history && HistoryKeeper::isFileExist(!password.isEmpty()))
{
p->history->import(*HistoryKeeper::getInstance(*p)); p->history->import(*HistoryKeeper::getInstance(*p));
}
return p; return p;
} }
@ -200,7 +213,10 @@ Profile* Profile::createProfile(QString name, QString password)
Profile::~Profile() Profile::~Profile()
{ {
if (!isRemoved && core->isReady()) if (!isRemoved && core->isReady())
{
saveToxSave(); saveToxSave();
}
delete core; delete core;
delete coreThread; delete coreThread;
if (!isRemoved) if (!isRemoved)
@ -227,7 +243,10 @@ QVector<QString> Profile::getFilesByExt(QString extension)
QFileInfoList list = dir.entryInfoList(); QFileInfoList list = dir.entryInfoList();
out.reserve(list.size()); out.reserve(list.size());
for (QFileInfo file : list) for (QFileInfo file : list)
{
out += file.completeBaseName(); out += file.completeBaseName();
}
return out; return out;
} }
@ -242,7 +261,10 @@ void Profile::scanProfiles()
for (QString toxfile : toxfiles) for (QString toxfile : toxfiles)
{ {
if (!inifiles.contains(toxfile)) if (!inifiles.contains(toxfile))
{
Settings::getInstance().createPersonal(toxfile); Settings::getInstance().createPersonal(toxfile);
}
profiles.append(toxfile); profiles.append(toxfile);
} }
} }
@ -319,17 +341,21 @@ QByteArray Profile::loadToxSave()
} }
uint8_t salt[TOX_PASS_SALT_LENGTH]; uint8_t salt[TOX_PASS_SALT_LENGTH];
tox_get_salt(reinterpret_cast<uint8_t *>(data.data()), salt); tox_get_salt(reinterpret_cast<uint8_t*>(data.data()), salt);
passkey = *core->createPasskey(password, salt); passkey = *core->createPasskey(password, salt);
data = core->decryptData(data, passkey); data = core->decryptData(data, passkey);
if (data.isEmpty()) if (data.isEmpty())
{
qCritical() << "Failed to decrypt the tox save file"; qCritical() << "Failed to decrypt the tox save file";
}
} }
else else
{ {
if (!password.isEmpty()) if (!password.isEmpty())
{
qWarning() << "We have a password, but the tox save file is not encrypted"; qWarning() << "We have a password, but the tox save file is not encrypted";
}
} }
fail: fail:
@ -402,7 +428,7 @@ void Profile::saveToxSave(QByteArray data)
* @param forceUnencrypted If true, return the path to the plaintext file even if this is an encrypted profile. * @param forceUnencrypted If true, return the path to the plaintext file even if this is an encrypted profile.
* @return Path to the avatar. * @return Path to the avatar.
*/ */
QString Profile::avatarPath(const QString &ownerId, bool forceUnencrypted) QString Profile::avatarPath(const QString& ownerId, bool forceUnencrypted)
{ {
if (password.isEmpty() || forceUnencrypted) if (password.isEmpty() || forceUnencrypted)
return Settings::getInstance().getSettingsDirPath() + "avatars/" + ownerId + ".png"; return Settings::getInstance().getSettingsDirPath() + "avatars/" + ownerId + ".png";
@ -433,7 +459,7 @@ QPixmap Profile::loadAvatar()
* @param ownerId Friend ID to load avatar. * @param ownerId Friend ID to load avatar.
* @return Avatar as QPixmap. * @return Avatar as QPixmap.
*/ */
QPixmap Profile::loadAvatar(const QString &ownerId) QPixmap Profile::loadAvatar(const QString& ownerId)
{ {
QPixmap pic; QPixmap pic;
pic.loadFromData(loadAvatarData(ownerId)); pic.loadFromData(loadAvatarData(ownerId));
@ -445,7 +471,7 @@ QPixmap Profile::loadAvatar(const QString &ownerId)
* @param ownerId Friend ID to load avatar. * @param ownerId Friend ID to load avatar.
* @return Avatar as QByteArray. * @return Avatar as QByteArray.
*/ */
QByteArray Profile::loadAvatarData(const QString &ownerId) QByteArray Profile::loadAvatarData(const QString& ownerId)
{ {
return loadAvatarData(ownerId, password); return loadAvatarData(ownerId, password);
} }
@ -456,7 +482,7 @@ QByteArray Profile::loadAvatarData(const QString &ownerId)
* @param password Profile password to decrypt data. * @param password Profile password to decrypt data.
* @return Avatar as QByteArray. * @return Avatar as QByteArray.
*/ */
QByteArray Profile::loadAvatarData(const QString &ownerId, const QString &password) QByteArray Profile::loadAvatarData(const QString& ownerId, const QString& password)
{ {
QString path = avatarPath(ownerId); QString path = avatarPath(ownerId);
bool encrypted = !password.isEmpty(); bool encrypted = !password.isEmpty();
@ -470,16 +496,19 @@ QByteArray Profile::loadAvatarData(const QString &ownerId, const QString &passwo
QFile file(path); QFile file(path);
if (!file.open(QIODevice::ReadOnly)) if (!file.open(QIODevice::ReadOnly))
{
return {}; return {};
}
QByteArray pic = file.readAll(); QByteArray pic = file.readAll();
if (encrypted && !pic.isEmpty()) if (encrypted && !pic.isEmpty())
{ {
uint8_t salt[TOX_PASS_SALT_LENGTH]; uint8_t salt[TOX_PASS_SALT_LENGTH];
tox_get_salt(reinterpret_cast<uint8_t *>(pic.data()), salt); tox_get_salt(reinterpret_cast<uint8_t*>(pic.data()), salt);
auto passkey = core->createPasskey(password, salt); auto passkey = core->createPasskey(password, salt);
pic = core->decryptData(pic, *passkey); pic = core->decryptData(pic, *passkey);
} }
return pic; return pic;
} }
@ -488,10 +517,12 @@ QByteArray Profile::loadAvatarData(const QString &ownerId, const QString &passwo
* @param pic Picture to save. * @param pic Picture to save.
* @param ownerId ID of avatar owner. * @param ownerId ID of avatar owner.
*/ */
void Profile::saveAvatar(QByteArray pic, const QString &ownerId) void Profile::saveAvatar(QByteArray pic, const QString& ownerId)
{ {
if (!password.isEmpty() && !pic.isEmpty()) if (!password.isEmpty() && !pic.isEmpty())
{
pic = core->encryptData(pic, passkey); pic = core->encryptData(pic, passkey);
}
QString path = avatarPath(ownerId); QString path = avatarPath(ownerId);
QDir(Settings::getInstance().getSettingsDirPath()).mkdir("avatars"); QDir(Settings::getInstance().getSettingsDirPath()).mkdir("avatars");
@ -517,7 +548,7 @@ void Profile::saveAvatar(QByteArray pic, const QString &ownerId)
* @param ownerId Friend ID to get hash. * @param ownerId Friend ID to get hash.
* @return Avatar tox hash. * @return Avatar tox hash.
*/ */
QByteArray Profile::getAvatarHash(const QString &ownerId) QByteArray Profile::getAvatarHash(const QString& ownerId)
{ {
QByteArray pic = loadAvatarData(ownerId); QByteArray pic = loadAvatarData(ownerId);
QByteArray avatarHash(TOX_HASH_LENGTH, 0); QByteArray avatarHash(TOX_HASH_LENGTH, 0);
@ -546,7 +577,7 @@ bool Profile::isHistoryEnabled()
* @brief Get chat history. * @brief Get chat history.
* @return May return a nullptr if the history failed to load. * @return May return a nullptr if the history failed to load.
*/ */
History *Profile::getHistory() History* Profile::getHistory()
{ {
return history.get(); return history.get();
} }
@ -555,7 +586,7 @@ History *Profile::getHistory()
* @brief Removes a cached avatar. * @brief Removes a cached avatar.
* @param ownerId Friend ID whose avater to delete. * @param ownerId Friend ID whose avater to delete.
*/ */
void Profile::removeAvatar(const QString &ownerId) void Profile::removeAvatar(const QString& ownerId)
{ {
QFile::remove(avatarPath(ownerId)); QFile::remove(avatarPath(ownerId));
if (ownerId == core->getSelfId().publicKey) if (ownerId == core->getSelfId().publicKey)
@ -656,15 +687,14 @@ QVector<QString> Profile::remove()
qWarning() << "Could not remove file " << historyLegacyUnencrypted.fileName(); qWarning() << "Could not remove file " << historyLegacyUnencrypted.fileName();
} }
if (history) QString dbPath = getDbPath(name);
if (database->isOpen() && !database->remove() && QFile::exists(dbPath))
{ {
if (!history->remove() && QFile::exists(History::getDbPath(name))) ret.push_back(dbPath);
{ qWarning() << "Could not remove file " << dbPath;
ret.push_back(History::getDbPath(name));
qWarning() << "Could not remove file " << History::getDbPath(name);
}
history.release();
} }
history.release();
database.reset();
return ret; return ret;
} }
@ -680,17 +710,24 @@ bool Profile::rename(QString newName)
newPath = Settings::getInstance().getSettingsDirPath() + newName; newPath = Settings::getInstance().getSettingsDirPath() + newName;
if (!ProfileLocker::lock(newName)) if (!ProfileLocker::lock(newName))
{
return false; return false;
}
QFile::rename(path + ".tox", newPath + ".tox");
QFile::rename(path + ".ini", newPath + ".ini");
if (database)
{
database->rename(newName);
}
QFile::rename(path+".tox", newPath+".tox");
QFile::rename(path+".ini", newPath+".ini");
if (history)
history->rename(newName);
bool resetAutorun = Settings::getInstance().getAutorun(); bool resetAutorun = Settings::getInstance().getAutorun();
Settings::getInstance().setAutorun(false); Settings::getInstance().setAutorun(false);
Settings::getInstance().setCurrentProfile(newName); Settings::getInstance().setCurrentProfile(newName);
if (resetAutorun) if (resetAutorun)
{
Settings::getInstance().setAutorun(true); // fixes -p flag in autostart command line Settings::getInstance().setAutorun(true); // fixes -p flag in autostart command line
}
name = newName; name = newName;
return true; return true;
@ -703,7 +740,9 @@ bool Profile::rename(QString newName)
bool Profile::checkPassword() bool Profile::checkPassword()
{ {
if (isRemoved) if (isRemoved)
{
return false; return false;
}
return !loadToxSave().isEmpty(); return !loadToxSave().isEmpty();
} }
@ -725,7 +764,10 @@ void Profile::restartCore()
{ {
GUI::setEnabled(false); // Core::reset re-enables it GUI::setEnabled(false); // Core::reset re-enables it
if (!isRemoved && core->isReady()) if (!isRemoved && core->isReady())
{
saveToxSave(); saveToxSave();
}
QMetaObject::invokeMethod(core, "reset"); QMetaObject::invokeMethod(core, "reset");
} }
@ -733,7 +775,7 @@ void Profile::restartCore()
* @brief Changes the encryption password and re-saves everything with it * @brief Changes the encryption password and re-saves everything with it
* @param newPassword Password for encryption. * @param newPassword Password for encryption.
*/ */
void Profile::setPassword(const QString &newPassword) void Profile::setPassword(const QString& newPassword)
{ {
QByteArray avatar = loadAvatarData(core->getSelfId().publicKey); QByteArray avatar = loadAvatarData(core->getSelfId().publicKey);
QString oldPassword = password; QString oldPassword = password;
@ -741,11 +783,12 @@ void Profile::setPassword(const QString &newPassword)
passkey = *core->createPasskey(password); passkey = *core->createPasskey(password);
saveToxSave(); saveToxSave();
if (history) if (database)
{ {
history->setPassword(newPassword); database->setPassword(newPassword);
Nexus::getDesktopGUI()->reloadHistory();
} }
Nexus::getDesktopGUI()->reloadHistory();
saveAvatar(avatar, core->getSelfId().publicKey); saveAvatar(avatar, core->getSelfId().publicKey);
QVector<uint32_t> friendList = core->getFriendList(); QVector<uint32_t> friendList = core->getFriendList();
@ -756,3 +799,13 @@ void Profile::setPassword(const QString &newPassword)
saveAvatar(loadAvatarData(friendPublicKey,oldPassword),friendPublicKey); saveAvatar(loadAvatarData(friendPublicKey,oldPassword),friendPublicKey);
} }
} }
/**
* @brief Retrieves the path to the database file for a given profile.
* @param profileName Profile name.
* @return Path to database.
*/
QString Profile::getDbPath(const QString& profileName)
{
return Settings::getInstance().getSettingsDirPath() + profileName + ".db";
}

10
src/persistence/profile.h

@ -1,5 +1,5 @@
/* /*
Copyright © 2015 by The qTox Project Copyright © 2015-2016 by The qTox Project
This file is part of qTox, a Qt-based graphical interface for Tox. This file is part of qTox, a Qt-based graphical interface for Tox.
@ -35,7 +35,7 @@ class QThread;
class Profile class Profile
{ {
public: public:
static Profile* loadProfile(QString name, const QString &password = QString()); static Profile* loadProfile(QString name, const QString& password = QString());
static Profile* createProfile(QString name, QString password); static Profile* createProfile(QString name, QString password);
~Profile(); ~Profile();
@ -48,7 +48,7 @@ public:
bool isEncrypted() const; bool isEncrypted() const;
bool checkPassword(); bool checkPassword();
QString getPassword() const; QString getPassword() const;
void setPassword(const QString &newPassword); void setPassword(const QString& newPassword);
const TOX_PASS_KEY& getPasskey() const; const TOX_PASS_KEY& getPasskey() const;
QByteArray loadToxSave(); QByteArray loadToxSave();
@ -76,9 +76,10 @@ public:
static bool exists(QString name); static bool exists(QString name);
static bool isEncrypted(QString name); static bool isEncrypted(QString name);
static QString getDbPath(const QString& profileName);
private: private:
Profile(QString name, const QString &password, bool newProfile); Profile(QString name, const QString& password, bool newProfile);
static QVector<QString> getFilesByExt(QString extension); static QVector<QString> getFilesByExt(QString extension);
QString avatarPath(const QString& ownerId, bool forceUnencrypted = false); QString avatarPath(const QString& ownerId, bool forceUnencrypted = false);
@ -87,6 +88,7 @@ private:
QThread* coreThread; QThread* coreThread;
QString name, password; QString name, password;
TOX_PASS_KEY passkey; TOX_PASS_KEY passkey;
std::shared_ptr<RawDatabase> database;
std::unique_ptr<History> history; std::unique_ptr<History> history;
bool newProfile; bool newProfile;
bool isRemoved; bool isRemoved;

Loading…
Cancel
Save