1
0
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:
tux3 2015-06-06 20:18:00 +02:00
commit adad71e565
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()