1
0
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:
tux3 2015-06-06 15:54:58 +02:00
parent de8eb9c693
commit 13bea16292
No known key found for this signature in database
GPG Key ID: 7E086DD661263264
16 changed files with 814 additions and 79 deletions

View File

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

View File

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

View File

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

View File

@ -38,6 +38,8 @@
#include <algorithm>
#include <cassert>
TOX_PASS_KEY* Core::encryptionKey = nullptr;
void Core::setPassword(const QString& password, uint8_t* salt)
{
clearPassword();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View 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

View File

@ -292,7 +292,7 @@ void ProfileForm::onDeleteClicked()
void ProfileForm::onLogoutClicked()
{
Nexus& nexus = Nexus::getInstance();
Settings::getInstance().save();
Settings::getInstance().saveGlobal();
nexus.showLogin();
}

View File

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

View File

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

View File

@ -209,7 +209,7 @@ void LoginScreen::onAutoLoginToggled(int state)
else
Settings::getInstance().setAutoLogin(true);
Settings::getInstance().save(false);
Settings::getInstance().saveGlobal();
}
void LoginScreen::retranslateUi()