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

qTox v1.2.2 bugfix release

Windows:
- Installer fixes for non-admin users
All:
- Many translation updates (Italian, German, Lithuanian, ...)
- New SQLCipher database code improves reliability
- Miscellaneous fixes
This commit is contained in:
tux3 2015-12-21 13:37:28 +01:00
commit 8b671916ab
No known key found for this signature in database
GPG Key ID: 7E086DD661263264
48 changed files with 1562 additions and 667 deletions

View File

@ -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
<a name="ubuntu-other-deps" />
#### 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
```

View File

@ -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

View File

@ -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

View File

@ -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<qreal>(frequency)) * 1000;
int remaining = timer->interval();
if (duration > remaining)
timer->start(duration);
alDeleteBuffers(1, &buffer);
}

View File

@ -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);
}

View File

@ -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 <tox/tox.h>
#include <tox/toxencryptsave.h>
#include <QApplication>
@ -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.size();
return QByteArray();
}
int sz = data.size() - TOX_PASS_ENCRYPTION_EXTRA_LENGTH;
uint8_t decrypted[sz];
if (!tox_pass_key_decrypt(reinterpret_cast<const uint8_t*>(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)

View File

@ -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<uint32_t>::max())
{
qWarning() << "sendAvatarFile: Can't create the Tox file sender";
qWarning() << "sendAvatarFile: Can't create the Tox file sender, error"<<err;
return;
}
//qDebug() << QString("sendAvatarFile: Created file sender %1 with friend %2").arg(fileNum).arg(friendId);
@ -421,7 +422,6 @@ void CoreFile::onFileRecvChunkCallback(Tox *tox, uint32_t friendId, uint32_t fil
if (file->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);

View File

@ -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();
}

View File

@ -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.

View File

@ -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;

View File

@ -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;

View File

@ -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()

View File

@ -375,6 +375,7 @@ bool AutoUpdater::downloadUpdate()
{
qDebug() << "Skipping already downloaded file '" + fileMeta.installpath+ "'";
fileFile.close();
progressValue = initialProgress + step;
continue;
}

View File

@ -24,6 +24,7 @@
#include <QDebug>
QByteArray AvatarBroadcaster::avatarData;
QMap<uint32_t, bool> 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<uint32_t> 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)

View File

@ -24,7 +24,9 @@
#include <QByteArray>
#include <QMap>
/// 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<uint32_t, bool> friendsSentTo;
};
#endif // AVATARBROADCASTER_H

View File

@ -0,0 +1,486 @@
#include "rawdatabase.h"
#include <QDebug>
#include <QMetaObject>
#include <QMutexLocker>
#include <QCoreApplication>
#include <QFile>
#include <cassert>
#include <tox/toxencryptsave.h>
/// 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 <sqlcipher/sqlite3.h>
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"<<path<<"with error:"<<sqlite3_errmsg(sqlite);
return false;
}
if (!hexKey.isEmpty())
{
if (!execNow("PRAGMA key = \"x'"+hexKey+"'\""))
{
qWarning() << "Failed to set encryption key";
close();
return false;
}
if (!execNow("SELECT count(*) FROM sqlite_master"))
{
qWarning() << "Database is unusable, check that the password is correct";
close();
return false;
}
}
return true;
}
void RawDatabase::close()
{
if (QThread::currentThread() != workerThread.get())
return (void)QMetaObject::invokeMethod(this, "close", Qt::BlockingQueuedConnection);
// We assume we're in the ctor or dtor, so we just need to finish processing our transactions
process();
if (sqlite3_close(sqlite) == SQLITE_OK)
sqlite = nullptr;
else
qWarning() << "Error closing database:"<<sqlite3_errmsg(sqlite);
}
bool RawDatabase::isOpen()
{
// We don't need thread safety since only the ctor/dtor can write this pointer
return sqlite != nullptr;
}
bool RawDatabase::execNow(const QString& statement)
{
return execNow(Query{statement});
}
bool RawDatabase::execNow(const RawDatabase::Query &statement)
{
return execNow(QVector<Query>{statement});
}
bool RawDatabase::execNow(const QVector<RawDatabase::Query> &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<Query>{statement});
}
void RawDatabase::execLater(const QVector<RawDatabase::Query> &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 "<<path;
close();
return QFile::remove(path);
}
QString RawDatabase::deriveKey(QString password)
{
if (password.isEmpty())
return {};
QByteArray passData = password.toUtf8();
static_assert(TOX_PASS_KEY_LENGTH >= 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<int>(compileTail - query.query.data()),
&stmt, &compileTail)) != SQLITE_OK)
{
qWarning() << "Failed to prepare statement"<<query.query<<"with error"<<r;
goto cleanupStatements;
}
query.statements += stmt;
// Now we can bind our params to this statement
int nParams = sqlite3_bind_parameter_count(stmt);
if (query.blobs.size() < curParam+nParams)
{
qWarning() << "Not enough parameters to bind to query "<<query.query;
goto cleanupStatements;
}
for (int i=0; i<nParams; ++i)
{
const QByteArray& blob = query.blobs[curParam+i];
if (sqlite3_bind_blob(stmt, i+1, blob.data(), blob.size(), SQLITE_STATIC) != SQLITE_OK)
{
qWarning() << "Failed to bind param"<<curParam+i<<"to query "<<query.query;
goto cleanupStatements;
}
}
curParam += nParams;
} while (compileTail != query.query.data()+query.query.size());
}
// Execute each statement of each query of our transaction
for (Query& query : trans.queries)
{
for (sqlite3_stmt* stmt : query.statements)
{
int column_count = sqlite3_column_count(stmt);
int result;
do {
result = sqlite3_step(stmt);
// Execute our row callback
if (result == SQLITE_ROW && query.rowCallback)
{
QVector<QVariant> row;
for (int i=0; i<column_count; ++i)
row += extractData(stmt, i);
query.rowCallback(row);
}
} while (result == SQLITE_ROW);
if (result == SQLITE_ERROR)
{
qWarning() << "Error executing query "<<query.query;
goto cleanupStatements;
}
else if (result == SQLITE_MISUSE)
{
qWarning() << "Misuse executing query "<<query.query;
goto cleanupStatements;
}
else if (result == SQLITE_CONSTRAINT)
{
qWarning() << "Constraint error executing query "<<query.query;
goto cleanupStatements;
}
else if (result != SQLITE_DONE)
{
qWarning() << "Unknown error"<<result<<"executing query "<<query.query;
goto cleanupStatements;
}
}
if (query.insertCallback)
query.insertCallback(sqlite3_last_insert_rowid(sqlite));
}
if (trans.success != nullptr)
trans.success->store(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<const char*>(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<const char*>(sqlite3_column_blob(stmt, col));
int len = sqlite3_column_bytes(stmt, col);
return QByteArray::fromRawData(data, len);
}
}

View File

@ -0,0 +1,118 @@
#ifndef RAWDATABASE_H
#define RAWDATABASE_H
#include <QString>
#include <QByteArray>
#include <QThread>
#include <QQueue>
#include <QVector>
#include <QPair>
#include <QMutex>
#include <QVariant>
#include <memory>
#include <atomic>
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<QByteArray> blobs = {}, std::function<void(int64_t)> insertCallback={})
: query{query.toUtf8()}, blobs{blobs}, insertCallback{insertCallback} {}
Query(QString query, std::function<void(int64_t)> insertCallback)
: query{query.toUtf8()}, insertCallback{insertCallback} {}
Query(QString query, std::function<void(const QVector<QVariant>&)> rowCallback)
: query{query.toUtf8()}, rowCallback{rowCallback} {}
Query() = default;
private:
QByteArray query; ///< UTF-8 query string
QVector<QByteArray> blobs; ///< Bound data blobs
std::function<void(int64_t)> insertCallback; ///< Called after execution with the last insert rowid
std::function<void(const QVector<QVariant>&)> rowCallback; ///< Called during execution for each row
QVector<sqlite3_stmt*> 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<Query>& statements);
/// Executes a SQL transaction asynchronously.
void execLater(const QString& statement);
void execLater(const Query& statement);
void execLater(const QVector<Query>& 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<Query> 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<QThread> workerThread;
QQueue<Transaction> pendingTransactions;
/// Protects pendingTransactions
QMutex transactionsMutex;
QString path;
QString currentHexKey;
};
#endif // RAWDATABASE_H

237
src/persistence/history.cpp Normal file
View File

@ -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 <QDebug>
#include <cassert>
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<RawDatabase::Query> History::generateNewMessageQueries(const QString &friendPk, const QString &message,
const QString &sender, const QDateTime &time, bool isSent, QString dispName,
std::function<void(int64_t)> insertIdCallback)
{
QVector<RawDatabase::Query> 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<void(int64_t)> insertIdCallback)
{
db.execLater(generateNewMessageQueries(friendPk, message, sender, time, isSent, dispName, insertIdCallback));
}
QList<History::HistMessage> History::getChatHistory(const QString &friendPk, const QDateTime &from, const QDateTime &to)
{
QList<HistMessage> messages;
auto rowCallback = [&messages](const QVector<QVariant>& 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<QVariant>& 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<RawDatabase::Query> queries;
constexpr int batchSize = 1000;
queries.reserve(batchSize);
QList<HistoryKeeper::HistMessage> 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"<<t.elapsed()<<"ms";
}

79
src/persistence/history.h Normal file
View File

@ -0,0 +1,79 @@
#ifndef HISTORY_H
#define HISTORY_H
#include <tox/toxencryptsave.h>
#include <QDateTime>
#include <QVector>
#include <QHash>
#include <cstdint>
#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<void(int64_t)> insertIdCallback={});
/// Fetches chat messages from the database
QList<HistMessage> getChatHistory(const QString& friendPk, const QDateTime &from, const QDateTime &to);
/// Marks a message as sent, removing it from the faux-offline pending messages list
void markAsSent(qint64 id);
protected:
/// Makes sure the history tables are created
void init();
static QString getDbPath(const QString& profileName);
QVector<RawDatabase::Query> generateNewMessageQueries(const QString& friendPk, const QString& message,
const QString& sender, const QDateTime &time, bool isSent, QString dispName,
std::function<void(int64_t)> insertIdCallback={});
private:
RawDatabase db;
// Cached mappings to speed up message saving
QHash<QString, int64_t> peers; ///< Maps friend public keys to unique IDs by index
};
#endif // HISTORY_H

View File

@ -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<QString> 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::HistMessage> HistoryKeeper::getChatHistory(HistoryKeeper::ChatType ct, const QString &chat,
const QDateTime &time_from, const QDateTime &time_to)
{
QList<HistMessage> 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::HistMessage> 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<HistMessage> res;
@ -274,41 +172,6 @@ QList<HistoryKeeper::HistMessage> HistoryKeeper::exportMessages()
return res;
}
void HistoryKeeper::importMessages(const QList<HistoryKeeper::HistMessage> &lst)
{
db->exec("BEGIN TRANSACTION;");
for (const HistMessage &msg : lst)
{
QList<QString> 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<QString> HistoryKeeper::generateAddChatEntryCmd(const QString& chat, const QString& message, const QString& sender, const QDateTime &dt, bool isSent, QString dispName)
{
QList<QString> 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<int, HistoryKeeper::ChatType> 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<ChatType>(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::HistMessage> HistoryKeeper::exportMessagesDeleteFile()
{
auto msgs = getInstance()->exportMessages();
auto msgs = getInstance(*Nexus::getProfile())->exportMessages();
qDebug() << "Messages exported";
getInstance()->removeHistory();
getInstance(*Nexus::getProfile())->removeHistory();
return msgs;
}

View File

@ -27,13 +27,19 @@
#include <QMutex>
#include <tox/toxencryptsave.h>
/**
* 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<HistMessage> 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<HistMessage> 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<HistMessage> exportMessages();
void importMessages(const QList<HistoryKeeper::HistMessage> &lst);
void setSyncType(Db::syncType sType);
private:
HistoryKeeper(GenericDdInterface *db_);
HistoryKeeper(HistoryKeeper &hk) = delete;
HistoryKeeper& operator=(const HistoryKeeper&) = delete;
void updateChatsID();
void updateAliases();
QPair<int, ChatType> getChatID(const QString &id_str, ChatType ct);
int getAliasID(const QString &id_str);
QString wrapMessage(const QString &str);
QString unWrapMessage(const QString &str);
QList<QString> 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<QString, int> aliases;
QMap<QString, QPair<int, ChatType>> chats;
qint64 messageID;
GenericDdInterface *oldDb;
};
#endif // HISTORYKEEPER_H

View File

@ -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 <QMutexLocker>
#include <QTimer>
@ -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 &timestamp)
void OfflineMsgEngine::registerReceipt(int receipt, int64_t messageID, ChatMessage::Ptr msg, const QDateTime &timestamp)
{
QMutexLocker ml(&mutex);
@ -78,13 +81,13 @@ void OfflineMsgEngine::deliverOfflineMsgs()
if (undeliveredMsgs.size() == 0)
return;
QMap<int, MsgPtr> msgs = undeliveredMsgs;
QMap<int64_t, MsgPtr> 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)
{

View File

@ -39,7 +39,7 @@ public:
static QMutex globalMutex;
void dischargeReceipt(int receipt);
void registerReceipt(int receipt, int messageID, ChatMessage::Ptr msg, const QDateTime &timestamp = QDateTime::currentDateTime());
void registerReceipt(int receipt, int64_t messageID, ChatMessage::Ptr msg, const QDateTime &timestamp = QDateTime::currentDateTime());
public slots:
void deliverOfflineMsgs();
@ -54,8 +54,8 @@ private:
QMutex mutex;
Friend* f;
QHash<int, int> receipts;
QMap<int, MsgPtr> undeliveredMsgs;
QHash<int, int64_t> receipts;
QMap<int64_t, MsgPtr> undeliveredMsgs;
static const int offlineTimeout;
};

View File

@ -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"<<name;
GUI::showError(QObject::tr("Error"), QObject::tr("qTox couldn't open your chat logs, they will be disabled."));
history.release();
}
coreThread = new QThread();
coreThread->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<uint8_t *>(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<HistoryKeeper::HistMessage> 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);
}

View File

@ -26,6 +26,8 @@
#include <QByteArray>
#include <QPixmap>
#include <tox/toxencryptsave.h>
#include <memory>
#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> 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<QString> profiles;

View File

@ -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

View File

@ -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);

View File

@ -21,6 +21,7 @@
#define NETCAMVIEW_H
#include "genericnetcamview.h"
#include <QVector>
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<QMetaObject::Connection> connections;
};
#endif // NETCAMVIEW_H

View File

@ -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"),

View File

@ -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);
}
}

View File

@ -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

View File

@ -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))

View File

@ -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"

View File

@ -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"

View File

@ -30,8 +30,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>493</width>
<height>534</height>
<width>496</width>
<height>626</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2" stretch="0,1,0,0">
@ -160,7 +160,7 @@
<item row="3" column="0" colspan="2">
<widget class="QLabel" name="updateText">
<property name="text">
<string>Update text</string>
<string notr="true">Update text</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
@ -209,12 +209,12 @@
<string>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;p style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Sans Serif'; font-size:10pt; color:#000000;&quot;&gt;Copyright © 2014-2015 by The qTox Project&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Ubuntu'; font-size:10pt;&quot;&gt;qTox is a Qt-based graphical interface for Tox.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Sans Serif'; font-size:10pt;&quot;&gt;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.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Sans Serif'; font-size:10pt;&quot;&gt;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. &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Ubuntu'; font-size:10pt;&quot;&gt;You should have received a copy of the GNU General Public License along with this program. If not, see &lt;/span&gt;&lt;a href=&quot;https://www.gnu.org/copyleft/gpl.html&quot;&gt;&lt;span style=&quot; font-family:'Ubuntu'; font-size:10pt; text-decoration: underline; color:#007af4;&quot;&gt;https://www.gnu.org/copyleft/gpl.html&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-family:'Ubuntu'; font-size:10pt;&quot;&gt;.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Noto Sans'; font-size:10pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;p style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Sans Serif'; color:#000000;&quot;&gt;Copyright © 2014-2015 by The qTox Project&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Ubuntu';&quot;&gt;qTox is a Qt-based graphical interface for Tox.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Sans Serif';&quot;&gt;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.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Sans Serif';&quot;&gt;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. &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Ubuntu';&quot;&gt;You should have received a copy of the GNU General Public License along with this program. If not, see &lt;/span&gt;&lt;a href=&quot;https://www.gnu.org/copyleft/gpl.html&quot;&gt;&lt;span style=&quot; font-family:'Ubuntu'; text-decoration: underline; color:#007af4;&quot;&gt;https://www.gnu.org/copyleft/gpl.html&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-family:'Ubuntu';&quot;&gt;.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="searchPaths">
<stringlist/>

View File

@ -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<int>(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<QComboBox*>())
{
cb->installEventFilter(this);
cb->setFocusPolicy(Qt::StrongFocus);
}
for (QCheckBox *cb : findChildren<QCheckBox*>()) // 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<int>(Db::syncType::stFull);
bodyUI->syncTypeComboBox->setCurrentIndex(index);
onDbSyncTypeUpdated();
}
bool AdvancedForm::eventFilter(QObject *o, QEvent *e)
{
if ((e->type() == QEvent::Wheel) &&
(qobject_cast<QComboBox*>(o) || qobject_cast<QAbstractSpinBox*>(o) || qobject_cast<QCheckBox*>(o)))
(qobject_cast<QAbstractSpinBox*>(o) || qobject_cast<QCheckBox*>(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)"));
}

View File

@ -41,7 +41,6 @@ protected:
private slots:
void onMakeToxPortableUpdated();
void onDbSyncTypeUpdated();
void resetToDefault();
private:

View File

@ -24,8 +24,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>396</width>
<height>454</height>
<width>398</width>
<height>456</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
@ -62,39 +62,6 @@
</property>
</widget>
</item>
<item alignment="Qt::AlignTop">
<widget class="QGroupBox" name="historyGroup">
<property name="title">
<string>Chat history</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="dbLabel">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;http://www.sqlite.org/pragma.html#pragma_synchronous&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;Writing to DB&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="textFormat">
<enum>Qt::RichText</enum>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="syncTypeComboBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">

View File

@ -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 <QMessageBox>
#include <QFile>
#include <QDebug>
@ -63,7 +65,7 @@ void PrivacyForm::onEnableLoggingUpdated()
QMessageBox::Yes|QMessageBox::No);
if (dialogDelHistory == QMessageBox::Yes)
{
HistoryKeeper::getInstance()->removeHistory();
Nexus::getProfile()->getHistory()->eraseHistory();
}
}
}

View File

@ -26,7 +26,6 @@
#include "groupwidget.h"
#include "circlewidget.h"
#include "widget.h"
#include "src/persistence/historykeeper.h"
#include <QGridLayout>
#include <QMimeData>
#include <QDragEnterEvent>

View File

@ -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
{

View File

@ -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();
}
}

View File

@ -221,6 +221,7 @@ private:
static bool filterOnline(int index);
static bool filterOffline(int index);
void retranslateUi();
void focusChatInput();
private:
SystemTrayIcon *icon;

55
translations/de.ts vendored
View File

@ -110,6 +110,15 @@ Zu hohe Auflösungen können daher zu Problemen in Videoanrufen führen.</transl
<source>Qt version:</source>
<translation>Qt-Version:</translation>
</message>
<message>
<source>Restart qTox to install version %1</source>
<translation>Starte qTox neu, um Version %1 zu installieren.</translation>
</message>
<message>
<source>qTox is downloading update %1</source>
<comment>%1 is the version of the update</comment>
<translation>qTox lädt Update %1 herunter.</translation>
</message>
</context>
<context>
<name>AboutSettings</name>
@ -117,13 +126,30 @@ Zu hohe Auflösungen können daher zu Problemen in Videoanrufen führen.</transl
<source>Version</source>
<translation>Version</translation>
</message>
<message>
<source>You are using qTox version $GIT_DESCRIBE.</source>
<translation>Du verwendest qTox $GIT_DESCRIBE.</translation>
</message>
<message>
<source>Downloading update: %p%</source>
<translation>Herunterladen des Updates: %p %</translation>
</message>
<message>
<source>License</source>
<translation>Lizenz</translation>
</message>
<message>
<source>You are using a qTox nightly build.</source>
<translation>Du verwendest qTox &quot;Nightly Build&quot;.</translation>
<message utf8="true">
<source>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:&apos;Noto Sans&apos;; font-size:10pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;p style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;Sans Serif&apos;; color:#000000;&quot;&gt;Copyright © 2014-2015 by The qTox Project&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;Ubuntu&apos;;&quot;&gt;qTox is a Qt-based graphical interface for Tox.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;Sans Serif&apos;;&quot;&gt;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.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;Sans Serif&apos;;&quot;&gt;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. &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;Ubuntu&apos;;&quot;&gt;You should have received a copy of the GNU General Public License along with this program. If not, see &lt;/span&gt;&lt;a href=&quot;https://www.gnu.org/copyleft/gpl.html&quot;&gt;&lt;span style=&quot; font-family:&apos;Ubuntu&apos;; text-decoration: underline; color:#007af4;&quot;&gt;https://www.gnu.org/copyleft/gpl.html&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-family:&apos;Ubuntu&apos;;&quot;&gt;.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
<translatorcomment>Ausgelassen, da Lizenz original auf Englisch</translatorcomment>
<translation></translation>
</message>
<message>
<source>Commit hash: &lt;a href=&quot;https://github.com/tux3/qTox/commit/$GIT_VERSION&quot;&gt;$GIT_VERSION&lt;/a&gt;</source>
@ -137,26 +163,13 @@ Zu hohe Auflösungen können daher zu Problemen in Videoanrufen führen.</transl
<source>Qt version:</source>
<translation>Qt-Version:</translation>
</message>
<message utf8="true">
<source>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:&apos;Oxygen-Sans&apos;; font-size:10pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;p style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;Sans Serif&apos;; color:#000000;&quot;&gt;Copyright © 2014-2015 by The qTox Project&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;Ubuntu&apos;;&quot;&gt;qTox is a Qt-based graphical interface for Tox.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;Sans Serif&apos;;&quot;&gt;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.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;Sans Serif&apos;;&quot;&gt;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. &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;Ubuntu&apos;;&quot;&gt;You should have received a copy of the GNU General Public License along with this program. If not, see &lt;/span&gt;&lt;a href=&quot;https://www.gnu.org/copyleft/gpl.html&quot;&gt;&lt;span style=&quot; font-family:&apos;Ubuntu&apos;; text-decoration: underline; color:#007af4;&quot;&gt;https://www.gnu.org/copyleft/gpl.html&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-family:&apos;Ubuntu&apos;;&quot;&gt;.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
<translatorcomment>ausgelassen, Lizenz Original in Englisch</translatorcomment>
<translation></translation>
</message>
<message>
<source>Authors</source>
<translation>Entwickler</translation>
</message>
<message>
<source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Original author: &lt;a href=&quot;https://github.com/tux3&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;tux3&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;See a full list of &lt;a href=&quot;https://github.com/tux3/qTox/graphs/contributors&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;contributors&lt;/span&gt;&lt;/a&gt; at Github&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
<translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Ursprünglich entwickelt von: &lt;a href=&quot;https://github.com/tux3&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;tux3&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;Alle Mitwirkenden findest du unter &lt;a href=&quot;https://github.com/tux3/qTox/graphs/contributors&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;contributors&lt;/span&gt;&lt;/a&gt; auf GitHub&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
<translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Ursprünglich entwickelt von: &lt;a href=&quot;https://github.com/tux3&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;tux3&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;Alle Mitwirkenden findest du unter &lt;a href=&quot;https://github.com/tux3/qTox/graphs/contributors&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;Contributors&lt;/span&gt;&lt;/a&gt; auf GitHub.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
</message>
<message>
<source>Known Issues</source>
@ -164,7 +177,7 @@ p, li { white-space: pre-wrap; }
</message>
<message>
<source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;A list of all known issues may be found at our &lt;a href=&quot;https://github.com/tux3/qTox/issues&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;bug-tracker&lt;/span&gt;&lt;/a&gt; at Github. If you discover a bug or security vulnerability within qTox, please report it according to the guidelines in our &lt;a href=&quot;https://github.com/tux3/qTox/wiki/Writing-Useful-Bug-Reports&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;Writing Useful Bug Reports&lt;/span&gt;&lt;/a&gt; wiki article.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
<translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Eine Liste aller bekannten Probleme findest du in unserem &lt;a href=&quot;https://github.com/tux3/qTox/issues&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;Bug-Tracker (Englisch)&lt;/span&gt;&lt;/a&gt; auf GitHub. Wenn du einen Fehler oder eine Sicherheitslücke in qTox findest, melde sie bitte wie in unserem Wiki Artikel &lt;a href=&quot;https://github.com/tux3/qTox/wiki/Writing-Useful-Bug-Reports&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;Writing Useful Bug Reports (Englisch)&lt;/span&gt;&lt;/a&gt; beschrieben.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
<translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Eine Liste aller bekannten Probleme findest du in unserem &lt;a href=&quot;https://github.com/tux3/qTox/issues&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;Bug-Tracker (Englisch)&lt;/span&gt;&lt;/a&gt; auf GitHub. Wenn du einen Fehler oder eine Sicherheitslücke in qTox findest, melde sie bitte wie in unserem Wiki-Artikel &lt;a href=&quot;https://github.com/tux3/qTox/wiki/Writing-Useful-Bug-Reports&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;Writing Useful Bug Reports (Englisch)&lt;/span&gt;&lt;/a&gt; beschrieben.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
</message>
</context>
<context>
@ -822,7 +835,7 @@ Disabling chat history now will leave the encrypted history intact (but not usab
</message>
<message>
<source>Show details</source>
<translation>Zeige Profil</translation>
<translation>Zeige Details</translation>
</message>
<message>
<source>Choose an auto accept directory</source>
@ -1793,6 +1806,10 @@ Teile sie einfach deinen Bekannten mit!</translation>
</context>
<context>
<name>QObject</name>
<message>
<source>Version %1, %2</source>
<translation>Version %1, %2</translation>
</message>
<message>
<source>Update</source>
<comment>The title of a message box</comment>

323
translations/it.ts vendored
View File

@ -24,6 +24,10 @@
<source>Default resolution</source>
<translation>Risoluzione di default</translation>
</message>
<message>
<source>None</source>
<translation>Nessuno</translation>
</message>
</context>
<context>
<name>AVSettings</name>
@ -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.</translation>
</message>
<message>
<source>Rescan devices</source>
<translation>Scansiona dispositivi</translation>
</message>
</context>
<context>
<name>AboutForm</name>
<message>
<source>About</source>
<translation>About</translation>
<translation>Informazioni su qTox</translation>
</message>
<message>
<source>Qt version:</source>
@ -146,6 +154,11 @@ qualità video elevate, questo può causare problemi con le chiamate video.</tra
<source>You are using a qTox nightly build.</source>
<translation>Stai utilizzando la versione nightly build di qTox.</translation>
</message>
<message>
<location filename="../src/widget/form/settings/aboutsettings.ui" line="131"/>
<source>You are using qTox version $GIT_DESCRIBE.</source>
<translation>Stai utilizzando la versione $GIT_DESCRIBE.</translation>
</message>
<message>
<source>Commit hash: &lt;a href=&quot;https://github.com/tux3/qTox/commit/$GIT_VERSION&quot;&gt;$GIT_VERSION&lt;/a&gt;</source>
<translation>Commit hash: &lt;a href=&quot;https://github.com/tux3/qTox/commit/$GIT_VERSION&quot;&gt;$GIT_VERSION&lt;/a&gt;</translation>
@ -172,15 +185,7 @@ p, li { white-space: pre-wrap; }
&lt;p style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;Sans Serif&apos;;&quot;&gt;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.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;Sans Serif&apos;;&quot;&gt;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. &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;Ubuntu&apos;;&quot;&gt;You should have received a copy of the GNU General Public License along with this program. If not, see &lt;/span&gt;&lt;a href=&quot;https://www.gnu.org/copyleft/gpl.html&quot;&gt;&lt;span style=&quot; font-family:&apos;Ubuntu&apos;; text-decoration: underline; color:#007af4;&quot;&gt;https://www.gnu.org/copyleft/gpl.html&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-family:&apos;Ubuntu&apos;;&quot;&gt;.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
<translation>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:&apos;Oxygen-Sans&apos;; font-size:10pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;p style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;Sans Serif&apos;; color:#000000;&quot;&gt;Copyright © 2014-2015 by The qTox Project&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;Ubuntu&apos;;&quot;&gt;qTox è un'interfaccia grafica per Tox basato su Qt.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;Sans Serif&apos;;&quot;&gt;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.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;Sans Serif&apos;;&quot;&gt;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. &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;Ubuntu&apos;;&quot;&gt;Dovresti aver ricevuto una copia della GNU General Public License insieme a questo programma. In caso contrario, vedere &lt;/span&gt;&lt;a href=&quot;https://www.gnu.org/copyleft/gpl.html&quot;&gt;&lt;span style=&quot; font-family:&apos;Ubuntu&apos;; text-decoration: underline; color:#007af4;&quot;&gt;https://www.gnu.org/copyleft/gpl.html&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-family:&apos;Ubuntu&apos;;&quot;&gt;.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
<translation></translation>
</message>
<message>
<source>Authors</source>
@ -188,7 +193,7 @@ p, li { white-space: pre-wrap; }
</message>
<message>
<source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Original author: &lt;a href=&quot;https://github.com/tux3&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;tux3&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;See a full list of &lt;a href=&quot;https://github.com/tux3/qTox/graphs/contributors&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;contributors&lt;/span&gt;&lt;/a&gt; at Github&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
<translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Autore originale : &lt;a href=&quot;https://github.com/tux3&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;tux3&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;Guarda la lista completa degli &lt;a href=&quot;https://github.com/tux3/qTox/graphs/contributors&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;sviluppatori&lt;/span&gt;&lt;/a&gt; su Github&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
<translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Autore originale: &lt;a href=&quot;https://github.com/tux3&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;tux3&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;Guarda la lista completa degli &lt;a href=&quot;https://github.com/tux3/qTox/graphs/contributors&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;sviluppatori&lt;/span&gt;&lt;/a&gt; su Github&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
</message>
<message>
<source>Known Issues</source>
@ -196,14 +201,14 @@ p, li { white-space: pre-wrap; }
</message>
<message>
<source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;A list of all known issues may be found at our &lt;a href=&quot;https://github.com/tux3/qTox/issues&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;bug-tracker&lt;/span&gt;&lt;/a&gt; at Github. If you discover a bug or security vulnerability within qTox, please report it according to the guidelines in our &lt;a href=&quot;https://github.com/tux3/qTox/wiki/Writing-Useful-Bug-Reports&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;Writing Useful Bug Reports&lt;/span&gt;&lt;/a&gt; wiki article.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
<translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Un elenco di tutti i problemi noti può essere trovato presso il nostro &lt;a href=&quot;https://github.com/tux3/qTox/issues&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;bug-tracker&lt;/span&gt;&lt;/a&gt; su Github. Se si trova un bug o una vulnerabilità all'interno di qTox, si prega di segnalarlo secondo le linee guida nel nostro &lt;a href=&quot;https://github.com/tux3/qTox/wiki/Writing-Useful-Bug-Reports&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;Writing Useful Bug Reports&lt;/span&gt;&lt;/a&gt; articolo di wiki.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
<translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Un elenco di tutti i problemi noti può essere trovato presso il nostro &lt;a href=&quot;https://github.com/tux3/qTox/issues&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;bug-tracker&lt;/span&gt;&lt;/a&gt; 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 &lt;a href=&quot;https://github.com/tux3/qTox/wiki/Writing-Useful-Bug-Reports&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;Writing Useful Bug Reports&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
</message>
</context>
<context>
<name>AboutUser</name>
<message>
<source>Dialog</source>
<translation type="unfinished"></translation>
<translation>Dialogo</translation>
</message>
<message>
<source>username</source>
@ -231,11 +236,11 @@ p, li { white-space: pre-wrap; }
</message>
<message>
<source>Auto accept for this contact is disabled</source>
<translation type="unfinished"></translation>
<translation>I file scaricati automaticamente da questo contatto sono disabilitati</translation>
</message>
<message>
<source>Auto accept files</source>
<translation>Accetta automaticamente i file</translation>
<translation>Accetta automaticamente i file inviati da questo contatto</translation>
</message>
<message>
<source>Remove history (operation can not be undone!)</source>
@ -243,7 +248,7 @@ p, li { white-space: pre-wrap; }
</message>
<message>
<source>Notes</source>
<translation>Note</translation>
<translation>Appunti</translation>
</message>
<message>
<source>You can save comment about this contact here.</source>
@ -252,7 +257,7 @@ p, li { white-space: pre-wrap; }
<message>
<source>Choose an auto accept directory</source>
<comment>popup title</comment>
<translation type="unfinished">Scegliere una cartella dove accettare automaticamente i file</translation>
<translation>Scegliere una cartella per i file scaricati automaticamente</translation>
</message>
<message>
<source>History removed</source>
@ -326,6 +331,11 @@ Ignorare le impostazioni del proxy e connettersi direttamente alla rete Tox?</tr
<comment>DNS error</comment>
<translation>Questo Tox ID non esiste</translation>
</message>
<message>
<source>either 76 hexadecimal characters or name@example.com</source>
<comment>Tox ID format description</comment>
<translation>76 caratteri esadecimali oppure nome@esempio.com</translation>
</message>
</context>
<context>
<name>AdvancedForm</name>
@ -335,19 +345,16 @@ Ignorare le impostazioni del proxy e connettersi direttamente alla rete Tox?</tr
<translation>Avanzate</translation>
</message>
<message>
<location filename="../src/widget/form/settings/advancedform.cpp" line="39"/>
<source>FULL - very safe, slowest (recommended)</source>
<translation>COMPLETO - molto sicuro; lento (consigliato)</translation>
<source>Synchronized - safe (recommended)</source>
<translation>Sincronizzato - sicuro (consigliato)</translation>
</message>
<message>
<location filename="../src/widget/form/settings/advancedform.cpp" line="40"/>
<source>NORMAL - almost as safe as FULL, about 20% faster than FULL</source>
<translation>NORMALE - quasi sicuro come COMPLETO, ma circa 20% più veloce</translation>
<source>Partially async - risky (20% faster)</source>
<translation>Parzialmente sincronizzato - rischioso (20% più veloce)</translation>
</message>
<message>
<location filename="../src/widget/form/settings/advancedform.cpp" line="41"/>
<source>OFF - disables all safety, when something goes wrong your history may be lost, fastest (not recommended)</source>
<translation>DISABILITATO - disabilita tutta la sicurezza, quando qualcosa va male i log possono essere persi; veloce (non consigliato)</translation>
<source>Asynchronous - dangerous (fastest)</source>
<translation>Asincrono - pericoloso (più veloce)</translation>
</message>
</context>
<context>
@ -385,8 +392,8 @@ Ignorare le impostazioni del proxy e connettersi direttamente alla rete Tox?</tr
</message>
<message>
<location filename="../src/widget/form/settings/advancedsettings.ui" line="76"/>
<source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;http://www.sqlite.org/pragma.html#pragma_synchronous&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;Synchronous writing to DB&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
<translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;http://www.sqlite.org/pragma.html#pragma_synchronous&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;Scritture sincrone sul DB</translation>
<source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;http://www.sqlite.org/pragma.html#pragma_synchronous&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;Writing to DB&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
<translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;http://www.sqlite.org/pragma.html#pragma_synchronous&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;Scrittura nel DB&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
</message>
<message>
<location filename="../src/widget/form/settings/advancedsettings.ui" line="107"/>
@ -552,25 +559,23 @@ p, li { white-space: pre-wrap; }
<message>
<location filename="../src/widget/form/chatform.cpp" line="393"/>
<source>Cancel video call</source>
<translation>Rifiuta videochiamata</translation>
<translation>Annulla videochiamata</translation>
</message>
<message>
<location filename="../src/widget/form/chatform.cpp" line="401"/>
<source>Cancel audio call</source>
<translation>Rifiuta chiamata</translation>
<translation>Annulla chiamata</translation>
</message>
<message>
<location filename="../src/widget/form/chatform.cpp" line="409"/>
<source>Calling to %1</source>
<source>Calling %1</source>
<translation>Stai chiamando %1</translation>
</message>
<message>
<location filename="../src/widget/form/chatform.cpp" line="663"/>
<source>Start audio call</source>
<translation>Avvia chiamata</translation>
</message>
<message>
<location filename="../src/widget/form/chatform.cpp" line="666"/>
<source>Start video call</source>
<translation>Avvia videochiamata</translation>
</message>
@ -647,6 +652,23 @@ p, li { white-space: pre-wrap; }
<translation>Scrivi il tuo messaggio qui...</translation>
</message>
</context>
<context>
<name>CircleWidget</name>
<message>
<source>Rename circle</source>
<comment>Menu for renaming a circle</comment>
<translation>Rinomina circolo</translation>
</message>
<message>
<source>Remove circle</source>
<comment>Menu for removing a circle</comment>
<translation>Elimina circolo</translation>
</message>
<message>
<source>Open all in new window</source>
<translation>Apri tutto in una nuova finestra</translation>
</message>
</context>
<context>
<name>Core</name>
<message>
@ -834,20 +856,19 @@ Disabilitando la cronologia delle chat lascerà la cronologia criptata intatta (
<context>
<name>FilesForm</name>
<message>
<location filename="../src/widget/form/filesform.cpp" line="91"/>
<source>Transfered Files</source>
<source>Transferred Files</source>
<comment>&quot;Headline&quot; of the window</comment>
<translation>Files Trasferiti</translation>
<translation>File Trasferiti</translation>
</message>
<message>
<location filename="../src/widget/form/filesform.cpp" line="92"/>
<source>Downloads</source>
<translation>Files Ricevuti</translation>
<translation>File Ricevuti</translation>
</message>
<message>
<location filename="../src/widget/form/filesform.cpp" line="93"/>
<source>Uploads</source>
<translation>Files Inviati</translation>
<translation>File Inviati</translation>
</message>
</context>
<context>
@ -898,7 +919,7 @@ Disabilitando la cronologia delle chat lascerà la cronologia criptata intatta (
<location filename="../src/widget/friendwidget.cpp" line="76"/>
<source>Auto accept files from this friend</source>
<comment>context menu entry</comment>
<translation>Accetta automaticamente i files inviati da questo contatto</translation>
<translation>Accetta automaticamente i file inviati da questo contatto</translation>
</message>
<message>
<location filename="../src/widget/friendwidget.cpp" line="60"/>
@ -906,6 +927,23 @@ Disabilitando la cronologia delle chat lascerà la cronologia criptata intatta (
<comment>Menu to invite a friend to a groupchat</comment>
<translation>Invita nel gruppo</translation>
</message>
<message>
<source>Move to circle...</source>
<comment>Menu to move a friend into a different circle</comment>
<translation>Sposta nel circolo...</translation>
</message>
<message>
<source>To new circle</source>
<translation>In un nuovo circolo</translation>
</message>
<message>
<source>Remove from circle &apos;%1&apos;</source>
<translation>Rimuovi dal circolo &quot;%1&quot;</translation>
</message>
<message>
<source>Move to circle &quot;%1&quot;</source>
<translation>Sposta nel circolo &quot;%1&quot;</translation>
</message>
<message>
<location filename="../src/widget/friendwidget.cpp" line="73"/>
<source>Set alias...</source>
@ -917,11 +955,15 @@ Disabilitando la cronologia delle chat lascerà la cronologia criptata intatta (
<comment>Menu to remove the friend from our friendlist</comment>
<translation>Rimuovi contatto</translation>
</message>
<message>
<source>Show details</source>
<translation>Mostra dettagli</translation>
</message>
<message>
<location filename="../src/widget/friendwidget.cpp" line="111"/>
<source>Choose an auto accept directory</source>
<comment>popup title</comment>
<translation>Scegli dove salvare i files accettati automaticamente</translation>
<translation>Scegli dove salvare i file accettati automaticamente</translation>
</message>
<message>
<location filename="../src/widget/friendwidget.cpp" line="175"/>
@ -948,6 +990,10 @@ Disabilitando la cronologia delle chat lascerà la cronologia criptata intatta (
<source>Offline</source>
<translation>Offline</translation>
</message>
<message>
<source>Open chat in new window</source>
<translation>Apri la chat in una nuova finestra</translation>
</message>
<message>
<location filename="../src/widget/friendwidget.cpp" line="266"/>
<source>User alias</source>
@ -996,7 +1042,7 @@ Soprannome:</translation>
<location filename="../src/widget/form/settings/generalform.cpp" line="305"/>
<source>Choose an auto accept directory</source>
<comment>popup title</comment>
<translation>Scegli dove salvare i files accettati automaticamente</translation>
<translation>Scegli dove salvare i file accettati automaticamente</translation>
</message>
<message>
<location filename="../src/widget/form/settings/generalform.cpp" line="361"/>
@ -1037,7 +1083,7 @@ Soprannome:</translation>
<message>
<location filename="../src/widget/form/settings/generalsettings.ui" line="129"/>
<source>Show system tray icon</source>
<translation>Mostra icona nella traybar</translation>
<translation>Mostra icona nella barra di sistema</translation>
</message>
<message>
<location filename="../src/widget/form/settings/generalsettings.ui" line="156"/>
@ -1054,7 +1100,7 @@ Soprannome:</translation>
<location filename="../src/widget/form/settings/generalsettings.ui" line="119"/>
<source>qTox will start minimized in tray.</source>
<comment>toolTip for Start in tray setting</comment>
<translation>qTox sarà avviato minimizzato nella traybar.</translation>
<translation>qTox sarà avviato minimizzato nella barra di sistema.</translation>
</message>
<message>
<location filename="../src/widget/form/settings/generalsettings.ui" line="142"/>
@ -1062,7 +1108,7 @@ Soprannome:</translation>
instead of closing itself.</source>
<comment>toolTip for close to tray setting</comment>
<translation>Premendo l&apos;icona &quot;chiudi&quot; (X) qTox sarà minimizzato
nella traybar invece che essere chiuso.</translation>
nella barra di sistema invece che essere chiuso.</translation>
</message>
<message>
<location filename="../src/widget/form/settings/generalsettings.ui" line="169"/>
@ -1070,7 +1116,7 @@ nella traybar invece che essere chiuso.</translation>
instead of system taskbar.</source>
<comment>toolTip for minimize to tray setting</comment>
<translation>Premendo l&apos;icona &quot;minimizza&quot; (_) qTox sarà minimizzato
nella traybar invece che nella taskbar.</translation>
nella barra di sistema invece che nella barra delle applicazioni.</translation>
</message>
<message>
<location filename="../src/widget/form/settings/generalsettings.ui" line="252"/>
@ -1080,7 +1126,7 @@ nella traybar invece che nella taskbar.</translation>
<message>
<location filename="../src/widget/form/settings/generalsettings.ui" line="282"/>
<source>Set where files will be saved.</source>
<translation>Scegli dove salvare i files ricevuti.</translation>
<translation>Scegli dove salvare i file ricevuti.</translation>
</message>
<message>
<location filename="../src/widget/form/settings/generalsettings.ui" line="242"/>
@ -1097,6 +1143,16 @@ nella traybar invece che nella taskbar.</translation>
<source>Auto away after (0 to disable):</source>
<translation>Mostra come assente dopo (0 per disabilitare):</translation>
</message>
<message>
<location filename="../src/widget/form/settings/generalsettings.ui" line="283"/>
<source>Autoaccept files</source>
<translation>Accetta automaticamente i file</translation>
</message>
<message>
<location filename="../src/widget/form/settings/generalsettings.ui" line="267"/>
<source>Default directory to save files:</source>
<translation>Cartella predefinita per salvare i file:</translation>
</message>
<message>
<location filename="../src/widget/form/settings/generalsettings.ui" line="294"/>
<source>Chat</source>
@ -1137,7 +1193,7 @@ nella traybar invece che nella taskbar.</translation>
<message>
<location filename="../src/widget/form/settings/generalsettings.ui" line="269"/>
<source>Autoaccept and save files:</source>
<translation>Accetta automaticamente e salva i files in:</translation>
<translation>Accetta automaticamente e salva i file in:</translation>
</message>
<message>
<location filename="../src/widget/form/settings/generalsettings.ui" line="311"/>
@ -1166,6 +1222,27 @@ nella traybar invece che nella taskbar.</translation>
<source>Emoticon size:</source>
<translation>Dimensione:</translation>
</message>
<message>
<source>Open qTox&apos;s window when you receive a new message and no window is open yet.</source>
<comment>tooltip for Show window setting</comment>
<translation>Apri la finestra di qTox quando si riceve un nuovo messaggio.</translation>
</message>
<message>
<source>Start qTox on operating system startup (current profile).</source>
<translation>Apri qTox all'avvio del sistema operativo (profilo corrente).</translation>
</message>
<message>
<source>Open window</source>
<translation>Apri finestra</translation>
</message>
<message>
<source>Multiple windows mode</source>
<translation>Modalità finestre multiple</translation>
</message>
<message>
<source>Open each chat in an individual window</source>
<translation>Apri ogni chat in una finestra singola</translation>
</message>
<message>
<location filename="../src/widget/form/settings/generalsettings.ui" line="473"/>
<source>Style:</source>
@ -1203,20 +1280,25 @@ nella traybar invece che nella taskbar.</translation>
<extracomment>Text on proxy addr label</extracomment>
<translation>Indirizzo:</translation>
</message>
<message>
<source>Port:</source>
<extracomment>Text on proxy port label</extracomment>
<translation>Porta:</translation>
</message>
<message>
<location filename="../src/widget/form/settings/generalsettings.ui" line="122"/>
<source>Start in tray</source>
<translation>Avvia nella traybar</translation>
<translation>Avvia nella barra di sistema</translation>
</message>
<message>
<location filename="../src/widget/form/settings/generalsettings.ui" line="146"/>
<source>Close to tray</source>
<translation>Chiudi nella traybar</translation>
<translation>Chiudi nella barra di sistema</translation>
</message>
<message>
<location filename="../src/widget/form/settings/generalsettings.ui" line="173"/>
<source>Minimize to tray</source>
<translation>Minimizza nella traybar</translation>
<translation>Minimizza nella barra di sistema</translation>
</message>
<message>
<location filename="../src/widget/form/settings/generalsettings.ui" line="349"/>
@ -1354,7 +1436,7 @@ will be sent to them when they appear online to you.</source>
<message>
<location filename="../src/widget/form/genericchatform.cpp" line="494"/>
<source>Send file(s)</source>
<translation>Invia file(s)</translation>
<translation>Invia file</translation>
</message>
<message>
<location filename="../src/widget/form/genericchatform.cpp" line="495"/>
@ -1363,13 +1445,13 @@ will be sent to them when they appear online to you.</source>
</message>
<message>
<location filename="../src/widget/form/genericchatform.cpp" line="496"/>
<source>Start an audio call</source>
<translation>Inizia una chiamata</translation>
<source>Start audio call</source>
<translation>Avvia chiamata</translation>
</message>
<message>
<location filename="../src/widget/form/genericchatform.cpp" line="497"/>
<source>Start a video call</source>
<translation>Inizia una videochiamata</translation>
<source>Start video call</source>
<translation>Avvia videochiamata</translation>
</message>
<message>
<location filename="../src/widget/form/genericchatform.cpp" line="351"/>
@ -1393,6 +1475,21 @@ will be sent to them when they appear online to you.</source>
<translation>Pulito</translation>
</message>
</context>
<context>
<name>GenericNetCamView</name>
<message>
<source>Tox video</source>
<translation>Video Tox</translation>
</message>
<message>
<source>Show Messages</source>
<translation>Mostra messaggi</translation>
</message>
<message>
<source>Hide Messages</source>
<translation>Nascondi messaggi</translation>
</message>
</context>
<context>
<name>GroupChatForm</name>
<message>
@ -1477,6 +1574,10 @@ Title:</source>
<translation>Per impostare un nome puoi anche cliccare sul gruppo direttamente dalla chat.
Nome gruppo:</translation>
</message>
<message>
<source>Open chat in new window</source>
<translation>Apri la chat in una nuova finestra</translation>
</message>
</context>
<context>
<name>IdentitySettings</name>
@ -1524,6 +1625,10 @@ Puoi condividere questo codice QR al posto del tuo Tox ID.</translation>
<source>Profile</source>
<translation>Profilo</translation>
</message>
<message>
<source>&lt;p&gt;&lt;a href=&quot;file:///Dir_Path&quot;&gt;&lt;span style=&quot; text-decoration: NONE; color:#000000;&quot;&gt;Current profile location: Dir_Path&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;</source>
<translation>&lt;p&gt;&lt;a href=&quot;file:///Dir_Path&quot;&gt;&lt;span style=&quot; text-decoration: NONE; color:#000000;&quot;&gt;Posizione del profilo attuale: Dir_Path&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;</translation>
</message>
<message>
<location filename="../src/widget/form/profileform.ui" line="176"/>
<source>Rename profile.</source>
@ -1546,7 +1651,7 @@ Puoi condividere questo codice QR al posto del tuo Tox ID.</translation>
<location filename="../src/widget/form/profileform.ui" line="210"/>
<source>Logout</source>
<comment>import profile button</comment>
<translation>Logout</translation>
<translation>Esci</translation>
</message>
<message>
<location filename="../src/widget/form/profileform.ui" line="247"/>
@ -1600,12 +1705,12 @@ I profili non contengono la cronologia messaggi.</translation>
<message>
<location filename="../src/widget/form/loadhistorydialog.ui" line="14"/>
<source>Load History Dialog</source>
<translation>Carica log</translation>
<translation>Carica cronologia chat</translation>
</message>
<message>
<location filename="../src/widget/form/loadhistorydialog.ui" line="23"/>
<source>Load history from:</source>
<translation>Carica log dal giorno:</translation>
<translation>Carica cronologia chat del giorno:</translation>
</message>
</context>
<context>
@ -1630,8 +1735,7 @@ I profili non contengono la cronologia messaggi.</translation>
<message>
<location filename="../src/loginscreen.ui" line="388"/>
<source>Confirm:</source>
<translation>Conferma
password:</translation>
<translation>Conferma:</translation>
</message>
<message>
<location filename="../src/loginscreen.ui" line="408"/>
@ -1643,6 +1747,18 @@ password:</translation>
<source>Create Account</source>
<translation>Crea Profilo</translation>
</message>
<message>
<source>Load automatically</source>
<translation>Accedi automaticamente</translation>
</message>
<message>
<source>Load</source>
<translation>Accedi</translation>
</message>
<message>
<source>Load Profile</source>
<translation>Accedi al profilo</translation>
</message>
<message>
<location filename="../src/loginscreen.ui" line="757"/>
<source>If the profile does not have a password, qTox can skip the login screen</source>
@ -1663,6 +1779,10 @@ password:</translation>
<location filename="../src/loginscreen.ui" line="1071"/>
<source>New Profile</source>
<translation>Nuovo Profilo</translation>
</message>
<message>
<source>Create Profile</source>
<translation>Crea Profilo</translation>
</message>
<message>
<location filename="../src/widget/loginscreen.cpp" line="114"/>
@ -1812,9 +1932,14 @@ Se incontri questo errore, riportalo agli sviluppatori.</translation>
<translation>NoSpam</translation>
</message>
<message>
<location filename="../src/widget/form/settings/privacysettings.ui" line="80"/>
<source>The NoSpam is part of your Tox ID, if you are getting spammed with friend requests, change the NoSpam.</source>
<translation>Il valore nospam è parte del tuo Tox ID. Se ricevi molte richieste d&apos;amicizia indesiderate, cambia questo valore.</translation>
<location filename="../src/widget/form/settings/privacysettings.ui" line="68"/>
<source>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.</source>
<comment>toolTip for nospam</comment>
<translation>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.</translation>
</message>
<message>
<location filename="../src/widget/form/settings/privacysettings.ui" line="99"/>
@ -1830,24 +1955,27 @@ Save format changes are possible, which may result in data loss.</source>
Il formato del file potrebbe cambiare (questo potrebbe causare perdita di dati).</translation>
</message>
<message>
<location filename="../src/widget/form/settings/privacysettings.ui" line="68"/>
<source>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.</source>
<comment>toolTip for nospam</comment>
<translation>Il valore &quot;nospam&quot; fa parte del tuo Tox ID.
Lo si deve modificare quando si ritiene che si stanno ricevendo troppe richiste d&apos;amicizia da persone sconosciute.
Quando modifichi il valore &quot;nospam&quot;, 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.</translation>
<location filename="../src/widget/form/settings/privacysettings.ui" line="79"/>
<source>NoSpam is a part of your ID that can be changed at will.
If you are getting spammed with friend requests, change the NoSpam.</source>
<translation>Il valore NoSpam è parte del tuo Tox ID che può essere cambiata a piacimento.
Se ricevi molte richieste di amicizia indesiderate cambia questo valore.</translation>
</message>
</context>
<context>
<name>ProfileForm</name>
<message>
<source>User Profile</source>
<translation>Profilo Utente</translation>
</message>
<message>
<location filename="../src/widget/form/profileform.cpp" line="195"/>
<source>Choose a profile picture</source>
<translation>Scegli un&apos;immagine per il profilo</translation>
</message>
<message>
<source>Current profile: </source>
<translation>Profilo attuale: </translation>
</message>
<message>
<location filename="../src/widget/form/profileform.cpp" line="205"/>
@ -2106,9 +2234,28 @@ Permettimi di aggiungerti alla mia lista contatti.</translation>
<translation>Ridimensiona</translation>
</message>
<message>
<location filename="../src/widget/form/profileform.cpp" line="353"/>
<source>User Profile</source>
<translation>Profilo Utente</translation>
<source>None</source>
<comment>No camera device set</comment>
<translation>Nessuno</translation>
</message>
</context>
<context>
<name>RemoveFriendDialog</name>
<message>
<source>Remove friend</source>
<translation>Rimuovi contatto</translation>
</message>
<message>
<source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Are you sure you want to remove &lt;span style=&quot; font-weight:600;&quot;&gt;&amp;lt;name&amp;gt;&lt;/span&gt; from your contacts list?&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
<translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Sei sicuro di voler rimuovere &lt;span style=&quot; font-weight:600;&quot;&gt;&amp;lt;name&amp;gt;&lt;/span&gt; dalla tua lista contatti?&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
</message>
<message>
<source>Also remove chat history</source>
<translation>Rimuovi anche la cronologia chat</translation>
</message>
<message>
<source>Remove</source>
<translation>Rimuovi</translation>
</message>
</context>
<context>
@ -2133,6 +2280,14 @@ Permettimi di aggiungerti alla mia lista contatti.</translation>
<source>Set your password</source>
<translation>Imposta password</translation>
</message>
<message>
<source>Confirm:</source>
<translation>Conferma:</translation>
</message>
<message>
<source>Password strength: %p%</source>
<translation>Robustezza password: %p%</translation>
</message>
<message>
<location filename="../src/widget/form/setpassworddialog.ui" line="31"/>
<source>Repeat password</source>
@ -2306,6 +2461,18 @@ Se non sei sicuro, scegli &quot;No&quot;, così le informazioni inviate al serve
<source>Add friend</source>
<translation>Aggiungi contatto</translation>
</message>
<message>
<source>Add new circle...</source>
<translation>Aggiungi un nuovo circolo...</translation>
</message>
<message>
<source>By Name</source>
<translation>Per Nome</translation>
</message>
<message>
<source>By Activity</source>
<translation>Per Attività</translation>
</message>
<message>
<location filename="../src/widget/widget.cpp" line="1473"/>
<source>All</source>
@ -2339,7 +2506,7 @@ Se non sei sicuro, scegli &quot;No&quot;, così le informazioni inviate al serve
<message>
<location filename="../src/widget/widget.cpp" line="433"/>
<source>File transfers</source>
<translation>Files trasferiti</translation>
<translation>File trasferiti</translation>
</message>
<message>
<location filename="../src/widget/widget.cpp" line="444"/>
@ -2351,7 +2518,7 @@ Se non sei sicuro, scegli &quot;No&quot;, così le informazioni inviate al serve
<location filename="../src/widget/widget.cpp" line="444"/>
<source>You have asked qTox to open an executable file. Executable files can potentially damage your computer. Are you sure want to open this file?</source>
<comment>popup text</comment>
<translation>Hai chiesto a qTox di aprire un file eseguibile. I files eseguibili possono danneggiare il tuo computer. Sei sicuro di voler aprire questo file?</translation>
<translation>Hai chiesto a qTox di aprire un file eseguibile. I file eseguibili possono danneggiare il tuo computer. Sei sicuro di voler aprire questo file?</translation>
</message>
<message>
<location filename="../src/widget/widget.cpp" line="512"/>

65
translations/lt.ts vendored
View File

@ -109,6 +109,15 @@ Jei Jūsų interneto ryšys yra per prastas, turėsite keblumų su vaizdo skambu
<source>Qt version:</source>
<translation>Qt grafinės aplinkos versija:</translation>
</message>
<message>
<source>Restart qTox to install version %1</source>
<translation>Paleiskite qTox iš naujo, kad būtų įdiegta versija: %1</translation>
</message>
<message>
<source>qTox is downloading update %1</source>
<comment>%1 is the version of the update</comment>
<translation>qTox siunčia programos atnaujinimą: %1</translation>
</message>
</context>
<context>
<name>AboutSettings</name>
@ -116,10 +125,38 @@ Jei Jūsų interneto ryšys yra per prastas, turėsite keblumų su vaizdo skambu
<source>Version</source>
<translation>Versija</translation>
</message>
<message>
<source>You are using qTox version $GIT_DESCRIBE.</source>
<translation>Naudojate qTox versiją: $GIT_DESCRIBE.</translation>
</message>
<message>
<source>Downloading update: %p%</source>
<translation>Siunčiamas atnaujinimas: %p%</translation>
</message>
<message>
<source>License</source>
<translation>Licencija</translation>
</message>
<message>
<source>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:&apos;Noto Sans&apos;; font-size:10pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;p style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;Sans Serif&apos;; color:#000000;&quot;&gt;Copyright © 2014-2015 by The qTox Project&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;Ubuntu&apos;;&quot;&gt;qTox is a Qt-based graphical interface for Tox.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;Sans Serif&apos;;&quot;&gt;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.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;Sans Serif&apos;;&quot;&gt;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. &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;Ubuntu&apos;;&quot;&gt;You should have received a copy of the GNU General Public License along with this program. If not, see &lt;/span&gt;&lt;a href=&quot;https://www.gnu.org/copyleft/gpl.html&quot;&gt;&lt;span style=&quot; font-family:&apos;Ubuntu&apos;; text-decoration: underline; color:#007af4;&quot;&gt;https://www.gnu.org/copyleft/gpl.html&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-family:&apos;Ubuntu&apos;;&quot;&gt;.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
<translation>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; }
&lt;/style&gt;&lt;/head&gt;&lt;body style= &quot;font-family:&apos;Noto Sans&apos;; font-size:10pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;p style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;Sans Serif&apos;; color:#000000;&quot;&gt;Autorių teisės: qTox projektas, 2014-2015.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;Ubuntu&apos;;&quot;&gt;qTox Tox protokolu veikianti programa su Qt grafine aplinka.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;Sans Serif&apos;;&quot;&gt;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ą.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;Sans Serif&apos;;&quot;&gt;Tikimės, kad qTox bus Jums naudingas, bet nesuteikiame JOKIOS GARANTIJOS, įskaitant PERKAMUMO ar TINKAMUMO KOKIAM NORS TIKSLUI. Detalesnė informacija pateikta GPL licencijoje. &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;Ubuntu&apos;;&quot;&gt;GPL licencijos kopiją turėjote gauti kartu su šia programine įranga. Priešingu atveju, apsilankykite &lt;/span&gt;&lt;a href=&quot;https://www.gnu.org/copyleft/gpl.html&quot;&gt;&lt;span style=&quot; font-family:&apos;Ubuntu&apos;; text-decoration: underline; color:#007af4;&quot;&gt;https://www.gnu.org/copyleft/gpl.html&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-family:&apos;Ubuntu&apos;;&quot;&gt;.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
</message>
<message>
<source>toxcore version: $TOXCOREVERSION</source>
<translation>toxcore versija: $TOXCOREVERSION</translation>
@ -132,30 +169,6 @@ Jei Jūsų interneto ryšys yra per prastas, turėsite keblumų su vaizdo skambu
<source>Commit hash: &lt;a href=&quot;https://github.com/tux3/qTox/commit/$GIT_VERSION&quot;&gt;$GIT_VERSION&lt;/a&gt;</source>
<translation>Atnaujinimo maiša (&lt;i&gt;hash&lt;/i&gt;): &lt;a href=&quot;https://github.com/tux3/qTox/commit/$GIT_VERSION&quot;&gt;$GIT_VERSION&lt;/a&gt;</translation>
</message>
<message>
<source>You are using a qTox nightly build.</source>
<translation>Naudojate momentinę qTox versiją.</translation>
</message>
<message>
<source>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:&apos;Oxygen-Sans&apos;; font-size:10pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;p style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;Sans Serif&apos;; color:#000000;&quot;&gt;Copyright © 2014-2015 by The qTox Project&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;Ubuntu&apos;;&quot;&gt;qTox is a Qt-based graphical interface for Tox.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;Sans Serif&apos;;&quot;&gt;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.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;Sans Serif&apos;;&quot;&gt;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. &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;Ubuntu&apos;;&quot;&gt;You should have received a copy of the GNU General Public License along with this program. If not, see &lt;/span&gt;&lt;a href=&quot;https://www.gnu.org/copyleft/gpl.html&quot;&gt;&lt;span style=&quot; font-family:&apos;Ubuntu&apos;; text-decoration: underline; color:#007af4;&quot;&gt;https://www.gnu.org/copyleft/gpl.html&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-family:&apos;Ubuntu&apos;;&quot;&gt;.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
<translation>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; }
&lt;/style&gt;&lt;/head&gt;&lt;body style= &quot;font-family:&apos;Oxygen-Sans&apos;; font-size:10pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;p style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;Sans Serif&apos;; color:#000000;&quot;&gt;Autorių teisės: qTox projektas, 2014-2015.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;Ubuntu&apos;;&quot;&gt;qTox Tox protokolu veikianti programa su Qt grafine aplinka.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;Sans Serif&apos;;&quot;&gt;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ą.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;Sans Serif&apos;;&quot;&gt;Tikimės, kad qTox bus Jums naudingas, bet nesuteikiame JOKIOS GARANTIJOS, įskaitant PERKAMUMO ar TINKAMUMO KOKIAM NORS TIKSLUI. Detalesnė informacija pateikta GPL licencijoje. &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;Ubuntu&apos;;&quot;&gt;GPL licencijos kopiją turėjote gauti kartu su šia programine įranga. Priešingu atveju, apsilankykite &lt;/span&gt;&lt;a href=&quot;https://www.gnu.org/copyleft/gpl.html&quot;&gt;&lt;span style=&quot; font-family:&apos;Ubuntu&apos;; text-decoration: underline; color:#007af4;&quot;&gt;https://www.gnu.org/copyleft/gpl.html&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-family:&apos;Ubuntu&apos;;&quot;&gt;.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
</message>
<message>
<source>Authors</source>
<translation>Kūrėjai</translation>
@ -1775,6 +1788,10 @@ Nusiųskite ją tiems, su kuriais norite bendrauti.</translation>
</context>
<context>
<name>QObject</name>
<message>
<source>Version %1, %2</source>
<translation>Versija %1 (%2)</translation>
</message>
<message>
<source>Update</source>
<comment>The title of a message box</comment>

View File

@ -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

View File

@ -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