diff --git a/src/core/core.cpp b/src/core/core.cpp index 7ec5b1f11..f5fcf370c 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -292,7 +292,7 @@ void Core::start() emit idSet(id); // tox core is already decrypted - if (Settings::getInstance().getEnableLogging() && Nexus::getProfile()->isEncrypted()) + if (Nexus::getProfile()->isEncrypted()) checkEncryptedHistory(); loadFriends(); @@ -329,6 +329,7 @@ void Core::start() toxav_register_audio_callback(toxav, playCallAudio, this); toxav_register_video_callback(toxav, playCallVideo, this); + HistoryKeeper::getInstance()->importAvatarToDatabase(getSelfId().toString().left(64)); QPixmap pic = Settings::getInstance().getSavedAvatar(getSelfId().toString()); if (!pic.isNull() && !pic.size().isEmpty()) { diff --git a/src/core/coreencryption.cpp b/src/core/coreencryption.cpp index f840672c2..8a182b972 100644 --- a/src/core/coreencryption.cpp +++ b/src/core/coreencryption.cpp @@ -144,7 +144,6 @@ void Core::checkEncryptedHistory() if (pw.isEmpty()) { Settings::getInstance().setEnableLogging(false); - HistoryKeeper::resetInstance(); return; } else diff --git a/src/friend.cpp b/src/friend.cpp index cb02a6d55..86ba69ab8 100644 --- a/src/friend.cpp +++ b/src/friend.cpp @@ -25,6 +25,7 @@ #include "widget/gui.h" #include "src/core/core.h" #include "src/persistence/settings.h" +#include "src/persistence/historykeeper.h" Friend::Friend(uint32_t FriendId, const ToxId &UserId) : userName{Core::getInstance()->getPeerName(UserId)}, @@ -39,6 +40,7 @@ Friend::Friend(uint32_t FriendId, const ToxId &UserId) widget = new FriendWidget(friendId, getDisplayedName()); chatForm = new ChatForm(this); + HistoryKeeper::getInstance()->importAvatarToDatabase(UserId.publicKey); } Friend::~Friend() diff --git a/src/persistence/db/plaindb.cpp b/src/persistence/db/plaindb.cpp index a6f6bc90f..1fec866c9 100644 --- a/src/persistence/db/plaindb.cpp +++ b/src/persistence/db/plaindb.cpp @@ -26,17 +26,21 @@ PlainDb::PlainDb(const QString &db_name, QList initList) { db = new QSqlDatabase(); *db = QSqlDatabase::addDatabase("QSQLITE"); + db->setConnectOptions("QSQLITE_ENABLE_SHARED_CACHE=1"); db->setDatabaseName(db_name); if (!db->open()) { qWarning() << QString("Can't open file: %1, history will not be saved!").arg(db_name); db->setDatabaseName(":memory:"); + db->setConnectOptions(); db->open(); } for (const QString &cmd : initList) + { db->exec(cmd); + } } PlainDb::~PlainDb() diff --git a/src/persistence/historykeeper.cpp b/src/persistence/historykeeper.cpp index 4836e2480..0c0a2bb36 100644 --- a/src/persistence/historykeeper.cpp +++ b/src/persistence/historykeeper.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include @@ -35,9 +36,11 @@ #include "src/persistence/db/encrypteddb.h" static HistoryKeeper *historyInstance = nullptr; +QMutex HistoryKeeper::historyMutex; HistoryKeeper *HistoryKeeper::getInstance() { + historyMutex.lock(); if (historyInstance == nullptr) { QList initLst; @@ -50,25 +53,23 @@ HistoryKeeper *HistoryKeeper::getInstance() QString path(":memory:"); GenericDdInterface *dbIntf; - if (Settings::getInstance().getEnableLogging()) + if (Nexus::getProfile()->isEncrypted()) { - if (Nexus::getProfile()->isEncrypted()) - { - path = getHistoryPath(); - dbIntf = new EncryptedDb(path, initLst); + path = getHistoryPath(); + dbIntf = new EncryptedDb(path, initLst); - historyInstance = new HistoryKeeper(dbIntf); - return historyInstance; - } - else - { - path = getHistoryPath(); - } + historyInstance = new HistoryKeeper(dbIntf); + return historyInstance; + } + else + { + path = getHistoryPath(); } dbIntf = new PlainDb(path, initLst); historyInstance = new HistoryKeeper(dbIntf); } + historyMutex.unlock(); return historyInstance; } @@ -78,7 +79,7 @@ bool HistoryKeeper::checkPassword(const TOX_PASS_KEY &passkey, int encrypted) if (!Settings::getInstance().getEnableLogging() && (encrypted == -1)) return true; - if ((encrypted == 1) || (encrypted == -1 && Nexus::getProfile()->isEncrypted())) + if ((encrypted == 1) || (encrypted == -1)) return EncryptedDb::check(passkey, getHistoryPath(Nexus::getProfile()->getName(), encrypted)); return true; @@ -95,10 +96,12 @@ HistoryKeeper::HistoryKeeper(GenericDdInterface *db_) : name -- chat's name (for user to user conversation it is opposite user public key) ctype -- chat type, reserved for group chats - alisases: + aliases: * user_id -> id map - id -- auto-incrementing number - name -- user's public key + id -- auto-incrementing number + user_id -- user's public key + av_hash -- hash of user's avatar + avatar -- user's avatar history: id -- auto-incrementing number @@ -126,14 +129,33 @@ HistoryKeeper::HistoryKeeper(GenericDdInterface *db_) : } } //check table stuct - ans = db->exec("PRAGMA table_info(\"history\")"); + ans = db->exec("PRAGMA table_info (\"history\")"); ans.seek(5); if (!ans.value(1).toString().contains("alias")) { //add collum in table db->exec("ALTER TABLE history ADD COLUMN alias TEXT"); + qDebug() << "Struct DB updated: Added column alias in table history."; } + ans.clear(); + ans = db->exec("PRAGMA table_info('aliases')"); + ans.seek(2); + if (!ans.value(1).toString().contains("av_hash")) + { + //add collum in table + db->exec("ALTER TABLE aliases ADD COLUMN av_hash BLOB"); + qDebug() << "Struct DB updated: Added column av_hash in table aliases."; + } + + ans.seek(3); + if (!ans.value(1).toString().contains("avatar")) + { + //add collum in table + needImport = true; + db->exec("ALTER TABLE aliases ADD COLUMN avatar BLOB"); + qDebug() << "Struct DB updated: Added column avatar in table aliases."; + } updateChatsID(); updateAliases(); @@ -146,6 +168,24 @@ HistoryKeeper::HistoryKeeper(GenericDdInterface *db_) : messageID = sqlAnswer.value(0).toLongLong(); } +void HistoryKeeper::importAvatarToDatabase(const QString& ownerId) +{ + if (needImport) + { + QString puth (Settings::getInstance().getSettingsDirPath() + + QString("avatars") + QDir::separator() + + ownerId +".png"); + qDebug() << QString("Try import avatar for: %1.").arg(ownerId); + getAliasID(ownerId); + if (QFile::exists(puth) && !hasAvatar(ownerId)) + { + QPixmap pic(puth); + saveAvatar(pic,ownerId); + qDebug() << QString("Import avatar for: %1.").arg(ownerId); + } + } +} + HistoryKeeper::~HistoryKeeper() { delete db; @@ -455,21 +495,84 @@ bool HistoryKeeper::isFileExist() return file.exists(); } -bool HistoryKeeper::removeHistory(int encrypted) +void HistoryKeeper::removeHistory() { - resetInstance(); - - QString path = getHistoryPath(QString(), encrypted); - QFile DbFile(path); - return DbFile.remove(); + db->exec("BEGIN TRANSACTION;"); + db->exec("DELETE FROM sent_status;"); + db->exec("DELETE FROM history;"); + db->exec("COMMIT TRANSACTION;"); } -QList HistoryKeeper::exportMessagesDeleteFile(int encrypted) +QList HistoryKeeper::exportMessagesDeleteFile() { auto msgs = getInstance()->exportMessages(); qDebug() << "Messages exported"; - if (!removeHistory(encrypted)) - qWarning() << "couldn't delete old log file!"; + getInstance()->removeHistory(); return msgs; } + +void HistoryKeeper::removeAvatar(const QString& ownerId) +{ + QSqlQuery query; + query.prepare("UPDATE aliases SET avatar=NULL, av_hash=NULL WHERE user_id = (:id)"); + query.bindValue(":id", ownerId.left(64)); + query.exec(); +} + +bool HistoryKeeper::hasAvatar(const QString& ownerId) +{ + QSqlQuery sqlAnswer = db->exec(QString("SELECT avatar FROM aliases WHERE user_id= '%1'").arg(ownerId.left(64))); + if (sqlAnswer.first() && sqlAnswer.value(0).toByteArray()!=NULL) + { + return true; + } + return false; +} + +void HistoryKeeper::saveAvatar(QPixmap& pic, const QString& ownerId) +{ + QByteArray bArray; + QBuffer buffer(&bArray); + buffer.open(QIODevice::WriteOnly); + pic.save(&buffer, "PNG"); + + QSqlQuery query; + query.prepare("UPDATE aliases SET avatar=:image WHERE user_id = (:id)"); + query.bindValue(":image", bArray); + query.bindValue(":id", ownerId.left(64)); + query.exec(); +} + +QPixmap HistoryKeeper::getSavedAvatar(const QString &ownerId) +{ + QByteArray bArray; + QPixmap pixmap = QPixmap(); + QSqlQuery sqlAnswer = db->exec(QString("SELECT avatar FROM aliases WHERE user_id= '%1'").arg(ownerId.left(64))); + if (sqlAnswer.first()) + { + bArray = sqlAnswer.value(0).toByteArray(); + pixmap.loadFromData(bArray); + } + return pixmap; +} + +void HistoryKeeper::saveAvatarHash(const QByteArray& hash, const QString& ownerId) +{ + QSqlQuery query; + query.prepare("UPDATE aliases SET av_hash=:hash WHERE user_id = (:id)"); + query.bindValue(":hash", QString(hash.toBase64())); + query.bindValue(":id", ownerId.left(64)); + query.exec(); +} + +QByteArray HistoryKeeper::getAvatarHash(const QString& ownerId) +{ + QByteArray bArray; + QSqlQuery sqlAnswer = db->exec(QString("SELECT avatar FROM aliases WHERE user_id= '%1'").arg(ownerId.left(64))); + if (sqlAnswer.first()) + { + bArray = sqlAnswer.value(0).toByteArray(); + } + return bArray; +} diff --git a/src/persistence/historykeeper.h b/src/persistence/historykeeper.h index 96846028c..bc1c0a6fd 100644 --- a/src/persistence/historykeeper.h +++ b/src/persistence/historykeeper.h @@ -23,6 +23,8 @@ #include #include #include +#include +#include #include class GenericDdInterface; @@ -32,6 +34,7 @@ class HistoryKeeper { public: enum ChatType {ctSingle = 0, ctGroup}; + static QMutex historyMutex; struct HistMessage { @@ -56,8 +59,8 @@ public: static bool checkPassword(const TOX_PASS_KEY& passkey, int encrypted = -1); static bool isFileExist(); static void renameHistory(QString from, QString to); - static bool removeHistory(int encrypted = -1); - static QList exportMessagesDeleteFile(int encrypted = -1); + void removeHistory(); + static QList exportMessagesDeleteFile(); void removeFriendHistory(const QString& chat); qint64 addChatEntry(const QString& chat, const QString& message, const QString& sender, const QDateTime &dt, bool isSent, QString dispName); @@ -71,6 +74,17 @@ public: void setSyncType(Db::syncType sType); + void saveAvatar(QPixmap& pic, const QString& ownerId); + QPixmap getSavedAvatar(const QString &ownerId); + + void saveAvatarHash(const QByteArray& hash, const QString& ownerId); + QByteArray getAvatarHash(const QString& ownerId); + + void removeAvatar(const QString& ownerId); + bool hasAvatar(const QString& ownerId); + + void importAvatarToDatabase(const QString& ownerId); // may be deleted after all move to new db structure + private: HistoryKeeper(GenericDdInterface *db_); HistoryKeeper(HistoryKeeper &hk) = delete; @@ -85,7 +99,7 @@ private: QList generateAddChatEntryCmd(const QString& chat, const QString& message, const QString& sender, const QDateTime &dt, bool isSent, QString dispName); ChatType convertToChatType(int); - + bool needImport = false; // must be deleted with "importAvatarToDatabase" GenericDdInterface *db; QMap aliases; QMap> chats; diff --git a/src/persistence/settings.cpp b/src/persistence/settings.cpp index f452643ef..54f0092e8 100644 --- a/src/persistence/settings.cpp +++ b/src/persistence/settings.cpp @@ -26,6 +26,7 @@ #include "src/widget/gui.h" #include "src/persistence/profilelocker.h" #include "src/persistence/settingsserializer.h" +#include "src/persistence/historykeeper.h" #include "src/nexus.h" #include "src/persistence/profile.h" #ifdef QTOX_PLATFORM_EXT @@ -533,59 +534,22 @@ QString Settings::getSettingsDirPath() QPixmap Settings::getSavedAvatar(const QString &ownerId) { - QDir dir(getSettingsDirPath()); - QString filePath = dir.filePath("avatars/"+ownerId.left(64)+".png"); - QFileInfo info(filePath); - QPixmap pic; - if (!info.exists()) - { - QString filePath = dir.filePath("avatar_"+ownerId.left(64)); - if (!QFileInfo(filePath).exists()) // try without truncation, for old self avatars - filePath = dir.filePath("avatar_"+ownerId); - - pic.load(filePath); - saveAvatar(pic, ownerId); - QFile::remove(filePath); - } - else - { - pic.load(filePath); - } - return pic; + return HistoryKeeper::getInstance()->getSavedAvatar(ownerId); } void Settings::saveAvatar(QPixmap& pic, const QString& ownerId) { - QDir dir(getSettingsDirPath()); - dir.mkdir("avatars/"); - // ignore nospam (good idea, and also the addFriend funcs which call getAvatar don't have it) - QString filePath = dir.filePath("avatars/"+ownerId.left(64)+".png"); - pic.save(filePath, "png"); + HistoryKeeper::getInstance()->saveAvatar(pic,ownerId); } void Settings::saveAvatarHash(const QByteArray& hash, const QString& ownerId) { - QDir dir(getSettingsDirPath()); - dir.mkdir("avatars/"); - QFile file(dir.filePath("avatars/"+ownerId.left(64)+".hash")); - if (!file.open(QIODevice::WriteOnly)) - return; - - file.write(hash); - file.close(); + HistoryKeeper::getInstance()->saveAvatarHash(hash,ownerId); } QByteArray Settings::getAvatarHash(const QString& ownerId) { - QDir dir(getSettingsDirPath()); - dir.mkdir("avatars/"); - QFile file(dir.filePath("avatars/"+ownerId.left(64)+".hash")); - if (!file.open(QIODevice::ReadOnly)) - return QByteArray(); - - QByteArray out = file.readAll(); - file.close(); - return out; + return HistoryKeeper::getInstance()->getAvatarHash(ownerId); } const QList& Settings::getDhtServerList() const diff --git a/src/widget/form/profileform.cpp b/src/widget/form/profileform.cpp index afc0f0cc7..f4cd55774 100644 --- a/src/widget/form/profileform.cpp +++ b/src/widget/form/profileform.cpp @@ -184,18 +184,13 @@ void ProfileForm::showProfilePictureContextMenu(const QPoint &point) QPoint pos = profilePicture->mapToGlobal(point); QMenu contextMenu; - QAction *removeAction = contextMenu.addAction(tr("Remove")); + QAction *removeAction = contextMenu.addAction(style()->standardIcon(QStyle::SP_DialogCancelButton), tr("Remove")); QAction *selectedItem = contextMenu.exec(pos); if (selectedItem == removeAction) { QString selfPubKey = Core::getInstance()->getSelfId().publicKey; - if (!QFile::remove(Settings::getInstance().getSettingsDirPath()+"avatars/"+selfPubKey.left(64)+".png")) - { - GUI::showError(tr("Error"), tr("Could not remove avatar.")); - return; - } - + HistoryKeeper::getInstance()->removeAvatar(selfPubKey); Core::getInstance()->setAvatar({}); } } diff --git a/src/widget/form/settings/privacyform.cpp b/src/widget/form/settings/privacyform.cpp index fca8d1c12..8a337939d 100644 --- a/src/widget/form/settings/privacyform.cpp +++ b/src/widget/form/settings/privacyform.cpp @@ -55,7 +55,6 @@ PrivacyForm::~PrivacyForm() void PrivacyForm::onEnableLoggingUpdated() { Settings::getInstance().setEnableLogging(bodyUI->cbKeepHistory->isChecked()); - HistoryKeeper::resetInstance(); Widget::getInstance()->clearAllReceipts(); if (!bodyUI->cbKeepHistory->isChecked()) { @@ -64,10 +63,7 @@ void PrivacyForm::onEnableLoggingUpdated() QMessageBox::Yes|QMessageBox::No); if (dialogDelHistory == QMessageBox::Yes) { - if (!HistoryKeeper::removeHistory()) - { - QMessageBox::critical(0, tr("Error"), tr("Could not delete chat history"), QMessageBox::Ok); - } + HistoryKeeper::getInstance()->removeHistory(); } } }