Skip to content

Commit

Permalink
Refactored config to correctly save state on exit
Browse files Browse the repository at this point in the history
  • Loading branch information
medo64 committed Dec 14, 2021
1 parent 1a87692 commit 011187b
Show file tree
Hide file tree
Showing 3 changed files with 165 additions and 97 deletions.
1 change: 1 addition & 0 deletions src/app.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ int main(int argc, char* argv[]) {
Config::setStateFilePath(dataPaths[0] + "/.qtext.user"); //store state file in the first directory
QApplication::connect(State::instance(), &State::writeToConfig, [ = ] (QString key, QString value) { Config::stateWrite("State!" + key, value); });
QApplication::connect(State::instance(), &State::readFromConfig, [ = ] (QString key) { return Config::stateRead("State!" + key, QString()); });
QApplication::connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, [ = ] () { Config::quit(); });

storage = new Storage(dataPaths);
MainWindow w { storage };
Expand Down
205 changes: 130 additions & 75 deletions src/medo/config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,18 @@
#include <QSaveFile>
#include <QStandardPaths>
#include <QTextStream>
#include <QTimer>

QMutex Config::_publicAccessMutex(QMutex::Recursive);
QMutex Config::_configFileMutex(QMutex::NonRecursive);
QMutex Config::_stateFileMutex(QMutex::NonRecursive);
QString Config::_configurationFilePath;
QString Config::_stateFilePath;
QString Config::_dataDirectoryPath;
Config::PortableStatus Config::_isPortable(PortableStatus::Unknown);
bool Config::_immediateSave(true);
Config::ConfigFile* Config::_configFile(nullptr);
Config::ConfigFile* Config::_stateFile(nullptr);
Config::ConfigSaveThread Config::_configSaveThread{};

