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

docs(persistence): Change comment style

This commit is contained in:
Diadlo 2016-07-27 01:20:54 +03:00
parent 1552bfb114
commit 1c547fc73f
No known key found for this signature in database
GPG Key ID: 5AF9F2E29107C727
24 changed files with 601 additions and 173 deletions

View File

@ -29,6 +29,11 @@
#include <QDebug> #include <QDebug>
#include <QSqlError> #include <QSqlError>
/**
@var static TOX_PASS_KEY EncryptedDb::decryptionKey
@note When importing, the decryption key may not be the same as the profile key
*/
qint64 EncryptedDb::encryptedChunkSize = 4096; qint64 EncryptedDb::encryptedChunkSize = 4096;
qint64 EncryptedDb::plainChunkSize = EncryptedDb::encryptedChunkSize - TOX_PASS_ENCRYPTION_EXTRA_LENGTH; qint64 EncryptedDb::plainChunkSize = EncryptedDb::encryptedChunkSize - TOX_PASS_ENCRYPTION_EXTRA_LENGTH;

View File

@ -46,7 +46,7 @@ private:
static qint64 plainChunkSize; static qint64 plainChunkSize;
static qint64 encryptedChunkSize; static qint64 encryptedChunkSize;
static TOX_PASS_KEY decryptionKey; ///< When importing, the decryption key may not be the same as the profile key static TOX_PASS_KEY decryptionKey;
qint64 chunkPosition; qint64 chunkPosition;
QByteArray buffer; QByteArray buffer;

View File

