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:
commit
8b671916ab
|
@ -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
|
||||
```
|
||||
|
||||
|
||||
|
|
16
qtox.pro
16
qtox.pro
|
@ -143,8 +143,8 @@ win32 {
|
|||
RC_FILE = windows/qtox.rc
|
||||
LIBS += -L$$PWD/libs/lib -ltoxav -ltoxcore -ltoxencryptsave -ltoxdns -lsodium -lvpx -lpthread
|
||||
LIBS += -L$$PWD/libs/lib -lavdevice -lavformat -lavcodec -lavutil -lswscale -lOpenAL32 -lopus
|
||||
LIBS += -lqrencode -lsqlcipher -lcrypto
|
||||
LIBS += -lopengl32 -lole32 -loleaut32 -lvfw32 -lws2_32 -liphlpapi -lgdi32 -lshlwapi -luuid
|
||||
LIBS += -lqrencode
|
||||
LIBS += -lstrmiids # For DirectShow
|
||||
contains(DEFINES, QTOX_FILTER_AUDIO) {
|
||||
contains(STATICPKG, YES) {
|
||||
|
@ -160,7 +160,7 @@ win32 {
|
|||
QMAKE_INFO_PLIST = osx/info.plist
|
||||
QMAKE_MACOSX_DEPLOYMENT_TARGET = 10.7
|
||||
LIBS += -L$$PWD/libs/lib/ -ltoxcore -ltoxav -ltoxencryptsave -ltoxdns -lsodium -lvpx -lopus -framework OpenAL -lavformat -lavdevice -lavcodec -lavutil -lswscale -mmacosx-version-min=10.7
|
||||
LIBS += -lqrencode
|
||||
LIBS += -lqrencode -lsqlcipher
|
||||
contains(DEFINES, QTOX_PLATFORM_EXT) { LIBS += -framework IOKit -framework CoreFoundation }
|
||||
contains(DEFINES, QTOX_FILTER_AUDIO) { LIBS += -lfilteraudio }
|
||||
} else {
|
||||
|
@ -181,10 +181,10 @@ win32 {
|
|||
LIBS += -L$$PWD/libs/lib/ -lopus -lvpx -lopenal -Wl,-Bstatic -ltoxcore -ltoxav -ltoxencryptsave -ltoxdns -lsodium -lavformat -lavdevice -lavcodec -lavutil -lswscale -lz -Wl,-Bdynamic
|
||||
LIBS += -Wl,-Bstatic -ljpeg -ltiff -lpng -ljasper -lIlmImf -lIlmThread -lIex -ldc1394 -lraw1394 -lHalf -lz -llzma -ljbig
|
||||
LIBS += -Wl,-Bdynamic -lv4l1 -lv4l2 -lavformat -lavcodec -lavutil -lswscale -lusb-1.0
|
||||
LIBS += -lqrencode
|
||||
LIBS += -lqrencode -lsqlcipher
|
||||
} else {
|
||||
LIBS += -L$$PWD/libs/lib/ -ltoxcore -ltoxav -ltoxencryptsave -ltoxdns -lvpx -lsodium -lopenal -lavformat -lavdevice -lavcodec -lavutil -lswscale
|
||||
LIBS += -lqrencode
|
||||
LIBS += -lqrencode -lsqlcipher
|
||||
}
|
||||
|
||||
contains(DEFINES, QTOX_PLATFORM_EXT) {
|
||||
|
@ -515,7 +515,9 @@ SOURCES += \
|
|||
src/widget/tool/removefrienddialog.cpp \
|
||||
src/video/groupnetcamview.cpp \
|
||||
src/core/toxcall.cpp \
|
||||
src/widget/about/aboutuser.cpp
|
||||
src/widget/about/aboutuser.cpp \
|
||||
src/persistence/db/rawdatabase.cpp \
|
||||
src/persistence/history.cpp
|
||||
|
||||
HEADERS += \
|
||||
src/audio/audio.h \
|
||||
|
@ -569,4 +571,6 @@ HEADERS += \
|
|||
src/video/groupnetcamview.h \
|
||||
src/core/indexedlist.h \
|
||||
src/core/toxcall.h \
|
||||
src/widget/about/aboutuser.h
|
||||
src/widget/about/aboutuser.h \
|
||||
src/persistence/db/rawdatabase.h \
|
||||
src/persistence/history.h
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -375,6 +375,7 @@ bool AutoUpdater::downloadUpdate()
|
|||
{
|
||||
qDebug() << "Skipping already downloaded file '" + fileMeta.installpath+ "'";
|
||||
fileFile.close();
|
||||
progressValue = initialProgress + step;
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
486
src/persistence/db/rawdatabase.cpp
Normal file
486
src/persistence/db/rawdatabase.cpp
Normal 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);
|
||||
}
|
||||
}
|
118
src/persistence/db/rawdatabase.h
Normal file
118
src/persistence/db/rawdatabase.h
Normal 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
237
src/persistence/history.cpp
Normal 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
79
src/persistence/history.h
Normal 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
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 ×tamp)
|
||||
void OfflineMsgEngine::registerReceipt(int receipt, int64_t messageID, ChatMessage::Ptr msg, const QDateTime ×tamp)
|
||||
{
|
||||
QMutexLocker ml(&mutex);
|
||||
|
||||
|
@ -78,13 +81,13 @@ void OfflineMsgEngine::deliverOfflineMsgs()
|
|||
if (undeliveredMsgs.size() == 0)
|
||||
return;
|
||||
|
||||
QMap<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)
|
||||
{
|
||||
|
|
|
@ -39,7 +39,7 @@ public:
|
|||
static QMutex globalMutex;
|
||||
|
||||
void dischargeReceipt(int receipt);
|
||||
void registerReceipt(int receipt, int messageID, ChatMessage::Ptr msg, const QDateTime ×tamp = QDateTime::currentDateTime());
|
||||
void registerReceipt(int receipt, int64_t messageID, ChatMessage::Ptr msg, const QDateTime ×tamp = QDateTime::currentDateTime());
|
||||
|
||||
public slots:
|
||||
void deliverOfflineMsgs();
|
||||
|
@ -54,8 +54,8 @@ private:
|
|||
|
||||
QMutex mutex;
|
||||
Friend* f;
|
||||
QHash<int, int> receipts;
|
||||
QMap<int, MsgPtr> undeliveredMsgs;
|
||||
QHash<int, int64_t> receipts;
|
||||
QMap<int64_t, MsgPtr> undeliveredMsgs;
|
||||
|
||||
static const int offlineTimeout;
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"),
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
|
||||
<html><head><meta name="qrichtext" content="1" /><style type="text/css">
|
||||
p, li { white-space: pre-wrap; }
|
||||
</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;">
|
||||
<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-size:10pt; color:#000000;">Copyright © 2014-2015 by The qTox Project</span></p>
|
||||
<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu'; font-size:10pt;">qTox is a Qt-based graphical interface for Tox.</span></p>
|
||||
<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-size:10pt;">qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.</span></p>
|
||||
<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-size:10pt;">qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. </span></p>
|
||||
<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu'; font-size:10pt;">You should have received a copy of the GNU General Public License along with this program. If not, see </span><a href="https://www.gnu.org/copyleft/gpl.html"><span style=" font-family:'Ubuntu'; font-size:10pt; text-decoration: underline; color:#007af4;">https://www.gnu.org/copyleft/gpl.html</span></a><span style=" font-family:'Ubuntu'; font-size:10pt;">.</span></p></body></html></string>
|
||||
</style></head><body style=" font-family:'Noto Sans'; font-size:10pt; font-weight:400; font-style:normal;">
|
||||
<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; color:#000000;">Copyright © 2014-2015 by The qTox Project</span></p>
|
||||
<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu';">qTox is a Qt-based graphical interface for Tox.</span></p>
|
||||
<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif';">qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.</span></p>
|
||||
<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif';">qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. </span></p>
|
||||
<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu';">You should have received a copy of the GNU General Public License along with this program. If not, see </span><a href="https://www.gnu.org/copyleft/gpl.html"><span style=" font-family:'Ubuntu'; text-decoration: underline; color:#007af4;">https://www.gnu.org/copyleft/gpl.html</span></a><span style=" font-family:'Ubuntu';">.</span></p></body></html></string>
|
||||
</property>
|
||||
<property name="searchPaths">
|
||||
<stringlist/>
|
||||
|
|
|
@ -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)"));
|
||||
}
|
||||
|
|
|
@ -41,7 +41,6 @@ protected:
|
|||
|
||||
private slots:
|
||||
void onMakeToxPortableUpdated();
|
||||
void onDbSyncTypeUpdated();
|
||||
void resetToDefault();
|
||||
|
||||
private:
|
||||
|
|
|
@ -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><html><head/><body><p><a href="http://www.sqlite.org/pragma.html#pragma_synchronous"><span style=" text-decoration: underline; color:#0000ff;">Writing to DB</span></a></p></body></html></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">
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,7 +26,6 @@
|
|||
#include "groupwidget.h"
|
||||
#include "circlewidget.h"
|
||||
#include "widget.h"
|
||||
#include "src/persistence/historykeeper.h"
|
||||
#include <QGridLayout>
|
||||
#include <QMimeData>
|
||||
#include <QDragEnterEvent>
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
55
translations/de.ts
vendored
|
@ -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 "Nightly Build".</translation>
|
||||
<message utf8="true">
|
||||
<source><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
|
||||
<html><head><meta name="qrichtext" content="1" /><style type="text/css">
|
||||
p, li { white-space: pre-wrap; }
|
||||
</style></head><body style=" font-family:'Noto Sans'; font-size:10pt; font-weight:400; font-style:normal;">
|
||||
<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; color:#000000;">Copyright © 2014-2015 by The qTox Project</span></p>
|
||||
<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu';">qTox is a Qt-based graphical interface for Tox.</span></p>
|
||||
<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif';">qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.</span></p>
|
||||
<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif';">qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. </span></p>
|
||||
<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu';">You should have received a copy of the GNU General Public License along with this program. If not, see </span><a href="https://www.gnu.org/copyleft/gpl.html"><span style=" font-family:'Ubuntu'; text-decoration: underline; color:#007af4;">https://www.gnu.org/copyleft/gpl.html</span></a><span style=" font-family:'Ubuntu';">.</span></p></body></html></source>
|
||||
<translatorcomment>Ausgelassen, da Lizenz original auf Englisch</translatorcomment>
|
||||
<translation></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Commit hash: <a href="https://github.com/tux3/qTox/commit/$GIT_VERSION">$GIT_VERSION</a></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><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
|
||||
<html><head><meta name="qrichtext" content="1" /><style type="text/css">
|
||||
p, li { white-space: pre-wrap; }
|
||||
</style></head><body style=" font-family:'Oxygen-Sans'; font-size:10pt; font-weight:400; font-style:normal;">
|
||||
<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; color:#000000;">Copyright © 2014-2015 by The qTox Project</span></p>
|
||||
<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu';">qTox is a Qt-based graphical interface for Tox.</span></p>
|
||||
<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif';">qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.</span></p>
|
||||
<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif';">qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. </span></p>
|
||||
<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu';">You should have received a copy of the GNU General Public License along with this program. If not, see </span><a href="https://www.gnu.org/copyleft/gpl.html"><span style=" font-family:'Ubuntu'; text-decoration: underline; color:#007af4;">https://www.gnu.org/copyleft/gpl.html</span></a><span style=" font-family:'Ubuntu';">.</span></p></body></html></source>
|
||||
<translatorcomment>ausgelassen, Lizenz Original in Englisch</translatorcomment>
|
||||
<translation></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Authors</source>
|
||||
<translation>Entwickler</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source><html><head/><body><p>Original author: <a href="https://github.com/tux3"><span style=" text-decoration: underline; color:#0000ff;">tux3</span></a></p><p>See a full list of <a href="https://github.com/tux3/qTox/graphs/contributors"><span style=" text-decoration: underline; color:#0000ff;">contributors</span></a> at Github</p></body></html></source>
|
||||
<translation><html><head/><body><p>Ursprünglich entwickelt von: <a href="https://github.com/tux3"><span style=" text-decoration: underline; color:#0000ff;">tux3</span></a></p><p>Alle Mitwirkenden findest du unter <a href="https://github.com/tux3/qTox/graphs/contributors"><span style=" text-decoration: underline; color:#0000ff;">contributors</span></a> auf GitHub</p></body></html></translation>
|
||||
<translation><html><head/><body><p>Ursprünglich entwickelt von: <a href="https://github.com/tux3"><span style=" text-decoration: underline; color:#0000ff;">tux3</span></a></p><p>Alle Mitwirkenden findest du unter <a href="https://github.com/tux3/qTox/graphs/contributors"><span style=" text-decoration: underline; color:#0000ff;">Contributors</span></a> auf GitHub.</p></body></html></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Known Issues</source>
|
||||
|
@ -164,7 +177,7 @@ p, li { white-space: pre-wrap; }
|
|||
</message>
|
||||
<message>
|
||||
<source><html><head/><body><p>A list of all known issues may be found at our <a href="https://github.com/tux3/qTox/issues"><span style=" text-decoration: underline; color:#0000ff;">bug-tracker</span></a> at Github. If you discover a bug or security vulnerability within qTox, please report it according to the guidelines in our <a href="https://github.com/tux3/qTox/wiki/Writing-Useful-Bug-Reports"><span style=" text-decoration: underline; color:#0000ff;">Writing Useful Bug Reports</span></a> wiki article.</p></body></html></source>
|
||||
<translation><html><head/><body><p>Eine Liste aller bekannten Probleme findest du in unserem <a href="https://github.com/tux3/qTox/issues"><span style=" text-decoration: underline; color:#0000ff;">Bug-Tracker (Englisch)</span></a> auf GitHub. Wenn du einen Fehler oder eine Sicherheitslücke in qTox findest, melde sie bitte wie in unserem Wiki Artikel <a href="https://github.com/tux3/qTox/wiki/Writing-Useful-Bug-Reports"><span style=" text-decoration: underline; color:#0000ff;">Writing Useful Bug Reports (Englisch)</span></a> beschrieben.</p></body></html></translation>
|
||||
<translation><html><head/><body><p>Eine Liste aller bekannten Probleme findest du in unserem <a href="https://github.com/tux3/qTox/issues"><span style=" text-decoration: underline; color:#0000ff;">Bug-Tracker (Englisch)</span></a> auf GitHub. Wenn du einen Fehler oder eine Sicherheitslücke in qTox findest, melde sie bitte wie in unserem Wiki-Artikel <a href="https://github.com/tux3/qTox/wiki/Writing-Useful-Bug-Reports"><span style=" text-decoration: underline; color:#0000ff;">Writing Useful Bug Reports (Englisch)</span></a> beschrieben.</p></body></html></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
323
translations/it.ts
vendored
|
@ -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: <a href="https://github.com/tux3/qTox/commit/$GIT_VERSION">$GIT_VERSION</a></source>
|
||||
<translation>Commit hash: <a href="https://github.com/tux3/qTox/commit/$GIT_VERSION">$GIT_VERSION</a></translation>
|
||||
|
@ -172,15 +185,7 @@ p, li { white-space: pre-wrap; }
|
|||
<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif';">qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.</span></p>
|
||||
<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif';">qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. </span></p>
|
||||
<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu';">You should have received a copy of the GNU General Public License along with this program. If not, see </span><a href="https://www.gnu.org/copyleft/gpl.html"><span style=" font-family:'Ubuntu'; text-decoration: underline; color:#007af4;">https://www.gnu.org/copyleft/gpl.html</span></a><span style=" font-family:'Ubuntu';">.</span></p></body></html></source>
|
||||
<translation><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
|
||||
<html><head><meta name="qrichtext" content="1" /><style type="text/css">
|
||||
p, li { white-space: pre-wrap; }
|
||||
</style></head><body style=" font-family:'Oxygen-Sans'; font-size:10pt; font-weight:400; font-style:normal;">
|
||||
<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; color:#000000;">Copyright © 2014-2015 by The qTox Project</span></p>
|
||||
<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu';">qTox è un'interfaccia grafica per Tox basato su Qt.</span></p>
|
||||
<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif';">qTox è un software libero: è possibile ridistribuirlo o modificarlo secondo i termini della GNU General Public License come pubblicata dalla Free Software Foundation, o la versione 3 della licenza o (a tua scelta) qualsiasi versione successiva.</span></p>
|
||||
<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif';">qTox è distribuito nella speranza che sia utile, ma senza alcuna garanzia; senza neppure la garanzia implicita di commerciabilità o idoneità per uno scopo particolare. Vedi la GNU General Public License per maggiori dettagli. </span></p>
|
||||
<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu';">Dovresti aver ricevuto una copia della GNU General Public License insieme a questo programma. In caso contrario, vedere </span><a href="https://www.gnu.org/copyleft/gpl.html"><span style=" font-family:'Ubuntu'; text-decoration: underline; color:#007af4;">https://www.gnu.org/copyleft/gpl.html</span></a><span style=" font-family:'Ubuntu';">.</span></p></body></html></translation>
|
||||
<translation></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Authors</source>
|
||||
|
@ -188,7 +193,7 @@ p, li { white-space: pre-wrap; }
|
|||
</message>
|
||||
<message>
|
||||
<source><html><head/><body><p>Original author: <a href="https://github.com/tux3"><span style=" text-decoration: underline; color:#0000ff;">tux3</span></a></p><p>See a full list of <a href="https://github.com/tux3/qTox/graphs/contributors"><span style=" text-decoration: underline; color:#0000ff;">contributors</span></a> at Github</p></body></html></source>
|
||||
<translation><html><head/><body><p>Autore originale : <a href="https://github.com/tux3"><span style=" text-decoration: underline; color:#0000ff;">tux3</span></a></p><p>Guarda la lista completa degli <a href="https://github.com/tux3/qTox/graphs/contributors"><span style=" text-decoration: underline; color:#0000ff;">sviluppatori</span></a> su Github</p></body></html></translation>
|
||||
<translation><html><head/><body><p>Autore originale: <a href="https://github.com/tux3"><span style=" text-decoration: underline; color:#0000ff;">tux3</span></a></p><p>Guarda la lista completa degli <a href="https://github.com/tux3/qTox/graphs/contributors"><span style=" text-decoration: underline; color:#0000ff;">sviluppatori</span></a> su Github</p></body></html></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Known Issues</source>
|
||||
|
@ -196,14 +201,14 @@ p, li { white-space: pre-wrap; }
|
|||
</message>
|
||||
<message>
|
||||
<source><html><head/><body><p>A list of all known issues may be found at our <a href="https://github.com/tux3/qTox/issues"><span style=" text-decoration: underline; color:#0000ff;">bug-tracker</span></a> at Github. If you discover a bug or security vulnerability within qTox, please report it according to the guidelines in our <a href="https://github.com/tux3/qTox/wiki/Writing-Useful-Bug-Reports"><span style=" text-decoration: underline; color:#0000ff;">Writing Useful Bug Reports</span></a> wiki article.</p></body></html></source>
|
||||
<translation><html><head/><body><p>Un elenco di tutti i problemi noti può essere trovato presso il nostro <a href="https://github.com/tux3/qTox/issues"><span style=" text-decoration: underline; color:#0000ff;">bug-tracker</span></a> su Github. Se si trova un bug o una vulnerabilità all'interno di qTox, si prega di segnalarlo secondo le linee guida nel nostro <a href="https://github.com/tux3/qTox/wiki/Writing-Useful-Bug-Reports"><span style=" text-decoration: underline; color:#0000ff;">Writing Useful Bug Reports</span></a> articolo di wiki.</p></body></html></translation>
|
||||
<translation><html><head/><body><p>Un elenco di tutti i problemi noti può essere trovato presso il nostro <a href="https://github.com/tux3/qTox/issues"><span style=" text-decoration: underline; color:#0000ff;">bug-tracker</span></a> su Github. Se si trova un bug o una vulnerabilità all'interno di qTox, si prega di segnalarlo secondo le linee guida del nostro articolo wiki <a href="https://github.com/tux3/qTox/wiki/Writing-Useful-Bug-Reports"><span style=" text-decoration: underline; color:#0000ff;">Writing Useful Bug Reports</span></a>.</p></body></html></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><html><head/><body><p><a href="http://www.sqlite.org/pragma.html#pragma_synchronous"><span style=" text-decoration: underline; color:#0000ff;">Synchronous writing to DB</span></a></p></body></html></source>
|
||||
<translation><html><head/><body><p><a href="http://www.sqlite.org/pragma.html#pragma_synchronous"><span style=" text-decoration: underline; color:#0000ff;">Scritture sincrone sul DB</translation>
|
||||
<source><html><head/><body><p><a href="http://www.sqlite.org/pragma.html#pragma_synchronous"><span style=" text-decoration: underline; color:#0000ff;">Writing to DB</span></a></p></body></html></source>
|
||||
<translation><html><head/><body><p><a href="http://www.sqlite.org/pragma.html#pragma_synchronous"><span style=" text-decoration: underline; color:#0000ff;">Scrittura nel DB</span></a></p></body></html></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>"Headline" 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 '%1'</source>
|
||||
<translation>Rimuovi dal circolo "%1"</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Move to circle "%1"</source>
|
||||
<translation>Sposta nel circolo "%1"</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'icona "chiudi" (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'icona "minimizza" (_) 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'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><p><a href="file:///Dir_Path"><span style=" text-decoration: NONE; color:#000000;">Current profile location: Dir_Path</span></a></p></source>
|
||||
<translation><p><a href="file:///Dir_Path"><span style=" text-decoration: NONE; color:#000000;">Posizione del profilo attuale: Dir_Path</span></a></p></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'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 "nospam" fa parte del tuo Tox ID.
|
||||
Lo si deve modificare quando si ritiene che si stanno ricevendo troppe richiste d'amicizia da persone sconosciute.
|
||||
Quando modifichi il valore "nospam", i contatti che hai già aggiunto potranno continuare a comunicare con te,
|
||||
ma i nuovi contatti dovranno conoscere il tuo nuovo Tox ID per aggiungerti.</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'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><html><head/><body><p>Are you sure you want to remove <span style=" font-weight:600;">&lt;name&gt;</span> from your contacts list?</p></body></html></source>
|
||||
<translation><html><head/><body><p>Sei sicuro di voler rimuovere <span style=" font-weight:600;">&lt;name&gt;</span> dalla tua lista contatti?</p></body></html></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 "No", 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 "No", 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 "No", 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
65
translations/lt.ts
vendored
|
@ -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><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
|
||||
<html><head><meta name="qrichtext" content="1" /><style type="text/css">
|
||||
p, li { white-space: pre-wrap; }
|
||||
</style></head><body style=" font-family:'Noto Sans'; font-size:10pt; font-weight:400; font-style:normal;">
|
||||
<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; color:#000000;">Copyright © 2014-2015 by The qTox Project</span></p>
|
||||
<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu';">qTox is a Qt-based graphical interface for Tox.</span></p>
|
||||
<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif';">qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.</span></p>
|
||||
<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif';">qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. </span></p>
|
||||
<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu';">You should have received a copy of the GNU General Public License along with this program. If not, see </span><a href="https://www.gnu.org/copyleft/gpl.html"><span style=" font-family:'Ubuntu'; text-decoration: underline; color:#007af4;">https://www.gnu.org/copyleft/gpl.html</span></a><span style=" font-family:'Ubuntu';">.</span></p></body></html></source>
|
||||
<translation><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
|
||||
<html><head><meta name="qrichtext" content="1" /><style type="text/css">
|
||||
p, li { white-space: pre-wrap; }
|
||||
</style></head><body style= "font-family:'Noto Sans'; font-size:10pt; font-weight:400; font-style:normal;">
|
||||
<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; color:#000000;">Autorių teisės: qTox projektas, 2014-2015.</span></p>
|
||||
<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu';">qTox – Tox protokolu veikianti programa su Qt grafine aplinka.</span></p>
|
||||
<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif';">qTox yra atvira programinė įranga: galite ją platinti ir (arba) modifikuoti pagal GNU atvirosios programinės įrangos licencijos (GPL), kurią parengė Laisvosios programinės įrangos fondas (FSF), trečiąją (ar bet kurią vėlesnę) versiją.</span></p>
|
||||
<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif';">Tikimės, kad qTox bus Jums naudingas, bet nesuteikiame JOKIOS GARANTIJOS, įskaitant PERKAMUMO ar TINKAMUMO KOKIAM NORS TIKSLUI. Detalesnė informacija pateikta GPL licencijoje. </span></p>
|
||||
<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu';">GPL licencijos kopiją turėjote gauti kartu su šia programine įranga. Priešingu atveju, apsilankykite </span><a href="https://www.gnu.org/copyleft/gpl.html"><span style=" font-family:'Ubuntu'; text-decoration: underline; color:#007af4;">https://www.gnu.org/copyleft/gpl.html</span></a><span style=" font-family:'Ubuntu';">.</span></p></body></html></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: <a href="https://github.com/tux3/qTox/commit/$GIT_VERSION">$GIT_VERSION</a></source>
|
||||
<translation>Atnaujinimo maiša (<i>hash</i>): <a href="https://github.com/tux3/qTox/commit/$GIT_VERSION">$GIT_VERSION</a></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>You are using a qTox nightly build.</source>
|
||||
<translation>Naudojate momentinę qTox versiją.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
|
||||
<html><head><meta name="qrichtext" content="1" /><style type="text/css">
|
||||
p, li { white-space: pre-wrap; }
|
||||
</style></head><body style=" font-family:'Oxygen-Sans'; font-size:10pt; font-weight:400; font-style:normal;">
|
||||
<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; color:#000000;">Copyright © 2014-2015 by The qTox Project</span></p>
|
||||
<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu';">qTox is a Qt-based graphical interface for Tox.</span></p>
|
||||
<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif';">qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.</span></p>
|
||||
<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif';">qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. </span></p>
|
||||
<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu';">You should have received a copy of the GNU General Public License along with this program. If not, see </span><a href="https://www.gnu.org/copyleft/gpl.html"><span style=" font-family:'Ubuntu'; text-decoration: underline; color:#007af4;">https://www.gnu.org/copyleft/gpl.html</span></a><span style=" font-family:'Ubuntu';">.</span></p></body></html></source>
|
||||
<translation><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
|
||||
<html><head><meta name="qrichtext" content="1" /><style type="text/css">
|
||||
p, li { white-space: pre-wrap; }
|
||||
</style></head><body style= "font-family:'Oxygen-Sans'; font-size:10pt; font-weight:400; font-style:normal;">
|
||||
<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; color:#000000;">Autorių teisės: qTox projektas, 2014-2015.</span></p>
|
||||
<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu';">qTox – Tox protokolu veikianti programa su Qt grafine aplinka.</span></p>
|
||||
<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif';">qTox yra atvira programinė įranga: galite ją platinti ir (arba) modifikuoti pagal GNU atvirosios programinės įrangos licencijos (GPL), kurią parengė Laisvosios programinės įrangos fondas (FSF), trečiąją (ar bet kurią vėlesnę) versiją.</span></p>
|
||||
<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif';">Tikimės, kad qTox bus Jums naudingas, bet nesuteikiame JOKIOS GARANTIJOS, įskaitant PERKAMUMO ar TINKAMUMO KOKIAM NORS TIKSLUI. Detalesnė informacija pateikta GPL licencijoje. </span></p>
|
||||
<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu';">GPL licencijos kopiją turėjote gauti kartu su šia programine įranga. Priešingu atveju, apsilankykite </span><a href="https://www.gnu.org/copyleft/gpl.html"><span style=" font-family:'Ubuntu'; text-decoration: underline; color:#007af4;">https://www.gnu.org/copyleft/gpl.html</span></a><span style=" font-family:'Ubuntu';">.</span></p></body></html></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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue
Block a user