mirror of
https://github.com/qTox/qTox.git
synced 2024-03-22 14:00:36 +08:00
Add encrypting profile settings serializer
We now use a binary serialized format to save space and allow clean encryption of the user settings. All the settings can (and should) be edited from the GUI so there is no loss of functionnality. It can still read the old .ini format, and will seamlessly upgrade to the new format. Fixes #1810
This commit is contained in:
parent
de8eb9c693
commit
13bea16292
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