@ -14,6 +14,60 @@
#include <sqlcipher/sqlite3.h> #include <sqlcipher/sqlite3.h>
/**
@class RawDatabase
@brief Implements a low level RAII interface to a SQLCipher (SQlite3) database.
Thread-safe, does all database operations on a worker thread.
The queries must not contain transaction commands (BEGIN/COMMIT/...) or the behavior is undefined.
@var QMutex RawDatabase::transactionsMutex;
@brief Protects pendingTransactions
*/
/**
@class Query
@brief A query to be executed by the database.
Can be composed of one or more SQL statements in the query,
optional BLOB parameters to be bound, and callbacks fired when the query is executed
Calling any database method from a query callback is undefined behavior.
@var QByteArray RawDatabase::Query::query
@brief UTF-8 query string
@var QVector<QByteArray> RawDatabase::Query::blobs
@brief Bound data blobs
@var std::function<void(int64_t)> RawDatabase::Query::insertCallback
@brief Called after execution with the last insert rowid
@var std::function<void(const QVector<QVariant>&)> RawDatabase::Query::rowCallback
@brief Called during execution for each row
@var QVector<sqlite3_stmt*> RawDatabase::Query::statements
@brief Statements to be compiled from the query
*/
/**
@struct Transaction
@brief SQL transactions to be processed.
A transaction is made of queries, which can have bound BLOBs.
@var std::atomic_bool* RawDatabase::Transaction::success = nullptr;
@brief If not a nullptr, the result of the transaction will be set
@var std::atomic_bool* RawDatabase::Transaction::done = nullptr;
@brief If not a nullptr, will be set to true when the transaction has been executed
*/
/**
@brief Tries to open a database.
@param path Path to database.
@param password If empty, the database will be opened unencrypted.
Otherwise we will use toxencryptsave to derive a key and encrypt the database.
*/
RawDatabase::RawDatabase(const QString &path, const QString& password) RawDatabase::RawDatabase(const QString &path, const QString& password)
: workerThread{new QThread}, path{path}, currentHexKey{deriveKey(password)} : workerThread{new QThread}, path{path}, currentHexKey{deriveKey(password)}
{ {
@ -33,6 +87,12 @@ RawDatabase::~RawDatabase()
workerThread->wait(50); workerThread->wait(50);
} }
/**
@brief Tries to open the database with the given (possibly empty) key.
@param path Path to database.
@param hexKey Hex representation of the key in string.
@return True if success, false otherwise.
*/
bool RawDatabase::open(const QString& path, const QString &hexKey) bool RawDatabase::open(const QString& path, const QString &hexKey)
{ {
if (QThread::currentThread() != workerThread.get()) if (QThread::currentThread() != workerThread.get())
@ -75,6 +135,9 @@ bool RawDatabase::open(const QString& path, const QString &hexKey)
return true; return true;
} }
/**
@brief Close the database and free its associated resources.
*/
void RawDatabase::close() void RawDatabase::close()
{ {
if (QThread::currentThread() != workerThread.get()) if (QThread::currentThread() != workerThread.get())
@ -89,22 +152,41 @@ void RawDatabase::close()
qWarning() << "Error closing database:"<<sqlite3_errmsg(sqlite); qWarning() << "Error closing database:"<<sqlite3_errmsg(sqlite);
} }
/**
@brief Checks, that the database is open.
@return True if the database was opened successfully.
*/
bool RawDatabase::isOpen() bool RawDatabase::isOpen()
{ {
// We don't need thread safety since only the ctor/dtor can write this pointer // We don't need thread safety since only the ctor/dtor can write this pointer
return sqlite != nullptr; return sqlite != nullptr;
} }
/**
@brief Executes a SQL transaction synchronously.
@param statement Statement to execute.
@return Whether the transaction was successful.
*/
bool RawDatabase::execNow(const QString& statement) bool RawDatabase::execNow(const QString& statement)
{ {
return execNow(Query{statement}); return execNow(Query{statement});
} }
/**
@brief Executes a SQL transaction synchronously.
@param statement Statement to execute.
@return Whether the transaction was successful.
*/
bool RawDatabase::execNow(const RawDatabase::Query &statement) bool RawDatabase::execNow(const RawDatabase::Query &statement)
{ {
return execNow(QVector<Query>{statement}); return execNow(QVector<Query>{statement});
} }
/**
@brief Executes a SQL transaction synchronously.
@param statements List of statements to execute.
@return Whether the transaction was successful.
*/
bool RawDatabase::execNow(const QVector<RawDatabase::Query> &statements) bool RawDatabase::execNow(const QVector<RawDatabase::Query> &statements)
{ {
if (!sqlite) if (!sqlite)
@ -134,6 +216,10 @@ bool RawDatabase::execNow(const QVector<RawDatabase::Query> &statements)
return success.load(std::memory_order_acquire); return success.load(std::memory_order_acquire);
} }
/**
@brief Executes a SQL transaction asynchronously.
@param statement Statement to execute.
*/
void RawDatabase::execLater(const QString &statement) void RawDatabase::execLater(const QString &statement)
{ {
execLater(Query{statement}); execLater(Query{statement});
@ -162,11 +248,20 @@ void RawDatabase::execLater(const QVector<RawDatabase::Query> &statements)
QMetaObject::invokeMethod(this, "process"); QMetaObject::invokeMethod(this, "process");
} }
/**
@brief Waits until all the pending transactions are executed.
*/
void RawDatabase::sync() void RawDatabase::sync()
{ {
QMetaObject::invokeMethod(this, "process", Qt::BlockingQueuedConnection); QMetaObject::invokeMethod(this, "process", Qt::BlockingQueuedConnection);
} }
/**
@brief Changes the database password, encrypting or decrypting if necessary.
@param password If password is empty, the database will be decrypted.
@return True if success, false otherwise.
@note Will process all transactions before changing the password.
*/
bool RawDatabase::setPassword(const QString& password) bool RawDatabase::setPassword(const QString& password)
{ {
if (!sqlite) if (!sqlite)
@ -260,6 +355,13 @@ bool RawDatabase::setPassword(const QString& password)
return true; return true;
} }
/**
@brief Moves the database file on disk to match the new path.
@param newPath Path to move database file.
@return True if success, false otherwise.
@note Will process all transactions before renaming
*/
bool RawDatabase::rename(const QString &newPath) bool RawDatabase::rename(const QString &newPath)
{ {
if (!sqlite) if (!sqlite)
@ -291,6 +393,11 @@ bool RawDatabase::rename(const QString &newPath)
return open(path, currentHexKey); return open(path, currentHexKey);
} }
/**
@brief Deletes the on disk database file after closing it.
@note Will process all transactions before deletings.
@return True if success, false otherwise.
*/
bool RawDatabase::remove() bool RawDatabase::remove()
{ {
if (!sqlite) if (!sqlite)
@ -311,7 +418,11 @@ bool RawDatabase::remove()
return QFile::remove(path); return QFile::remove(path);
} }
/**
@brief Derives a 256bit key from the password and returns it hex-encoded
@param password Password to decrypt database
@return String representation of key
*/
QString RawDatabase::deriveKey(const QString &password) QString RawDatabase::deriveKey(const QString &password)
{ {
if (password.isEmpty()) if (password.isEmpty())
@ -327,6 +438,12 @@ QString RawDatabase::deriveKey(const QString &password)
return QByteArray((char*)key.key, 32).toHex(); return QByteArray((char*)key.key, 32).toHex();
} }
/**
@brief Implements the actual processing of pending transactions.
Unqueues, compiles, binds and executes queries, then notifies of results
@warning MUST only be called from the worker thread
*/
void RawDatabase::process() void RawDatabase::process()
{ {
assert(QThread::currentThread() == workerThread.get()); assert(QThread::currentThread() == workerThread.get());
@ -460,6 +577,12 @@ void RawDatabase::process()
} }
} }
/**
@brief Extracts a variant from one column of a result row depending on the column type.
@param stmt Statement to execute.
@param col Number of column to extract.
@return Extracted data.
*/
QVariant RawDatabase::extractData(sqlite3_stmt *stmt, int col) QVariant RawDatabase::extractData(sqlite3_stmt *stmt, int col)
{ {
int type = sqlite3_column_type(stmt, col); int type = sqlite3_column_type(stmt, col);

View File

@ -15,17 +15,11 @@
struct sqlite3; struct sqlite3;
struct sqlite3_stmt; struct sqlite3_stmt;
/// Implements a low level RAII interface to a SQLCipher (SQlite3) database
/// Thread-safe, does all database operations on a worker thread
/// The queries must not contain transaction commands (BEGIN/COMMIT/...) or the behavior is undefined
class RawDatabase : QObject class RawDatabase : QObject
{ {
Q_OBJECT Q_OBJECT
public: public:
/// A query to be executed by the database. Can be composed of one or more SQL statements in the query,
/// optional BLOB parameters to be bound, and callbacks fired when the query is executed
/// Calling any database method from a query callback is undefined behavior
class Query class Query
{ {
public: public:
@ -37,71 +31,49 @@ public:
: query{query.toUtf8()}, rowCallback{rowCallback} {} : query{query.toUtf8()}, rowCallback{rowCallback} {}
Query() = default; Query() = default;
private: private:
QByteArray query; ///< UTF-8 query string QByteArray query;
QVector<QByteArray> blobs; ///< Bound data blobs QVector<QByteArray> blobs;
std::function<void(int64_t)> insertCallback; ///< Called after execution with the last insert rowid std::function<void(int64_t)> insertCallback;
std::function<void(const QVector<QVariant>&)> rowCallback; ///< Called during execution for each row std::function<void(const QVector<QVariant>&)> rowCallback;
QVector<sqlite3_stmt*> statements; ///< Statements to be compiled from the query QVector<sqlite3_stmt*> statements;
friend class RawDatabase; friend class RawDatabase;
}; };
public: public:
/// Tries to open a database
/// If password is empty, the database will be opened unencrypted
/// Otherwise we will use toxencryptsave to derive a key and encrypt the database
RawDatabase(const QString& path, const QString& password); RawDatabase(const QString& path, const QString& password);
~RawDatabase(); ~RawDatabase();
bool isOpen(); ///< Returns true if the database was opened successfully bool isOpen();
/// Executes a SQL transaction synchronously.
/// Returns whether the transaction was successful.
bool execNow(const QString& statement); bool execNow(const QString& statement);
bool execNow(const Query& statement); bool execNow(const Query& statement);
bool execNow(const QVector<Query>& statements); bool execNow(const QVector<Query>& statements);
/// Executes a SQL transaction asynchronously.
void execLater(const QString& statement); void execLater(const QString& statement);
void execLater(const Query& statement); void execLater(const Query& statement);
void execLater(const QVector<Query>& statements); void execLater(const QVector<Query>& statements);
/// Waits until all the pending transactions are executed
void sync(); void sync();
public slots: public slots:
/// Changes the database password, encrypting or decrypting if necessary
/// If password is empty, the database will be decrypted
/// Will process all transactions before changing the password
bool setPassword(const QString& password); bool setPassword(const QString& password);
/// Moves the database file on disk to match the new path
/// Will process all transactions before renaming
bool rename(const QString& newPath); bool rename(const QString& newPath);
/// Deletes the on disk database file after closing it
/// Will process all transactions before deletings
bool remove(); bool remove();
protected slots: protected slots:
/// Tries to open the database with the given (possibly empty) key
bool open(const QString& path, const QString& hexKey = {}); bool open(const QString& path, const QString& hexKey = {});
/// Closes the database and free its associated resources
void close(); void close();
/// Implements the actual processing of pending transactions
/// Unqueues, compiles, binds and executes queries, then notifies of results
/// MUST only be called from the worker thread
void process(); void process();
protected: protected:
/// Derives a 256bit key from the password and returns it hex-encoded
static QString deriveKey(const QString &password); static QString deriveKey(const QString &password);
/// Extracts a variant from one column of a result row depending on the column type
static QVariant extractData(sqlite3_stmt* stmt, int col); static QVariant extractData(sqlite3_stmt* stmt, int col);
private: private:
/// SQL transactions to be processed
/// A transaction is made of queries, which can have bound BLOBs
struct Transaction struct Transaction
{ {
QVector<Query> queries; QVector<Query> queries;
/// If not a nullptr, the result of the transaction will be set
std::atomic_bool* success = nullptr; std::atomic_bool* success = nullptr;
/// If not a nullptr, will be set to true when the transaction has been executed
std::atomic_bool* done = nullptr; std::atomic_bool* done = nullptr;
}; };
@ -109,7 +81,6 @@ private:
sqlite3* sqlite; sqlite3* sqlite;
std::unique_ptr<QThread> workerThread; std::unique_ptr<QThread> workerThread;
QQueue<Transaction> pendingTransactions; QQueue<Transaction> pendingTransactions;
/// Protects pendingTransactions
QMutex transactionsMutex; QMutex transactionsMutex;
QString path; QString path;
QString currentHexKey; QString currentHexKey;

View File

@ -8,12 +8,32 @@
using namespace std; using namespace std;
/**
@class History
@brief Interacts with the profile database to save the chat history.
@var QHash<QString, int64_t> History::peers
@brief Maps friend public keys to unique IDs by index.
Caches mappings to speed up message saving.
*/
/**
@brief Opens the profile database and prepares to work with the history.
@param profileName Profile name to load.
@param password If empty, the database will be opened unencrypted.
*/
History::History(const QString &profileName, const QString &password) History::History(const QString &profileName, const QString &password)
: db{getDbPath(profileName), password} : db{getDbPath(profileName), password}
{ {
init(); init();
} }
/**
@brief Opens the profile database, and import from the old database.
@param profileName Profile name to load.
@param password If empty, the database will be opened unencrypted.
@param oldHistory Old history to import.
*/
History::History(const QString &profileName, const QString &password, const HistoryKeeper &oldHistory) History::History(const QString &profileName, const QString &password, const HistoryKeeper &oldHistory)
: History{profileName, password} : History{profileName, password}
{ {
@ -27,26 +47,45 @@ History::~History()
db.sync(); db.sync();
} }
/**
@brief Checks if the database was opened successfully
@return True if database if opened, false otherwise.
*/
bool History::isValid() bool History::isValid()
{ {
return db.isOpen(); return db.isOpen();
} }
/**
@brief Changes the database password, will encrypt or decrypt if necessary.
@param password Password to set.
*/
void History::setPassword(const QString& password) void History::setPassword(const QString& password)
{ {
db.setPassword(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) void History::rename(const QString &newName)
{ {
db.rename(getDbPath(newName)); db.rename(getDbPath(newName));
} }
/**
@brief Deletes the on-disk database file.
@return True if success, false otherwise.
*/
bool History::remove() bool History::remove()
{ {
return db.remove(); return db.remove();
} }
/**
@brief Erases all the chat history from the database.
*/
void History::eraseHistory() void History::eraseHistory()
{ {
db.execNow("DELETE FROM faux_offline_pending;" db.execNow("DELETE FROM faux_offline_pending;"
@ -56,6 +95,10 @@ void History::eraseHistory()
"VACUUM;"); "VACUUM;");
} }
/**
@brief Erases the chat history with one friend.
@param friendPk Friend public key to erase.
*/
void History::removeFriendHistory(const QString &friendPk) void History::removeFriendHistory(const QString &friendPk)
{ {
if (!peers.contains(friendPk)) if (!peers.contains(friendPk))
@ -81,6 +124,16 @@ void History::removeFriendHistory(const QString &friendPk)
} }
} }
/**
@brief Generate query to insert new message in database
@param friendPk Friend publick key to save.
@param message Message to save.
@param sender Sender to save.
@param time Time of message sending.
@param isSent True if message was already sent.
@param dispName Name, which should be displayed.
@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)
@ -139,12 +192,29 @@ QVector<RawDatabase::Query> History::generateNewMessageQueries(const QString &fr
return queries; return queries;
} }
/**
@brief Saves a chat message in the database.
@param friendPk Friend publick key to save.
@param message Message to save.
@param sender Sender to save.
@param time Time of message sending.
@param isSent True if message was already sent.
@param dispName Name, which should be displayed.
@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 QString &sender,
const QDateTime &time, bool isSent, QString dispName, std::function<void(int64_t)> insertIdCallback) const QDateTime &time, bool isSent, QString dispName, std::function<void(int64_t)> insertIdCallback)
{ {
db.execLater(generateNewMessageQueries(friendPk, message, sender, time, isSent, dispName, insertIdCallback)); db.execLater(generateNewMessageQueries(friendPk, message, sender, time, isSent, dispName, insertIdCallback));
} }
/**
@brief Fetches chat messages from the database.
@param friendPk Friend publick key to fetch.
@param from Start of period to fetch.
@param to End of period to fetch.
@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)
{ {
QList<HistMessage> messages; QList<HistMessage> messages;
@ -174,16 +244,30 @@ QList<History::HistMessage> History::getChatHistory(const QString &friendPk, con
return messages; return messages;
} }
/**
@brief Marks a message as sent.
Removing message from the faux-offline pending messages list.
@param id Message ID.
*/
void History::markAsSent(qint64 id) void History::markAsSent(qint64 id)
{ {
db.execLater(QString("DELETE FROM faux_offline_pending WHERE id=%1;").arg(id)); 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) QString History::getDbPath(const QString &profileName)
{ {
return Settings::getInstance().getSettingsDirPath() + profileName + ".db"; return Settings::getInstance().getSettingsDirPath() + profileName + ".db";
} }
/**
@brief Makes sure the history tables are created
*/
void History::init() void History::init()
{ {
if (!isValid()) if (!isValid())
@ -207,6 +291,10 @@ void History::init()
}}); }});
} }
/**
@brief Imports messages from the old history file.
@param oldHistory Old history to import.
*/
void History::import(const HistoryKeeper &oldHistory) void History::import(const HistoryKeeper &oldHistory)
{ {
if (!isValid()) if (!isValid())

View File

@ -12,7 +12,6 @@ class Profile;
class HistoryKeeper; class HistoryKeeper;
class RawDatabase; class RawDatabase;
/// Interacts with the profile database to save the chat history
class History class History
{ {
public: public:
@ -31,40 +30,26 @@ public:
}; };
public: public:
/// Opens the profile database and prepares to work with the history
/// If password is empty, the database will be opened unencrypted
History(const QString& profileName, const QString& password); History(const QString& profileName, const QString& password);
/// Opens the profile database, and import from the old database
/// If password is empty, the database will be opened unencrypted
History(const QString& profileName, const QString& password, const HistoryKeeper& oldHistory); History(const QString& profileName, const QString& password, const HistoryKeeper& oldHistory);
~History(); ~History();
/// Checks if the database was opened successfully
bool isValid(); bool isValid();
/// Imports messages from the old history file
void import(const HistoryKeeper& oldHistory); void import(const HistoryKeeper& oldHistory);
/// Changes the database password, will encrypt or decrypt if necessary
void setPassword(const QString& password); void setPassword(const QString& password);
/// Moves the database file on disk to match the new name
void rename(const QString& newName); void rename(const QString& newName);
/// Deletes the on-disk database file
bool remove(); bool remove();
/// Erases all the chat history from the database
void eraseHistory(); void eraseHistory();
/// Erases the chat history with one friend
void removeFriendHistory(const QString& friendPk); void removeFriendHistory(const QString& friendPk);
/// Saves a chat message in the database
void addNewMessage(const QString& friendPk, const QString& message, const QString& sender, void addNewMessage(const QString& friendPk, const QString& message, const QString& sender,
const QDateTime &time, bool isSent, QString dispName, const QDateTime &time, bool isSent, QString dispName,
std::function<void(int64_t)> insertIdCallback={}); std::function<void(int64_t)> insertIdCallback={});
/// Fetches chat messages from the database
QList<HistMessage> getChatHistory(const QString& friendPk, const QDateTime &from, const QDateTime &to); QList<HistMessage> getChatHistory(const QString& friendPk, const QDateTime &from, const QDateTime &to);
/// Marks a message as sent, removing it from the faux-offline pending messages list
void markAsSent(qint64 id); void markAsSent(qint64 id);
/// Retrieves the path to the database file for a given profile.
static QString getDbPath(const QString& profileName); static QString getDbPath(const QString& profileName);
protected: protected:
/// Makes sure the history tables are created
void init(); void init();
QVector<RawDatabase::Query> generateNewMessageQueries(const QString& friendPk, const QString& message, QVector<RawDatabase::Query> 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,
@ -72,8 +57,7 @@ protected:
private: private:
RawDatabase db; RawDatabase db;
// Cached mappings to speed up message saving QHash<QString, int64_t> peers;
QHash<QString, int64_t> peers; ///< Maps friend public keys to unique IDs by index
}; };
#endif // HISTORY_H #endif // HISTORY_H

View File

@ -35,9 +35,19 @@
#include "src/persistence/db/plaindb.h" #include "src/persistence/db/plaindb.h"
#include "src/persistence/db/encrypteddb.h" #include "src/persistence/db/encrypteddb.h"
/**
@class HistoryKeeper
@brief THIS IS A LEGACY CLASS KEPT FOR BACKWARDS COMPATIBILITY
@deprecated See the History class instead
@warning DO NOT USE!
*/
static HistoryKeeper *historyInstance = nullptr; static HistoryKeeper *historyInstance = nullptr;
QMutex HistoryKeeper::historyMutex; QMutex HistoryKeeper::historyMutex;
/**
@brief Returns the singleton instance.
*/
HistoryKeeper *HistoryKeeper::getInstance(const Profile& profile) HistoryKeeper *HistoryKeeper::getInstance(const Profile& profile)
{ {
historyMutex.lock(); historyMutex.lock();

View File

@ -27,12 +27,6 @@
#include <QMutex> #include <QMutex>
#include <tox/toxencryptsave.h> #include <tox/toxencryptsave.h>
/**
* THIS IS A LEGACY CLASS KEPT FOR BACKWARDS COMPATIBILITY
* DO NOT USE!
* See the History class instead
*/
class Profile; class Profile;
class GenericDdInterface; class GenericDdInterface;
namespace Db { enum class syncType; } namespace Db { enum class syncType; }

View File

@ -26,6 +26,15 @@
#include <QMutexLocker> #include <QMutexLocker>
#include <QTimer> #include <QTimer>
/**
@var static const int OfflineMsgEngine::offlineTimeout
@brief timeout after which faux offline messages get to be re-sent.
Originally was 2s, but since that was causing lots of duplicated
messages on receiving end, make qTox be more lazy about re-sending
should be 20s.
*/
const int OfflineMsgEngine::offlineTimeout = 20000; const int OfflineMsgEngine::offlineTimeout = 20000;
QMutex OfflineMsgEngine::globalMutex; QMutex OfflineMsgEngine::globalMutex;

View File

@ -57,10 +57,6 @@ private:
QHash<int, int64_t> receipts; QHash<int, int64_t> receipts;
QMap<int64_t, MsgPtr> undeliveredMsgs; QMap<int64_t, MsgPtr> undeliveredMsgs;
// timeout after which faux offline messages get to be re-sent
// originally was 2s, but since that was causing lots of duplicated
// messages on receiving end, make qTox be more lazy about re-sending
// should be 20s
static const int offlineTimeout; static const int offlineTimeout;
}; };

View File

@ -35,6 +35,21 @@
#include <QDebug> #include <QDebug>
#include <sodium.h> #include <sodium.h>
/**
@class Profile
@brief Manages user profiles.
@var bool Profile::newProfile
@brief True if this is a newly created profile, with no .tox save file yet.
@var bool Profile::isRemoved
@brief True if the profile has been removed by remove().
@var static constexpr int Profile::encryptHeaderSize = 8
@brief How much data we need to read to check if the file is encrypted.
@note Must be >= TOX_ENC_SAVE_MAGIC_LENGTH (8), which isn't publicly defined.
*/
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)
@ -65,6 +80,14 @@ Profile::Profile(QString name, const QString &password, bool isNewProfile)
QObject::connect(coreThread, &QThread::started, core, &Core::start); QObject::connect(coreThread, &QThread::started, core, &Core::start);
} }
/**
@brief Locks and loads an existing profile and creates the associate Core* instance.
@param name Profile name.
@param password Profile password.
@return Returns a nullptr on error. Profile pointer otherwise.
@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())
@ -142,6 +165,14 @@ Profile* Profile::loadProfile(QString name, const QString &password)
return p; return p;
} }
/**
@brief Creates a new profile and the associated Core* instance.
@param name Username.
@param password If password is not empty, the profile will be encrypted.
@return Returns a nullptr on error. Profile pointer otherwise.
@example If the profile is already in use return nullptr.
*/
Profile* Profile::createProfile(QString name, QString password) Profile* Profile::createProfile(QString name, QString password)
{ {
if (ProfileLocker::hasLock()) if (ProfileLocker::hasLock())
@ -182,6 +213,11 @@ Profile::~Profile()
} }
} }
/**
@brief Lists all the files in the config dir with a given extension
@param extension Raw extension, e.g. "jpeg" not ".jpeg".
@return Vector of filenames.
*/
QVector<QString> Profile::getFilesByExt(QString extension) QVector<QString> Profile::getFilesByExt(QString extension)
{ {
QDir dir(Settings::getInstance().getSettingsDirPath()); QDir dir(Settings::getInstance().getSettingsDirPath());
@ -195,6 +231,10 @@ QVector<QString> Profile::getFilesByExt(QString extension)
return out; return out;
} }
/**
@brief Scan for profile, automatically importing them if needed.
@warning NOT thread-safe.
*/
void Profile::scanProfiles() void Profile::scanProfiles()
{ {
profiles.clear(); profiles.clear();
@ -222,6 +262,9 @@ QString Profile::getName() const
return name; return name;
} }
/**
@brief Starts the Core thread
*/
void Profile::startCore() void Profile::startCore()
{ {
coreThread->start(); coreThread->start();
@ -232,6 +275,10 @@ bool Profile::isNewProfile()
return newProfile; return newProfile;
} }
/**
@brief Loads the profile's .tox save from file, unencrypted
@return Byte array of loaded profile save.
*/
QByteArray Profile::loadToxSave() QByteArray Profile::loadToxSave()
{ {
assert(!isRemoved); assert(!isRemoved);
@ -290,6 +337,10 @@ fail:
return data; return data;
} }
/**
@brief Saves the profile's .tox save, encrypted if needed.
@warning Invalid on deleted profiles.
*/
void Profile::saveToxSave() void Profile::saveToxSave()
{ {
assert(core->isReady()); assert(core->isReady());
@ -298,6 +349,11 @@ void Profile::saveToxSave()
saveToxSave(data); saveToxSave(data);
} }
/**
@brief Write the .tox save, encrypted if needed.
@param data Byte array of profile save.
@warning Invalid on deleted profiles.
*/
void Profile::saveToxSave(QByteArray data) void Profile::saveToxSave(QByteArray data)
{ {
assert(!isRemoved); assert(!isRemoved);
@ -340,6 +396,12 @@ void Profile::saveToxSave(QByteArray data)
} }
} }
/**
@brief Gets the path of the avatar file cached by this profile and corresponding to this owner ID.
@param ownerId Path to avatar of friend with this ID will returned.
@param forceUnencrypted If true, return the path to the plaintext file even if this is an encrypted profile.
@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)
@ -357,11 +419,20 @@ QString Profile::avatarPath(const QString &ownerId, bool forceUnencrypted)
return Settings::getInstance().getSettingsDirPath() + "avatars/" + hash.toHex().toUpper() + ".png"; return Settings::getInstance().getSettingsDirPath() + "avatars/" + hash.toHex().toUpper() + ".png";
} }
/**
@brief Get our avatar from cache.
@return Avatar as QPixmap.
*/
QPixmap Profile::loadAvatar() QPixmap Profile::loadAvatar()
{ {
return loadAvatar(core->getSelfId().publicKey); return loadAvatar(core->getSelfId().publicKey);
} }
/**
@brief Get a contact's avatar from cache.
@param ownerId Friend ID to load avatar.
@return Avatar as QPixmap.
*/
QPixmap Profile::loadAvatar(const QString &ownerId) QPixmap Profile::loadAvatar(const QString &ownerId)
{ {
QPixmap pic; QPixmap pic;
@ -369,11 +440,22 @@ QPixmap Profile::loadAvatar(const QString &ownerId)
return pic; return pic;
} }
/**
@brief Get a contact's avatar from cache
@param ownerId Friend ID to load avatar.
@return Avatar as QByteArray.
*/
QByteArray Profile::loadAvatarData(const QString &ownerId) QByteArray Profile::loadAvatarData(const QString &ownerId)
{ {
return loadAvatarData(ownerId, password); return loadAvatarData(ownerId, password);
} }
/**
@brief Get a contact's avatar from cache, with a specified profile password.
@param ownerId Friend ID to load avatar.
@param password Profile password to decrypt data.
@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);
@ -401,6 +483,11 @@ QByteArray Profile::loadAvatarData(const QString &ownerId, const QString &passwo
return pic; return pic;
} }
/**
@brief Save an avatar to cache.
@param pic Picture to save.
@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())
@ -425,6 +512,11 @@ void Profile::saveAvatar(QByteArray pic, const QString &ownerId)
} }
} }
/**
@brief Get the tox hash of a cached avatar.
@param ownerId Friend ID to get 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);
@ -433,21 +525,36 @@ QByteArray Profile::getAvatarHash(const QString &ownerId)
return avatarHash; return avatarHash;
} }
/**
@brief Removes our own avatar.
*/
void Profile::removeAvatar() void Profile::removeAvatar()
{ {
removeAvatar(core->getSelfId().publicKey); removeAvatar(core->getSelfId().publicKey);
} }
/**
@brief Checks that the history is enabled in the settings, and loaded successfully for this profile.
@return True if enabled, false otherwise.
*/
bool Profile::isHistoryEnabled() bool Profile::isHistoryEnabled()
{ {
return Settings::getInstance().getEnableLogging() && history; return Settings::getInstance().getEnableLogging() && history;
} }
/**
@brief Get chat history.
@return May return a nullptr if the history failed to load.
*/
History *Profile::getHistory() History *Profile::getHistory()
{ {
return history.get(); return history.get();
} }
/**
@brief Removes a cached avatar.
@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));
@ -461,11 +568,21 @@ bool Profile::exists(QString name)
return QFile::exists(path+".tox"); return QFile::exists(path+".tox");
} }
/**
@brief Checks, if profile has a password.
@return True if we have a password set (doesn't check the actual file on disk).
*/
bool Profile::isEncrypted() const bool Profile::isEncrypted() const
{ {
return !password.isEmpty(); return !password.isEmpty();
} }
/**
@brief Checks if profile is encrypted.
@note Checks the actual file on disk.
@param name Profile name.
@return True if profile is encrypted, false otherwise.
*/
bool Profile::isEncrypted(QString name) bool Profile::isEncrypted(QString name)
{ {
uint8_t data[encryptHeaderSize] = {0}; uint8_t data[encryptHeaderSize] = {0};
@ -483,6 +600,12 @@ bool Profile::isEncrypted(QString name)
return tox_is_data_encrypted(data); return tox_is_data_encrypted(data);
} }
/**
@brief Removes the profile permanently.
Updates the profiles vector.
@return Vector of filenames that could not be removed.
@warning It is invalid to call loadToxSave or saveToxSave on a deleted profile.
*/
QVector<QString> Profile::remove() QVector<QString> Profile::remove()
{ {
if (isRemoved) if (isRemoved)
@ -546,6 +669,11 @@ QVector<QString> Profile::remove()
return ret; return ret;
} }
/**
@brief Tries to rename the profile.
@param newName New name for the profile.
@return False on error, true otherwise.
*/
bool Profile::rename(QString newName) bool Profile::rename(QString newName)
{ {
QString path = Settings::getInstance().getSettingsDirPath() + name, QString path = Settings::getInstance().getSettingsDirPath() + name,
@ -568,6 +696,10 @@ bool Profile::rename(QString newName)
return true; return true;
} }
/**
@brief Checks whether the password is valid.
@return True, if password is valid, false otherwise.
*/
bool Profile::checkPassword() bool Profile::checkPassword()
{ {
if (isRemoved) if (isRemoved)
@ -586,6 +718,9 @@ const TOX_PASS_KEY& Profile::getPasskey() const
return passkey; return passkey;
} }
/**
@brief Delete core and restart a new one
*/
void Profile::restartCore() void Profile::restartCore()
{ {
GUI::setEnabled(false); // Core::reset re-enables it GUI::setEnabled(false); // Core::reset re-enables it
@ -594,6 +729,10 @@ void Profile::restartCore()
QMetaObject::invokeMethod(core, "reset"); QMetaObject::invokeMethod(core, "reset");
} }
/**
@brief Changes the encryption password and re-saves everything with it
@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);

View File

@ -32,76 +32,54 @@
class Core; class Core;
class QThread; class QThread;
/// Manages user profiles
class Profile class Profile
{ {
public: public:
/// Locks and loads an existing profile and create the associate Core* instance
/// Returns a nullptr on error, for example if the profile is already in use
static Profile* loadProfile(QString name, const QString &password = QString()); static Profile* loadProfile(QString name, const QString &password = QString());
/// Creates a new profile and the associated Core* instance
/// If password is not empty, the profile will be encrypted
/// Returns a nullptr on error, for example if the profile already exists
static Profile* createProfile(QString name, QString password); static Profile* createProfile(QString name, QString password);
~Profile(); ~Profile();
Core* getCore(); Core* getCore();
QString getName() const; QString getName() const;
void startCore(); ///< Starts the Core thread void startCore();
void restartCore(); ///< Delete core and restart a new one void restartCore();
bool isNewProfile(); bool isNewProfile();
bool isEncrypted() const; ///< Returns true if we have a password set (doesn't check the actual file on disk) bool isEncrypted() const;
bool checkPassword(); ///< Checks whether the password is valid bool checkPassword();
QString getPassword() const; QString getPassword() const;
void setPassword(const QString &newPassword); ///< Changes the encryption password and re-saves everything with it void setPassword(const QString &newPassword);
const TOX_PASS_KEY& getPasskey() const; const TOX_PASS_KEY& getPasskey() const;
QByteArray loadToxSave(); ///< Loads the profile's .tox save from file, unencrypted QByteArray loadToxSave();
void saveToxSave(); ///< Saves the profile's .tox save, encrypted if needed. Invalid on deleted profiles. void saveToxSave();
void saveToxSave(QByteArray data); ///< Write the .tox save, encrypted if needed. Invalid on deleted profiles. void saveToxSave(QByteArray data);
QPixmap loadAvatar(); ///< Get our avatar from cache QPixmap loadAvatar();
QPixmap loadAvatar(const QString& ownerId); ///< Get a contact's avatar from cache QPixmap loadAvatar(const QString& ownerId);
QByteArray loadAvatarData(const QString& ownerId); ///< Get a contact's avatar from cache QByteArray loadAvatarData(const QString& ownerId);
QByteArray loadAvatarData(const QString& ownerId, const QString& password); ///< Get a contact's avatar from cache, with a specified profile password. QByteArray loadAvatarData(const QString& ownerId, const QString& password);
void saveAvatar(QByteArray pic, const QString& ownerId); ///< Save an avatar to cache void saveAvatar(QByteArray pic, const QString& ownerId);
QByteArray getAvatarHash(const QString& ownerId); ///< Get the tox hash of a cached avatar QByteArray getAvatarHash(const QString& ownerId);
void removeAvatar(const QString& ownerId); ///< Removes a cached avatar void removeAvatar(const QString& ownerId);
void removeAvatar(); ///< Removes our own avatar void removeAvatar();
/// Returns true if the history is enabled in the settings, and loaded successfully for this profile
bool isHistoryEnabled(); bool isHistoryEnabled();
/// May return a nullptr if the history failed to load
History* getHistory(); History* getHistory();
/// Removes the profile permanently
/// It is invalid to call loadToxSave or saveToxSave on a deleted profile
/// Updates the profiles vector
/// Returns a vector of filenames that could not be removed.
QVector<QString> remove(); QVector<QString> remove();
/// Tries to rename the profile
bool rename(QString newName); bool rename(QString newName);
/// Scan for profile, automatically importing them if needed
/// NOT thread-safe
static void scanProfiles(); static void scanProfiles();
static QVector<QString> getProfiles(); static QVector<QString> getProfiles();
static bool exists(QString name); static bool exists(QString name);
static bool isEncrypted(QString name); ///< Returns false on error. Checks the actual file on disk. static bool isEncrypted(QString name);
private: private:
Profile(QString name, const QString &password, bool newProfile); Profile(QString name, const QString &password, bool newProfile);
/// Lists all the files in the config dir with a given extension
/// Pass the raw extension, e.g. "jpeg" not ".jpeg".
static QVector<QString> getFilesByExt(QString extension); static QVector<QString> getFilesByExt(QString extension);
/// Creates a .ini file for the given .tox profile
/// Only pass the basename, without extension
static void importProfile(QString name);
/// Gets the path of the avatar file cached by this profile and corresponding to this owner ID
/// If forceUnencrypted, we return the path to the plaintext file even if we're an encrypted profile
QString avatarPath(const QString& ownerId, bool forceUnencrypted = false); QString avatarPath(const QString& ownerId, bool forceUnencrypted = false);
private: private:
@ -110,11 +88,9 @@ private:
QString name, password; QString name, password;
TOX_PASS_KEY passkey; TOX_PASS_KEY passkey;
std::unique_ptr<History> history; std::unique_ptr<History> history;
bool newProfile; ///< True if this is a newly created profile, with no .tox save file yet. bool newProfile;
bool isRemoved; ///< True if the profile has been removed by remove() bool isRemoved;
static QVector<QString> profiles; static QVector<QString> profiles;
/// How much data we need to read to check if the file is encrypted
/// Must be >= TOX_ENC_SAVE_MAGIC_LENGTH (8), which isn't publicly defined
static constexpr int encryptHeaderSize = 8; static constexpr int encryptHeaderSize = 8;
}; };

View File

@ -23,6 +23,14 @@
#include <QDir> #include <QDir>
#include <QDebug> #include <QDebug>
/**
@class ProfileLocker
@brief Locks a Tox profile so that multiple instances can not use the same profile.
Only one lock can be acquired at the same time, which means
that there is little need for manually unlocking.
The current lock will expire if you exit or acquire a new one.
*/
using namespace std; using namespace std;
unique_ptr<QLockFile> ProfileLocker::lockfile; unique_ptr<QLockFile> ProfileLocker::lockfile;
@ -33,6 +41,14 @@ QString ProfileLocker::lockPathFromName(const QString& name)
return Settings::getInstance().getSettingsDirPath()+'/'+name+".lock"; return Settings::getInstance().getSettingsDirPath()+'/'+name+".lock";
} }
/**
@brief Checks if a profile is currently locked by *another* instance.
If we own the lock, we consider it lockable.
There is no guarantee that the result will still be valid by the
time it is returned, this is provided on a best effort basis.
@param profile Profile name to check.
@return True, if profile locked, false otherwise.
*/
bool ProfileLocker::isLockable(QString profile) bool ProfileLocker::isLockable(QString profile)
{ {
// If we already have the lock, it's definitely lockable // If we already have the lock, it's definitely lockable
@ -43,6 +59,11 @@ bool ProfileLocker::isLockable(QString profile)
return newLock.tryLock(); return newLock.tryLock();
} }
/**
@brief Tries to acquire the lock on a profile, will not block.
@param profile Profile to lock.
@return Returns true if we already own the lock.
*/
bool ProfileLocker::lock(QString profile) bool ProfileLocker::lock(QString profile)
{ {
if (lockfile && curLockName == profile) if (lockfile && curLockName == profile)
@ -62,16 +83,26 @@ bool ProfileLocker::lock(QString profile)
return true; return true;
} }
/**
@brief Releases the lock on the current profile.
*/
void ProfileLocker::unlock() void ProfileLocker::unlock()
{ {
if (!lockfile) if (!lockfile)
return; return;
lockfile->unlock(); lockfile->unlock();
delete lockfile.release(); delete lockfile.release();
lockfile = nullptr; lockfile = nullptr;
curLockName.clear(); curLockName.clear();
} }
/**
@brief Check that we actually own the lock.
In case the file was deleted on disk, restore it.
If we can't get a lock, exit qTox immediately.
If we never had a lock in the first place, exit immediately.
*/
void ProfileLocker::assertLock() void ProfileLocker::assertLock()
{ {
if (!lockfile) if (!lockfile)
@ -96,17 +127,28 @@ void ProfileLocker::assertLock()
} }
} }
/**
@brief Print an error then exit immediately.
*/
void ProfileLocker::deathByBrokenLock() void ProfileLocker::deathByBrokenLock()
{ {
qCritical() << "Lock is *BROKEN*, exiting immediately"; qCritical() << "Lock is *BROKEN*, exiting immediately";
abort(); abort();
} }
/**
@brief Chacks, that profile locked.
@return Returns true if we're currently holding a lock.
*/
bool ProfileLocker::hasLock() bool ProfileLocker::hasLock()
{ {
return lockfile.operator bool(); return lockfile.operator bool();
} }
/**
@brief Get current locked profile name.
@return Return the name of the currently loaded profile, a null string if there is none.
*/
QString ProfileLocker::getCurLockName() QString ProfileLocker::getCurLockName()
{ {
if (lockfile) if (lockfile)

View File

@ -24,39 +24,22 @@
#include <QLockFile> #include <QLockFile>
#include <memory> #include <memory>
/// Locks a Tox profile so that multiple instances can not use the same profile.
/// Only one lock can be acquired at the same time, which means
/// that there is little need for manually unlocking.
/// The current lock will expire if you exit or acquire a new one.
class ProfileLocker class ProfileLocker
{ {
private: private:
ProfileLocker()=delete; ProfileLocker()=delete;
public: public:
/// Checks if a profile is currently locked by *another* instance
/// If we own the lock, we consider it lockable
/// There is no guarantee that the result will still be valid by the
/// time it is returned, this is provided on a best effort basis
static bool isLockable(QString profile); static bool isLockable(QString profile);
/// Tries to acquire the lock on a profile, will not block
/// Returns true if we already own the lock
static bool lock(QString profile); static bool lock(QString profile);
/// Releases the lock on the current profile
static void unlock(); static void unlock();
/// Returns true if we're currently holding a lock
static bool hasLock(); static bool hasLock();
/// Return the name of the currently loaded profile, a null string if there is none
static QString getCurLockName(); static QString getCurLockName();
/// Check that we actually own the lock
/// In case the file was deleted on disk, restore it
/// If we can't get a lock, exit qTox immediately
/// If we never had a lock in the firt place, exit immediately
static void assertLock(); static void assertLock();
private: private:
static QString lockPathFromName(const QString& name); static QString lockPathFromName(const QString& name);
static void deathByBrokenLock(); ///< Print an error then exit immediately static void deathByBrokenLock();
private: private:
static std::unique_ptr<QLockFile> lockfile; static std::unique_ptr<QLockFile> lockfile;

View File

@ -17,9 +17,14 @@
along with qTox. If not, see <http://www.gnu.org/licenses/>. along with qTox. If not, see <http://www.gnu.org/licenses/>.
*/ */
#include "src/persistence/serialize.h" #include "src/persistence/serialize.h"
/**
@file serialize.cpp
Most of functions in this file are unsafe unless otherwise specified.
@warning Do not use them on untrusted data (e.g. check a signature first).
*/
QByteArray doubleToData(double num) QByteArray doubleToData(double num)
{ {
union union

View File

@ -25,9 +25,6 @@
#include <QByteArray> #include <QByteArray>
#include <QString> #include <QString>
/// Most of those functions are unsafe unless otherwise specified
/// Do not use them on untrusted data (e.g. check a signature first)
QByteArray doubleToData(double num); QByteArray doubleToData(double num);
QByteArray floatToData(float num); QByteArray floatToData(float num);
float dataToFloat(QByteArray data); float dataToFloat(QByteArray data);

View File

@ -49,6 +49,17 @@
#define SHOW_SYSTEM_TRAY_DEFAULT (bool) true #define SHOW_SYSTEM_TRAY_DEFAULT (bool) true
/**
@var QHash<QString, QByteArray> Settings::widgetSettings
@brief Assume all widgets have unique names
@warning Don't use it to save every single thing you want to save, use it
for some general purpose widgets, such as MainWindows or Splitters,
which have widget->saveX() and widget->loadX() methods.
@var QString Settings::toxmeInfo
@brief Toxme info like name@server
*/
const QString Settings::globalSettingsFile = "qtox.ini"; const QString Settings::globalSettingsFile = "qtox.ini";
Settings* Settings::settings{nullptr}; Settings* Settings::settings{nullptr};
QMutex Settings::bigLock{QMutex::Recursive}; QMutex Settings::bigLock{QMutex::Recursive};
@ -73,6 +84,9 @@ Settings::~Settings()
delete settingsThread; delete settingsThread;
} }
/**
@brief Returns the singleton instance.
*/
Settings& Settings::getInstance() Settings& Settings::getInstance()
{ {
if (!settings) if (!settings)
@ -379,6 +393,9 @@ void Settings::loadPersonal(Profile* profile)
ps.endGroup(); ps.endGroup();
} }
/**
@brief Asynchronous, saves the global settings.
*/
void Settings::saveGlobal() void Settings::saveGlobal()
{ {
if (QThread::currentThread() != settingsThread) if (QThread::currentThread() != settingsThread)
@ -498,11 +515,18 @@ void Settings::saveGlobal()
s.endGroup(); s.endGroup();
} }
/**
@brief Asynchronous, saves the current profile.
*/
void Settings::savePersonal() void Settings::savePersonal()
{ {
savePersonal(Nexus::getProfile()); savePersonal(Nexus::getProfile());
} }
/**
@brief Asynchronous, saves the profile.
@param profile Profile to save.
*/
void Settings::savePersonal(Profile* profile) void Settings::savePersonal(Profile* profile)
{ {
if (!profile) if (!profile)
@ -600,6 +624,10 @@ uint32_t Settings::makeProfileId(const QString& profile)
return dwords[0] ^ dwords[1] ^ dwords[2] ^ dwords[3]; return dwords[0] ^ dwords[1] ^ dwords[2] ^ dwords[3];
} }
/**
@brief Get path to directory, where the settings files are stored.
@return Path to settings directory, ends with a directory separator.
*/
QString Settings::getSettingsDirPath() QString Settings::getSettingsDirPath()
{ {
QMutexLocker locker{&bigLock}; QMutexLocker locker{&bigLock};
@ -619,6 +647,10 @@ QString Settings::getSettingsDirPath()
#endif #endif
} }
/**
@brief Get path to directory, where the application data are stored.
@return Path to application data, ends with a directory separator.
*/
QString Settings::getAppDataDirPath() QString Settings::getAppDataDirPath()
{ {
QMutexLocker locker{&bigLock}; QMutexLocker locker{&bigLock};
@ -640,6 +672,10 @@ QString Settings::getAppDataDirPath()
#endif #endif
} }
/**
@brief Get path to directory, where the application cache are stored.
@return Path to application cache, ends with a directory separator.
*/
QString Settings::getAppCacheDirPath() QString Settings::getAppCacheDirPath()
{ {
QMutexLocker locker{&bigLock}; QMutexLocker locker{&bigLock};
@ -1846,6 +1882,11 @@ void Settings::setAutoLogin(bool state)
autoLogin = state; autoLogin = state;
} }
/**
@brief Write a default personal .ini settings file for a profile.
@param basename Filename without extension to save settings.
@example If basename is "profile", settings will be saved in profile.ini
*/
void Settings::createPersonal(QString basename) void Settings::createPersonal(QString basename)
{ {
QString path = getSettingsDirPath() + QDir::separator() + basename + ".ini"; QString path = getSettingsDirPath() + QDir::separator() + basename + ".ini";
@ -1862,6 +1903,9 @@ void Settings::createPersonal(QString basename)
ps.endGroup(); ps.endGroup();
} }
/**
@brief Creates a path to the settings dir, if it doesn't already exist
*/
void Settings::createSettingsDir() void Settings::createSettingsDir()
{ {
QString dir = Settings::getSettingsDirPath(); QString dir = Settings::getSettingsDirPath();
@ -1870,6 +1914,9 @@ void Settings::createSettingsDir()
qCritical() << "Error while creating directory " << dir; qCritical() << "Error while creating directory " << dir;
} }
/**
@brief Waits for all asynchronous operations to complete
*/
void Settings::sync() void Settings::sync()
{ {
if (QThread::currentThread() != settingsThread) if (QThread::currentThread() != settingsThread)

View File

@ -44,15 +44,15 @@ class Settings : public QObject
public: public:
static Settings& getInstance(); static Settings& getInstance();
static void destroyInstance(); static void destroyInstance();
QString getSettingsDirPath(); ///< The returned path ends with a directory separator QString getSettingsDirPath();
QString getAppDataDirPath(); ///< The returned path ends with a directory separator QString getAppDataDirPath();
QString getAppCacheDirPath(); ///< The returned path ends with a directory separator QString getAppCacheDirPath();
void createSettingsDir(); ///< Creates a path to the settings dir, if it doesn't already exist void createSettingsDir();
void createPersonal(QString basename); ///< Write a default personal .ini settings file for a profile void createPersonal(QString basename);
void savePersonal(); ///< Asynchronous, saves the current profile void savePersonal();
void savePersonal(Profile *profile); ///< Asynchronous void savePersonal(Profile *profile);
void loadGlobal(); void loadGlobal();
void loadPersonal(); void loadPersonal();
@ -67,8 +67,8 @@ public:
public slots: public slots:
void saveGlobal(); ///< Asynchronous void saveGlobal();
void sync(); ///< Waits for all asynchronous operations to complete void sync();
signals: signals:
void dhtServerListChanged(); void dhtServerListChanged();
@ -76,7 +76,6 @@ signals:
void emojiFontChanged(); void emojiFontChanged();
public: public:
// Getter/setters
const QList<DhtServer>& getDhtServerList() const; const QList<DhtServer>& getDhtServerList() const;
void setDhtServerList(const QList<DhtServer>& newDhtServerList); void setDhtServerList(const QList<DhtServer>& newDhtServerList);
@ -329,10 +328,6 @@ public:
void removeFriendRequest(int index); void removeFriendRequest(int index);
void readFriendRequest(int index); void readFriendRequest(int index);
// Assume all widgets have unique names
// Don't use it to save every single thing you want to save, use it
// for some general purpose widgets, such as MainWindows or Splitters,
// which have widget->saveX() and widget->loadX() methods.
QByteArray getWidgetData(const QString& uniqueName) const; QByteArray getWidgetData(const QString& uniqueName) const;
void setWidgetData(const QString& uniqueName, const QByteArray& data); void setWidgetData(const QString& uniqueName, const QByteArray& data);
@ -401,7 +396,7 @@ private:
uint32_t currentProfileId; uint32_t currentProfileId;
// Toxme Info // Toxme Info
QString toxmeInfo; // name@server QString toxmeInfo;
QString toxmeBio; QString toxmeBio;
bool toxmePriv; bool toxmePriv;
QString toxmePass; QString toxmePass;

View File

@ -28,8 +28,37 @@
#include <memory> #include <memory>
#include <cassert> #include <cassert>
/**
@class SettingsSerializer
@brief Serializes a QSettings's data in an (optionally) encrypted binary format.
SettingsSerializer can detect regular .ini files and serialized ones,
it will read both regular and serialized .ini, but only save in serialized format.
The file is encrypted with the current profile's password, if any.
The file is only written to disk if save() is called, the destructor does not save to disk
All member functions are reentrant, but not thread safe.
@enum SettingsSerializer::RecordTag
@var Value
Followed by a QString key then a QVariant value
@var GroupStart
Followed by a QString group name
@var ArrayStart
Followed by a QString array name and a vuint array size
@var ArrayValue
Followed by a vuint array index, a QString key then a QVariant value
@var ArrayEnd
Not followed by any data
*/
enum class RecordTag : uint8_t
{
};
using namespace std; using namespace std;
/**
@var static const char magic[];
@brief Little endian ASCII "QTOX" magic
*/
const char SettingsSerializer::magic[] = {0x51,0x54,0x4F,0x58}; const char SettingsSerializer::magic[] = {0x51,0x54,0x4F,0x58};
QDataStream& writeStream(QDataStream& dataStream, const SettingsSerializer::RecordTag& tag) QDataStream& writeStream(QDataStream& dataStream, const SettingsSerializer::RecordTag& tag)
@ -199,6 +228,11 @@ SettingsSerializer::Value* SettingsSerializer::findValue(const QString& key)
return const_cast<Value*>(const_cast<const SettingsSerializer*>(this)->findValue(key)); return const_cast<Value*>(const_cast<const SettingsSerializer*>(this)->findValue(key));
} }
/**
@brief Checks if the file is serialized settings.
@param filePath Path to file to check.
@return False on error, true otherwise.
*/
bool SettingsSerializer::isSerializedFormat(QString filePath) bool SettingsSerializer::isSerializedFormat(QString filePath)
{ {
QFile f(filePath); QFile f(filePath);
@ -210,6 +244,9 @@ bool SettingsSerializer::isSerializedFormat(QString filePath)
return !memcmp(fmagic, magic, 4) || tox_is_data_encrypted((uint8_t*)fmagic); return !memcmp(fmagic, magic, 4) || tox_is_data_encrypted((uint8_t*)fmagic);
} }
/**
@brief Loads the settings from file.
*/
void SettingsSerializer::load() void SettingsSerializer::load()
{ {
if (isSerializedFormat(path)) if (isSerializedFormat(path))
@ -218,6 +255,9 @@ void SettingsSerializer::load()
readIni(); readIni();
} }
/**
@brief Saves the current settings back to file
*/
void SettingsSerializer::save() void SettingsSerializer::save()
{ {
QSaveFile f(path); QSaveFile f(path);
@ -545,6 +585,11 @@ void SettingsSerializer::readIni()
group = array = -1; group = array = -1;
} }
/**
@brief Remove group.
@note The group must be empty.
@param group ID of group to remove.
*/
void SettingsSerializer::removeGroup(int group) void SettingsSerializer::removeGroup(int group)
{ {
assert(group<groups.size()); assert(group<groups.size());

View File

@ -25,21 +25,15 @@
#include <QString> #include <QString>
#include <QDataStream> #include <QDataStream>
/// Serializes a QSettings's data in an (optionally) encrypted binary format
/// SettingsSerializer can detect regular .ini files and serialized ones,
/// it will read both regular and serialized .ini, but only save in serialized format.
/// The file is encrypted with the current profile's password, if any.
/// The file is only written to disk if save() is called, the destructor does not save to disk
/// All member functions are reentrant, but not thread safe.
class SettingsSerializer class SettingsSerializer
{ {
public: public:
SettingsSerializer(QString filePath, const QString &password=QString()); SettingsSerializer(QString filePath, const QString &password=QString());
static bool isSerializedFormat(QString filePath); ///< Check if the file is serialized settings. False on error. static bool isSerializedFormat(QString filePath);
void load(); ///< Loads the settings from file void load();
void save(); ///< Saves the current settings back to file void save();
void beginGroup(const QString &prefix); void beginGroup(const QString &prefix);
void endGroup(); void endGroup();
@ -55,15 +49,10 @@ public:
private: private:
enum class RecordTag : uint8_t enum class RecordTag : uint8_t
{ {
/// Followed by a QString key then a QVariant value
Value=0, Value=0,
/// Followed by a QString group name
GroupStart=1, GroupStart=1,
/// Followed by a QString array name and a vuint array size
ArrayStart=2, ArrayStart=2,
/// Followed by a vuint array index, a QString key then a QVariant value
ArrayValue=3, ArrayValue=3,
/// Not followed by any data
ArrayEnd=4, ArrayEnd=4,
}; };
friend QDataStream& writeStream(QDataStream& dataStream, const SettingsSerializer::RecordTag& tag); friend QDataStream& writeStream(QDataStream& dataStream, const SettingsSerializer::RecordTag& tag);
@ -94,7 +83,7 @@ private:
void readSerialized(); void readSerialized();
void readIni(); void readIni();
void removeValue(const QString& key); void removeValue(const QString& key);
void removeGroup(int group); ///< The group must be empty void removeGroup(int group);
void writePackedVariant(QDataStream& dataStream, const QVariant& v); void writePackedVariant(QDataStream& dataStream, const QVariant& v);
private: private:
@ -104,7 +93,7 @@ private:
QVector<QString> groups; QVector<QString> groups;
QVector<Array> arrays; QVector<Array> arrays;
QVector<Value> values; QVector<Value> values;
static const char magic[]; ///< Little endian ASCII "QTOX" magic static const char magic[];
}; };
#endif // SETTINGSSERIALIZER_H #endif // SETTINGSSERIALIZER_H

View File

@ -35,6 +35,23 @@
#include <QStringBuilder> #include <QStringBuilder>
#include <QtConcurrent/QtConcurrentRun> #include <QtConcurrent/QtConcurrentRun>
/**
@class SmileyPack
@brief Maps emoticons to smileys.
@var QHash<QString, QString> SmileyPack::filenameTable
@brief Matches an emoticon to its corresponding smiley ie. ":)" -> "happy.png"
@var QHash<QString, QIcon> SmileyPack::iconCache
@brief representation of a smiley ie. "happy.png" -> data
@var QList<QStringList> SmileyPack::emoticons
@brief {{ ":)", ":-)" }, {":(", ...}, ... }
@var QString SmileyPack::path
@brief directory containing the cfg and image files
*/
SmileyPack::SmileyPack() SmileyPack::SmileyPack()
{ {
loadingMutex.lock(); loadingMutex.lock();
@ -42,6 +59,9 @@ SmileyPack::SmileyPack()
connect(&Settings::getInstance(), &Settings::smileyPackChanged, this, &SmileyPack::onSmileyPackChanged); connect(&Settings::getInstance(), &Settings::smileyPackChanged, this, &SmileyPack::onSmileyPackChanged);
} }
/**
@brief Returns the singleton instance.
*/
SmileyPack& SmileyPack::getInstance() SmileyPack& SmileyPack::getInstance()
{ {
static SmileyPack smileyPack; static SmileyPack smileyPack;
@ -92,6 +112,12 @@ bool SmileyPack::isValid(const QString &filename)
return QFile(filename).exists(); return QFile(filename).exists();
} }
/**
@brief Load smile pack
@note The caller must lock loadingMutex and should run it in a thread
@param filename Filename of smilepack.
@return False if cannot open file, true otherwise.
*/
bool SmileyPack::load(const QString& filename) bool SmileyPack::load(const QString& filename)
{ {
// discard old data // discard old data

View File

@ -32,7 +32,6 @@
":/smileys", "./smileys", "/usr/share/qtox/smileys", "/usr/share/emoticons", "~/.kde4/share/emoticons", "~/.kde/share/emoticons" \ ":/smileys", "./smileys", "/usr/share/qtox/smileys", "/usr/share/emoticons", "~/.kde4/share/emoticons", "~/.kde/share/emoticons" \
} }
//maps emoticons to smileys
class SmileyPack : public QObject class SmileyPack : public QObject
{ {
Q_OBJECT Q_OBJECT
@ -54,14 +53,14 @@ private:
SmileyPack(SmileyPack&) = delete; SmileyPack(SmileyPack&) = delete;
SmileyPack& operator=(const SmileyPack&) = delete; SmileyPack& operator=(const SmileyPack&) = delete;
bool load(const QString& filename); ///< The caller must lock loadingMutex and should run it in a thread bool load(const QString& filename);
void cacheSmiley(const QString& name); void cacheSmiley(const QString& name);
QIcon getCachedSmiley(const QString& key); QIcon getCachedSmiley(const QString& key);
QHash<QString, QString> filenameTable; // matches an emoticon to its corresponding smiley ie. ":)" -> "happy.png" QHash<QString, QString> filenameTable;
QHash<QString, QIcon> iconCache; // representation of a smiley ie. "happy.png" -> data QHash<QString, QIcon> iconCache;
QList<QStringList> emoticons; // {{ ":)", ":-)" }, {":(", ...}, ... } QList<QStringList> emoticons;
QString path; // directory containing the cfg and image files QString path;
mutable QMutex loadingMutex; mutable QMutex loadingMutex;
}; };

View File

@ -33,6 +33,12 @@ bool toxSaveEventHandler(const QByteArray& eventData)
return true; return true;
} }
/**
@brief Import new profile.
@note Will wait until the core is ready first.
@param path Path to .tox file.
@return True if import success, false, otherwise.
*/
bool handleToxSave(const QString& path) bool handleToxSave(const QString& path)
{ {
Core* core = Core::getInstance(); Core* core = Core::getInstance();

View File

@ -23,7 +23,6 @@
class QString; class QString;
class QByteArray; class QByteArray;
/// Will wait until the core is ready first
bool handleToxSave(const QString& path); bool handleToxSave(const QString& path);
// Internals // Internals