mirror of
https://github.com/qTox/qTox.git
synced 2024-03-22 14:00:36 +08:00
Merge branch 'encrypted_settings'
This commit is contained in:
commit
adad71e565
6
qtox.pro
6
qtox.pro
|
@ -480,7 +480,8 @@ SOURCES += \
|
|||
src/video/corevideosource.cpp \
|
||||
src/core/toxid.cpp \
|
||||
src/persistence/profile.cpp \
|
||||
src/widget/translator.cpp
|
||||
src/widget/translator.cpp \
|
||||
src/persistence/settingsserializer.cpp
|
||||
|
||||
HEADERS += \
|
||||
src/audio/audio.h \
|
||||
|
@ -515,4 +516,5 @@ HEADERS += \
|
|||
src/video/videomode.h \
|
||||
src/core/toxid.h \
|
||||
src/persistence/profile.h \
|
||||
src/widget/translator.h
|
||||
src/widget/translator.h \
|
||||
src/persistence/settingsserializer.h
|
||||
|
|
|
@ -260,7 +260,6 @@ void Core::start()
|
|||
{
|
||||
qDebug() << "Creating a new profile";
|
||||
makeTox(QByteArray());
|
||||
Settings::getInstance().load();
|
||||
setStatusMessage(tr("Toxing on qTox"));
|
||||
setUsername(profile.getName());
|
||||
}
|
||||
|
|
|
@ -302,7 +302,7 @@ private:
|
|||
QMutex messageSendMutex;
|
||||
bool ready;
|
||||
|
||||
TOX_PASS_KEY* encryptionKey = nullptr; // use the pw's hash as the "pw"
|
||||
static TOX_PASS_KEY* encryptionKey; // use the pw's hash as the "pw"
|
||||
|
||||
static const int videobufsize;
|
||||
static uint8_t* videobuf;
|
||||
|
|
|
@ -38,6 +38,8 @@
|
|||
#include <algorithm>
|
||||
#include <cassert>
|
||||
|
||||
TOX_PASS_KEY* Core::encryptionKey = nullptr;
|
||||
|
||||
void Core::setPassword(const QString& password, uint8_t* salt)
|
||||
{
|
||||
clearPassword();
|
||||
|
|
|
@ -59,8 +59,7 @@ Nexus::~Nexus()
|
|||
#endif
|
||||
delete loginScreen;
|
||||
delete profile;
|
||||
if (profile)
|
||||
Settings::getInstance().save();
|
||||
Settings::getInstance().saveGlobal();
|
||||
}
|
||||
|
||||
void Nexus::start()
|
||||
|
@ -77,6 +76,7 @@ void Nexus::start()
|
|||
qRegisterMetaType<int32_t>("int32_t");
|
||||
qRegisterMetaType<int64_t>("int64_t");
|
||||
qRegisterMetaType<QPixmap>("QPixmap");
|
||||
qRegisterMetaType<Profile*>("Profile*");
|
||||
qRegisterMetaType<ToxFile>("ToxFile");
|
||||
qRegisterMetaType<ToxFile::FileDirection>("ToxFile::FileDirection");
|
||||
qRegisterMetaType<std::shared_ptr<VideoFrame>>("std::shared_ptr<VideoFrame>");
|
||||
|
@ -220,6 +220,8 @@ Profile* Nexus::getProfile()
|
|||
void Nexus::setProfile(Profile* profile)
|
||||
{
|
||||
getInstance().profile = profile;
|
||||
if (profile)
|
||||
Settings::getInstance().loadPersonnal(profile);
|
||||
}
|
||||
|
||||
AndroidGUI* Nexus::getAndroidGUI()
|
||||
|
|
|
@ -42,7 +42,7 @@ Profile::Profile(QString name, QString password, bool isNewProfile)
|
|||
{
|
||||
Settings& s = Settings::getInstance();
|
||||
s.setCurrentProfile(name);
|
||||
s.save(false);
|
||||
s.saveGlobal();
|
||||
HistoryKeeper::resetInstance();
|
||||
|
||||
coreThread = new QThread();
|
||||
|
@ -99,6 +99,8 @@ Profile::~Profile()
|
|||
saveToxSave();
|
||||
delete core;
|
||||
delete coreThread;
|
||||
Settings::getInstance().savePersonal(this);
|
||||
Settings::getInstance().sync();
|
||||
ProfileLocker::assertLock();
|
||||
assert(ProfileLocker::getCurLockName() == name);
|
||||
ProfileLocker::unlock();
|
||||
|
|
|
@ -204,6 +204,21 @@ uint64_t dataToUint64(QByteArray data)
|
|||
+(((uint64_t)(uint8_t)data[7])<<56);
|
||||
}
|
||||
|
||||
size_t dataToVUint(const QByteArray& data)
|
||||
{
|
||||
unsigned char num3;
|
||||
size_t num = 0;
|
||||
int num2 = 0;
|
||||
int i=0;
|
||||
do
|
||||
{
|
||||
num3 = data[i]; i++;
|
||||
num |= (num3 & 0x7f) << num2;
|
||||
num2 += 7;
|
||||
} while ((num3 & 0x80) != 0);
|
||||
return num;
|
||||
}
|
||||
|
||||
unsigned getVUint32Size(QByteArray data)
|
||||
{
|
||||
unsigned lensize=0;
|
||||
|
@ -255,3 +270,18 @@ QByteArray uint64ToData(uint64_t num)
|
|||
data[7] = (uint8_t)((num>>56) & 0xFF);
|
||||
return data;
|
||||
}
|
||||
|
||||
QByteArray vuintToData(size_t num)
|
||||
{
|
||||
QByteArray data(sizeof(size_t), 0);
|
||||
// Write the size in a Uint of variable lenght (8-32 bits)
|
||||
int i=0;
|
||||
while (num >= 0x80)
|
||||
{
|
||||
data[i] = (unsigned char)(num | 0x80); i++;
|
||||
num = num >> 7;
|
||||
}
|
||||
data[i]=num;
|
||||
data.resize(i+1);
|
||||
return data;
|
||||
}
|
||||
|
|
|
@ -39,10 +39,12 @@ uint8_t dataToUint8(QByteArray data);
|
|||
uint16_t dataToUint16(QByteArray data);
|
||||
uint32_t dataToUint32(QByteArray data);
|
||||
uint64_t dataToUint64(QByteArray data);
|
||||
size_t dataToVUint(const QByteArray& data);
|
||||
unsigned getVUint32Size(QByteArray data);
|
||||
QByteArray uint8ToData(uint8_t num);
|
||||
QByteArray uint16ToData(uint16_t num);
|
||||
QByteArray uint32ToData(uint32_t num);
|
||||
QByteArray uint64ToData(uint64_t num);
|
||||
QByteArray vuintToData(size_t num);
|
||||
|
||||
#endif // SERIALIZE_H
|
||||
|
|
|
@ -25,6 +25,9 @@
|
|||
#include "src/core/core.h"
|
||||
#include "src/widget/gui.h"
|
||||
#include "src/persistence/profilelocker.h"
|
||||
#include "src/persistence/settingsserializer.h"
|
||||
#include "src/nexus.h"
|
||||
#include "src/persistence/profile.h"
|
||||
#ifdef QTOX_PLATFORM_EXT
|
||||
#include "src/platform/autorun.h"
|
||||
#endif
|
||||
|
@ -34,7 +37,6 @@
|
|||
#include <QApplication>
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QSettings>
|
||||
#include <QStandardPaths>
|
||||
#include <QDebug>
|
||||
#include <QList>
|
||||
|
@ -58,7 +60,7 @@ Settings::Settings() :
|
|||
settingsThread->setObjectName("qTox Settings");
|
||||
settingsThread->start(QThread::LowPriority);
|
||||
moveToThread(settingsThread);
|
||||
load();
|
||||
loadGlobal();
|
||||
}
|
||||
|
||||
Settings::~Settings()
|
||||
|
@ -83,7 +85,7 @@ void Settings::destroyInstance()
|
|||
settings = nullptr;
|
||||
}
|
||||
|
||||
void Settings::load()
|
||||
void Settings::loadGlobal()
|
||||
{
|
||||
QMutexLocker locker{&bigLock};
|
||||
|
||||
|
@ -254,66 +256,63 @@ void Settings::load()
|
|||
}
|
||||
|
||||
loaded = true;
|
||||
}
|
||||
|
||||
void Settings::loadPersonnal()
|
||||
{
|
||||
Profile* profile = Nexus::getProfile();
|
||||
if (!profile)
|
||||
{
|
||||
// load from a profile specific friend data list if possible
|
||||
QString tmp = dir.filePath(currentProfile + ".ini");
|
||||
if (QFile(tmp).exists()) // otherwise, filePath remains the global file
|
||||
filePath = tmp;
|
||||
|
||||
QSettings ps(filePath, QSettings::IniFormat);
|
||||
friendLst.clear();
|
||||
ps.beginGroup("Friends");
|
||||
int size = ps.beginReadArray("Friend");
|
||||
for (int i = 0; i < size; i ++)
|
||||
{
|
||||
ps.setArrayIndex(i);
|
||||
friendProp fp;
|
||||
fp.addr = ps.value("addr").toString();
|
||||
fp.alias = ps.value("alias").toString();
|
||||
fp.autoAcceptDir = ps.value("autoAcceptDir").toString();
|
||||
friendLst[ToxId(fp.addr).publicKey] = fp;
|
||||
}
|
||||
ps.endArray();
|
||||
ps.endGroup();
|
||||
|
||||
ps.beginGroup("Privacy");
|
||||
typingNotification = ps.value("typingNotification", false).toBool();
|
||||
enableLogging = ps.value("enableLogging", false).toBool();
|
||||
ps.endGroup();
|
||||
qCritical() << "No active profile, couldn't load personnal settings";
|
||||
return;
|
||||
}
|
||||
loadPersonnal(profile);
|
||||
}
|
||||
|
||||
void Settings::save(bool writePersonal)
|
||||
{
|
||||
if (QThread::currentThread() != settingsThread)
|
||||
return (void) QMetaObject::invokeMethod(&getInstance(), "save",
|
||||
Q_ARG(bool, writePersonal));
|
||||
|
||||
QString filePath = getSettingsDirPath();
|
||||
save(filePath, writePersonal);
|
||||
}
|
||||
|
||||
void Settings::save(QString path, bool writePersonal)
|
||||
{
|
||||
if (QThread::currentThread() != settingsThread)
|
||||
return (void) QMetaObject::invokeMethod(&getInstance(), "save",
|
||||
Q_ARG(QString, path), Q_ARG(bool, writePersonal));
|
||||
|
||||
#ifndef Q_OS_ANDROID
|
||||
if (IPC::getInstance().isCurrentOwner())
|
||||
#endif
|
||||
saveGlobal(path);
|
||||
|
||||
if (writePersonal)
|
||||
savePersonal(path);
|
||||
}
|
||||
|
||||
void Settings::saveGlobal(QString path)
|
||||
void Settings::loadPersonnal(Profile* profile)
|
||||
{
|
||||
QMutexLocker locker{&bigLock};
|
||||
path += globalSettingsFile;
|
||||
qDebug() << "Saving settings at " + path;
|
||||
|
||||
QDir dir(getSettingsDirPath());
|
||||
QString filePath = dir.filePath(globalSettingsFile);
|
||||
|
||||
// load from a profile specific friend data list if possible
|
||||
QString tmp = dir.filePath(profile->getName() + ".ini");
|
||||
if (QFile(tmp).exists()) // otherwise, filePath remains the global file
|
||||
filePath = tmp;
|
||||
|
||||
qDebug()<<"Loading personnal settings from"<<filePath;
|
||||
|
||||
SettingsSerializer ps(filePath, profile->getPassword());
|
||||
friendLst.clear();
|
||||
ps.beginGroup("Friends");
|
||||
int size = ps.beginReadArray("Friend");
|
||||
for (int i = 0; i < size; i ++)
|
||||
{
|
||||
ps.setArrayIndex(i);
|
||||
friendProp fp;
|
||||
fp.addr = ps.value("addr").toString();
|
||||
fp.alias = ps.value("alias").toString();
|
||||
fp.autoAcceptDir = ps.value("autoAcceptDir").toString();
|
||||
friendLst[ToxId(fp.addr).publicKey] = fp;
|
||||
}
|
||||
ps.endArray();
|
||||
ps.endGroup();
|
||||
|
||||
ps.beginGroup("Privacy");
|
||||
typingNotification = ps.value("typingNotification", false).toBool();
|
||||
enableLogging = ps.value("enableLogging", false).toBool();
|
||||
ps.endGroup();
|
||||
}
|
||||
|
||||
void Settings::saveGlobal()
|
||||
{
|
||||
if (QThread::currentThread() != settingsThread)
|
||||
return (void) QMetaObject::invokeMethod(&getInstance(), "saveGlobal");
|
||||
|
||||
QMutexLocker locker{&bigLock};
|
||||
QString path = getSettingsDirPath() + globalSettingsFile;
|
||||
qDebug() << "Saving global settings at " + path;
|
||||
|
||||
QSettings s(path, QSettings::IniFormat);
|
||||
|
||||
|
@ -411,18 +410,34 @@ void Settings::saveGlobal(QString path)
|
|||
s.endGroup();
|
||||
}
|
||||
|
||||
void Settings::savePersonal(QString path)
|
||||
void Settings::savePersonal()
|
||||
{
|
||||
QMutexLocker locker{&bigLock};
|
||||
if (currentProfile.isEmpty())
|
||||
savePersonal(Nexus::getProfile());
|
||||
}
|
||||
|
||||
void Settings::savePersonal(Profile* profile)
|
||||
{
|
||||
if (!profile)
|
||||
{
|
||||
qDebug() << "could not save personal settings because currentProfile profile is empty";
|
||||
qDebug() << "Could not save personal settings because there is no active profile";
|
||||
return;
|
||||
}
|
||||
savePersonal(profile->getName(), profile->getPassword());
|
||||
}
|
||||
|
||||
qDebug() << "Saving personal settings in " << path;
|
||||
void Settings::savePersonal(QString profileName, QString password)
|
||||
{
|
||||
if (QThread::currentThread() != settingsThread)
|
||||
return (void) QMetaObject::invokeMethod(&getInstance(), "savePersonal",
|
||||
Q_ARG(QString, profileName), Q_ARG(QString, password));
|
||||
|
||||
QSettings ps(path + currentProfile + ".ini", QSettings::IniFormat);
|
||||
QMutexLocker locker{&bigLock};
|
||||
|
||||
QString path = getSettingsDirPath() + profileName + ".ini";
|
||||
|
||||
qDebug() << "Saving personal settings at " << path;
|
||||
|
||||
SettingsSerializer ps(path, password);
|
||||
ps.beginGroup("Friends");
|
||||
ps.beginWriteArray("Friend", friendLst.size());
|
||||
int index = 0;
|
||||
|
@ -441,6 +456,8 @@ void Settings::savePersonal(QString path)
|
|||
ps.setValue("typingNotification", typingNotification);
|
||||
ps.setValue("enableLogging", enableLogging);
|
||||
ps.endGroup();
|
||||
|
||||
ps.write();
|
||||
}
|
||||
|
||||
uint32_t Settings::makeProfileId(const QString& profile)
|
||||
|
@ -559,7 +576,7 @@ void Settings::setMakeToxPortable(bool newValue)
|
|||
QMutexLocker locker{&bigLock};
|
||||
QFile(getSettingsDirPath()+globalSettingsFile).remove();
|
||||
makeToxPortable = newValue;
|
||||
save(false);
|
||||
saveGlobal();
|
||||
}
|
||||
|
||||
bool Settings::getAutorun() const
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
#include "src/core/corestructs.h"
|
||||
|
||||
class ToxId;
|
||||
class Profile;
|
||||
namespace Db { enum class syncType; }
|
||||
|
||||
enum ProxyType {ptNone, ptSOCKS5, ptHTTP};
|
||||
|
@ -43,10 +44,15 @@ public:
|
|||
void createSettingsDir(); ///< Creates a path to the settings dir, if it doesn't already exist
|
||||
void createPersonal(QString basename); ///< Write a default personnal .ini settings file for a profile
|
||||
|
||||
void savePersonal(); ///< Asynchronous, saves the current profile
|
||||
void savePersonal(Profile *profile); ///< Asynchronous
|
||||
|
||||
void loadGlobal();
|
||||
void loadPersonnal();
|
||||
void loadPersonnal(Profile *profile);
|
||||
|
||||
public slots:
|
||||
void load();
|
||||
void save(bool writePersonal = true); ///< Asynchronous
|
||||
void save(QString path, bool writePersonal = true); ///< Asynchronous
|
||||
void saveGlobal(); ///< Asynchronous
|
||||
void sync(); ///< Waits for all asynchronous operations to complete
|
||||
|
||||
signals:
|
||||
|
@ -264,8 +270,9 @@ private:
|
|||
Settings(Settings &settings) = delete;
|
||||
Settings& operator=(const Settings&) = delete;
|
||||
static uint32_t makeProfileId(const QString& profile);
|
||||
void saveGlobal(QString path);
|
||||
void savePersonal(QString path);
|
||||
|
||||
private slots:
|
||||
void savePersonal(QString profileName, QString password);
|
||||
|
||||
private:
|
||||
bool loaded;
|
||||
|
|
564
src/persistence/settingsserializer.cpp
Normal file
564
src/persistence/settingsserializer.cpp
Normal file
|
@ -0,0 +1,564 @@
|
|||
/*
|
||||
Copyright © 2015 by The qTox Project
|
||||
|
||||
This file is part of qTox, a Qt-based graphical interface for Tox.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with qTox. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "settingsserializer.h"
|
||||
#include "serialize.h"
|
||||
#include "src/nexus.h"
|
||||
#include "src/persistence/profile.h"
|
||||
#include "src/core/core.h"
|
||||
#include <QFile>
|
||||
#include <QDebug>
|
||||
#include <memory>
|
||||
#include <cassert>
|
||||
|
||||
using namespace std;
|
||||
|
||||
constexpr char SettingsSerializer::magic[] = {0x51,0x54,0x4F,0x58};
|
||||
|
||||
QDataStream& operator<<(QDataStream& dataStream, const SettingsSerializer::RecordTag& tag)
|
||||
{
|
||||
return dataStream << static_cast<uint8_t>(tag);
|
||||
}
|
||||
|
||||
QDataStream& operator<<(QDataStream& dataStream, const QString& str)
|
||||
{
|
||||
return dataStream << str.toUtf8();
|
||||
}
|
||||
|
||||
QDataStream& operator<<(QDataStream& dataStream, const QByteArray& data)
|
||||
{
|
||||
QByteArray size = vuintToData(data.size());
|
||||
dataStream.writeRawData(size.data(), size.size());
|
||||
dataStream.writeRawData(data.data(), data.size());
|
||||
return dataStream;
|
||||
}
|
||||
|
||||
QDataStream& operator>>(QDataStream& dataStream, SettingsSerializer::RecordTag& tag)
|
||||
{
|
||||
return dataStream.operator >>((uint8_t&)tag);
|
||||
}
|
||||
|
||||
QDataStream& operator>>(QDataStream& dataStream, QByteArray& data)
|
||||
{
|
||||
unsigned char num3;
|
||||
size_t num = 0;
|
||||
int num2 = 0;
|
||||
do
|
||||
{
|
||||
dataStream.readRawData((char*)&num3, 1);
|
||||
num |= (num3 & 0x7f) << num2;
|
||||
num2 += 7;
|
||||
} while ((num3 & 0x80) != 0);
|
||||
data.resize(num);
|
||||
dataStream.readRawData(data.data(), num);
|
||||
return dataStream;
|
||||
}
|
||||
|
||||
SettingsSerializer::SettingsSerializer(QString filePath, QString password)
|
||||
: path{filePath}, password{password},
|
||||
group{-1}, array{-1}, arrayIndex{-1}
|
||||
{
|
||||
if (isSerializedFormat(filePath))
|
||||
readSerialized();
|
||||
else
|
||||
readIni();
|
||||
|
||||
/* Dump state for debugging
|
||||
qDebug() << "SettingsSerializer data:";
|
||||
for (int i=0; i<groups.size(); i++)
|
||||
qDebug()<<"Group"<<i<<"is"<<groups[i];
|
||||
for (int i=0; i<arrays.size(); i++)
|
||||
qDebug()<<"Array"<<i<<"size"<<arrays[i].size<<arrays[i].values.size()<<"of group"<<arrays[i].group<<"is"<<arrays[i].name;
|
||||
for (int i=0; i<values.size(); i++)
|
||||
qDebug()<<"Value"<<i<<"of group"<<values[i].group<<"array"<<values[i].array<<values[i].arrayIndex<<"key"<<values[i].key;
|
||||
*/
|
||||
}
|
||||
|
||||
void SettingsSerializer::beginGroup(const QString &prefix)
|
||||
{
|
||||
if (prefix.isEmpty())
|
||||
endGroup();
|
||||
int index = groups.indexOf(prefix);
|
||||
if (index >= 0)
|
||||
{
|
||||
group = index;
|
||||
}
|
||||
else
|
||||
{
|
||||
group = groups.size();
|
||||
groups.append(prefix);
|
||||
}
|
||||
}
|
||||
|
||||
void SettingsSerializer::endGroup()
|
||||
{
|
||||
group = -1;
|
||||
}
|
||||
|
||||
int SettingsSerializer::beginReadArray(const QString &prefix)
|
||||
{
|
||||
auto index = find_if(begin(arrays), end(arrays), [=](const Array& a){return a.name==prefix;});
|
||||
if (index != end(arrays))
|
||||
{
|
||||
array = index-begin(arrays);
|
||||
arrayIndex = -1;
|
||||
return index->size;
|
||||
}
|
||||
else
|
||||
{
|
||||
array = arrays.size();
|
||||
arrays.push_back({group, 0, prefix, {}});
|
||||
arrayIndex = -1;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
void SettingsSerializer::beginWriteArray(const QString &prefix, int size)
|
||||
{
|
||||
auto index = find_if(begin(arrays), end(arrays), [=](const Array& a){return a.name==prefix;});
|
||||
if (index != end(arrays))
|
||||
{
|
||||
array = index-begin(arrays);
|
||||
arrayIndex = -1;
|
||||
if (size > 0)
|
||||
index->size = max(index->size, (quint64)size);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (size < 0)
|
||||
size = 0;
|
||||
array = arrays.size();
|
||||
arrays.push_back({group, (uint64_t)size, prefix, {}});
|
||||
arrayIndex = -1;
|
||||
}
|
||||
}
|
||||
|
||||
void SettingsSerializer::endArray()
|
||||
{
|
||||
array = -1;
|
||||
}
|
||||
|
||||
void SettingsSerializer::setArrayIndex(unsigned i)
|
||||
{
|
||||
arrayIndex = i;
|
||||
}
|
||||
|
||||
void SettingsSerializer::setValue(const QString &key, const QVariant &value)
|
||||
{
|
||||
Value* v = findValue(key);
|
||||
if (v)
|
||||
{
|
||||
v->value = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
Value nv{group, array, arrayIndex, key, value};
|
||||
if (array >= 0)
|
||||
arrays[array].values.append(values.size());
|
||||
values.append(nv);
|
||||
}
|
||||
}
|
||||
|
||||
QVariant SettingsSerializer::value(const QString &key, const QVariant &defaultValue) const
|
||||
{
|
||||
const Value* v = findValue(key);
|
||||
if (v)
|
||||
return v->value;
|
||||
else
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
const SettingsSerializer::Value* SettingsSerializer::findValue(const QString& key) const
|
||||
{
|
||||
if (array != -1)
|
||||
{
|
||||
for (const Array& a : arrays)
|
||||
{
|
||||
if (a.group != group)
|
||||
continue;
|
||||
for (int vi : a.values)
|
||||
if (values[vi].arrayIndex == arrayIndex && values[vi].key == key)
|
||||
return &values[vi];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (const Value& v : values)
|
||||
if (v.group == group && v.array == -1 && v.key == key)
|
||||
return &v;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
SettingsSerializer::Value* SettingsSerializer::findValue(const QString& key)
|
||||
{
|
||||
return const_cast<Value*>(const_cast<const SettingsSerializer*>(this)->findValue(key));
|
||||
}
|
||||
|
||||
bool SettingsSerializer::isSerializedFormat(QString filePath)
|
||||
{
|
||||
QFile f(filePath);
|
||||
if (!f.open(QIODevice::ReadOnly))
|
||||
return false;
|
||||
char fmagic[8];
|
||||
if (f.read(fmagic, sizeof(fmagic)) != sizeof(fmagic))
|
||||
return false;
|
||||
return !memcmp(fmagic, magic, 4) || tox_is_data_encrypted((uint8_t*)fmagic);
|
||||
}
|
||||
|
||||
void SettingsSerializer::write()
|
||||
{
|
||||
QFile f(path);
|
||||
if (!f.open(QIODevice::Truncate | QIODevice::WriteOnly))
|
||||
{
|
||||
qWarning() << "Couldn't open file";
|
||||
return;
|
||||
}
|
||||
|
||||
QByteArray data(magic, 4);
|
||||
QDataStream stream(&data, QIODevice::ReadWrite | QIODevice::Append);
|
||||
stream.setVersion(QDataStream::Qt_5_0);
|
||||
|
||||
for (int g=-1; g<groups.size(); g++)
|
||||
{
|
||||
// Save the group name, if any
|
||||
if (g!=-1)
|
||||
{
|
||||
stream << RecordTag::GroupStart;
|
||||
stream << groups[g].toUtf8();
|
||||
//qDebug()<<"#Group"<<groups[g];
|
||||
}
|
||||
|
||||
// Save all the arrays of this group
|
||||
for (const Array& a : arrays)
|
||||
{
|
||||
if (a.group != g)
|
||||
continue;
|
||||
if (a.size <= 0)
|
||||
continue;
|
||||
stream << RecordTag::ArrayStart;
|
||||
stream << a.name.toUtf8();
|
||||
stream << vuintToData(a.size);
|
||||
//qDebug()<<"#array start"<<a.name<<a.size;
|
||||
for (uint64_t vi : a.values)
|
||||
{
|
||||
stream << RecordTag::ArrayValue;
|
||||
stream << vuintToData(values[vi].arrayIndex);
|
||||
stream << values[vi].key.toUtf8();
|
||||
writePackedVariant(stream, values[vi].value);
|
||||
//qDebug()<<"#key (in array)"<<values[vi].key;
|
||||
}
|
||||
stream << RecordTag::ArrayEnd;
|
||||
}
|
||||
|
||||
// Save all the values of this group that aren't in an array
|
||||
for (const Value& v : values)
|
||||
{
|
||||
if (v.group != g || v.array != -1)
|
||||
continue;
|
||||
stream << RecordTag::Value;
|
||||
stream << v.key.toUtf8();
|
||||
writePackedVariant(stream, v.value);
|
||||
//qDebug()<<"#key (standalone)"<<v.key;
|
||||
}
|
||||
}
|
||||
|
||||
// Encrypt
|
||||
if (!password.isEmpty())
|
||||
{
|
||||
Core* core = Nexus::getCore();
|
||||
core->setPassword(password);
|
||||
data = core->encryptData(data);
|
||||
}
|
||||
|
||||
f.write(data);
|
||||
f.close();
|
||||
}
|
||||
|
||||
void SettingsSerializer::readSerialized()
|
||||
{
|
||||
QFile f(path);
|
||||
if (!f.open(QIODevice::ReadOnly))
|
||||
{
|
||||
qWarning() << "Couldn't open file";
|
||||
return;
|
||||
}
|
||||
QByteArray data = f.readAll();
|
||||
f.close();
|
||||
|
||||
// Decrypt
|
||||
if (tox_is_data_encrypted((uint8_t*)data.data()))
|
||||
{
|
||||
if (password.isEmpty())
|
||||
{
|
||||
qCritical() << "The settings file is encrypted, but we don't have a password!";
|
||||
return;
|
||||
}
|
||||
|
||||
Core* core = Nexus::getCore();
|
||||
|
||||
uint8_t salt[TOX_PASS_SALT_LENGTH];
|
||||
tox_get_salt(reinterpret_cast<uint8_t *>(data.data()), salt);
|
||||
core->setPassword(password, salt);
|
||||
|
||||
data = core->decryptData(data);
|
||||
if (data.isEmpty())
|
||||
qCritical() << "Failed to decrypt the settings file";
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!password.isEmpty())
|
||||
qWarning() << "We have a password, but the settings file is not encrypted";
|
||||
}
|
||||
|
||||
if (memcmp(data.data(), magic, 4))
|
||||
{
|
||||
qWarning() << "Bad magic!";
|
||||
return;
|
||||
}
|
||||
data = data.mid(4);
|
||||
|
||||
QDataStream stream(&data, QIODevice::ReadOnly);
|
||||
stream.setVersion(QDataStream::Qt_5_0);
|
||||
|
||||
while (!stream.atEnd())
|
||||
{
|
||||
RecordTag tag;
|
||||
stream >> tag;
|
||||
if (tag == RecordTag::Value)
|
||||
{
|
||||
QByteArray key;
|
||||
QByteArray value;
|
||||
stream >> key;
|
||||
stream >> value;
|
||||
setValue(QString::fromUtf8(key), QVariant(QString::fromUtf8(value)));
|
||||
//qDebug() << "!Got key"<<key;
|
||||
}
|
||||
else if (tag == RecordTag::GroupStart)
|
||||
{
|
||||
QByteArray prefix;
|
||||
stream >> prefix;
|
||||
beginGroup(QString::fromUtf8(prefix));
|
||||
//qDebug()<<"!Group start"<<prefix;
|
||||
}
|
||||
else if (tag == RecordTag::ArrayStart)
|
||||
{
|
||||
QByteArray prefix;
|
||||
stream >> prefix;
|
||||
beginReadArray(QString::fromUtf8(prefix));
|
||||
QByteArray sizeData;
|
||||
stream >> sizeData;
|
||||
quint64 size = dataToVUint(sizeData);
|
||||
arrays[array].size = max(size, arrays[array].size);
|
||||
//qDebug()<<"!Array start"<<prefix;
|
||||
}
|
||||
else if (tag == RecordTag::ArrayValue)
|
||||
{
|
||||
QByteArray indexData;
|
||||
stream >> indexData;
|
||||
quint64 index = dataToVUint(indexData);
|
||||
setArrayIndex(index);
|
||||
QByteArray key;
|
||||
QByteArray value;
|
||||
stream >> key;
|
||||
stream >> value;
|
||||
setValue(QString::fromUtf8(key), QVariant(QString::fromUtf8(value)));
|
||||
//qDebug() << "!Got array key"<<key<<"index"<<index;
|
||||
}
|
||||
else if (tag == RecordTag::ArrayEnd)
|
||||
{
|
||||
endArray();
|
||||
//qDebug() <<"!Array end";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SettingsSerializer::readIni()
|
||||
{
|
||||
QSettings s(path, QSettings::IniFormat);
|
||||
|
||||
// Read all keys of all groups, reading arrays as raw keys
|
||||
QList<QString> gstack;
|
||||
do {
|
||||
// Add all keys
|
||||
if (!s.group().isEmpty())
|
||||
beginGroup(s.group());
|
||||
for (QString k : s.childKeys())
|
||||
{
|
||||
setValue(k, s.value(k));
|
||||
//qDebug() << "Read key "<<k<<" in group "<<group<<":\""<<groups[group]<<"\"";
|
||||
}
|
||||
|
||||
// Add all groups
|
||||
gstack.push_back(QString());
|
||||
for (QString g : s.childGroups())
|
||||
gstack.push_back(g);
|
||||
|
||||
// Visit the next group, if any
|
||||
while (!gstack.isEmpty())
|
||||
{
|
||||
QString g = gstack.takeLast();
|
||||
if (g.isEmpty())
|
||||
{
|
||||
if (gstack.isEmpty())
|
||||
break;
|
||||
else
|
||||
s.endGroup();
|
||||
}
|
||||
else
|
||||
{
|
||||
s.beginGroup(g);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} while (!gstack.isEmpty());
|
||||
|
||||
// We can convert keys that look like arrays into real arrays
|
||||
// If a group's only key is called size, we'll consider it to be an array,
|
||||
// and its elements are all groups matching the pattern "[<group>/]<arrayName>/<arrayIndex>"
|
||||
|
||||
// Find groups that only have 1 key
|
||||
std::unique_ptr<int[]> groupSizes{new int[groups.size()]};
|
||||
memset(groupSizes.get(), 0, groups.size()*sizeof(int));
|
||||
for (const Value& v : values)
|
||||
{
|
||||
if (v.group < 0 || v.group > groups.size())
|
||||
continue;
|
||||
groupSizes[v.group]++;
|
||||
}
|
||||
|
||||
// Find arrays, remove their size key from the values, and add them to `arrays`
|
||||
QVector<int> groupsToKill;
|
||||
for (int i=values.size()-1; i>=0; i--)
|
||||
{
|
||||
const Value& v = values[i];
|
||||
if (v.group < 0 || v.group > groups.size())
|
||||
continue;
|
||||
if (groupSizes[v.group] != 1)
|
||||
continue;
|
||||
if (v.key != "size")
|
||||
continue;
|
||||
if (!v.value.canConvert(QVariant::Int))
|
||||
continue;
|
||||
|
||||
Array a;
|
||||
a.size = v.value.toInt();
|
||||
int slashIndex = groups[v.group].lastIndexOf('/');
|
||||
if (slashIndex == -1)
|
||||
{
|
||||
a.group = -1;
|
||||
a.name = groups[v.group];
|
||||
a.size = v.value.toInt();
|
||||
}
|
||||
else
|
||||
{
|
||||
a.group = -1;
|
||||
for (int i=0; i<groups.size(); i++)
|
||||
if (groups[i] == groups[v.group].left(slashIndex))
|
||||
a.group = i;
|
||||
a.name = groups[v.group].mid(slashIndex+1);
|
||||
|
||||
}
|
||||
groupSizes[v.group]--;
|
||||
groupsToKill.append(v.group);
|
||||
arrays.append(a);
|
||||
values.removeAt(i);
|
||||
//qDebug() << "Found array"<<a.name<<"in group"<<a.group<<"size"<<a.size;
|
||||
}
|
||||
|
||||
// Associate each array's values with the array
|
||||
for (int ai=0; ai<arrays.size(); ai++)
|
||||
{
|
||||
Array& a = arrays[ai];
|
||||
QString arrayPrefix;
|
||||
if (a.group != -1)
|
||||
arrayPrefix += groups[a.group]+'/';
|
||||
arrayPrefix += a.name+'/';
|
||||
|
||||
// Find groups which represent each array index
|
||||
for (int g=0; g<groups.size(); g++)
|
||||
{
|
||||
if (!groups[g].startsWith(arrayPrefix))
|
||||
continue;
|
||||
bool ok;
|
||||
quint64 groupArrayIndex = groups[g].mid(arrayPrefix.size()).toInt(&ok);
|
||||
if (!ok)
|
||||
continue;
|
||||
groupsToKill.append(g);
|
||||
//qDebug() << "Found element"<<groupArrayIndex<<"of array"<<a.name;
|
||||
|
||||
if (groupArrayIndex > a.size)
|
||||
a.size = groupArrayIndex;
|
||||
|
||||
// Associate the values for this array index
|
||||
for (int vi=values.size()-1; vi>=0; vi--)
|
||||
{
|
||||
Value& v = values[vi];
|
||||
if (v.group != g)
|
||||
continue;
|
||||
groupSizes[g]--;
|
||||
v.group = a.group;
|
||||
v.array = ai;
|
||||
v.arrayIndex = groupArrayIndex;
|
||||
a.values.append(vi);
|
||||
//qDebug() << "Found key"<<v.key<<"at index"<<groupArrayIndex<<"of array"<<a.name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up spurious array element groups
|
||||
sort(begin(groupsToKill), end(groupsToKill), std::greater_equal<int>());
|
||||
for (int g : groupsToKill)
|
||||
{
|
||||
if (groupSizes[g])
|
||||
continue;
|
||||
//qDebug() << "Removing spurious array group"<<g<<groupSizes[g];
|
||||
removeGroup(g);
|
||||
}
|
||||
}
|
||||
|
||||
void SettingsSerializer::removeGroup(int group)
|
||||
{
|
||||
assert(group<groups.size());
|
||||
for (Array& a : arrays)
|
||||
{
|
||||
assert(a.group != group);
|
||||
if (a.group > group)
|
||||
a.group--;
|
||||
}
|
||||
for (Value& v : values)
|
||||
{
|
||||
assert(v.group != group);
|
||||
if (v.group > group)
|
||||
v.group--;
|
||||
}
|
||||
groups.removeAt(group);
|
||||
}
|
||||
|
||||
void SettingsSerializer::writePackedVariant(QDataStream& stream, const QVariant& v)
|
||||
{
|
||||
assert(v.canConvert(QVariant::String));
|
||||
QString str = v.toString();
|
||||
if (str == "true")
|
||||
stream << QString("1");
|
||||
else if (str == "false")
|
||||
stream << QString("0");
|
||||
else
|
||||
stream << str.toUtf8();
|
||||
}
|
108
src/persistence/settingsserializer.h
Normal file
108
src/persistence/settingsserializer.h
Normal file
|
@ -0,0 +1,108 @@
|
|||
/*
|
||||
Copyright © 2015 by The qTox Project
|
||||
|
||||
This file is part of qTox, a Qt-based graphical interface for Tox.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with qTox. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef SETTINGSSERIALIZER_H
|
||||
#define SETTINGSSERIALIZER_H
|
||||
|
||||
#include <QSettings>
|
||||
#include <QVector>
|
||||
#include <QString>
|
||||
|
||||
/// Serializes a QSettings's data in an (optionally) encrypted binary format
|
||||
/// SettingsSerializer can detect regular .ini files and serialized ones,
|
||||
/// it will read both regular and serialized .ini, but only save in serialized format.
|
||||
/// The file is encrypted with the current profile's password, if any.
|
||||
/// The file is only written to disk if write() is called, the destructor does not write to disk
|
||||
/// All member functions are reentrant, but not thread safe.
|
||||
class SettingsSerializer
|
||||
{
|
||||
public:
|
||||
SettingsSerializer(QString filePath, QString password=QString()); ///< Loads the settings from file
|
||||
|
||||
static bool isSerializedFormat(QString filePath); ///< Check if the file is serialized settings. False on error.
|
||||
|
||||
void write(); ///< Writes the current settings back to file
|
||||
|
||||
void beginGroup(const QString &prefix);
|
||||
void endGroup();
|
||||
|
||||
int beginReadArray(const QString &prefix);
|
||||
void beginWriteArray(const QString &prefix, int size = -1);
|
||||
void endArray();
|
||||
void setArrayIndex(unsigned i);
|
||||
|
||||
void setValue(const QString &key, const QVariant &value);
|
||||
QVariant value(const QString &key, const QVariant &defaultValue = QVariant()) const;
|
||||
|
||||
private:
|
||||
enum class RecordTag : uint8_t
|
||||
{
|
||||
/// Followed by a QString key then a QVariant value
|
||||
Value=0,
|
||||
/// Followed by a QString group name
|
||||
GroupStart=1,
|
||||
/// Followed by a QString array name and a vuint array size
|
||||
ArrayStart=2,
|
||||
/// Followed by a vuint array index, a QString key then a QVariant value
|
||||
ArrayValue=3,
|
||||
/// Not followed by any data
|
||||
ArrayEnd=4,
|
||||
};
|
||||
friend QDataStream& operator<<(QDataStream& dataStream, const SettingsSerializer::RecordTag& tag);
|
||||
friend QDataStream& operator>>(QDataStream& dataStream, SettingsSerializer::RecordTag& tag);
|
||||
|
||||
struct Value
|
||||
{
|
||||
Value() : group{-2},array{-2},key{QString()},value{}{}
|
||||
Value(qint64 group, qint64 array, qint64 arrayIndex, QString key, QVariant value)
|
||||
: group{group}, array{array}, arrayIndex{arrayIndex}, key{key}, value{value} {}
|
||||
qint64 group;
|
||||
qint64 array, arrayIndex;
|
||||
QString key;
|
||||
QVariant value;
|
||||
};
|
||||
|
||||
struct Array
|
||||
{
|
||||
qint64 group;
|
||||
quint64 size;
|
||||
QString name;
|
||||
QVector<quint64> values;
|
||||
};
|
||||
|
||||
private:
|
||||
const Value *findValue(const QString& key) const;
|
||||
Value *findValue(const QString& key);
|
||||
void readSerialized();
|
||||
void readIni();
|
||||
void removeValue(const QString& key);
|
||||
void removeGroup(int group); ///< The group must be empty
|
||||
void writePackedVariant(QDataStream& dataStream, const QVariant& v);
|
||||
|
||||
private:
|
||||
QString path;
|
||||
QString password;
|
||||
int group, array, arrayIndex;
|
||||
QVector<QString> groups;
|
||||
QVector<Array> arrays;
|
||||
QVector<Value> values;
|
||||
static const char magic[]; ///< Little endian ASCII "QTOX" magic
|
||||
};
|
||||
|
||||
#endif // SETTINGSSERIALIZER_H
|
|
@ -292,7 +292,7 @@ void ProfileForm::onDeleteClicked()
|
|||
void ProfileForm::onLogoutClicked()
|
||||
{
|
||||
Nexus& nexus = Nexus::getInstance();
|
||||
Settings::getInstance().save();
|
||||
Settings::getInstance().saveGlobal();
|
||||
nexus.showLogin();
|
||||
}
|
||||
|
||||
|
|
|
@ -232,7 +232,7 @@ void GeneralForm::onSetShowSystemTray()
|
|||
Settings::getInstance().setShowSystemTray(bodyUI->showSystemTray->isChecked());
|
||||
emit parent->setShowSystemTray(bodyUI->showSystemTray->isChecked());
|
||||
bodyUI->lightTrayIcon->setEnabled(bodyUI->showSystemTray->isChecked());
|
||||
Settings::getInstance().save();
|
||||
Settings::getInstance().saveGlobal();
|
||||
}
|
||||
|
||||
void GeneralForm::onSetAutostartInTray()
|
||||
|
|
|
@ -251,7 +251,7 @@ void FriendWidget::setAlias(const QString& _alias)
|
|||
Friend* f = FriendList::findFriend(friendId);
|
||||
f->setAlias(alias);
|
||||
Settings::getInstance().setFriendAlias(f->getToxId(), alias);
|
||||
Settings::getInstance().save(true);
|
||||
Settings::getInstance().savePersonal();
|
||||
hide();
|
||||
show();
|
||||
}
|
||||
|
|
|
@ -209,7 +209,7 @@ void LoginScreen::onAutoLoginToggled(int state)
|
|||
else
|
||||
Settings::getInstance().setAutoLogin(true);
|
||||
|
||||
Settings::getInstance().save(false);
|
||||
Settings::getInstance().saveGlobal();
|
||||
}
|
||||
|
||||
void LoginScreen::retranslateUi()
|
||||
|
|
Loading…
Reference in New Issue
Block a user