diff --git a/INSTALL.md b/INSTALL.md index 5d9fd66c2..07e3dd503 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -49,6 +49,7 @@ | OpenAL Soft | >= 1.16.0 | | | filter_audio | most recent | | | qrencode | >= 3.0.3 | | +| sqlcipher | >= 3.2.0 | | | libXScrnSaver | >= 1.2 | | @@ -184,7 +185,7 @@ sudo pacman -S --needed base-devel qt5 openal libxss qrencode ffmpeg If you use stable, you have to add backports to your `sources.list` for FFmpeg and others. Instructions here: http://backports.debian.org/Instructions/ ```bash -sudo apt-get install build-essential qt5-qmake qt5-default qttools5-dev-tools libqt5opengl5-dev libqt5svg5-dev libopenal-dev libxss-dev qrencode libqrencode-dev libglib2.0-dev libgdk-pixbuf2.0-dev libgtk2.0-dev ffmpeg +sudo apt-get install build-essential qt5-qmake qt5-default qttools5-dev-tools libqt5opengl5-dev libqt5svg5-dev libopenal-dev libxss-dev qrencode libqrencode-dev libglib2.0-dev libgdk-pixbuf2.0-dev libgtk2.0-dev ffmpeg libsqlcipher-dev ``` @@ -215,7 +216,7 @@ List of all the ``qTox`` dependencies and their SlackBuilds can be found here: h **This means that you have to compile FFmpeg yourself, otherwise compiling qTox will fail.** ```bash -sudo apt-get install build-essential qt5-qmake qt5-default qttools5-dev-tools libqt5opengl5-dev libqt5svg5-dev libopenal-dev libxss-dev qrencode libqrencode-dev libglib2.0-dev libgdk-pixbuf2.0-dev libgtk2.0-dev +sudo apt-get install build-essential qt5-qmake qt5-default qttools5-dev-tools libqt5opengl5-dev libqt5svg5-dev libopenal-dev libxss-dev qrencode libqrencode-dev libglib2.0-dev libgdk-pixbuf2.0-dev libgtk2.0-dev libsqlcipher-dev ``` **Go to [FFmpeg](#ffmpeg) section to compile it.** @@ -224,7 +225,7 @@ sudo apt-get install build-essential qt5-qmake qt5-default qttools5-dev-tools li #### Ubuntu >=15.04: ```bash -sudo apt-get install build-essential qt5-qmake qt5-default qttools5-dev-tools libqt5opengl5-dev libqt5svg5-dev libopenal-dev libxss-dev qrencode libqrencode-dev libavutil-ffmpeg-dev libswresample-ffmpeg-dev libavcodec-ffmpeg-dev libswscale-ffmpeg-dev libavfilter-ffmpeg-dev libavdevice-ffmpeg-dev libglib2.0-dev libgdk-pixbuf2.0-dev libgtk2.0-dev +sudo apt-get install build-essential qt5-qmake qt5-default qttools5-dev-tools libqt5opengl5-dev libqt5svg5-dev libopenal-dev libxss-dev qrencode libqrencode-dev libavutil-ffmpeg-dev libswresample-ffmpeg-dev libavcodec-ffmpeg-dev libswscale-ffmpeg-dev libavfilter-ffmpeg-dev libavdevice-ffmpeg-dev libglib2.0-dev libgdk-pixbuf2.0-dev libgtk2.0-dev libsqlcipher-dev ``` diff --git a/qtox.pro b/qtox.pro index 2c8b487da..2cc0f86ec 100644 --- a/qtox.pro +++ b/qtox.pro @@ -143,8 +143,8 @@ win32 { RC_FILE = windows/qtox.rc LIBS += -L$$PWD/libs/lib -ltoxav -ltoxcore -ltoxencryptsave -ltoxdns -lsodium -lvpx -lpthread LIBS += -L$$PWD/libs/lib -lavdevice -lavformat -lavcodec -lavutil -lswscale -lOpenAL32 -lopus + LIBS += -lqrencode -lsqlcipher -lcrypto LIBS += -lopengl32 -lole32 -loleaut32 -lvfw32 -lws2_32 -liphlpapi -lgdi32 -lshlwapi -luuid - LIBS += -lqrencode LIBS += -lstrmiids # For DirectShow contains(DEFINES, QTOX_FILTER_AUDIO) { contains(STATICPKG, YES) { @@ -160,7 +160,7 @@ win32 { QMAKE_INFO_PLIST = osx/info.plist QMAKE_MACOSX_DEPLOYMENT_TARGET = 10.7 LIBS += -L$$PWD/libs/lib/ -ltoxcore -ltoxav -ltoxencryptsave -ltoxdns -lsodium -lvpx -lopus -framework OpenAL -lavformat -lavdevice -lavcodec -lavutil -lswscale -mmacosx-version-min=10.7 - LIBS += -lqrencode + LIBS += -lqrencode -lsqlcipher contains(DEFINES, QTOX_PLATFORM_EXT) { LIBS += -framework IOKit -framework CoreFoundation } contains(DEFINES, QTOX_FILTER_AUDIO) { LIBS += -lfilteraudio } } else { @@ -181,10 +181,10 @@ win32 { LIBS += -L$$PWD/libs/lib/ -lopus -lvpx -lopenal -Wl,-Bstatic -ltoxcore -ltoxav -ltoxencryptsave -ltoxdns -lsodium -lavformat -lavdevice -lavcodec -lavutil -lswscale -lz -Wl,-Bdynamic LIBS += -Wl,-Bstatic -ljpeg -ltiff -lpng -ljasper -lIlmImf -lIlmThread -lIex -ldc1394 -lraw1394 -lHalf -lz -llzma -ljbig LIBS += -Wl,-Bdynamic -lv4l1 -lv4l2 -lavformat -lavcodec -lavutil -lswscale -lusb-1.0 - LIBS += -lqrencode + LIBS += -lqrencode -lsqlcipher } else { LIBS += -L$$PWD/libs/lib/ -ltoxcore -ltoxav -ltoxencryptsave -ltoxdns -lvpx -lsodium -lopenal -lavformat -lavdevice -lavcodec -lavutil -lswscale - LIBS += -lqrencode + LIBS += -lqrencode -lsqlcipher } contains(DEFINES, QTOX_PLATFORM_EXT) { @@ -515,7 +515,9 @@ SOURCES += \ src/widget/tool/removefrienddialog.cpp \ src/video/groupnetcamview.cpp \ src/core/toxcall.cpp \ - src/widget/about/aboutuser.cpp + src/widget/about/aboutuser.cpp \ + src/persistence/db/rawdatabase.cpp \ + src/persistence/history.cpp HEADERS += \ src/audio/audio.h \ @@ -569,4 +571,6 @@ HEADERS += \ src/video/groupnetcamview.h \ src/core/indexedlist.h \ src/core/toxcall.h \ - src/widget/about/aboutuser.h + src/widget/about/aboutuser.h \ + src/persistence/db/rawdatabase.h \ + src/persistence/history.h diff --git a/res/settings.ini b/res/settings.ini index c0a1dddb1..8fa005318 100644 --- a/res/settings.ini +++ b/res/settings.ini @@ -1,5 +1,5 @@ [DHT%20Server] -dhtServerList\size=14 +dhtServerList\size=11 dhtServerList\1\name=sonOfRa dhtServerList\1\userId=04119E835DF3E78BACF0F84235B300546AF8B936F035185E2A8E9E0A67C8924F dhtServerList\1\address=144.76.60.215 @@ -8,13 +8,13 @@ dhtServerList\2\name=stal dhtServerList\2\userId=A09162D68618E742FFBCA1C2C70385E6679604B2D80EA6E84AD0996A1AC8A074 dhtServerList\2\address=23.226.230.47 dhtServerList\2\port=33445 -dhtServerList\3\name=Munrek -dhtServerList\3\userId=E398A69646B8CEACA9F0B84F553726C1C49270558C57DF5F3C368F05A7D71354 -dhtServerList\3\address=195.154.119.113 +dhtServerList\3\name=WIeschie +dhtServerList\3\userId=6A4D0607A296838434A6A7DDF99F50EF9D60A2C510BBF31FE538A25CB6B4652F +dhtServerList\3\address=192.99.168.140 dhtServerList\3\port=33445 dhtServerList\4\name=nurupo dhtServerList\4\userId=F404ABAA1C99A9D37D61AB54898F56793E1DEF8BD46B1038B9D822E8460FAB67 -dhtServerList\4\address=192.210.149.121 +dhtServerList\4\address=198.46.138.44 dhtServerList\4\port=33445 dhtServerList\5\name=Impyy dhtServerList\5\userId=788236D34978D1D5BD822F0A5BEBD2C53C64CC31CD3149350EE27D4D9A2F9B6B @@ -32,27 +32,15 @@ dhtServerList\8\name=Busindre dhtServerList\8\userId=A179B09749AC826FF01F37A9613F6B57118AE014D4196A0E1105A98F93A54702 dhtServerList\8\address=205.185.116.116 dhtServerList\8\port=33445 -dhtServerList\9\name=Busindre +dhtServerList\9\name=Busindre dhtServerList\9\userId=1D5A5F2F5D6233058BF0259B09622FB40B482E4FA0931EB8FD3AB8E7BF7DAF6F dhtServerList\9\address=198.98.51.198 -dhtServerList\9\port=33445 -dhtServerList\10\name=ray65536 -dhtServerList\10\userId=8E7D0B859922EF569298B4D261A8CCB5FEA14FB91ED412A7603A585A25698832 -dhtServerList\10\address=108.61.165.198 +dhtServerList\9\port=443 +dhtServerList\10\name=fluke571 +dhtServerList\10\userId=3CEE1F054081E7A011234883BC4FC39F661A55B73637A5AC293DDF1251D9432B +dhtServerList\10\address=194.249.212.109 dhtServerList\10\port=33445 -dhtServerList\11\name=Kr9r0x -dhtServerList\11\userId=C4CEB8C7AC607C6B374E2E782B3C00EA3A63B80D4910B8649CCACDD19F260819 -dhtServerList\11\address=212.71.252.109 +dhtServerList\11\name=MAH69K +dhtServerList\11\userId=DA4E4ED4B697F2E9B000EEFE3A34B554ACD3F45F5C96EAEA2516DD7FF9AF7B43 +dhtServerList\11\address=185.25.116.107 dhtServerList\11\port=33445 -dhtServerList\12\name=fluke571 -dhtServerList\12\userId=3CEE1F054081E7A011234883BC4FC39F661A55B73637A5AC293DDF1251D9432B -dhtServerList\12\address=194.249.212.109 -dhtServerList\12\port=33445 -dhtServerList\13\name=MAH69K -dhtServerList\13\userId=DA4E4ED4B697F2E9B000EEFE3A34B554ACD3F45F5C96EAEA2516DD7FF9AF7B43 -dhtServerList\13\address=185.25.116.107 -dhtServerList\13\port=33445 -dhtServerList\14\name=WIeschie -dhtServerList\14\userId=6A4D0607A296838434A6A7DDF99F50EF9D60A2C510BBF31FE538A25CB6B4652F -dhtServerList\14\address=192.99.168.140 -dhtServerList\14\port=33445 diff --git a/src/audio/audio.cpp b/src/audio/audio.cpp index 859f68666..84bb94e52 100644 --- a/src/audio/audio.cpp +++ b/src/audio/audio.cpp @@ -65,11 +65,7 @@ Audio::Audio() , inputVolume(1.0) , alMainSource(0) , alContext(nullptr) - , timer(new QTimer(this)) { - timer->setSingleShot(true); - connect(timer, &QTimer::timeout, this, &Audio::closeOutput); - audioThread->setObjectName("qTox Audio"); connect(audioThread, &QThread::finished, audioThread, &QThread::deleteLater); } @@ -201,7 +197,24 @@ void Audio::openInput(const QString& inDevDescr) const uint32_t chnls = AUDIO_CHANNELS; const ALCsizei bufSize = (frameDuration * sampleRate * 4) / 1000 * chnls; if (inDevDescr.isEmpty()) - alInDev = alcCaptureOpenDevice(nullptr, sampleRate, stereoFlag, bufSize); + { + const ALchar *pDeviceList = alcGetString(NULL, ALC_CAPTURE_DEVICE_SPECIFIER); + if (pDeviceList) + { + alInDev = alcCaptureOpenDevice(pDeviceList, sampleRate, stereoFlag, bufSize); + int len = strlen(pDeviceList); +#ifdef Q_OS_WIN + QString inDev = QString::fromUtf8(pDeviceList, len); +#else + QString inDev = QString::fromLocal8Bit(pDeviceList, len); +#endif + Settings::getInstance().setInDev(inDev); + } + else + { + alInDev = alcCaptureOpenDevice(nullptr, sampleRate, stereoFlag, bufSize); + } + } else alInDev = alcCaptureOpenDevice(inDevDescr.toStdString().c_str(), sampleRate, stereoFlag, bufSize); @@ -386,11 +399,6 @@ void Audio::playMono16Sound(const QByteArray& data) ALint frequency; alGetBufferi(buffer, AL_FREQUENCY, &frequency); - qreal duration = (lengthInSamples / static_cast(frequency)) * 1000; - int remaining = timer->interval(); - - if (duration > remaining) - timer->start(duration); alDeleteBuffers(1, &buffer); } diff --git a/src/core/core.cpp b/src/core/core.cpp index 0e2ad7f70..0f18b63f5 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -25,7 +25,6 @@ #include "src/core/coreav.h" #include "src/persistence/settings.h" #include "src/widget/gui.h" -#include "src/persistence/historykeeper.h" #include "src/audio/audio.h" #include "src/persistence/profilelocker.h" #include "src/net/avatarbroadcaster.h" @@ -129,6 +128,9 @@ void Core::makeTox(QByteArray savedata) bool enableIPv6 = Settings::getInstance().getEnableIPv6(); bool forceTCP = Settings::getInstance().getForceTCP(); ProxyType proxyType = Settings::getInstance().getProxyType(); + int proxyPort = Settings::getInstance().getProxyPort(); + QString proxyAddr = Settings::getInstance().getProxyAddr(); + QByteArray proxyAddrData = proxyAddr.toUtf8(); if (enableIPv6) qDebug() << "Core starting with IPv6 enabled"; @@ -152,9 +154,6 @@ void Core::makeTox(QByteArray savedata) if (proxyType != ProxyType::ptNone) { - QString proxyAddr = Settings::getInstance().getProxyAddr(); - int proxyPort = Settings::getInstance().getProxyPort(); - if (proxyAddr.length() > 255) { qWarning() << "proxy address" << proxyAddr << "is too long"; @@ -168,11 +167,7 @@ void Core::makeTox(QByteArray savedata) else if (proxyType == ProxyType::ptHTTP) toxOptions.proxy_type = TOX_PROXY_TYPE_HTTP; - QByteArray proxyAddrData = proxyAddr.toUtf8(); - /// TODO: We're leaking a tiny amount of memory there, go fix that later - char* proxyAddrCopy = new char[proxyAddrData.size()+1]; - memcpy(proxyAddrCopy, proxyAddrData.data(), proxyAddrData.size()+1); - toxOptions.proxy_host = proxyAddrCopy; + toxOptions.proxy_host = proxyAddrData.data(); toxOptions.proxy_port = proxyPort; } } @@ -274,7 +269,8 @@ void Core::start() if (!id.isEmpty()) emit idSet(id); - // tox core is already decrypted + /// TODO: NOTE: This is a backwards compatibility check, + /// once most people have been upgraded away from the old HistoryKeeper, remove this if (Nexus::getProfile()->isEncrypted()) checkEncryptedHistory(); @@ -597,7 +593,9 @@ void Core::requestFriendship(const QString& friendAddress, const QString& messag if (message.length()) inviteStr = tr("/me offers friendship, \"%1\"").arg(message); - HistoryKeeper::getInstance()->addChatEntry(userId, inviteStr, getSelfId().publicKey, QDateTime::currentDateTime(), true, QString()); + Profile* profile = Nexus::getProfile(); + if (profile->isHistoryEnabled()) + profile->getHistory()->addNewMessage(userId, inviteStr, getSelfId().publicKey, QDateTime::currentDateTime(), true, QString()); emit friendAdded(friendId, userId); emit friendshipChanged(friendId); } diff --git a/src/core/coreencryption.cpp b/src/core/coreencryption.cpp index 8a182b972..50ed20f99 100644 --- a/src/core/coreencryption.cpp +++ b/src/core/coreencryption.cpp @@ -25,9 +25,9 @@ #include "src/widget/gui.h" #include "src/persistence/settings.h" #include "src/core/cstring.h" -#include "src/persistence/historykeeper.h" #include "src/nexus.h" #include "src/persistence/profile.h" +#include "src/persistence/historykeeper.h" #include #include #include @@ -75,6 +75,11 @@ QByteArray Core::decryptData(const QByteArray &data) QByteArray Core::decryptData(const QByteArray& data, const TOX_PASS_KEY& encryptionKey) { + if (data.size() < TOX_PASS_ENCRYPTION_EXTRA_LENGTH) + { + qWarning() << "Not enough data:"<(data.data()), data.size(), @@ -113,6 +118,8 @@ void Core::checkEncryptedHistory() { QString path = HistoryKeeper::getHistoryPath(); bool exists = QFile::exists(path) && QFile(path).size()>0; + if (!exists) + return; QByteArray salt = getSaltFromFile(path); if (exists && salt.size() == 0) diff --git a/src/core/corefile.cpp b/src/core/corefile.cpp index afbde8169..e885fca3f 100644 --- a/src/core/corefile.cpp +++ b/src/core/corefile.cpp @@ -68,12 +68,13 @@ void CoreFile::sendAvatarFile(Core* core, uint32_t friendId, const QByteArray& d uint8_t avatarHash[TOX_HASH_LENGTH]; tox_hash(avatarHash, (uint8_t*)data.data(), data.size()); uint64_t filesize = data.size(); + TOX_ERR_FILE_SEND err; uint32_t fileNum = tox_file_send(core->tox, friendId, TOX_FILE_KIND_AVATAR, filesize, - avatarHash, avatarHash, TOX_HASH_LENGTH, nullptr); + avatarHash, avatarHash, TOX_HASH_LENGTH, &err); if (fileNum == std::numeric_limits::max()) { - qWarning() << "sendAvatarFile: Can't create the Tox file sender"; + qWarning() << "sendAvatarFile: Can't create the Tox file sender, error"<bytesSent != position) { - /// TODO: Allow ooo receiving for non-stream transfers, with very careful checking qWarning("onFileRecvChunkCallback: Received a chunk out-of-order, aborting transfer"); if (file->fileKind != TOX_FILE_KIND_AVATAR) emit core->fileTransferCancelled(*file); diff --git a/src/core/toxid.cpp b/src/core/toxid.cpp index 5b4b7410c..a20de0b80 100644 --- a/src/core/toxid.cpp +++ b/src/core/toxid.cpp @@ -62,7 +62,7 @@ bool ToxId::operator!=(const ToxId &other) const return publicKey != other.publicKey; } -bool ToxId::isActiveProfile() const +bool ToxId::isSelf() const { return *this == Core::getInstance()->getSelfId(); } diff --git a/src/core/toxid.h b/src/core/toxid.h index 65551df9e..10fc3c728 100644 --- a/src/core/toxid.h +++ b/src/core/toxid.h @@ -38,7 +38,7 @@ public: bool operator==(const ToxId& other) const; ///< Compares only publicKey. bool operator!=(const ToxId& other) const; ///< Compares only publicKey. - bool isActiveProfile() const; ///< Returns true if this Tox ID is equals to + bool isSelf() const; ///< Returns true if this Tox ID is equals to /// the Tox ID of the currently active profile. QString toString() const; ///< Returns the Tox ID as QString. void clear(); ///< Clears all elements of the Tox ID. diff --git a/src/friend.cpp b/src/friend.cpp index 8d2b5b22b..a2db70fb7 100644 --- a/src/friend.cpp +++ b/src/friend.cpp @@ -25,7 +25,8 @@ #include "widget/gui.h" #include "src/core/core.h" #include "src/persistence/settings.h" -#include "src/persistence/historykeeper.h" +#include "src/persistence/profile.h" +#include "src/nexus.h" Friend::Friend(uint32_t FriendId, const ToxId &UserId) : userName{Core::getInstance()->getPeerName(UserId)}, @@ -50,7 +51,7 @@ Friend::~Friend() void Friend::loadHistory() { - if (Settings::getInstance().getEnableLogging()) + if (Nexus::getProfile()->isHistoryEnabled()) { chatForm->loadHistory(QDateTime::currentDateTime().addDays(-7), true); widget->historyLoaded = true; diff --git a/src/group.cpp b/src/group.cpp index cdd3c375d..3497263de 100644 --- a/src/group.cpp +++ b/src/group.cpp @@ -86,7 +86,7 @@ void Group::regeneratePeerList() for (int i = 0; i < nPeers; i++) { ToxId id = Core::getInstance()->getGroupPeerToxId(groupId, i); - if (id.isActiveProfile()) + if (id.isSelf()) selfPeerNum = i; QString toxid = id.publicKey; diff --git a/src/ipc.cpp b/src/ipc.cpp index b8ab7cd6b..a959d6596 100644 --- a/src/ipc.cpp +++ b/src/ipc.cpp @@ -69,7 +69,7 @@ IPC::IPC() return; // We won't be able to do any IPC without being attached, let's get outta here } - timer.start(); + processEvents(); } IPC::~IPC() diff --git a/src/net/autoupdate.cpp b/src/net/autoupdate.cpp index 0d72cfd5c..0088b7b81 100644 --- a/src/net/autoupdate.cpp +++ b/src/net/autoupdate.cpp @@ -375,6 +375,7 @@ bool AutoUpdater::downloadUpdate() { qDebug() << "Skipping already downloaded file '" + fileMeta.installpath+ "'"; fileFile.close(); + progressValue = initialProgress + step; continue; } diff --git a/src/net/avatarbroadcaster.cpp b/src/net/avatarbroadcaster.cpp index 1e9542f1a..eefa0e971 100644 --- a/src/net/avatarbroadcaster.cpp +++ b/src/net/avatarbroadcaster.cpp @@ -24,6 +24,7 @@ #include QByteArray AvatarBroadcaster::avatarData; +QMap AvatarBroadcaster::friendsSentTo; static QMetaObject::Connection autoBroadcastConn; static auto autoBroadcast = [](uint32_t friendId, Status) @@ -33,7 +34,10 @@ static auto autoBroadcast = [](uint32_t friendId, Status) void AvatarBroadcaster::setAvatar(QByteArray data) { + if (avatarData == data) + return; avatarData = data; + friendsSentTo.clear(); QVector friends = Core::getInstance()->getFriendList(); for (uint32_t friendId : friends) @@ -42,9 +46,12 @@ void AvatarBroadcaster::setAvatar(QByteArray data) void AvatarBroadcaster::sendAvatarTo(uint32_t friendId) { + if (friendsSentTo.contains(friendId) && friendsSentTo[friendId]) + return; if (!Core::getInstance()->isFriendOnline(friendId)) return; Core::getInstance()->sendAvatarFile(friendId, avatarData); + friendsSentTo[friendId] = true; } void AvatarBroadcaster::enableAutoBroadcast(bool state) diff --git a/src/net/avatarbroadcaster.h b/src/net/avatarbroadcaster.h index 27aecaa2d..529beba01 100644 --- a/src/net/avatarbroadcaster.h +++ b/src/net/avatarbroadcaster.h @@ -24,7 +24,9 @@ #include #include -/// Takes care of broadcasting avatar changes to our friends +/// Takes care of broadcasting avatar changes to our friends in a smart way +/// Cache a copy of our current avatar and friends who have received it +/// so we don't spam avatar transfers to a friend who already has it. class AvatarBroadcaster { private: @@ -40,6 +42,7 @@ public: private: static QByteArray avatarData; + static QMap friendsSentTo; }; #endif // AVATARBROADCASTER_H diff --git a/src/persistence/db/rawdatabase.cpp b/src/persistence/db/rawdatabase.cpp new file mode 100644 index 000000000..6c2024e09 --- /dev/null +++ b/src/persistence/db/rawdatabase.cpp @@ -0,0 +1,486 @@ +#include "rawdatabase.h" +#include +#include +#include +#include +#include +#include +#include + +/// The two following defines are required to use SQLCipher +/// They are used by the sqlite3.h header +#define SQLITE_HAS_CODEC +#define SQLITE_TEMP_STORE 2 + +#include + +RawDatabase::RawDatabase(const QString &path, const QString& password) + : workerThread{new QThread}, path{path}, currentHexKey{deriveKey(password)} +{ + workerThread->setObjectName("qTox Database"); + moveToThread(workerThread.get()); + workerThread->start(); + + if (!open(path, currentHexKey)) + return; +} + +RawDatabase::~RawDatabase() +{ + close(); + workerThread->exit(0); + while (workerThread->isRunning()) + workerThread->wait(50); +} + +bool RawDatabase::open(const QString& path, const QString &hexKey) +{ + if (QThread::currentThread() != workerThread.get()) + { + bool ret; + QMetaObject::invokeMethod(this, "open", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, ret), + Q_ARG(const QString&, path), Q_ARG(const QString&, hexKey)); + return ret; + } + + if (!QFile::exists(path) && QFile::exists(path+".tmp")) + { + qWarning() << "Restoring database from temporary export file! Did we crash while changing the password?"; + QFile::rename(path+".tmp", path); + } + + if (sqlite3_open_v2(path.toUtf8().data(), &sqlite, + SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_NOMUTEX, nullptr) != SQLITE_OK) + { + qWarning() << "Failed to open database"<{statement}); +} + +bool RawDatabase::execNow(const QVector &statements) +{ + if (!sqlite) + { + qWarning() << "Trying to exec, but the database is not open"; + return false; + } + + std::atomic_bool done{false}; + std::atomic_bool success{false}; + + Transaction trans; + trans.queries = statements; + trans.done = &done; + trans.success = &success; + { + QMutexLocker locker{&transactionsMutex}; + pendingTransactions.enqueue(trans); + } + + // We can't use blocking queued here, otherwise we might process future transactions + // before returning, but we only want to wait until this transaction is done. + QMetaObject::invokeMethod(this, "process"); + while (!done.load(std::memory_order_acquire)) + QThread::msleep(10); + + return success.load(std::memory_order_acquire); +} + +void RawDatabase::execLater(const QString &statement) +{ + execLater(Query{statement}); +} + +void RawDatabase::execLater(const RawDatabase::Query &statement) +{ + execLater(QVector{statement}); +} + +void RawDatabase::execLater(const QVector &statements) +{ + if (!sqlite) + { + qWarning() << "Trying to exec, but the database is not open"; + return; + } + + Transaction trans; + trans.queries = statements; + { + QMutexLocker locker{&transactionsMutex}; + pendingTransactions.enqueue(trans); + } + + QMetaObject::invokeMethod(this, "process"); +} + +void RawDatabase::sync() +{ + QMetaObject::invokeMethod(this, "process", Qt::BlockingQueuedConnection); +} + +bool RawDatabase::setPassword(const QString& password) +{ + if (!sqlite) + { + qWarning() << "Trying to change the password, but the database is not open"; + return false; + } + + if (QThread::currentThread() != workerThread.get()) + { + bool ret; + QMetaObject::invokeMethod(this, "setPassword", Qt::BlockingQueuedConnection, + Q_RETURN_ARG(bool, ret), Q_ARG(const QString&, password)); + return ret; + } + + // If we need to decrypt or encrypt, we'll need to sync and close, + // so we always process the pending queue before rekeying for consistency + process(); + + if (QFile::exists(path+".tmp")) + { + qWarning() << "Found old temporary export file while rekeying, deleting it"; + QFile::remove(path+".tmp"); + } + + if (!password.isEmpty()) + { + QString newHexKey = deriveKey(password); + if (!currentHexKey.isEmpty()) + { + if (!execNow("PRAGMA rekey = \"x'"+newHexKey+"'\"")) + { + qWarning() << "Failed to change encryption key"; + close(); + return false; + } + } + else + { + // Need to encrypt the database + if (!execNow("ATTACH DATABASE '"+path+".tmp' AS encrypted KEY \"x'"+newHexKey+"'\";" + "SELECT sqlcipher_export('encrypted');" + "DETACH DATABASE encrypted;")) + { + qWarning() << "Failed to export encrypted database"; + close(); + return false; + } + + // This is racy as hell, but nobody will race with us since we hold the profile lock + // If we crash or die here, the rename should be atomic, so we can recover no matter what + close(); + QFile::remove(path); + QFile::rename(path+".tmp", path); + currentHexKey = newHexKey; + if (!open(path, currentHexKey)) + { + qWarning() << "Failed to open encrypted database"; + return false; + } + } + } + else + { + if (currentHexKey.isEmpty()) + return true; + + // Need to decrypt the database + if (!execNow("ATTACH DATABASE '"+path+".tmp' AS plaintext KEY '';" + "SELECT sqlcipher_export('plaintext');" + "DETACH DATABASE plaintext;")) + { + qWarning() << "Failed to export decrypted database"; + close(); + return false; + } + + // This is racy as hell, but nobody will race with us since we hold the profile lock + // If we crash or die here, the rename should be atomic, so we can recover no matter what + close(); + QFile::remove(path); + QFile::rename(path+".tmp", path); + currentHexKey.clear(); + if (!open(path)) + { + qCritical() << "Failed to open decrypted database"; + return false; + } + } + return true; +} + +bool RawDatabase::rename(const QString &newPath) +{ + if (!sqlite) + { + qWarning() << "Trying to change the password, but the database is not open"; + return false; + } + + if (QThread::currentThread() != workerThread.get()) + { + bool ret; + QMetaObject::invokeMethod(this, "rename", Qt::BlockingQueuedConnection, + Q_RETURN_ARG(bool, ret), Q_ARG(const QString&, newPath)); + return ret; + } + + process(); + + if (path == newPath) + return true; + + if (QFile::exists(newPath)) + return false; + + close(); + if (!QFile::rename(path, newPath)) + return false; + path = newPath; + return open(path, currentHexKey); +} + +bool RawDatabase::remove() +{ + if (!sqlite) + { + qWarning() << "Trying to remove the database, but it is not open"; + return false; + } + + if (QThread::currentThread() != workerThread.get()) + { + bool ret; + QMetaObject::invokeMethod(this, "remove", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, ret)); + return ret; + } + + qDebug() << "Removing database "<= 32, "toxcore must provide 256bit or longer keys"); + + static const uint8_t expandConstant[TOX_PASS_SALT_LENGTH+1] = "L'ignorance est le pire des maux"; + TOX_PASS_KEY key; + tox_derive_key_with_salt((uint8_t*)passData.data(), passData.size(), expandConstant, &key, nullptr); + return QByteArray((char*)key.key, 32).toHex(); +} + +void RawDatabase::process() +{ + assert(QThread::currentThread() == workerThread.get()); + + if (!sqlite) + return; + + forever + { + // Fetch the next transaction + Transaction trans; + { + QMutexLocker locker{&transactionsMutex}; + if (pendingTransactions.isEmpty()) + return; + trans = pendingTransactions.dequeue(); + } + + // In case we exit early, prepare to signal errors + if (trans.success != nullptr) + trans.success->store(false, std::memory_order_release); + + // Add transaction commands if necessary + if (trans.queries.size() > 1) + { + trans.queries.prepend({"BEGIN;"}); + trans.queries.append({"COMMIT;"}); + } + + // Compile queries + for (Query& query : trans.queries) + { + assert(query.statements.isEmpty()); + // sqlite3_prepare_v2 only compiles one statement at a time in the query, we need to loop over them all + int curParam=0; + const char* compileTail = query.query.data(); + do { + // Compile the next statement + sqlite3_stmt* stmt; + int r; + if ((r = sqlite3_prepare_v2(sqlite, compileTail, + query.query.size() - static_cast(compileTail - query.query.data()), + &stmt, &compileTail)) != SQLITE_OK) + { + qWarning() << "Failed to prepare statement"< row; + for (int i=0; istore(true, std::memory_order_release); + + // Free our statements + cleanupStatements: + for (Query& query : trans.queries) + { + for (sqlite3_stmt* stmt : query.statements) + sqlite3_finalize(stmt); + query.statements.clear(); + } + + // Signal transaction results + if (trans.done != nullptr) + trans.done->store(true, std::memory_order_release); + } +} + +QVariant RawDatabase::extractData(sqlite3_stmt *stmt, int col) +{ + int type = sqlite3_column_type(stmt, col); + if (type == SQLITE_INTEGER) + { + return sqlite3_column_int64(stmt, col); + } + else if (type == SQLITE_TEXT) + { + const char* str = reinterpret_cast(sqlite3_column_text(stmt, col)); + int len = sqlite3_column_bytes(stmt, col); + return QString::fromUtf8(str, len); + } + else if (type == SQLITE_NULL) + { + return QVariant{}; + } + else + { + const char* data = reinterpret_cast(sqlite3_column_blob(stmt, col)); + int len = sqlite3_column_bytes(stmt, col); + return QByteArray::fromRawData(data, len); + } +} diff --git a/src/persistence/db/rawdatabase.h b/src/persistence/db/rawdatabase.h new file mode 100644 index 000000000..55c8b81c8 --- /dev/null +++ b/src/persistence/db/rawdatabase.h @@ -0,0 +1,118 @@ +#ifndef RAWDATABASE_H +#define RAWDATABASE_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct sqlite3; +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 +{ + Q_OBJECT + +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 + { + public: + Query(QString query, QVector blobs = {}, std::function insertCallback={}) + : query{query.toUtf8()}, blobs{blobs}, insertCallback{insertCallback} {} + Query(QString query, std::function insertCallback) + : query{query.toUtf8()}, insertCallback{insertCallback} {} + Query(QString query, std::function&)> rowCallback) + : query{query.toUtf8()}, rowCallback{rowCallback} {} + Query() = default; + private: + QByteArray query; ///< UTF-8 query string + QVector blobs; ///< Bound data blobs + std::function insertCallback; ///< Called after execution with the last insert rowid + std::function&)> rowCallback; ///< Called during execution for each row + QVector statements; ///< Statements to be compiled from the query + + friend class RawDatabase; + }; + +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(); + bool isOpen(); ///< Returns true if the database was opened successfully + /// Executes a SQL transaction synchronously. + /// Returns whether the transaction was successful. + bool execNow(const QString& statement); + bool execNow(const Query& statement); + bool execNow(const QVector& statements); + /// Executes a SQL transaction asynchronously. + void execLater(const QString& statement); + void execLater(const Query& statement); + void execLater(const QVector& statements); + /// Waits until all the pending transactions are executed + void sync(); + +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); + /// Moves the database file on disk to match the new path + /// Will process all transactions before renaming + bool rename(const QString& newPath); + /// Deletes the on disk database file after closing it + /// Will process all transactions before deletings + bool remove(); + +protected slots: + /// Tries to open the database with the given (possibly empty) key + bool open(const QString& path, const QString& hexKey = {}); + /// Closes the database and free its associated resources + 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(); + +protected: + /// Derives a 256bit key from the password and returns it hex-encoded + static QString deriveKey(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); + +private: + /// SQL transactions to be processed + /// A transaction is made of queries, which can have bound BLOBs + struct Transaction + { + QVector queries; + /// If not a nullptr, the result of the transaction will be set + 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; + }; + +private: + sqlite3* sqlite; + std::unique_ptr workerThread; + QQueue pendingTransactions; + /// Protects pendingTransactions + QMutex transactionsMutex; + QString path; + QString currentHexKey; +}; + +#endif // RAWDATABASE_H diff --git a/src/persistence/history.cpp b/src/persistence/history.cpp new file mode 100644 index 000000000..48864b567 --- /dev/null +++ b/src/persistence/history.cpp @@ -0,0 +1,237 @@ +#include "history.h" +#include "src/persistence/profile.h" +#include "src/persistence/settings.h" +#include "src/persistence/db/rawdatabase.h" +#include "src/persistence/historykeeper.h" +#include +#include + +using namespace std; + +History::History(const QString &profileName, const QString &password) + : db{getDbPath(profileName), password} +{ + init(); +} + +History::History(const QString &profileName, const QString &password, const HistoryKeeper &oldHistory) + : History{profileName, password} +{ + import(oldHistory); +} + +History::~History() +{ + // We could have execLater requests pending with a lambda attached, + // so clear the pending transactions first + db.sync(); +} + +bool History::isValid() +{ + return db.isOpen(); +} + +void History::setPassword(const QString& password) +{ + db.setPassword(password); +} + +void History::rename(const QString &newName) +{ + db.rename(getDbPath(newName)); +} + +void History::remove() +{ + db.remove(); +} + +void History::eraseHistory() +{ + db.execNow("DELETE FROM faux_offline_pending;" + "DELETE FROM history;" + "DELETE FROM aliases;" + "DELETE FROM peers;" + "VACUUM;"); +} + +void History::removeFriendHistory(const QString &friendPk) +{ + if (!peers.contains(friendPk)) + return; + int64_t id = peers[friendPk]; + + if (db.execNow(QString("DELETE FROM faux_offline_pending " + "WHERE faux_offline_pending.id IN ( " + "SELECT faux_offline_pending.id FROM faux_offline_pending " + "LEFT JOIN history ON faux_offline_pending.id = history.id " + "WHERE chat_id=%1 " + "); " + "DELETE FROM history WHERE chat_id=%1; " + "DELETE FROM aliases WHERE owner=%1; " + "DELETE FROM peers WHERE id=%1; " + "VACUUM;").arg(id))) + { + peers.remove(friendPk); + } + else + { + qWarning() << "Failed to remove friend's history"; + } +} + +QVector History::generateNewMessageQueries(const QString &friendPk, const QString &message, + const QString &sender, const QDateTime &time, bool isSent, QString dispName, + std::function insertIdCallback) +{ + QVector queries; + + // Get the db id of the peer we're chatting with + int64_t peerId; + if (peers.contains(friendPk)) + { + peerId = peers[friendPk]; + } + else + { + if (peers.isEmpty()) + peerId = 0; + else + peerId = *max_element(begin(peers), end(peers))+1; + peers[friendPk] = peerId; + queries += RawDatabase::Query{("INSERT INTO peers (id, public_key) VALUES (%1, '"+friendPk+"');").arg(peerId)}; + } + + // Get the db id of the sender of the message + int64_t senderId; + if (peers.contains(sender)) + { + senderId = peers[sender]; + } + else + { + if (peers.isEmpty()) + senderId = 0; + else + senderId = *max_element(begin(peers), end(peers))+1; + peers[sender] = senderId; + queries += RawDatabase::Query{("INSERT INTO peers (id, public_key) VALUES (%1, '"+sender+"');").arg(senderId)}; + } + + queries += RawDatabase::Query(QString("INSERT OR IGNORE INTO aliases (owner, display_name) VALUES (%1, ?);") + .arg(senderId), {dispName.toUtf8()}); + + // If the alias already existed, the insert will ignore the conflict and last_insert_rowid() will return garbage, + // so we have to check changes() and manually fetch the row ID in this case + queries += RawDatabase::Query(QString("INSERT INTO history (timestamp, chat_id, message, sender_alias) " + "VALUES (%1, %2, ?, (" + " CASE WHEN changes() IS 0 THEN (" + " SELECT id FROM aliases WHERE owner=%3 AND display_name=?)" + " ELSE last_insert_rowid() END" + "));") + .arg(time.toMSecsSinceEpoch()).arg(peerId).arg(senderId), + {message.toUtf8(), dispName.toUtf8()}, insertIdCallback); + + if (!isSent) + queries += RawDatabase::Query{"INSERT INTO faux_offline_pending (id) VALUES (last_insert_rowid());"}; + + return queries; +} + +void History::addNewMessage(const QString &friendPk, const QString &message, const QString &sender, + const QDateTime &time, bool isSent, QString dispName, std::function insertIdCallback) +{ + db.execLater(generateNewMessageQueries(friendPk, message, sender, time, isSent, dispName, insertIdCallback)); +} + +QList History::getChatHistory(const QString &friendPk, const QDateTime &from, const QDateTime &to) +{ + QList messages; + + auto rowCallback = [&messages](const QVector& row) + { + // dispName and message could have null bytes, QString::fromUtf8 truncates on null bytes so we strip them + messages += {row[0].toLongLong(), + row[1].isNull(), + QDateTime::fromMSecsSinceEpoch(row[2].toLongLong()), + row[3].toString(), + QString::fromUtf8(row[4].toByteArray().replace('\0',"")), + row[5].toString(), + QString::fromUtf8(row[6].toByteArray().replace('\0',""))}; + }; + + // Don't forget to update the rowCallback if you change the selected columns! + db.execNow({QString("SELECT history.id, faux_offline_pending.id, timestamp, chat.public_key, " + "aliases.display_name, sender.public_key, message FROM history " + "LEFT JOIN faux_offline_pending ON history.id = faux_offline_pending.id " + "JOIN peers chat ON chat_id = chat.id " + "JOIN aliases ON sender_alias = aliases.id " + "JOIN peers sender ON aliases.owner = sender.id " + "WHERE timestamp BETWEEN %1 AND %2 AND chat.public_key='%3';") + .arg(from.toMSecsSinceEpoch()).arg(to.toMSecsSinceEpoch()).arg(friendPk), rowCallback}); + + return messages; +} + +void History::markAsSent(qint64 id) +{ + db.execLater(QString("DELETE FROM faux_offline_pending WHERE id=%1;").arg(id)); +} + +QString History::getDbPath(const QString &profileName) +{ + return Settings::getInstance().getSettingsDirPath() + profileName + ".db"; +} + +void History::init() +{ + if (!isValid()) + { + qWarning() << "Database not open, init failed"; + return; + } + + db.execLater("CREATE TABLE IF NOT EXISTS peers (id INTEGER PRIMARY KEY, public_key TEXT NOT NULL UNIQUE);" + "CREATE TABLE IF NOT EXISTS aliases (id INTEGER PRIMARY KEY, owner INTEGER," + "display_name BLOB NOT NULL, UNIQUE(owner, display_name));" + "CREATE TABLE IF NOT EXISTS history (id INTEGER PRIMARY KEY, timestamp INTEGER NOT NULL, " + "chat_id INTEGER NOT NULL, sender_alias INTEGER NOT NULL, " + "message BLOB NOT NULL);" + "CREATE TABLE IF NOT EXISTS faux_offline_pending (id INTEGER PRIMARY KEY);"); + + // Cache our current peers + db.execLater(RawDatabase::Query{"SELECT public_key, id FROM peers;", [this](const QVector& row) + { + peers[row[0].toString()] = row[1].toInt(); + }}); +} + +void History::import(const HistoryKeeper &oldHistory) +{ + if (!isValid()) + { + qWarning() << "New database not open, import failed"; + return; + } + + qDebug() << "Importing old database..."; + QTime t=QTime::currentTime(); + t.start(); + QVector queries; + constexpr int batchSize = 1000; + queries.reserve(batchSize); + QList oldMessages = oldHistory.exportMessagesDeleteFile(); + for (const HistoryKeeper::HistMessage& msg : oldMessages) + { + queries += generateNewMessageQueries(msg.chat, msg.message, msg.sender, msg.timestamp, true, msg.dispName); + if (queries.size() >= batchSize) + { + db.execLater(queries); + queries.clear(); + } + } + db.execLater(queries); + db.sync(); + qDebug() << "Imported old database in"< +#include +#include +#include +#include +#include "src/persistence/db/rawdatabase.h" + +class Profile; +class HistoryKeeper; +class RawDatabase; + +/// Interacts with the profile database to save the chat history +class History +{ +public: + struct HistMessage + { + HistMessage(qint64 id, bool isSent, QDateTime timestamp, QString chat, QString dispName, QString sender, QString message) : + chat{chat}, sender{sender}, message{message}, dispName{dispName}, timestamp{timestamp}, id{id}, isSent{isSent} {} + + QString chat; + QString sender; + QString message; + QString dispName; + QDateTime timestamp; + qint64 id; + bool isSent; + }; + +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); + /// 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(); + /// Checks if the database was opened successfully + bool isValid(); + /// Imports messages from the old history file + void import(const HistoryKeeper& oldHistory); + /// Changes the database password, will encrypt or decrypt if necessary + void setPassword(const QString& password); + /// Moves the database file on disk to match the new name + void rename(const QString& newName); + /// Deletes the on-disk database file + void remove(); + + /// Erases all the chat history from the database + void eraseHistory(); + /// Erases the chat history with one friend + void removeFriendHistory(const QString& friendPk); + /// Saves a chat message in the database + void addNewMessage(const QString& friendPk, const QString& message, const QString& sender, + const QDateTime &time, bool isSent, QString dispName, + std::function insertIdCallback={}); + /// Fetches chat messages from the database + QList 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); + +protected: + /// Makes sure the history tables are created + void init(); + static QString getDbPath(const QString& profileName); + QVector generateNewMessageQueries(const QString& friendPk, const QString& message, + const QString& sender, const QDateTime &time, bool isSent, QString dispName, + std::function insertIdCallback={}); + +private: + RawDatabase db; + // Cached mappings to speed up message saving + QHash peers; ///< Maps friend public keys to unique IDs by index +}; + +#endif // HISTORY_H diff --git a/src/persistence/historykeeper.cpp b/src/persistence/historykeeper.cpp index a8fe1e318..381bfe97d 100644 --- a/src/persistence/historykeeper.cpp +++ b/src/persistence/historykeeper.cpp @@ -38,7 +38,7 @@ static HistoryKeeper *historyInstance = nullptr; QMutex HistoryKeeper::historyMutex; -HistoryKeeper *HistoryKeeper::getInstance() +HistoryKeeper *HistoryKeeper::getInstance(const Profile& profile) { historyMutex.lock(); if (historyInstance == nullptr) @@ -53,9 +53,9 @@ HistoryKeeper *HistoryKeeper::getInstance() QString path(":memory:"); GenericDdInterface *dbIntf; - if (Nexus::getProfile()->isEncrypted()) + if (profile.isEncrypted()) { - path = getHistoryPath(); + path = getHistoryPath({}, 1); dbIntf = new EncryptedDb(path, initLst); historyInstance = new HistoryKeeper(dbIntf); @@ -64,7 +64,7 @@ HistoryKeeper *HistoryKeeper::getInstance() } else { - path = getHistoryPath(); + path = getHistoryPath({}, 0); } dbIntf = new PlainDb(path, initLst); @@ -87,7 +87,7 @@ bool HistoryKeeper::checkPassword(const TOX_PASS_KEY &passkey, int encrypted) } HistoryKeeper::HistoryKeeper(GenericDdInterface *db_) : - db(db_) + oldDb(db_) { /* DB format @@ -114,11 +114,11 @@ HistoryKeeper::HistoryKeeper(GenericDdInterface *db_) : */ // for old tables: - QSqlQuery ans = db->exec("SELECT seq FROM sqlite_sequence WHERE name=\"history\";"); + QSqlQuery ans = oldDb->exec("SELECT seq FROM sqlite_sequence WHERE name=\"history\";"); if (ans.first()) { int idMax = ans.value(0).toInt(); - QSqlQuery ret = db->exec("SELECT seq FROM sqlite_sequence WHERE name=\"sent_status\";"); + QSqlQuery ret = oldDb->exec("SELECT seq FROM sqlite_sequence WHERE name=\"sent_status\";"); int idCur = 0; if (ret.first()) idCur = ret.value(0).toInt(); @@ -126,131 +126,29 @@ HistoryKeeper::HistoryKeeper(GenericDdInterface *db_) : if (idCur != idMax) { QString cmd = QString("INSERT INTO sent_status (id, status) VALUES (%1, 1);").arg(idMax); - db->exec(cmd); + oldDb->exec(cmd); } } //check table stuct - ans = db->exec("PRAGMA table_info (\"history\")"); + ans = oldDb->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"); + oldDb->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(); - - setSyncType(Settings::getInstance().getDbSyncType()); - - messageID = 0; - QSqlQuery sqlAnswer = db->exec("SELECT seq FROM sqlite_sequence WHERE name=\"history\";"); - if (sqlAnswer.first()) - messageID = sqlAnswer.value(0).toLongLong(); } HistoryKeeper::~HistoryKeeper() { - delete db; -} - -void HistoryKeeper::removeFriendHistory(const QString& chat) -{ - int chat_id = getChatID(chat, ctSingle).first; - - db->exec("BEGIN TRANSACTION;"); - - QString cmd = QString("DELETE FROM chats WHERE name = '%1';").arg(chat); - db->exec(cmd); - cmd = QString("DELETE FROM aliases WHERE user_id = '%1';").arg(chat); - db->exec(cmd); - cmd = QString("DELETE FROM sent_status WHERE id IN (SELECT id FROM history WHERE chat_id = '%1');").arg(chat_id); - db->exec(cmd); - cmd = QString("DELETE FROM history WHERE chat_id = '%1';").arg(chat_id); - db->exec(cmd); - - db->exec("COMMIT TRANSACTION;"); -} - -qint64 HistoryKeeper::addChatEntry(const QString& chat, const QString& message, const QString& sender, const QDateTime &dt, bool isSent, QString dispName) -{ - QList cmds = generateAddChatEntryCmd(chat, message, sender, dt, isSent, dispName); - - db->exec("BEGIN TRANSACTION;"); - for (auto &it : cmds) - db->exec(it); - - db->exec("COMMIT TRANSACTION;"); - - messageID++; - return messageID; -} - -QList HistoryKeeper::getChatHistory(HistoryKeeper::ChatType ct, const QString &chat, - const QDateTime &time_from, const QDateTime &time_to) -{ - QList res; - - qint64 time64_from = time_from.toMSecsSinceEpoch(); - qint64 time64_to = time_to.toMSecsSinceEpoch(); - - int chat_id = getChatID(chat, ct).first; - - QSqlQuery dbAnswer; - if (ct == ctSingle) - { - dbAnswer = db->exec(QString("SELECT history.id, timestamp, user_id, message, status, alias FROM history LEFT JOIN sent_status ON history.id = sent_status.id ") + - QString("INNER JOIN aliases ON history.sender = aliases.id AND timestamp BETWEEN %1 AND %2 AND chat_id = %3;") - .arg(time64_from).arg(time64_to).arg(chat_id)); - } - else - { - // no groupchats yet - } - - while (dbAnswer.next()) - { - qint64 id = dbAnswer.value(0).toLongLong(); - qint64 timeInt = dbAnswer.value(1).toLongLong(); - QString sender = dbAnswer.value(2).toString(); - QString senderName = dbAnswer.value(5).toString(); - QString message = unWrapMessage(dbAnswer.value(3).toString()); - bool isSent = true; - if (!dbAnswer.value(4).isNull()) - isSent = dbAnswer.value(4).toBool(); - - QDateTime time = QDateTime::fromMSecsSinceEpoch(timeInt); - - res.push_back(HistMessage(id, "", sender, message, time, isSent, senderName)); - } - - return res; + delete oldDb; } QList HistoryKeeper::exportMessages() { QSqlQuery dbAnswer; - dbAnswer = db->exec(QString("SELECT history.id, timestamp, user_id, message, status, name, alias FROM history LEFT JOIN sent_status ON history.id = sent_status.id ") + + dbAnswer = oldDb->exec(QString("SELECT history.id, timestamp, user_id, message, status, name, alias FROM history LEFT JOIN sent_status ON history.id = sent_status.id ") + QString("INNER JOIN aliases ON history.sender = aliases.id INNER JOIN chats ON history.chat_id = chats.id;")); QList res; @@ -274,41 +172,6 @@ QList HistoryKeeper::exportMessages() return res; } -void HistoryKeeper::importMessages(const QList &lst) -{ - db->exec("BEGIN TRANSACTION;"); - for (const HistMessage &msg : lst) - { - QList cmds = generateAddChatEntryCmd(msg.chat, msg.message, msg.sender, msg.timestamp, msg.isSent, QString()); //!!! - for (auto &it : cmds) - db->exec(it); - - messageID++; - } - db->exec("COMMIT TRANSACTION;"); -} - -QList HistoryKeeper::generateAddChatEntryCmd(const QString& chat, const QString& message, const QString& sender, const QDateTime &dt, bool isSent, QString dispName) -{ - QList cmds; - - int chat_id = getChatID(chat, ctSingle).first; - int sender_id = getAliasID(sender); - - cmds.push_back(QString("INSERT INTO history (timestamp, chat_id, sender, message, alias) VALUES (%1, %2, %3, '%4', '%5');") - .arg(dt.toMSecsSinceEpoch()).arg(chat_id).arg(sender_id).arg(wrapMessage(message.toUtf8())).arg(QString(dispName.toUtf8()))); - cmds.push_back(QString("INSERT INTO sent_status (status) VALUES (%1);").arg(isSent)); - - return cmds; -} - -QString HistoryKeeper::wrapMessage(const QString &str) -{ - QString wrappedMessage(str); - wrappedMessage.replace("'", "''"); - return wrappedMessage; -} - QString HistoryKeeper::unWrapMessage(const QString &str) { QString unWrappedMessage(str); @@ -316,59 +179,6 @@ QString HistoryKeeper::unWrapMessage(const QString &str) return unWrappedMessage; } -void HistoryKeeper::updateChatsID() -{ - auto dbAnswer = db->exec(QString("SELECT * FROM chats;")); - - chats.clear(); - while (dbAnswer.next()) - { - QString name = dbAnswer.value(1).toString(); - int id = dbAnswer.value(0).toInt(); - ChatType ctype = convertToChatType(dbAnswer.value(2).toInt()); - - chats[name] = {id, ctype}; - } -} - -void HistoryKeeper::updateAliases() -{ - auto dbAnswer = db->exec(QString("SELECT * FROM aliases;")); - - aliases.clear(); - while (dbAnswer.next()) - { - QString user_id = dbAnswer.value(1).toString(); - int id = dbAnswer.value(0).toInt(); - - aliases[user_id] = id; - } -} - -QPair HistoryKeeper::getChatID(const QString &id_str, ChatType ct) -{ - auto it = chats.find(id_str); - if (it != chats.end()) - return it.value(); - - db->exec(QString("INSERT INTO chats (name, ctype) VALUES ('%1', '%2');").arg(id_str).arg(ct)); - updateChatsID(); - - return getChatID(id_str, ct); -} - -int HistoryKeeper::getAliasID(const QString &id_str) -{ - auto it = aliases.find(id_str); - if (it != aliases.end()) - return it.value(); - - db->exec(QString("INSERT INTO aliases (user_id) VALUES ('%1');").arg(id_str)); - updateAliases(); - - return getAliasID(id_str); -} - void HistoryKeeper::resetInstance() { if (historyInstance == nullptr) @@ -378,25 +188,6 @@ void HistoryKeeper::resetInstance() historyInstance = nullptr; } -qint64 HistoryKeeper::addGroupChatEntry(const QString &chat, const QString &message, const QString &sender, const QDateTime &dt) -{ - Q_UNUSED(chat) - Q_UNUSED(message) - Q_UNUSED(sender) - Q_UNUSED(dt) - // no groupchats yet - - return -1; -} - -HistoryKeeper::ChatType HistoryKeeper::convertToChatType(int ct) -{ - if (ct < 0 || ct > 1) - return ctSingle; - - return static_cast(ct); -} - QString HistoryKeeper::getHistoryPath(QString currentProfile, int encrypted) { QDir baseDir(Settings::getInstance().getSettingsDirPath()); @@ -409,70 +200,9 @@ QString HistoryKeeper::getHistoryPath(QString currentProfile, int encrypted) return baseDir.filePath(currentProfile + ".qtox_history"); } -void HistoryKeeper::renameHistory(QString from, QString to) +bool HistoryKeeper::isFileExist(bool encrypted) { - resetInstance(); - - QFile fileEnc(QDir(Settings::getInstance().getSettingsDirPath()).filePath(from + ".qtox_history.encrypted")); - if (fileEnc.exists()) - fileEnc.rename(QDir(Settings::getInstance().getSettingsDirPath()).filePath(to + ".qtox_history.encrypted")); - - QFile filePlain(QDir(Settings::getInstance().getSettingsDirPath()).filePath(from + ".qtox_history")); - if (filePlain.exists()) - filePlain.rename(QDir(Settings::getInstance().getSettingsDirPath()).filePath(to + ".qtox_history")); -} - -void HistoryKeeper::markAsSent(int m_id) -{ - db->exec(QString("UPDATE sent_status SET status = 1 WHERE id = %1;").arg(m_id)); -} - -QDate HistoryKeeper::getLatestDate(const QString &chat) -{ - int chat_id = getChatID(chat, ctSingle).first; - - QSqlQuery dbAnswer; - dbAnswer = db->exec(QString("SELECT MAX(timestamp) FROM history LEFT JOIN sent_status ON history.id = sent_status.id ") + - QString("INNER JOIN aliases ON history.sender = aliases.id AND chat_id = %3;") - .arg(chat_id)); - - if (dbAnswer.first()) - { - qint64 timeInt = dbAnswer.value(0).toLongLong(); - - if (timeInt != 0) - return QDateTime::fromMSecsSinceEpoch(timeInt).date(); - } - - return QDate(); -} - -void HistoryKeeper::setSyncType(Db::syncType sType) -{ - QString syncCmd; - - switch (sType) - { - case Db::syncType::stFull: - syncCmd = "FULL"; - break; - case Db::syncType::stNormal: - syncCmd = "NORMAL"; - break; - case Db::syncType::stOff: - syncCmd = "OFF"; - break; - default: - syncCmd = "FULL"; - break; - } - - db->exec(QString("PRAGMA synchronous=%1;").arg(syncCmd)); -} - -bool HistoryKeeper::isFileExist() -{ - QString path = getHistoryPath(); + QString path = getHistoryPath({}, encrypted ? 1 : 0); QFile file(path); return file.exists(); @@ -480,17 +210,16 @@ bool HistoryKeeper::isFileExist() void HistoryKeeper::removeHistory() { - db->exec("BEGIN TRANSACTION;"); - db->exec("DELETE FROM sent_status;"); - db->exec("DELETE FROM history;"); - db->exec("COMMIT TRANSACTION;"); + resetInstance(); + QFile::remove(getHistoryPath({}, 0)); + QFile::remove(getHistoryPath({}, 1)); } QList HistoryKeeper::exportMessagesDeleteFile() { - auto msgs = getInstance()->exportMessages(); + auto msgs = getInstance(*Nexus::getProfile())->exportMessages(); qDebug() << "Messages exported"; - getInstance()->removeHistory(); + getInstance(*Nexus::getProfile())->removeHistory(); return msgs; } diff --git a/src/persistence/historykeeper.h b/src/persistence/historykeeper.h index 4af427eef..6315a7249 100644 --- a/src/persistence/historykeeper.h +++ b/src/persistence/historykeeper.h @@ -27,13 +27,19 @@ #include #include +/** + * THIS IS A LEGACY CLASS KEPT FOR BACKWARDS COMPATIBILITY + * DO NOT USE! + * See the History class instead + */ + +class Profile; class GenericDdInterface; namespace Db { enum class syncType; } class HistoryKeeper { public: - enum ChatType {ctSingle = 0, ctGroup}; static QMutex historyMutex; struct HistMessage @@ -52,47 +58,23 @@ public: virtual ~HistoryKeeper(); - static HistoryKeeper* getInstance(); + static HistoryKeeper* getInstance(const Profile& profile); static void resetInstance(); static QString getHistoryPath(QString currentProfile = QString(), int encrypted = -1); // -1 defaults to checking settings, 0 or 1 to specify static bool checkPassword(const TOX_PASS_KEY& passkey, int encrypted = -1); - static bool isFileExist(); - static void renameHistory(QString from, QString to); + static bool isFileExist(bool encrypted); 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); - qint64 addGroupChatEntry(const QString& chat, const QString& message, const QString& sender, const QDateTime &dt); - QList getChatHistory(ChatType ct, const QString &chat, const QDateTime &time_from, const QDateTime &time_to); - void markAsSent(int m_id); - QDate getLatestDate(const QString& chat); - QList exportMessages(); - void importMessages(const QList &lst); - - void setSyncType(Db::syncType sType); private: HistoryKeeper(GenericDdInterface *db_); HistoryKeeper(HistoryKeeper &hk) = delete; HistoryKeeper& operator=(const HistoryKeeper&) = delete; - - void updateChatsID(); - void updateAliases(); - QPair getChatID(const QString &id_str, ChatType ct); - int getAliasID(const QString &id_str); - QString wrapMessage(const QString &str); QString unWrapMessage(const QString &str); - 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; - qint64 messageID; + GenericDdInterface *oldDb; }; #endif // HISTORYKEEPER_H diff --git a/src/persistence/offlinemsgengine.cpp b/src/persistence/offlinemsgengine.cpp index 54f20d4b6..48e911cb0 100644 --- a/src/persistence/offlinemsgengine.cpp +++ b/src/persistence/offlinemsgengine.cpp @@ -19,9 +19,10 @@ #include "offlinemsgengine.h" #include "src/friend.h" -#include "src/persistence/historykeeper.h" #include "src/persistence/settings.h" #include "src/core/core.h" +#include "src/nexus.h" +#include "src/persistence/profile.h" #include #include @@ -42,6 +43,7 @@ void OfflineMsgEngine::dischargeReceipt(int receipt) { QMutexLocker ml(&mutex); + Profile* profile = Nexus::getProfile(); auto it = receipts.find(receipt); if (it != receipts.end()) { @@ -49,7 +51,8 @@ void OfflineMsgEngine::dischargeReceipt(int receipt) auto msgIt = undeliveredMsgs.find(mID); if (msgIt != undeliveredMsgs.end()) { - HistoryKeeper::getInstance()->markAsSent(mID); + if (profile->isHistoryEnabled()) + profile->getHistory()->markAsSent(mID); msgIt.value().msg->markAsSent(QDateTime::currentDateTime()); undeliveredMsgs.erase(msgIt); } @@ -57,7 +60,7 @@ void OfflineMsgEngine::dischargeReceipt(int receipt) } } -void OfflineMsgEngine::registerReceipt(int receipt, int messageID, ChatMessage::Ptr msg, const QDateTime ×tamp) +void OfflineMsgEngine::registerReceipt(int receipt, int64_t messageID, ChatMessage::Ptr msg, const QDateTime ×tamp) { QMutexLocker ml(&mutex); @@ -78,13 +81,13 @@ void OfflineMsgEngine::deliverOfflineMsgs() if (undeliveredMsgs.size() == 0) return; - QMap msgs = undeliveredMsgs; + QMap msgs = undeliveredMsgs; removeAllReciepts(); for (auto iter = msgs.begin(); iter != msgs.end(); ++iter) - { - auto val = iter.value(); - auto key = iter.key(); + { + auto val = iter.value(); + auto key = iter.key(); if (val.timestamp.msecsTo(QDateTime::currentDateTime()) < offlineTimeout) { diff --git a/src/persistence/offlinemsgengine.h b/src/persistence/offlinemsgengine.h index 6cce8c919..386262a5c 100644 --- a/src/persistence/offlinemsgengine.h +++ b/src/persistence/offlinemsgengine.h @@ -39,7 +39,7 @@ public: static QMutex globalMutex; void dischargeReceipt(int receipt); - void registerReceipt(int receipt, int messageID, ChatMessage::Ptr msg, const QDateTime ×tamp = QDateTime::currentDateTime()); + void registerReceipt(int receipt, int64_t messageID, ChatMessage::Ptr msg, const QDateTime ×tamp = QDateTime::currentDateTime()); public slots: void deliverOfflineMsgs(); @@ -54,8 +54,8 @@ private: QMutex mutex; Friend* f; - QHash receipts; - QMap undeliveredMsgs; + QHash receipts; + QMap undeliveredMsgs; static const int offlineTimeout; }; diff --git a/src/persistence/profile.cpp b/src/persistence/profile.cpp index d0e92e979..8849bad3f 100644 --- a/src/persistence/profile.cpp +++ b/src/persistence/profile.cpp @@ -21,8 +21,8 @@ #include "profile.h" #include "profilelocker.h" #include "src/persistence/settings.h" -#include "src/core/core.h" #include "src/persistence/historykeeper.h" +#include "src/core/core.h" #include "src/widget/gui.h" #include "src/widget/widget.h" #include "src/nexus.h" @@ -47,7 +47,16 @@ Profile::Profile(QString name, QString password, bool isNewProfile) Settings& s = Settings::getInstance(); s.setCurrentProfile(name); s.saveGlobal(); - HistoryKeeper::resetInstance(); + + // At this point it's too early to load the personnal settings (Nexus will do it), so we always load + // the history, and if it fails we can't change the setting now, but we keep a nullptr + history.reset(new History{name, password}); + if (!history->isValid()) + { + qWarning() << "Failed to open history for profile"<setObjectName("qTox Core"); @@ -127,7 +136,10 @@ Profile* Profile::loadProfile(QString name, QString password) } } - return new Profile(name, password, false); + Profile* p = new Profile(name, password, false); + if (p->history && HistoryKeeper::isFileExist(!password.isEmpty())) + p->history->import(*HistoryKeeper::getInstance(*p)); + return p; } Profile* Profile::createProfile(QString name, QString password) @@ -211,7 +223,7 @@ Core* Profile::getCore() return core; } -QString Profile::getName() +QString Profile::getName() const { return name; } @@ -229,8 +241,6 @@ bool Profile::isNewProfile() QByteArray Profile::loadToxSave() { assert(!isRemoved); - - /// TODO: Cache the data, invalidate it only when we save QByteArray data; QString path = Settings::getInstance().getSettingsDirPath() + name + ".tox"; @@ -369,7 +379,7 @@ QByteArray Profile::loadAvatarData(const QString &ownerId) return {}; QByteArray pic = file.readAll(); - if (encrypted) + if (encrypted && !pic.isEmpty()) { uint8_t salt[TOX_PASS_SALT_LENGTH]; tox_get_salt(reinterpret_cast(pic.data()), salt); @@ -381,19 +391,26 @@ QByteArray Profile::loadAvatarData(const QString &ownerId) void Profile::saveAvatar(QByteArray pic, const QString &ownerId) { - if (!password.isEmpty()) + if (!password.isEmpty() && !pic.isEmpty()) pic = core->encryptData(pic, passkey); QString path = avatarPath(ownerId); QDir(Settings::getInstance().getSettingsDirPath()).mkdir("avatars"); - QSaveFile file(path); - if (!file.open(QIODevice::WriteOnly)) + if (pic.isEmpty()) { - qWarning() << "Tox avatar " << path << " couldn't be saved"; - return; + QFile::remove(path); + } + else + { + QSaveFile file(path); + if (!file.open(QIODevice::WriteOnly)) + { + qWarning() << "Tox avatar " << path << " couldn't be saved"; + return; + } + file.write(pic); + file.commit(); } - file.write(pic); - file.commit(); } QByteArray Profile::getAvatarHash(const QString &ownerId) @@ -409,9 +426,21 @@ void Profile::removeAvatar() removeAvatar(core->getSelfId().publicKey); } +bool Profile::isHistoryEnabled() +{ + return Settings::getInstance().getEnableLogging() && history; +} + +History *Profile::getHistory() +{ + return history.get(); +} + void Profile::removeAvatar(const QString &ownerId) { QFile::remove(avatarPath(ownerId)); + if (ownerId == core->getSelfId().publicKey) + core->setAvatar({}); } bool Profile::exists(QString name) @@ -420,7 +449,7 @@ bool Profile::exists(QString name) return QFile::exists(path+".tox") && QFile::exists(path+".ini"); } -bool Profile::isEncrypted() +bool Profile::isEncrypted() const { return !password.isEmpty(); } @@ -467,6 +496,11 @@ void Profile::remove() QFile::remove(HistoryKeeper::getHistoryPath(name, 0)); QFile::remove(HistoryKeeper::getHistoryPath(name, 1)); + if (history) + { + history->remove(); + history.release(); + } } bool Profile::rename(QString newName) @@ -479,7 +513,8 @@ bool Profile::rename(QString newName) QFile::rename(path+".tox", newPath+".tox"); QFile::rename(path+".ini", newPath+".ini"); - HistoryKeeper::renameHistory(name, newName); + if (history) + history->rename(newName); bool resetAutorun = Settings::getInstance().getAutorun(); Settings::getInstance().setAutorun(false); Settings::getInstance().setCurrentProfile(newName); @@ -498,12 +533,12 @@ bool Profile::checkPassword() return !loadToxSave().isEmpty(); } -QString Profile::getPassword() +QString Profile::getPassword() const { return password; } -const TOX_PASS_KEY& Profile::getPasskey() +const TOX_PASS_KEY& Profile::getPasskey() const { return passkey; } @@ -518,14 +553,16 @@ void Profile::restartCore() void Profile::setPassword(QString newPassword) { - QList oldMessages = HistoryKeeper::exportMessagesDeleteFile(); QByteArray avatar = loadAvatarData(core->getSelfId().publicKey); password = newPassword; passkey = *core->createPasskey(password); saveToxSave(); - HistoryKeeper::getInstance()->importMessages(oldMessages); - Nexus::getDesktopGUI()->reloadHistory(); + if (history) + { + history->setPassword(newPassword); + Nexus::getDesktopGUI()->reloadHistory(); + } saveAvatar(avatar, core->getSelfId().publicKey); } diff --git a/src/persistence/profile.h b/src/persistence/profile.h index b177a5152..4f4e454d4 100644 --- a/src/persistence/profile.h +++ b/src/persistence/profile.h @@ -26,6 +26,8 @@ #include #include #include +#include +#include "src/persistence/history.h" class Core; class QThread; @@ -44,16 +46,16 @@ public: ~Profile(); Core* getCore(); - QString getName(); + QString getName() const; void startCore(); ///< Starts the Core thread void restartCore(); ///< Delete core and restart a new one bool isNewProfile(); - bool isEncrypted(); ///< Returns true if we have a password set (doesn't check the actual file on disk) + bool isEncrypted() const; ///< Returns true if we have a password set (doesn't check the actual file on disk) bool checkPassword(); ///< Checks whether the password is valid - QString getPassword(); + QString getPassword() const; void setPassword(QString newPassword); ///< Changes the encryption password and re-saves everything with it - const TOX_PASS_KEY& getPasskey(); + const TOX_PASS_KEY& getPasskey() const; QByteArray loadToxSave(); ///< Loads the profile's .tox save from file, unencrypted void saveToxSave(); ///< Saves the profile's .tox save, encrypted if needed. Invalid on deleted profiles. @@ -67,6 +69,11 @@ public: void removeAvatar(const QString& ownerId); ///< Removes a cached avatar void removeAvatar(); ///< Removes our own avatar + /// Returns true if the history is enabled in the settings, and loaded successfully for this profile + bool isHistoryEnabled(); + /// May return a nullptr if the history failed to load + History* getHistory(); + /// Removes the profile permanently /// It is invalid to call loadToxSave or saveToxSave on a deleted profile /// Updates the profiles vector @@ -100,6 +107,7 @@ private: QThread* coreThread; QString name, password; TOX_PASS_KEY passkey; + std::unique_ptr history; bool newProfile; ///< True if this is a newly created profile, with no .tox save file yet. bool isRemoved; ///< True if the profile has been removed by remove() static QVector profiles; diff --git a/src/persistence/settings.cpp b/src/persistence/settings.cpp index 3aa214a45..da06b74ff 100644 --- a/src/persistence/settings.cpp +++ b/src/persistence/settings.cpp @@ -26,7 +26,6 @@ #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 diff --git a/src/video/netcamview.cpp b/src/video/netcamview.cpp index b6e1c29d0..997bbfe0a 100644 --- a/src/video/netcamview.cpp +++ b/src/video/netcamview.cpp @@ -56,16 +56,16 @@ NetCamView::NetCamView(int friendId, QWidget* parent) frameLayout->setMargin(0); updateRatio(); - connect(selfVideoSurface, &VideoSurface::ratioChanged, this, &NetCamView::updateRatio); + connections += connect(selfVideoSurface, &VideoSurface::ratioChanged, this, &NetCamView::updateRatio); - connect(videoSurface, &VideoSurface::boundaryChanged, [this]() + connections += connect(videoSurface, &VideoSurface::boundaryChanged, [this]() { QRect boundingRect = videoSurface->getBoundingRect(); updateFrameSize(boundingRect.size()); selfFrame->setBoundary(boundingRect); }); - connect(videoSurface, &VideoSurface::ratioChanged, [this]() + connections += connect(videoSurface, &VideoSurface::ratioChanged, [this]() { selfFrame->setMinimumWidth(selfFrame->minimumHeight() * selfVideoSurface->getRatio()); QRect boundingRect = videoSurface->getBoundingRect(); @@ -73,12 +73,12 @@ NetCamView::NetCamView(int friendId, QWidget* parent) selfFrame->resetBoundary(boundingRect); }); - connect(Core::getInstance(), &Core::selfAvatarChanged, [this](const QPixmap& pixmap) + connections += connect(Core::getInstance(), &Core::selfAvatarChanged, [this](const QPixmap& pixmap) { selfVideoSurface->setAvatar(pixmap); }); - connect(Core::getInstance(), &Core::friendAvatarChanged, [this](int FriendId, const QPixmap& pixmap) + connections += connect(Core::getInstance(), &Core::friendAvatarChanged, [this](int FriendId, const QPixmap& pixmap) { if (this->friendId == FriendId) videoSurface->setAvatar(pixmap); @@ -92,6 +92,12 @@ NetCamView::NetCamView(int friendId, QWidget* parent) videoMode.FPS = Settings::getInstance().getCamVideoFPS(); } +NetCamView::~NetCamView() +{ + for (QMetaObject::Connection conn : connections) + disconnect(conn); +} + void NetCamView::show(VideoSource *source, const QString &title) { setSource(source); diff --git a/src/video/netcamview.h b/src/video/netcamview.h index 2aa1309c7..79e72fc4d 100644 --- a/src/video/netcamview.h +++ b/src/video/netcamview.h @@ -21,6 +21,7 @@ #define NETCAMVIEW_H #include "genericnetcamview.h" +#include class QHBoxLayout; struct vpx_image; @@ -34,6 +35,7 @@ class NetCamView : public GenericNetCamView public: NetCamView(int friendId, QWidget *parent=0); + ~NetCamView(); virtual void show(VideoSource* source, const QString& title); virtual void hide(); @@ -54,6 +56,7 @@ private: MovableWidget* selfFrame; int friendId; bool e = false; + QVector connections; }; #endif // NETCAMVIEW_H diff --git a/src/widget/about/aboutuser.cpp b/src/widget/about/aboutuser.cpp index 1b6ba9bc3..c71f21075 100644 --- a/src/widget/about/aboutuser.cpp +++ b/src/widget/about/aboutuser.cpp @@ -1,7 +1,6 @@ #include "aboutuser.h" #include "ui_aboutuser.h" #include "src/persistence/settings.h" -#include "src/persistence/historykeeper.h" #include "src/persistence/profile.h" #include "src/nexus.h" @@ -97,7 +96,9 @@ void AboutUser::onAcceptedClicked() void AboutUser::onRemoveHistoryClicked() { - HistoryKeeper::getInstance()->removeFriendHistory(toxId.publicKey); + History* history = Nexus::getProfile()->getHistory(); + if (history) + history->removeFriendHistory(toxId.publicKey); QMessageBox::StandardButton reply; reply = QMessageBox::information(this, tr("History removed"), diff --git a/src/widget/form/addfriendform.cpp b/src/widget/form/addfriendform.cpp index 90a57098d..b55aa8389 100644 --- a/src/widget/form/addfriendform.cpp +++ b/src/widget/form/addfriendform.cpp @@ -177,7 +177,7 @@ void AddFriendForm::setIdFromClipboard() QString id = clipboard->text().trimmed(); if (Core::getInstance()->isReady() && !id.isEmpty() && ToxId::isToxId(id)) { - if (!ToxId(id).isActiveProfile()) + if (!ToxId(id).isSelf()) toxId.setText(id); } } diff --git a/src/widget/form/chatform.cpp b/src/widget/form/chatform.cpp index 1319a352e..5a482863b 100644 --- a/src/widget/form/chatform.cpp +++ b/src/widget/form/chatform.cpp @@ -38,7 +38,6 @@ #include "src/core/core.h" #include "src/core/coreav.h" #include "src/friend.h" -#include "src/persistence/historykeeper.h" #include "src/widget/style.h" #include "src/persistence/settings.h" #include "src/core/cstring.h" @@ -61,6 +60,8 @@ #include "src/widget/translator.h" #include "src/video/videosource.h" #include "src/video/camerasource.h" +#include "src/nexus.h" +#include "src/persistence/profile.h" ChatForm::ChatForm(Friend* chatFriend) : f(chatFriend) @@ -208,7 +209,7 @@ void ChatForm::startFileSend(ToxFile file) return; QString name; - if (!previousId.isActiveProfile()) + if (!previousId.isSelf()) { Core* core = Core::getInstance(); name = core->getUsername(); @@ -694,7 +695,7 @@ void ChatForm::loadHistory(QDateTime since, bool processUndelivered) } } - auto msgs = HistoryKeeper::getInstance()->getChatHistory(HistoryKeeper::ctSingle, f->getToxId().publicKey, since, now); + auto msgs = Nexus::getProfile()->getHistory()->getChatHistory(f->getToxId().publicKey, since, now); ToxId storedPrevId = previousId; ToxId prevId; @@ -716,13 +717,13 @@ void ChatForm::loadHistory(QDateTime since, bool processUndelivered) // Show each messages ToxId authorId = ToxId(it.sender); - QString authorStr = !it.dispName.isEmpty() ? it.dispName : (authorId.isActiveProfile() ? Core::getInstance()->getUsername() : resolveToxId(authorId)); + QString authorStr = !it.dispName.isEmpty() ? it.dispName : (authorId.isSelf() ? Core::getInstance()->getUsername() : resolveToxId(authorId)); bool isAction = it.message.startsWith("/me ", Qt::CaseInsensitive); ChatMessage::Ptr msg = ChatMessage::createChatMessage(authorStr, - isAction ? it.message.right(it.message.length() - 4) : it.message, + isAction ? it.message.mid(4) : it.message, isAction ? ChatMessage::ACTION : ChatMessage::NORMAL, - authorId.isActiveProfile(), + authorId.isSelf(), QDateTime()); if (!isAction && (prevId == authorId) && (prevMsgDateTime.secsTo(msgDateTime) < getChatLog()->repNameAfter) ) @@ -731,7 +732,7 @@ void ChatForm::loadHistory(QDateTime since, bool processUndelivered) prevId = authorId; prevMsgDateTime = msgDateTime; - if (it.isSent || !authorId.isActiveProfile()) + if (it.isSent || !authorId.isSelf()) { msg->markAsSent(msgDateTime); } @@ -810,6 +811,9 @@ void ChatForm::onScreenshotTaken(const QPixmap &pixmap) { void ChatForm::onLoadHistory() { + if (!Nexus::getProfile()->isHistoryEnabled()) + return; + LoadHistoryDialog dlg; if (dlg.exec()) @@ -937,9 +941,6 @@ void ChatForm::SendMessageStr(QString msg) bool status = !Settings::getInstance().getFauxOfflineMessaging(); - int id = HistoryKeeper::getInstance()->addChatEntry(f->getToxId().publicKey, qt_msg_hist, - Core::getInstance()->getSelfId().publicKey, timestamp, status, Core::getInstance()->getUsername()); - ChatMessage::Ptr ma = addSelfMessage(qt_msg, isAction, timestamp, false); int rec; @@ -948,7 +949,13 @@ void ChatForm::SendMessageStr(QString msg) else rec = Core::getInstance()->sendMessage(f->getFriendID(), qt_msg); - getOfflineMsgEngine()->registerReceipt(rec, id, ma); + auto* offMsgEngine = getOfflineMsgEngine(); + Nexus::getProfile()->getHistory()->addNewMessage(f->getToxId().publicKey, qt_msg_hist, + Core::getInstance()->getSelfId().publicKey, timestamp, status, Core::getInstance()->getUsername(), + [offMsgEngine,rec,ma](int64_t id) + { + offMsgEngine->registerReceipt(rec, id, ma); + }); msgEdit->setLastMessage(msg); //set last message only when sending it diff --git a/src/widget/form/genericchatform.cpp b/src/widget/form/genericchatform.cpp index 0d12e725f..55a8a05fb 100644 --- a/src/widget/form/genericchatform.cpp +++ b/src/widget/form/genericchatform.cpp @@ -312,7 +312,7 @@ void GenericChatForm::onChatContextMenuRequested(QPoint pos) ChatMessage::Ptr GenericChatForm::addMessage(const ToxId& author, const QString &message, bool isAction, const QDateTime &datetime, bool isSent) { - bool authorIsActiveProfile = author.isActiveProfile(); + bool authorIsActiveProfile = author.isSelf(); QString authorStr = authorIsActiveProfile ? Core::getInstance()->getUsername() : resolveToxId(author); ChatMessage::Ptr msg; @@ -347,7 +347,7 @@ ChatMessage::Ptr GenericChatForm::addSelfMessage(const QString &message, bool is void GenericChatForm::addAlertMessage(const ToxId &author, QString message, QDateTime datetime) { QString authorStr = resolveToxId(author); - ChatMessage::Ptr msg = ChatMessage::createChatMessage(authorStr, message, ChatMessage::ALERT, author.isActiveProfile(), datetime); + ChatMessage::Ptr msg = ChatMessage::createChatMessage(authorStr, message, ChatMessage::ALERT, author.isSelf(), datetime); insertChatMessage(msg); if ((author == previousId) && (prevMsgDateTime.secsTo(QDateTime::currentDateTime()) < getChatLog()->repNameAfter)) diff --git a/src/widget/form/groupchatform.cpp b/src/widget/form/groupchatform.cpp index 51e6c6114..e837d01ec 100644 --- a/src/widget/form/groupchatform.cpp +++ b/src/widget/form/groupchatform.cpp @@ -27,7 +27,6 @@ #include "src/core/core.h" #include "src/core/coreav.h" #include "src/widget/style.h" -#include "src/persistence/historykeeper.h" #include "src/widget/flowlayout.h" #include "src/widget/translator.h" #include "src/video/groupnetcamview.h" diff --git a/src/widget/form/profileform.cpp b/src/widget/form/profileform.cpp index 7d8a71fc1..f03747b5a 100644 --- a/src/widget/form/profileform.cpp +++ b/src/widget/form/profileform.cpp @@ -30,7 +30,6 @@ #include "src/widget/widget.h" #include "src/widget/gui.h" #include "src/widget/style.h" -#include "src/persistence/historykeeper.h" #include "src/persistence/profilelocker.h" #include "src/persistence/profile.h" #include "src/widget/translator.h" diff --git a/src/widget/form/settings/aboutsettings.ui b/src/widget/form/settings/aboutsettings.ui index f4662baa8..054542e82 100644 --- a/src/widget/form/settings/aboutsettings.ui +++ b/src/widget/form/settings/aboutsettings.ui @@ -30,8 +30,8 @@ 0 0 - 493 - 534 + 496 + 626 @@ -160,7 +160,7 @@ - Update text + Update text Qt::AlignCenter @@ -209,12 +209,12 @@ <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-size:10pt; color:#000000;">Copyright © 2014-2015 by The qTox Project</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu'; font-size:10pt;">qTox is a Qt-based graphical interface for Tox.</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-size:10pt;">qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-size:10pt;">qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. </span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu'; font-size:10pt;">You should have received a copy of the GNU General Public License along with this program. If not, see </span><a href="https://www.gnu.org/copyleft/gpl.html"><span style=" font-family:'Ubuntu'; font-size:10pt; text-decoration: underline; color:#007af4;">https://www.gnu.org/copyleft/gpl.html</span></a><span style=" font-family:'Ubuntu'; font-size:10pt;">.</span></p></body></html> +</style></head><body style=" font-family:'Noto Sans'; font-size:10pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; color:#000000;">Copyright © 2014-2015 by The qTox Project</span></p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu';">qTox is a Qt-based graphical interface for Tox.</span></p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif';">qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.</span></p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif';">qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. </span></p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu';">You should have received a copy of the GNU General Public License along with this program. If not, see </span><a href="https://www.gnu.org/copyleft/gpl.html"><span style=" font-family:'Ubuntu'; text-decoration: underline; color:#007af4;">https://www.gnu.org/copyleft/gpl.html</span></a><span style=" font-family:'Ubuntu';">.</span></p></body></html> diff --git a/src/widget/form/settings/advancedform.cpp b/src/widget/form/settings/advancedform.cpp index dbd035928..58d5e39c9 100644 --- a/src/widget/form/settings/advancedform.cpp +++ b/src/widget/form/settings/advancedform.cpp @@ -20,7 +20,6 @@ #include "ui_advancedsettings.h" #include "advancedform.h" -#include "src/persistence/historykeeper.h" #include "src/persistence/settings.h" #include "src/persistence/db/plaindb.h" #include "src/widget/translator.h" @@ -31,28 +30,11 @@ AdvancedForm::AdvancedForm() : bodyUI = new Ui::AdvancedSettings; bodyUI->setupUi(this); - bodyUI->dbLabel->setTextInteractionFlags(Qt::TextBrowserInteraction); - bodyUI->dbLabel->setOpenExternalLinks(true); - bodyUI->cbMakeToxPortable->setChecked(Settings::getInstance().getMakeToxPortable()); - bodyUI->syncTypeComboBox->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLength); - bodyUI->syncTypeComboBox->addItems({tr("Synchronized - safe (recommended)"), - tr("Partially async - risky (20% faster)"), - tr("Asynchronous - dangerous (fastest)") - }); - int index = 2 - static_cast(Settings::getInstance().getDbSyncType()); - bodyUI->syncTypeComboBox->setCurrentIndex(index); connect(bodyUI->cbMakeToxPortable, &QCheckBox::stateChanged, this, &AdvancedForm::onMakeToxPortableUpdated); - connect(bodyUI->syncTypeComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(onDbSyncTypeUpdated())); connect(bodyUI->resetButton, SIGNAL(clicked()), this, SLOT(resetToDefault())); - for (QComboBox* cb : findChildren()) - { - cb->installEventFilter(this); - cb->setFocusPolicy(Qt::StrongFocus); - } - for (QCheckBox *cb : findChildren()) // this one is to allow scrolling on checkboxes { cb->installEventFilter(this); @@ -72,24 +54,14 @@ void AdvancedForm::onMakeToxPortableUpdated() Settings::getInstance().setMakeToxPortable(bodyUI->cbMakeToxPortable->isChecked()); } -void AdvancedForm::onDbSyncTypeUpdated() -{ - int index = 2 - bodyUI->syncTypeComboBox->currentIndex(); - Settings::getInstance().setDbSyncType(index); - HistoryKeeper::getInstance()->setSyncType(Settings::getInstance().getDbSyncType()); -} - void AdvancedForm::resetToDefault() { - int index = 2 - static_cast(Db::syncType::stFull); - bodyUI->syncTypeComboBox->setCurrentIndex(index); - onDbSyncTypeUpdated(); } bool AdvancedForm::eventFilter(QObject *o, QEvent *e) { if ((e->type() == QEvent::Wheel) && - (qobject_cast(o) || qobject_cast(o) || qobject_cast(o))) + (qobject_cast(o) || qobject_cast(o))) { e->ignore(); return true; @@ -100,7 +72,4 @@ bool AdvancedForm::eventFilter(QObject *o, QEvent *e) void AdvancedForm::retranslateUi() { bodyUI->retranslateUi(this); - bodyUI->syncTypeComboBox->setItemText(0, tr("Synchronized - safe (recommended)")); - bodyUI->syncTypeComboBox->setItemText(1, tr("Partially async - risky (20% faster)")); - bodyUI->syncTypeComboBox->setItemText(2, tr("Asynchronous - dangerous (fastest)")); } diff --git a/src/widget/form/settings/advancedform.h b/src/widget/form/settings/advancedform.h index 1fb871f6f..8eea447f0 100644 --- a/src/widget/form/settings/advancedform.h +++ b/src/widget/form/settings/advancedform.h @@ -41,7 +41,6 @@ protected: private slots: void onMakeToxPortableUpdated(); - void onDbSyncTypeUpdated(); void resetToDefault(); private: diff --git a/src/widget/form/settings/advancedsettings.ui b/src/widget/form/settings/advancedsettings.ui index ff14b7950..abe13c920 100644 --- a/src/widget/form/settings/advancedsettings.ui +++ b/src/widget/form/settings/advancedsettings.ui @@ -24,8 +24,8 @@ 0 0 - 396 - 454 + 398 + 456 @@ -62,39 +62,6 @@ - - - - Chat history - - - - - - - - <html><head/><body><p><a href="http://www.sqlite.org/pragma.html#pragma_synchronous"><span style=" text-decoration: underline; color:#0000ff;">Writing to DB</span></a></p></body></html> - - - Qt::RichText - - - - - - - - 0 - 0 - - - - - - - - - diff --git a/src/widget/form/settings/privacyform.cpp b/src/widget/form/settings/privacyform.cpp index 8a337939d..6afd7d756 100644 --- a/src/widget/form/settings/privacyform.cpp +++ b/src/widget/form/settings/privacyform.cpp @@ -21,12 +21,14 @@ #include "ui_privacysettings.h" #include "src/widget/form/settingswidget.h" #include "src/persistence/settings.h" -#include "src/persistence/historykeeper.h" #include "src/core/core.h" #include "src/widget/widget.h" #include "src/widget/gui.h" #include "src/widget/form/setpassworddialog.h" #include "src/widget/translator.h" +#include "src/nexus.h" +#include "src/persistence/profile.h" +#include "src/persistence/history.h" #include #include #include @@ -63,7 +65,7 @@ void PrivacyForm::onEnableLoggingUpdated() QMessageBox::Yes|QMessageBox::No); if (dialogDelHistory == QMessageBox::Yes) { - HistoryKeeper::getInstance()->removeHistory(); + Nexus::getProfile()->getHistory()->eraseHistory(); } } } diff --git a/src/widget/friendlistwidget.cpp b/src/widget/friendlistwidget.cpp index f74fbda18..707091ad7 100644 --- a/src/widget/friendlistwidget.cpp +++ b/src/widget/friendlistwidget.cpp @@ -26,7 +26,6 @@ #include "groupwidget.h" #include "circlewidget.h" #include "widget.h" -#include "src/persistence/historykeeper.h" #include #include #include diff --git a/src/widget/gui.cpp b/src/widget/gui.cpp index 8b4362e28..9112e99b3 100644 --- a/src/widget/gui.cpp +++ b/src/widget/gui.cpp @@ -143,7 +143,12 @@ void GUI::showError(const QString& title, const QString& msg) { if (QThread::currentThread() == qApp->thread()) { - getInstance()._showError(title, msg); + // If the GUI hasn't started yet and we're on the main thread, + // we still want to be able to show error messages + if (!Nexus::getDesktopGUI()) + QMessageBox::critical(nullptr, title, msg); + else + getInstance()._showError(title, msg); } else { diff --git a/src/widget/widget.cpp b/src/widget/widget.cpp index 363f3d082..4c4ba6c51 100644 --- a/src/widget/widget.cpp +++ b/src/widget/widget.cpp @@ -37,7 +37,6 @@ #include "friendlistwidget.h" #include "form/chatform.h" #include "maskablepixmapwidget.h" -#include "src/persistence/historykeeper.h" #include "src/net/autoupdate.h" #include "src/audio/audio.h" #include "src/platform/timer.h" @@ -91,8 +90,11 @@ bool toxActivateEventHandler(const QByteArray&) { - if (!Widget::getInstance()->isActiveWindow()) - Widget::getInstance()->forceShow(); + Widget* widget = Nexus::getDesktopGUI(); + if (!widget) + return true; + if (!widget->isActiveWindow()) + widget->forceShow(); return true; } @@ -116,6 +118,10 @@ void Widget::init() { ui->setupUi(this); + QIcon themeIcon = QIcon::fromTheme("qtox"); + if (!themeIcon.isNull()) + setWindowIcon(themeIcon); + timer = new QTimer(); timer->start(1000); offlineMsgTimer = new QTimer(); @@ -424,7 +430,7 @@ void Widget::updateIcons() status = QStringLiteral("offline"); } - QIcon ico; + QIcon ico = QIcon::fromTheme("qtox-" + status); if (ico.isNull()) { QString color = Settings::getInstance().getLightTrayIcon() ? "light" : "dark"; @@ -1074,7 +1080,7 @@ void Widget::onFriendMessageReceived(int friendId, const QString& message, bool QDateTime timestamp = QDateTime::currentDateTime(); f->getChatForm()->addMessage(f->getToxId(), message, isAction, timestamp, true); - HistoryKeeper::getInstance()->addChatEntry(f->getToxId().publicKey, isAction ? "/me " + f->getDisplayedName() + " " + message : message, + Nexus::getProfile()->getHistory()->addNewMessage(f->getToxId().publicKey, isAction ? "/me " + f->getDisplayedName() + " " + message : message, f->getToxId().publicKey, timestamp, true, f->getDisplayedName()); newFriendMessageAlert(friendId); @@ -1223,8 +1229,11 @@ bool Widget::newMessageAlert(QWidget* currentWindow, bool isActive, bool sound, if (notify) { - QApplication::alert(currentWindow); - eventFlag = true; + if (inactiveWindow) + { + QApplication::alert(currentWindow); + eventFlag = true; + } if (Settings::getInstance().getShowWindow()) { @@ -1271,7 +1280,7 @@ void Widget::removeFriend(Friend* f, bool fake) if (!ask.accepted()) return; else if (ask.removeHistory()) - HistoryKeeper::getInstance()->removeFriendHistory(f->getToxId().publicKey); + Nexus::getProfile()->getHistory()->removeFriendHistory(f->getToxId().publicKey); } f->getFriendWidget()->setAsInactiveChatroom(); @@ -1434,7 +1443,7 @@ void Widget::onGroupMessageReceived(int groupnumber, int peernumber, const QStri return; ToxId author = Core::getInstance()->getGroupPeerToxId(groupnumber, peernumber); - bool targeted = !author.isActiveProfile() && (message.contains(nameMention) || message.contains(sanitizedNameMention)); + bool targeted = !author.isSelf() && (message.contains(nameMention) || message.contains(sanitizedNameMention)); if (targeted && !isAction) g->getChatForm()->addAlertMessage(author, message, QDateTime::currentDateTime()); else @@ -1574,6 +1583,10 @@ bool Widget::event(QEvent * e) { switch (e->type()) { + case QEvent::MouseButtonPress: + case QEvent::MouseButtonDblClick: + focusChatInput(); + break; case QEvent::WindowActivate: if (activeChatroomWidget != nullptr) { @@ -1581,6 +1594,7 @@ bool Widget::event(QEvent * e) activeChatroomWidget->updateStatusLight(); setWindowTitle(activeChatroomWidget->getTitle()); } + if (eventFlag) { eventFlag = false; @@ -1588,13 +1602,15 @@ bool Widget::event(QEvent * e) updateIcons(); } + focusChatInput(); + #ifdef Q_OS_MAC emit windowStateChanged(windowState()); case QEvent::WindowStateChange: Nexus::getInstance().updateWindowsStates(); #endif - + break; default: break; } @@ -2077,3 +2093,14 @@ void Widget::retranslateUi() previousConversationAction->setText(tr("Previous Conversation")); #endif } + +void Widget::focusChatInput() +{ + if (activeChatroomWidget) + { + if (Friend* f = activeChatroomWidget->getFriend()) + f->getChatForm()->focusInput(); + else if (Group* g = activeChatroomWidget->getGroup()) + g->getChatForm()->focusInput(); + } +} diff --git a/src/widget/widget.h b/src/widget/widget.h index 87a698978..7ca4f18e2 100644 --- a/src/widget/widget.h +++ b/src/widget/widget.h @@ -221,6 +221,7 @@ private: static bool filterOnline(int index); static bool filterOffline(int index); void retranslateUi(); + void focusChatInput(); private: SystemTrayIcon *icon; diff --git a/translations/de.ts b/translations/de.ts index 9c63fe2ae..c100d0f3f 100644 --- a/translations/de.ts +++ b/translations/de.ts @@ -110,6 +110,15 @@ Zu hohe Auflösungen können daher zu Problemen in Videoanrufen führen.Qt version: Qt-Version: + + Restart qTox to install version %1 + Starte qTox neu, um Version %1 zu installieren. + + + qTox is downloading update %1 + %1 is the version of the update + qTox lädt Update %1 herunter. + AboutSettings @@ -117,13 +126,30 @@ Zu hohe Auflösungen können daher zu Problemen in Videoanrufen führen.Version Version + + You are using qTox version $GIT_DESCRIBE. + Du verwendest qTox $GIT_DESCRIBE. + + + Downloading update: %p% + Herunterladen des Updates: %p % + License Lizenz - - You are using a qTox nightly build. - Du verwendest qTox "Nightly Build". + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Noto Sans'; font-size:10pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; color:#000000;">Copyright © 2014-2015 by The qTox Project</span></p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu';">qTox is a Qt-based graphical interface for Tox.</span></p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif';">qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.</span></p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif';">qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. </span></p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu';">You should have received a copy of the GNU General Public License along with this program. If not, see </span><a href="https://www.gnu.org/copyleft/gpl.html"><span style=" font-family:'Ubuntu'; text-decoration: underline; color:#007af4;">https://www.gnu.org/copyleft/gpl.html</span></a><span style=" font-family:'Ubuntu';">.</span></p></body></html> + Ausgelassen, da Lizenz original auf Englisch + Commit hash: <a href="https://github.com/tux3/qTox/commit/$GIT_VERSION">$GIT_VERSION</a> @@ -137,26 +163,13 @@ Zu hohe Auflösungen können daher zu Problemen in Videoanrufen führen.Qt version: Qt-Version: - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'Oxygen-Sans'; font-size:10pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; color:#000000;">Copyright © 2014-2015 by The qTox Project</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu';">qTox is a Qt-based graphical interface for Tox.</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif';">qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif';">qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. </span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu';">You should have received a copy of the GNU General Public License along with this program. If not, see </span><a href="https://www.gnu.org/copyleft/gpl.html"><span style=" font-family:'Ubuntu'; text-decoration: underline; color:#007af4;">https://www.gnu.org/copyleft/gpl.html</span></a><span style=" font-family:'Ubuntu';">.</span></p></body></html> - ausgelassen, Lizenz Original in Englisch - - Authors Entwickler <html><head/><body><p>Original author: <a href="https://github.com/tux3"><span style=" text-decoration: underline; color:#0000ff;">tux3</span></a></p><p>See a full list of <a href="https://github.com/tux3/qTox/graphs/contributors"><span style=" text-decoration: underline; color:#0000ff;">contributors</span></a> at Github</p></body></html> - <html><head/><body><p>Ursprünglich entwickelt von: <a href="https://github.com/tux3"><span style=" text-decoration: underline; color:#0000ff;">tux3</span></a></p><p>Alle Mitwirkenden findest du unter <a href="https://github.com/tux3/qTox/graphs/contributors"><span style=" text-decoration: underline; color:#0000ff;">contributors</span></a> auf GitHub</p></body></html> + <html><head/><body><p>Ursprünglich entwickelt von: <a href="https://github.com/tux3"><span style=" text-decoration: underline; color:#0000ff;">tux3</span></a></p><p>Alle Mitwirkenden findest du unter <a href="https://github.com/tux3/qTox/graphs/contributors"><span style=" text-decoration: underline; color:#0000ff;">Contributors</span></a> auf GitHub.</p></body></html> Known Issues @@ -164,7 +177,7 @@ p, li { white-space: pre-wrap; } <html><head/><body><p>A list of all known issues may be found at our <a href="https://github.com/tux3/qTox/issues"><span style=" text-decoration: underline; color:#0000ff;">bug-tracker</span></a> at Github. If you discover a bug or security vulnerability within qTox, please report it according to the guidelines in our <a href="https://github.com/tux3/qTox/wiki/Writing-Useful-Bug-Reports"><span style=" text-decoration: underline; color:#0000ff;">Writing Useful Bug Reports</span></a> wiki article.</p></body></html> - <html><head/><body><p>Eine Liste aller bekannten Probleme findest du in unserem <a href="https://github.com/tux3/qTox/issues"><span style=" text-decoration: underline; color:#0000ff;">Bug-Tracker (Englisch)</span></a> auf GitHub. Wenn du einen Fehler oder eine Sicherheitslücke in qTox findest, melde sie bitte wie in unserem Wiki Artikel <a href="https://github.com/tux3/qTox/wiki/Writing-Useful-Bug-Reports"><span style=" text-decoration: underline; color:#0000ff;">Writing Useful Bug Reports (Englisch)</span></a> beschrieben.</p></body></html> + <html><head/><body><p>Eine Liste aller bekannten Probleme findest du in unserem <a href="https://github.com/tux3/qTox/issues"><span style=" text-decoration: underline; color:#0000ff;">Bug-Tracker (Englisch)</span></a> auf GitHub. Wenn du einen Fehler oder eine Sicherheitslücke in qTox findest, melde sie bitte wie in unserem Wiki-Artikel <a href="https://github.com/tux3/qTox/wiki/Writing-Useful-Bug-Reports"><span style=" text-decoration: underline; color:#0000ff;">Writing Useful Bug Reports (Englisch)</span></a> beschrieben.</p></body></html> @@ -822,7 +835,7 @@ Disabling chat history now will leave the encrypted history intact (but not usab Show details - Zeige Profil + Zeige Details Choose an auto accept directory @@ -1793,6 +1806,10 @@ Teile sie einfach deinen Bekannten mit! QObject + + Version %1, %2 + Version %1, %2 + Update The title of a message box diff --git a/translations/it.ts b/translations/it.ts index 025ee8b6e..1380c688f 100644 --- a/translations/it.ts +++ b/translations/it.ts @@ -24,6 +24,10 @@ Default resolution Risoluzione di default + + None + Nessuno + AVSettings @@ -120,12 +124,16 @@ NOTA: più alta è la qualità video, più veloce deve essere la connessione ad Può capitare che la tua connessione ad internet non sia abbastanza veloce per gestire qualità video elevate, questo può causare problemi con le chiamate video. + + Rescan devices + Scansiona dispositivi + AboutForm About - About + Informazioni su qTox Qt version: @@ -146,6 +154,11 @@ qualità video elevate, questo può causare problemi con le chiamate video.You are using a qTox nightly build. Stai utilizzando la versione nightly build di qTox. + + + You are using qTox version $GIT_DESCRIBE. + Stai utilizzando la versione $GIT_DESCRIBE. + Commit hash: <a href="https://github.com/tux3/qTox/commit/$GIT_VERSION">$GIT_VERSION</a> Commit hash: <a href="https://github.com/tux3/qTox/commit/$GIT_VERSION">$GIT_VERSION</a> @@ -172,15 +185,7 @@ p, li { white-space: pre-wrap; } <p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif';">qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.</span></p> <p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif';">qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. </span></p> <p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu';">You should have received a copy of the GNU General Public License along with this program. If not, see </span><a href="https://www.gnu.org/copyleft/gpl.html"><span style=" font-family:'Ubuntu'; text-decoration: underline; color:#007af4;">https://www.gnu.org/copyleft/gpl.html</span></a><span style=" font-family:'Ubuntu';">.</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'Oxygen-Sans'; font-size:10pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; color:#000000;">Copyright © 2014-2015 by The qTox Project</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu';">qTox è un'interfaccia grafica per Tox basato su Qt.</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif';">qTox è un software libero: è possibile ridistribuirlo o modificarlo secondo i termini della GNU General Public License come pubblicata dalla Free Software Foundation, o la versione 3 della licenza o (a tua scelta) qualsiasi versione successiva.</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif';">qTox è distribuito nella speranza che sia utile, ma senza alcuna garanzia; senza neppure la garanzia implicita di commerciabilità o idoneità per uno scopo particolare. Vedi la GNU General Public License per maggiori dettagli. </span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu';">Dovresti aver ricevuto una copia della GNU General Public License insieme a questo programma. In caso contrario, vedere </span><a href="https://www.gnu.org/copyleft/gpl.html"><span style=" font-family:'Ubuntu'; text-decoration: underline; color:#007af4;">https://www.gnu.org/copyleft/gpl.html</span></a><span style=" font-family:'Ubuntu';">.</span></p></body></html> + Authors @@ -188,7 +193,7 @@ p, li { white-space: pre-wrap; } <html><head/><body><p>Original author: <a href="https://github.com/tux3"><span style=" text-decoration: underline; color:#0000ff;">tux3</span></a></p><p>See a full list of <a href="https://github.com/tux3/qTox/graphs/contributors"><span style=" text-decoration: underline; color:#0000ff;">contributors</span></a> at Github</p></body></html> - <html><head/><body><p>Autore originale : <a href="https://github.com/tux3"><span style=" text-decoration: underline; color:#0000ff;">tux3</span></a></p><p>Guarda la lista completa degli <a href="https://github.com/tux3/qTox/graphs/contributors"><span style=" text-decoration: underline; color:#0000ff;">sviluppatori</span></a> su Github</p></body></html> + <html><head/><body><p>Autore originale: <a href="https://github.com/tux3"><span style=" text-decoration: underline; color:#0000ff;">tux3</span></a></p><p>Guarda la lista completa degli <a href="https://github.com/tux3/qTox/graphs/contributors"><span style=" text-decoration: underline; color:#0000ff;">sviluppatori</span></a> su Github</p></body></html> Known Issues @@ -196,14 +201,14 @@ p, li { white-space: pre-wrap; } <html><head/><body><p>A list of all known issues may be found at our <a href="https://github.com/tux3/qTox/issues"><span style=" text-decoration: underline; color:#0000ff;">bug-tracker</span></a> at Github. If you discover a bug or security vulnerability within qTox, please report it according to the guidelines in our <a href="https://github.com/tux3/qTox/wiki/Writing-Useful-Bug-Reports"><span style=" text-decoration: underline; color:#0000ff;">Writing Useful Bug Reports</span></a> wiki article.</p></body></html> - <html><head/><body><p>Un elenco di tutti i problemi noti può essere trovato presso il nostro <a href="https://github.com/tux3/qTox/issues"><span style=" text-decoration: underline; color:#0000ff;">bug-tracker</span></a> su Github. Se si trova un bug o una vulnerabilità all'interno di qTox, si prega di segnalarlo secondo le linee guida nel nostro <a href="https://github.com/tux3/qTox/wiki/Writing-Useful-Bug-Reports"><span style=" text-decoration: underline; color:#0000ff;">Writing Useful Bug Reports</span></a> articolo di wiki.</p></body></html> + <html><head/><body><p>Un elenco di tutti i problemi noti può essere trovato presso il nostro <a href="https://github.com/tux3/qTox/issues"><span style=" text-decoration: underline; color:#0000ff;">bug-tracker</span></a> su Github. Se si trova un bug o una vulnerabilità all'interno di qTox, si prega di segnalarlo secondo le linee guida del nostro articolo wiki <a href="https://github.com/tux3/qTox/wiki/Writing-Useful-Bug-Reports"><span style=" text-decoration: underline; color:#0000ff;">Writing Useful Bug Reports</span></a>.</p></body></html> AboutUser Dialog - + Dialogo username @@ -231,11 +236,11 @@ p, li { white-space: pre-wrap; } Auto accept for this contact is disabled - + I file scaricati automaticamente da questo contatto sono disabilitati Auto accept files - Accetta automaticamente i file + Accetta automaticamente i file inviati da questo contatto Remove history (operation can not be undone!) @@ -243,7 +248,7 @@ p, li { white-space: pre-wrap; } Notes - Note + Appunti You can save comment about this contact here. @@ -252,7 +257,7 @@ p, li { white-space: pre-wrap; } Choose an auto accept directory popup title - Scegliere una cartella dove accettare automaticamente i file + Scegliere una cartella per i file scaricati automaticamente History removed @@ -326,6 +331,11 @@ Ignorare le impostazioni del proxy e connettersi direttamente alla rete Tox?DNS error Questo Tox ID non esiste + + either 76 hexadecimal characters or name@example.com + Tox ID format description + 76 caratteri esadecimali oppure nome@esempio.com + AdvancedForm @@ -335,19 +345,16 @@ Ignorare le impostazioni del proxy e connettersi direttamente alla rete Tox?Avanzate - - FULL - very safe, slowest (recommended) - COMPLETO - molto sicuro; lento (consigliato) + Synchronized - safe (recommended) + Sincronizzato - sicuro (consigliato) - - NORMAL - almost as safe as FULL, about 20% faster than FULL - NORMALE - quasi sicuro come COMPLETO, ma circa 20% più veloce + Partially async - risky (20% faster) + Parzialmente sincronizzato - rischioso (20% più veloce) - - OFF - disables all safety, when something goes wrong your history may be lost, fastest (not recommended) - DISABILITATO - disabilita tutta la sicurezza, quando qualcosa va male i log possono essere persi; veloce (non consigliato) + Asynchronous - dangerous (fastest) + Asincrono - pericoloso (più veloce) @@ -385,8 +392,8 @@ Ignorare le impostazioni del proxy e connettersi direttamente alla rete Tox? - <html><head/><body><p><a href="http://www.sqlite.org/pragma.html#pragma_synchronous"><span style=" text-decoration: underline; color:#0000ff;">Synchronous writing to DB</span></a></p></body></html> - <html><head/><body><p><a href="http://www.sqlite.org/pragma.html#pragma_synchronous"><span style=" text-decoration: underline; color:#0000ff;">Scritture sincrone sul DB + <html><head/><body><p><a href="http://www.sqlite.org/pragma.html#pragma_synchronous"><span style=" text-decoration: underline; color:#0000ff;">Writing to DB</span></a></p></body></html> + <html><head/><body><p><a href="http://www.sqlite.org/pragma.html#pragma_synchronous"><span style=" text-decoration: underline; color:#0000ff;">Scrittura nel DB</span></a></p></body></html> @@ -552,25 +559,23 @@ p, li { white-space: pre-wrap; } Cancel video call - Rifiuta videochiamata + Annulla videochiamata Cancel audio call - Rifiuta chiamata + Annulla chiamata - Calling to %1 + Calling %1 Stai chiamando %1 - Start audio call Avvia chiamata - Start video call Avvia videochiamata @@ -647,6 +652,23 @@ p, li { white-space: pre-wrap; } Scrivi il tuo messaggio qui... + + CircleWidget + + Rename circle + Menu for renaming a circle + Rinomina circolo + + + Remove circle + Menu for removing a circle + Elimina circolo + + + Open all in new window + Apri tutto in una nuova finestra + + Core @@ -834,20 +856,19 @@ Disabilitando la cronologia delle chat lascerà la cronologia criptata intatta ( FilesForm - - Transfered Files + Transferred Files "Headline" of the window - Files Trasferiti + File Trasferiti Downloads - Files Ricevuti + File Ricevuti Uploads - Files Inviati + File Inviati @@ -898,7 +919,7 @@ Disabilitando la cronologia delle chat lascerà la cronologia criptata intatta ( Auto accept files from this friend context menu entry - Accetta automaticamente i files inviati da questo contatto + Accetta automaticamente i file inviati da questo contatto @@ -906,6 +927,23 @@ Disabilitando la cronologia delle chat lascerà la cronologia criptata intatta ( Menu to invite a friend to a groupchat Invita nel gruppo + + Move to circle... + Menu to move a friend into a different circle + Sposta nel circolo... + + + To new circle + In un nuovo circolo + + + Remove from circle '%1' + Rimuovi dal circolo "%1" + + + Move to circle "%1" + Sposta nel circolo "%1" + Set alias... @@ -917,11 +955,15 @@ Disabilitando la cronologia delle chat lascerà la cronologia criptata intatta ( Menu to remove the friend from our friendlist Rimuovi contatto + + Show details + Mostra dettagli + Choose an auto accept directory popup title - Scegli dove salvare i files accettati automaticamente + Scegli dove salvare i file accettati automaticamente @@ -948,6 +990,10 @@ Disabilitando la cronologia delle chat lascerà la cronologia criptata intatta ( Offline Offline + + Open chat in new window + Apri la chat in una nuova finestra + User alias @@ -996,7 +1042,7 @@ Soprannome: Choose an auto accept directory popup title - Scegli dove salvare i files accettati automaticamente + Scegli dove salvare i file accettati automaticamente @@ -1037,7 +1083,7 @@ Soprannome: Show system tray icon - Mostra icona nella traybar + Mostra icona nella barra di sistema @@ -1054,7 +1100,7 @@ Soprannome: qTox will start minimized in tray. toolTip for Start in tray setting - qTox sarà avviato minimizzato nella traybar. + qTox sarà avviato minimizzato nella barra di sistema. @@ -1062,7 +1108,7 @@ Soprannome: instead of closing itself. toolTip for close to tray setting Premendo l'icona "chiudi" (X) qTox sarà minimizzato -nella traybar invece che essere chiuso. +nella barra di sistema invece che essere chiuso. @@ -1070,7 +1116,7 @@ nella traybar invece che essere chiuso. instead of system taskbar. toolTip for minimize to tray setting Premendo l'icona "minimizza" (_) qTox sarà minimizzato -nella traybar invece che nella taskbar. +nella barra di sistema invece che nella barra delle applicazioni. @@ -1080,7 +1126,7 @@ nella traybar invece che nella taskbar. Set where files will be saved. - Scegli dove salvare i files ricevuti. + Scegli dove salvare i file ricevuti. @@ -1097,6 +1143,16 @@ nella traybar invece che nella taskbar. Auto away after (0 to disable): Mostra come assente dopo (0 per disabilitare): + + + Autoaccept files + Accetta automaticamente i file + + + + Default directory to save files: + Cartella predefinita per salvare i file: + Chat @@ -1137,7 +1193,7 @@ nella traybar invece che nella taskbar. Autoaccept and save files: - Accetta automaticamente e salva i files in: + Accetta automaticamente e salva i file in: @@ -1166,6 +1222,27 @@ nella traybar invece che nella taskbar. Emoticon size: Dimensione: + + Open qTox's window when you receive a new message and no window is open yet. + tooltip for Show window setting + Apri la finestra di qTox quando si riceve un nuovo messaggio. + + + Start qTox on operating system startup (current profile). + Apri qTox all'avvio del sistema operativo (profilo corrente). + + + Open window + Apri finestra + + + Multiple windows mode + Modalità finestre multiple + + + Open each chat in an individual window + Apri ogni chat in una finestra singola + Style: @@ -1203,20 +1280,25 @@ nella traybar invece che nella taskbar. Text on proxy addr label Indirizzo: + + Port: + Text on proxy port label + Porta: + Start in tray - Avvia nella traybar + Avvia nella barra di sistema Close to tray - Chiudi nella traybar + Chiudi nella barra di sistema Minimize to tray - Minimizza nella traybar + Minimizza nella barra di sistema @@ -1354,7 +1436,7 @@ will be sent to them when they appear online to you. Send file(s) - Invia file(s) + Invia file @@ -1363,13 +1445,13 @@ will be sent to them when they appear online to you. - Start an audio call - Inizia una chiamata + Start audio call + Avvia chiamata - Start a video call - Inizia una videochiamata + Start video call + Avvia videochiamata @@ -1393,6 +1475,21 @@ will be sent to them when they appear online to you. Pulito + + GenericNetCamView + + Tox video + Video Tox + + + Show Messages + Mostra messaggi + + + Hide Messages + Nascondi messaggi + + GroupChatForm @@ -1477,6 +1574,10 @@ Title: Per impostare un nome puoi anche cliccare sul gruppo direttamente dalla chat. Nome gruppo: + + Open chat in new window + Apri la chat in una nuova finestra + IdentitySettings @@ -1524,6 +1625,10 @@ Puoi condividere questo codice QR al posto del tuo Tox ID. Profile Profilo + + <p><a href="file:///Dir_Path"><span style=" text-decoration: NONE; color:#000000;">Current profile location: Dir_Path</span></a></p> + <p><a href="file:///Dir_Path"><span style=" text-decoration: NONE; color:#000000;">Posizione del profilo attuale: Dir_Path</span></a></p> + Rename profile. @@ -1546,7 +1651,7 @@ Puoi condividere questo codice QR al posto del tuo Tox ID. Logout import profile button - Logout + Esci @@ -1600,12 +1705,12 @@ I profili non contengono la cronologia messaggi. Load History Dialog - Carica log + Carica cronologia chat Load history from: - Carica log dal giorno: + Carica cronologia chat del giorno: @@ -1630,8 +1735,7 @@ I profili non contengono la cronologia messaggi. Confirm: - Conferma -password: + Conferma: @@ -1643,6 +1747,18 @@ password: Create Account Crea Profilo + + Load automatically + Accedi automaticamente + + + Load + Accedi + + + Load Profile + Accedi al profilo + If the profile does not have a password, qTox can skip the login screen @@ -1663,6 +1779,10 @@ password: New Profile Nuovo Profilo + + + Create Profile + Crea Profilo @@ -1812,9 +1932,14 @@ Se incontri questo errore, riportalo agli sviluppatori. NoSpam - - The NoSpam is part of your Tox ID, if you are getting spammed with friend requests, change the NoSpam. - Il valore nospam è parte del tuo Tox ID. Se ricevi molte richieste d'amicizia indesiderate, cambia questo valore. + + NoSpam is part of your Tox ID. +If you are being spammed with friend requests, you should change your NoSpam. +People will be unable to add you with your old ID, but you will keep your current friends. + toolTip for nospam + Il valore NoSpam è parte del tuo ID Tox. +Se ricevi molte richieste di amicizia indesiderate, cambia questo valore. +Le persone non saranno più in grado di aggiungerti con il tuo vecchio Tox ID, ma manterrai i contatti attuali. @@ -1830,24 +1955,27 @@ Save format changes are possible, which may result in data loss. Il formato del file potrebbe cambiare (questo potrebbe causare perdita di dati). - - Nospam is part of your Tox ID. -It is there to help you change your Tox ID when you feel like you are getting too much spam friend requests. -When you change nospam, your current contacts still can communicate with you, -but new contacts need to know your new Tox ID to be able to add you. - toolTip for nospam - Il valore "nospam" fa parte del tuo Tox ID. -Lo si deve modificare quando si ritiene che si stanno ricevendo troppe richiste d'amicizia da persone sconosciute. -Quando modifichi il valore "nospam", i contatti che hai già aggiunto potranno continuare a comunicare con te, -ma i nuovi contatti dovranno conoscere il tuo nuovo Tox ID per aggiungerti. + + NoSpam is a part of your ID that can be changed at will. +If you are getting spammed with friend requests, change the NoSpam. + Il valore NoSpam è parte del tuo Tox ID che può essere cambiata a piacimento. +Se ricevi molte richieste di amicizia indesiderate cambia questo valore. ProfileForm + + User Profile + Profilo Utente + Choose a profile picture Scegli un'immagine per il profilo + + + Current profile: + Profilo attuale: @@ -2106,9 +2234,28 @@ Permettimi di aggiungerti alla mia lista contatti. Ridimensiona - - User Profile - Profilo Utente + None + No camera device set + Nessuno + + + + RemoveFriendDialog + + Remove friend + Rimuovi contatto + + + <html><head/><body><p>Are you sure you want to remove <span style=" font-weight:600;">&lt;name&gt;</span> from your contacts list?</p></body></html> + <html><head/><body><p>Sei sicuro di voler rimuovere <span style=" font-weight:600;">&lt;name&gt;</span> dalla tua lista contatti?</p></body></html> + + + Also remove chat history + Rimuovi anche la cronologia chat + + + Remove + Rimuovi @@ -2133,6 +2280,14 @@ Permettimi di aggiungerti alla mia lista contatti. Set your password Imposta password + + Confirm: + Conferma: + + + Password strength: %p% + Robustezza password: %p% + Repeat password @@ -2306,6 +2461,18 @@ Se non sei sicuro, scegli "No", così le informazioni inviate al serve Add friend Aggiungi contatto + + Add new circle... + Aggiungi un nuovo circolo... + + + By Name + Per Nome + + + By Activity + Per Attività + All @@ -2339,7 +2506,7 @@ Se non sei sicuro, scegli "No", così le informazioni inviate al serve File transfers - Files trasferiti + File trasferiti @@ -2351,7 +2518,7 @@ Se non sei sicuro, scegli "No", così le informazioni inviate al serve You have asked qTox to open an executable file. Executable files can potentially damage your computer. Are you sure want to open this file? popup text - Hai chiesto a qTox di aprire un file eseguibile. I files eseguibili possono danneggiare il tuo computer. Sei sicuro di voler aprire questo file? + Hai chiesto a qTox di aprire un file eseguibile. I file eseguibili possono danneggiare il tuo computer. Sei sicuro di voler aprire questo file? diff --git a/translations/lt.ts b/translations/lt.ts index a0a146beb..ba7db597f 100644 --- a/translations/lt.ts +++ b/translations/lt.ts @@ -109,6 +109,15 @@ Jei Jūsų interneto ryšys yra per prastas, turėsite keblumų su vaizdo skambu Qt version: Qt grafinės aplinkos versija: + + Restart qTox to install version %1 + Paleiskite qTox iš naujo, kad būtų įdiegta versija: %1 + + + qTox is downloading update %1 + %1 is the version of the update + qTox siunčia programos atnaujinimą: %1 + AboutSettings @@ -116,10 +125,38 @@ Jei Jūsų interneto ryšys yra per prastas, turėsite keblumų su vaizdo skambu Version Versija + + You are using qTox version $GIT_DESCRIBE. + Naudojate qTox versiją: $GIT_DESCRIBE. + + + Downloading update: %p% + Siunčiamas atnaujinimas: %p% + License Licencija + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Noto Sans'; font-size:10pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; color:#000000;">Copyright © 2014-2015 by The qTox Project</span></p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu';">qTox is a Qt-based graphical interface for Tox.</span></p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif';">qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.</span></p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif';">qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. </span></p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu';">You should have received a copy of the GNU General Public License along with this program. If not, see </span><a href="https://www.gnu.org/copyleft/gpl.html"><span style=" font-family:'Ubuntu'; text-decoration: underline; color:#007af4;">https://www.gnu.org/copyleft/gpl.html</span></a><span style=" font-family:'Ubuntu';">.</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style= "font-family:'Noto Sans'; font-size:10pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; color:#000000;">Autorių teisės: qTox projektas, 2014-2015.</span></p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu';">qTox – Tox protokolu veikianti programa su Qt grafine aplinka.</span></p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif';">qTox yra atvira programinė įranga: galite ją platinti ir (arba) modifikuoti pagal GNU atvirosios programinės įrangos licencijos (GPL), kurią parengė Laisvosios programinės įrangos fondas (FSF), trečiąją (ar bet kurią vėlesnę) versiją.</span></p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif';">Tikimės, kad qTox bus Jums naudingas, bet nesuteikiame JOKIOS GARANTIJOS, įskaitant PERKAMUMO ar TINKAMUMO KOKIAM NORS TIKSLUI. Detalesnė informacija pateikta GPL licencijoje. </span></p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu';">GPL licencijos kopiją turėjote gauti kartu su šia programine įranga. Priešingu atveju, apsilankykite </span><a href="https://www.gnu.org/copyleft/gpl.html"><span style=" font-family:'Ubuntu'; text-decoration: underline; color:#007af4;">https://www.gnu.org/copyleft/gpl.html</span></a><span style=" font-family:'Ubuntu';">.</span></p></body></html> + toxcore version: $TOXCOREVERSION toxcore versija: $TOXCOREVERSION @@ -132,30 +169,6 @@ Jei Jūsų interneto ryšys yra per prastas, turėsite keblumų su vaizdo skambu Commit hash: <a href="https://github.com/tux3/qTox/commit/$GIT_VERSION">$GIT_VERSION</a> Atnaujinimo maiša (<i>hash</i>): <a href="https://github.com/tux3/qTox/commit/$GIT_VERSION">$GIT_VERSION</a> - - You are using a qTox nightly build. - Naudojate momentinę qTox versiją. - - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'Oxygen-Sans'; font-size:10pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; color:#000000;">Copyright © 2014-2015 by The qTox Project</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu';">qTox is a Qt-based graphical interface for Tox.</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif';">qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif';">qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. </span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu';">You should have received a copy of the GNU General Public License along with this program. If not, see </span><a href="https://www.gnu.org/copyleft/gpl.html"><span style=" font-family:'Ubuntu'; text-decoration: underline; color:#007af4;">https://www.gnu.org/copyleft/gpl.html</span></a><span style=" font-family:'Ubuntu';">.</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style= "font-family:'Oxygen-Sans'; font-size:10pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; color:#000000;">Autorių teisės: qTox projektas, 2014-2015.</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu';">qTox – Tox protokolu veikianti programa su Qt grafine aplinka.</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif';">qTox yra atvira programinė įranga: galite ją platinti ir (arba) modifikuoti pagal GNU atvirosios programinės įrangos licencijos (GPL), kurią parengė Laisvosios programinės įrangos fondas (FSF), trečiąją (ar bet kurią vėlesnę) versiją.</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif';">Tikimės, kad qTox bus Jums naudingas, bet nesuteikiame JOKIOS GARANTIJOS, įskaitant PERKAMUMO ar TINKAMUMO KOKIAM NORS TIKSLUI. Detalesnė informacija pateikta GPL licencijoje. </span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu';">GPL licencijos kopiją turėjote gauti kartu su šia programine įranga. Priešingu atveju, apsilankykite </span><a href="https://www.gnu.org/copyleft/gpl.html"><span style=" font-family:'Ubuntu'; text-decoration: underline; color:#007af4;">https://www.gnu.org/copyleft/gpl.html</span></a><span style=" font-family:'Ubuntu';">.</span></p></body></html> - Authors Kūrėjai @@ -1775,6 +1788,10 @@ Nusiųskite ją tiems, su kuriais norite bendrauti. QObject + + Version %1, %2 + Versija %1 (%2) + Update The title of a message box diff --git a/windows/qtox.nsi b/windows/qtox.nsi index 99b09cdf7..bc0ca8b16 100644 --- a/windows/qtox.nsi +++ b/windows/qtox.nsi @@ -9,7 +9,6 @@ !define COPYRIGHT "The Tox Project" !define INSTALLER_NAME "setup-qtox.exe" !define MAIN_APP_EXE "bin\qtox.exe" -!define INSTALL_TYPE "SetShellVarContext current" !define REG_ROOT "HKLM" !define REG_APP_PATH "Software\Microsoft\Windows\CurrentVersion\App Paths\qtox.exe" !define UNINSTALL_PATH "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APP_NAME}" @@ -225,11 +224,17 @@ FunctionEnd !define MUI_FINISHPAGE_SHOWREADME_TEXT "Create Desktop Shortcut" !define MUI_FINISHPAGE_SHOWREADME_FUNCTION finishpageaction -!define MUI_FINISHPAGE_RUN "$INSTDIR\${MAIN_APP_EXE}" +!define MUI_FINISHPAGE_RUN_FUNCTION Launch_qTox_without_Admin +!define MUI_FINISHPAGE_RUN !define MUI_FINISHPAGE_LINK "Find qTox on GitHub" !define MUI_FINISHPAGE_LINK_LOCATION "https://github.com/tux3/qTox" !insertmacro MUI_PAGE_FINISH +Function Launch_qTox_without_Admin + SetOutPath $INSTDIR + ShellExecAsUser::ShellExecAsUser "" "$INSTDIR\${MAIN_APP_EXE}" "" +FunctionEnd + !define MUI_UNABORTWARNING !define MUI_UNFINISHPAGE_NOAUTOCLOSE !insertmacro MUI_UNPAGE_WELCOME @@ -243,25 +248,26 @@ FunctionEnd #INSTALL ################# Section "Install" + SetShellVarContext all # Install files ${SetOutPath} "$INSTDIR" ${WriteUninstaller} "uninstall.exe" - ${CreateDirectory} "bin" + ${CreateDirectory} "$INSTDIR\bin" ${SetOutPath} "$INSTDIR\bin" ${File} "qtox\*.*" - ${CreateDirectory} "imageformats" + ${CreateDirectory} "$INSTDIR\bin\imageformats" ${SetOutPath} "$INSTDIR\bin\imageformats" File /nonfatal "qtox\imageformats\*.*" ${SetOutPath} "$INSTDIR\bin" - ${CreateDirectory} "platforms" + ${CreateDirectory} "$INSTDIR\bin\platforms" ${SetOutPath} "$INSTDIR\bin\platforms" File /nonfatal "qtox\platforms\*.*" ${SetOutPath} "$INSTDIR\bin" - ${CreateDirectory} "sqldrivers" + ${CreateDirectory} "$INSTDIR\bin\sqldrivers" ${SetOutPath} "$INSTDIR\bin\sqldrivers" File /nonfatal "qtox\sqldrivers\*.*" ${SetOutPath} "$INSTDIR\bin" @@ -298,6 +304,7 @@ SectionEnd #UNINSTALL ################ Section Uninstall + SetShellVarContext all ;If there's no uninstall log, we'll try anyway to clean what we can IfFileExists "$INSTDIR\${UninstLog}" +3 Goto noLog diff --git a/windows/qtox64.nsi b/windows/qtox64.nsi index 0919f97c2..d029a15b0 100644 --- a/windows/qtox64.nsi +++ b/windows/qtox64.nsi @@ -9,7 +9,6 @@ !define COPYRIGHT "The Tox Project" !define INSTALLER_NAME "setup-qtox.exe" !define MAIN_APP_EXE "bin\qtox.exe" -!define INSTALL_TYPE "SetShellVarContext current" !define REG_ROOT "HKLM" !define REG_APP_PATH "Software\Microsoft\Windows\CurrentVersion\App Paths\qtox.exe" !define UNINSTALL_PATH "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APP_NAME}" @@ -225,11 +224,17 @@ FunctionEnd !define MUI_FINISHPAGE_SHOWREADME_TEXT "Create Desktop Shortcut" !define MUI_FINISHPAGE_SHOWREADME_FUNCTION finishpageaction -!define MUI_FINISHPAGE_RUN "$INSTDIR\${MAIN_APP_EXE}" +!define MUI_FINISHPAGE_RUN_FUNCTION Launch_qTox_without_Admin +!define MUI_FINISHPAGE_RUN !define MUI_FINISHPAGE_LINK "Find qTox on GitHub" !define MUI_FINISHPAGE_LINK_LOCATION "https://github.com/tux3/qTox" !insertmacro MUI_PAGE_FINISH +Function Launch_qTox_without_Admin + SetOutPath $INSTDIR + ShellExecAsUser::ShellExecAsUser "" "$INSTDIR\${MAIN_APP_EXE}" "" +FunctionEnd + !define MUI_UNABORTWARNING !define MUI_UNFINISHPAGE_NOAUTOCLOSE !insertmacro MUI_UNPAGE_WELCOME @@ -243,25 +248,26 @@ FunctionEnd #INSTALL ################# Section "Install" + SetShellVarContext all # Install files ${SetOutPath} "$INSTDIR" ${WriteUninstaller} "uninstall.exe" - ${CreateDirectory} "bin" + ${CreateDirectory} "$INSTDIR\bin" ${SetOutPath} "$INSTDIR\bin" ${File} "qtox\*.*" - ${CreateDirectory} "imageformats" + ${CreateDirectory} "$INSTDIR\bin\imageformats" ${SetOutPath} "$INSTDIR\bin\imageformats" File /nonfatal "qtox\imageformats\*.*" ${SetOutPath} "$INSTDIR\bin" - ${CreateDirectory} "platforms" + ${CreateDirectory} "$INSTDIR\bin\platforms" ${SetOutPath} "$INSTDIR\bin\platforms" File /nonfatal "qtox\platforms\*.*" ${SetOutPath} "$INSTDIR\bin" - ${CreateDirectory} "sqldrivers" + ${CreateDirectory} "$INSTDIR\bin\sqldrivers" ${SetOutPath} "$INSTDIR\bin\sqldrivers" File /nonfatal "qtox\sqldrivers\*.*" ${SetOutPath} "$INSTDIR\bin" @@ -298,6 +304,7 @@ SectionEnd #UNINSTALL ################ Section Uninstall + SetShellVarContext all ;If there's no uninstall log, we'll try anyway to clean what we can IfFileExists "$INSTDIR\${UninstLog}" +3 Goto noLog