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:
parent
1552bfb114
commit
1c547fc73f
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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())
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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; }
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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());
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
|
@ -59,7 +65,7 @@ bool handleToxSave(const QString& path)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString profilePath = Settings::getInstance().getSettingsDirPath()+profile+Core::TOX_EXT;
|
QString profilePath = Settings::getInstance().getSettingsDirPath() + profile + Core::TOX_EXT;
|
||||||
|
|
||||||
if (QFileInfo(profilePath).exists() && !GUI::askQuestion(QObject::tr("Profile already exists", "import confirm title"),
|
if (QFileInfo(profilePath).exists() && !GUI::askQuestion(QObject::tr("Profile already exists", "import confirm title"),
|
||||||
QObject::tr("A profile named \"%1\" already exists. Do you want to erase it?", "import confirm text").arg(profile)))
|
QObject::tr("A profile named \"%1\" already exists. Do you want to erase it?", "import confirm text").arg(profile)))
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue
Block a user