void Config::reset() {
qDebug().noquote().nospace() << "[Config] reset()";
Expand All @@ -35,35 +37,38 @@ void Config::reset() {
}

bool Config::load() {
QMutexLocker locker(&_publicAccessMutex);
qDebug().noquote().nospace() << "[Config] load()";
QElapsedTimer stopwatch; stopwatch.start();

QMutexLocker locker(&_publicAccessMutex);
resetConfigFile();
resetStateFile();
QFile file(configurationFilePath());

if (file.exists()) {
qDebug().noquote().nospace() << "[Config] load() done in " << stopwatch.elapsed() << "ms";
return true;
} else {
qDebug().noquote().nospace() << "[Config] load() failed in " << stopwatch.elapsed() << "ms";
return false;
}
}

bool Config::save() {
QMutexLocker locker(&_publicAccessMutex);
qDebug().noquote().nospace() << "[Config] save()";
QElapsedTimer stopwatch; stopwatch.start();

bool success = getConfigFile()->save();
getStateFile()->save(); // ignore status for return code
return success;
}

void Config::quit() {
QMutexLocker locker(&_publicAccessMutex);
if (getConfigFile()->save()) {
qDebug().noquote().nospace() << "[Config] save() done in " << stopwatch.elapsed() << "ms";
return true;
} else {
qDebug().noquote().nospace() << "[Config] save() failed in " << stopwatch.elapsed() << "ms";
return false;
}
qDebug().noquote().nospace() << "[Config] quit()";

getConfigFile()->save();
resetConfigFile();

getStateFile()->save();
resetStateFile();
}


Expand Down Expand Up @@ -131,7 +136,10 @@ bool Config::immediateSave() {
void Config::setImmediateSave(bool saveImmediately) {
QMutexLocker locker(&_publicAccessMutex);
_immediateSave = saveImmediately;
if (_immediateSave) { Config::_configSaveThread.requestSave(); } //to ensure any pending writes are cleared
if (_immediateSave) { //to ensure any pending writes are cleared
getConfigFile()->requestSave();
getStateFile()->requestSave();
}
}


Expand Down Expand Up @@ -602,13 +610,15 @@ void Config::stateWriteMany(QString key, QStringList values) {


Config::ConfigFile* Config::getConfigFile() {
QMutexLocker locker(&_configFileMutex);
if (_configFile == nullptr) {
_configFile = new ConfigFile(configurationFile());
_configFile = new ConfigFile("Config", configurationFile());
}
return _configFile;
}

void Config::resetConfigFile() {
QMutexLocker locker(&_configFileMutex);
if (_configFile != nullptr) {
delete _configFile;
_configFile = nullptr;
Expand All @@ -617,73 +627,28 @@ void Config::resetConfigFile() {


Config::ConfigFile* Config::getStateFile() {
QMutexLocker locker(&_stateFileMutex);
if (_stateFile == nullptr) {
_stateFile = new ConfigFile(stateFile());
_stateFile = new ConfigFile("State", stateFile());
}
return _stateFile;
}

void Config::resetStateFile() {
QMutexLocker locker(&_stateFileMutex);
if (_stateFile != nullptr) {
delete _stateFile;
_stateFile = nullptr;
}
}


Config::ConfigSaveThread::ConfigSaveThread(void) {
this->setObjectName("ConfigSaveThread");
this->start(LowPriority);
}

Config::ConfigSaveThread::~ConfigSaveThread() {
this->requestInterruption();
this->wait();
}

void Config::ConfigSaveThread::requestSave() {
QMutexLocker locker(&_syncRoot);
_saveRequested = true;
}

void Config::ConfigSaveThread::run() {
while (!this->isInterruptionRequested()) {
QMutexLocker locker(&_syncRoot);
bool saveRequested = _saveRequested;
locker.unlock();
if (saveRequested) {
qDebug().noquote().nospace() << "[Config] Background save()";
QElapsedTimer stopwatch; stopwatch.start();
if (Config::getConfigFile()->save()) {
locker.relock();
_saveRequested = false;
locker.unlock();
qDebug().noquote().nospace() << "[Config] Background save() done in " << stopwatch.elapsed() << "ms";
} else {
qDebug().noquote().nospace() << "[Config] Background save() failed in " << stopwatch.elapsed() << "ms";
this->msleep(2500); // give it some time
}
}
this->msleep(250);
}
Config::ConfigFile::ConfigFile(QString kind, QString filePath) {
qDebug().noquote().nospace() << "[Config:" << kind << "] load(" << filePath << ")";
QElapsedTimer stopwatch; stopwatch.start();

{ // check once more when exiting
QMutexLocker locker(&_syncRoot);
bool saveRequested = _saveRequested;
locker.unlock();
if (saveRequested) {
qDebug().noquote().nospace() << "[Config] Final background save()";
QElapsedTimer stopwatch; stopwatch.start();
if (Config::getConfigFile()->save()) {
qDebug().noquote().nospace() << "[Config] Final background save() done in " << stopwatch.elapsed() << "ms";
} else {
qDebug().noquote().nospace() << "[Config] Final background save() failed in " << stopwatch.elapsed() << "ms";
}
}
}
}
_kind = kind;

Config::ConfigFile::ConfigFile(QString filePath) {
QString fileContent;
QString lineEnding = QString();
QFile file(filePath);
Expand Down Expand Up @@ -734,21 +699,32 @@ Config::ConfigFile::ConfigFile(QString filePath) {
_lineEnding = !lineEnding.isNull() ? lineEnding : "\n";
#endif

qDebug().noquote().nospace() << "[Config:" << _kind << "] load() done in " << stopwatch.elapsed() << "ms";

#ifdef QT_DEBUG
qDebug().noquote() << "[Config]" << configurationFilePath();
for (LineData line : _lines) {
QString key = line.getKey();
QString value = line.getValue();
if (!key.isNull() && !key.isEmpty()) {
qDebug().noquote() << "[Config]" << key + ":" << value;
qDebug().noquote().nospace() << "[Config:" << _kind << "] " << key + ":" << value;
}
}
#endif

_filePath = filePath;
_backgroundSaveThread = new BackgroundSaveThread(this);
}

Config::ConfigFile::~ConfigFile() {
_backgroundSaveThread->~BackgroundSaveThread();
_backgroundSaveThread = nullptr;
qDebug().noquote().nospace() << "[Config:" << _kind << "] destructed()";
}

void Config::ConfigFile::processLine(QString lineText) {
qDebug().noquote().nospace() << "[Config:" << _kind << "] load()";
QElapsedTimer stopwatch; stopwatch.start();

QString valueSeparator = QString();

QString sbKey = QString();
Expand Down Expand Up @@ -926,24 +902,44 @@ void Config::ConfigFile::processLine(QString lineText) {


bool Config::ConfigFile::save() {
QMutexLocker lockerSave(&_saveMutex); // make sure save operations are ordered
qDebug().noquote().nospace() << "[Config:" << _kind << "] save()";
QElapsedTimer stopwatch; stopwatch.start();

_backgroundSaveThread->cancelRequestedSave(); // try skipping any save that might be queued

QMutexLocker locker(&_cacheMutex);
QString content;
for (int i = 0; i < _lines.length(); i++) {
if (i > 0) { content += _lineEnding; }
content.append(_lines[i].toString());
}
locker.unlock(); // needed only while composing what to save

qDebug().noquote().nospace() << "[Config:" << _kind << "] save() prepared in " << stopwatch.elapsed() << "ms";

QSaveFile file(_filePath);
if (file.open(QIODevice::WriteOnly)) {
QTextStream out(&file);
out.setCodec("UTF-8");
out << content;
return file.commit();
if (file.commit()) {
qDebug().noquote().nospace() << "[Config:" << _kind << "] save() done in " << stopwatch.elapsed() << "ms";
return true;
} else {
qDebug().noquote().nospace() << "[Config:" << _kind << "] save() failed in " << stopwatch.elapsed() << "ms";
return false;
}
} else {
qDebug().noquote() << "[Config]" << "Cannot write file!" << file.errorString();
qDebug().noquote().nospace() << "[Config:" << _kind << "] save() error in " << stopwatch.elapsed() << "ms (" << file.errorString() << ")";
return false;
}
}

void Config::ConfigFile::requestSave() {
_backgroundSaveThread->requestSave();
}

QString Config::ConfigFile::readOne(QString key) {
QMutexLocker locker(&_cacheMutex);
QVariant cacheValue = _cache.value(key.toUpper());
Expand Down Expand Up @@ -1017,7 +1013,8 @@ void Config::ConfigFile::writeOne(QString key, QString value) {
}
}

if (_immediateSave) { Config::_configSaveThread.requestSave(); }
qDebug().noquote().nospace() << "[Config:" << _kind << "] " << key + "=" << value;
if (_immediateSave) { requestSave(); }
}

void Config::ConfigFile::writeMany(QString key, QStringList values) {
Expand Down Expand Up @@ -1071,7 +1068,12 @@ void Config::ConfigFile::writeMany(QString key, QStringList values) {
}
}

if (_immediateSave) { Config::_configSaveThread.requestSave(); }
#ifdef QT_DEBUG
for (QString value : values) {
qDebug().noquote().nospace() << "[Config:" << _kind << "] " << key + "=" << value;
}
#endif
if (_immediateSave) { requestSave(); }
}

void Config::ConfigFile::removeMany(QString key) {
Expand All @@ -1084,15 +1086,19 @@ void Config::ConfigFile::removeMany(QString key) {
_lines.removeAt(i);
}
}
if (_immediateSave) { Config::_configSaveThread.requestSave(); }

qDebug().noquote().nospace() << "[Config:" << _kind << "] remove(" << key << ")";
if (_immediateSave) { requestSave(); }
}

void Config::ConfigFile::removeAll() {
QMutexLocker locker(&_cacheMutex);
_cache.clear(); //invalidate cache

_lines.clear();
if (_immediateSave) { Config::_configSaveThread.requestSave(); }

qDebug().noquote().nospace() << "[Config:" << _kind << "] removeAll()";
if (_immediateSave) { requestSave(); }
}


Expand Down Expand Up @@ -1228,3 +1234,52 @@ void Config::ConfigFile::LineData::escapeIntoStringBuilder(QString* sb, QString
}
}
}


Config::ConfigFile::BackgroundSaveThread::BackgroundSaveThread(ConfigFile* config) {
_config = config;

this->setObjectName("Config:BackgroundSave" + _config->_kind + "Thread");
this->start(LowPriority);
}

Config::ConfigFile::BackgroundSaveThread::~BackgroundSaveThread() {
this->requestInterruption();
this->wait();
}

void Config::ConfigFile::BackgroundSaveThread::requestSave() {
QMutexLocker locker(&_syncRoot);
_saveRequested = true;
}

void Config::ConfigFile::BackgroundSaveThread::cancelRequestedSave() {
QMutexLocker locker(&_syncRoot);
_saveRequested = false;
}

void Config::ConfigFile::BackgroundSaveThread::run() {
while (!this->isInterruptionRequested()) {
QMutexLocker locker(&_syncRoot);
bool saveRequested = _saveRequested;
_saveRequested = false; // reset signal for now
locker.unlock(); // unlock early

if (saveRequested) {
qDebug().noquote().nospace() << "[Config:" << _config->_kind << "] backgroundSave()";
_config->save(); // if it fails, ignore - nothing much to do
}

this->msleep(250);
}

{ // check once more when exiting
QMutexLocker locker(&_syncRoot);
bool saveRequested = _saveRequested;
locker.unlock();
if (saveRequested) {
qDebug().noquote().nospace() << "[Config:" << _config->_kind << "] backgroundSave()";
_config->save();
}
}
}
Loading

0 comments on commit 011187b

Please sign in to comment.