Skip to content

Commit

Permalink
Warn user about other instances owning the settings file.
Browse files Browse the repository at this point in the history
When multiple DME instances are created, only the first one gets to
own the settings file now; previously, the last DME instance to close
would rewrite the settings file, potentially causing data loss from the
previous instances.

A warning dialog will be shown on startup if the settings lockfile
cannot be acquired. On exit, if there are unsaved changes to the watch
list, the user will be given an opportunity to save it into a separate
`.dmw` file.
  • Loading branch information
cristian64 committed May 12, 2024
1 parent 84a18c4 commit 9f75a21
Show file tree
Hide file tree
Showing 4 changed files with 121 additions and 33 deletions.
11 changes: 11 additions & 0 deletions Source/GUI/MainWindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -555,6 +555,17 @@ void MainWindow::closeEvent(QCloseEvent* event)
SConfig::getInstance().setWatchModel(m_watcher->saveWatchModel());
SConfig::getInstance().setMainWindowGeometry(saveGeometry());
SConfig::getInstance().setMainWindowState(saveState());

if (!SConfig::getInstance().ownsSettingsFile())
{
// Give the user a chance to save the ephemeral watch model.
if (!m_watcher->warnIfUnsavedChanges())
{
event->setAccepted(false);
return;
}
}

m_viewer->close();
event->accept();
}
109 changes: 79 additions & 30 deletions Source/GUI/Settings/SConfig.cpp
Original file line number Diff line number Diff line change
@@ -1,9 +1,21 @@
#include "SConfig.h"

#include <cassert>
#include <iostream>

#include <qcoreapplication.h>
#include <qfile.h>

namespace
{
SConfig* g_instance{};
}

SConfig::SConfig()
{
assert(!g_instance && "Only a single SConfig instance is allowed");
g_instance = this;

const QString exeDir = QCoreApplication::applicationDirPath();
const QString portableFilePath = exeDir + "/portable.txt";
QFile file(portableFilePath);
Expand All @@ -18,155 +30,192 @@ SConfig::SConfig()
m_settings =
new QSettings(QSettings::IniFormat, QSettings::UserScope, organization, application);
}

const QString lockFilepath{m_settings->fileName() + "_lock"};
m_lockFile = std::make_unique<QLockFile>(lockFilepath);
if (!m_lockFile->tryLock())
{
std::cerr << "ERROR: Unable to lock \"" + lockFilepath.toStdString() +
"\". Is another instance running?\n";
}
}

SConfig::~SConfig()
{
delete m_settings;
m_lockFile.reset();

assert(g_instance == this && "Inconsistent handling of the SConfig global instance");
g_instance = nullptr;
}

SConfig& SConfig::getInstance()
{
static SConfig instance;
return instance;
assert(g_instance && "SConfig must be instantiated first");
return *g_instance;
}

QString SConfig::getWatchModel() const
{
return m_settings->value("watchModel", QString{}).toString();
return value("watchModel", QString{}).toString();
}

bool SConfig::getAutoHook() const
{
return m_settings->value("autoHook", true).toBool();
return value("autoHook", true).toBool();
}

QByteArray SConfig::getMainWindowGeometry() const
{
return m_settings->value("mainWindowSettings/mainWindowGeometry", QByteArray{}).toByteArray();
return value("mainWindowSettings/mainWindowGeometry", QByteArray{}).toByteArray();
}

QByteArray SConfig::getMainWindowState() const
{
return m_settings->value("mainWindowSettings/mainWindowState", QByteArray{}).toByteArray();
return value("mainWindowSettings/mainWindowState", QByteArray{}).toByteArray();
}

QByteArray SConfig::getSplitterState() const
{
return m_settings->value("mainWindowSettings/splitterState", QByteArray{}).toByteArray();
return value("mainWindowSettings/splitterState", QByteArray{}).toByteArray();
}

int SConfig::getTheme() const
{
return m_settings->value("coreSettings/theme", 0).toInt();
return value("coreSettings/theme", 0).toInt();
}

int SConfig::getWatcherUpdateTimerMs() const
{
return m_settings->value("timerSettings/watcherUpdateTimerMs", 100).toInt();
return value("timerSettings/watcherUpdateTimerMs", 100).toInt();
}

int SConfig::getFreezeTimerMs() const
{
return m_settings->value("timerSettings/freezeTimerMs", 10).toInt();
return value("timerSettings/freezeTimerMs", 10).toInt();
}

int SConfig::getScannerUpdateTimerMs() const
{
return m_settings->value("timerSettings/scannerUpdateTimerMs", 10).toInt();
return value("timerSettings/scannerUpdateTimerMs", 10).toInt();
}

int SConfig::getViewerUpdateTimerMs() const
{
return m_settings->value("timerSettings/viewerUpdateTimerMs", 100).toInt();
return value("timerSettings/viewerUpdateTimerMs", 100).toInt();
}

int SConfig::getScannerShowThreshold() const
{
return m_settings->value("scannerSettings/scannerShowThreshold", 1000).toInt();
return value("scannerSettings/scannerShowThreshold", 1000).toInt();
}

int SConfig::getViewerNbrBytesSeparator() const
{
return m_settings->value("viewerSettings/nbrBytesSeparator", 1).toInt();
return value("viewerSettings/nbrBytesSeparator", 1).toInt();
}

u32 SConfig::getMEM1Size() const
{
return m_settings->value("memorySettings/MEM1Size", 24u * 1024 * 1024).toUInt();
return value("memorySettings/MEM1Size", 24u * 1024 * 1024).toUInt();
}

u32 SConfig::getMEM2Size() const
{
return m_settings->value("memorySettings/MEM2Size", 64u * 1024 * 1024).toUInt();
return value("memorySettings/MEM2Size", 64u * 1024 * 1024).toUInt();
}

void SConfig::setWatchModel(const QString& json)
{
m_settings->setValue("watchModel", json);
setValue("watchModel", json);
}

void SConfig::setAutoHook(const bool enabled)
{
m_settings->setValue("autoHook", enabled);
setValue("autoHook", enabled);
}

void SConfig::setMainWindowGeometry(QByteArray const& geometry)
{
m_settings->setValue("mainWindowSettings/mainWindowGeometry", geometry);
setValue("mainWindowSettings/mainWindowGeometry", geometry);
}

void SConfig::setMainWindowState(QByteArray const& state)
{
m_settings->setValue("mainWindowSettings/mainWindowState", state);
setValue("mainWindowSettings/mainWindowState", state);
}

void SConfig::setSplitterState(QByteArray const& state)
{
m_settings->setValue("mainWindowSettings/splitterState", state);
setValue("mainWindowSettings/splitterState", state);
}

void SConfig::setTheme(const int theme)
{
m_settings->setValue("coreSettings/theme", theme);
setValue("coreSettings/theme", theme);
}

void SConfig::setWatcherUpdateTimerMs(const int updateTimerMs)
{
m_settings->setValue("timerSettings/watcherUpdateTimerMs", updateTimerMs);
setValue("timerSettings/watcherUpdateTimerMs", updateTimerMs);
}

void SConfig::setFreezeTimerMs(const int freezeTimerMs)
{
m_settings->setValue("timerSettings/freezeTimerMs", freezeTimerMs);
setValue("timerSettings/freezeTimerMs", freezeTimerMs);
}

void SConfig::setScannerUpdateTimerMs(const int scannerUpdateTimerMs)
{
m_settings->setValue("timerSettings/scannerUpdateTimerMs", scannerUpdateTimerMs);
setValue("timerSettings/scannerUpdateTimerMs", scannerUpdateTimerMs);
}

void SConfig::setViewerUpdateTimerMs(const int viewerUpdateTimerMs)
{
m_settings->setValue("timerSettings/viewerUpdateTimerMs", viewerUpdateTimerMs);
setValue("timerSettings/viewerUpdateTimerMs", viewerUpdateTimerMs);
}

void SConfig::setScannerShowThreshold(const int scannerShowThreshold)
{
m_settings->setValue("scannerSettings/scannerShowThreshold", scannerShowThreshold);
setValue("scannerSettings/scannerShowThreshold", scannerShowThreshold);
}

void SConfig::setViewerNbrBytesSeparator(const int viewerNbrBytesSeparator)
{
m_settings->setValue("viewerSettings/nbrBytesSeparator", viewerNbrBytesSeparator);
setValue("viewerSettings/nbrBytesSeparator", viewerNbrBytesSeparator);
}

void SConfig::setMEM1Size(const u32 mem1SizeReal)
{
m_settings->setValue("memorySettings/MEM1Size", mem1SizeReal);
setValue("memorySettings/MEM1Size", mem1SizeReal);
}

void SConfig::setMEM2Size(const u32 mem2SizeReal)
{
m_settings->setValue("memorySettings/MEM2Size", mem2SizeReal);
setValue("memorySettings/MEM2Size", mem2SizeReal);
}

bool SConfig::ownsSettingsFile() const
{
return m_lockFile->isLocked();
}

void SConfig::setValue(const QString& key, const QVariant& value)
{
m_map[key] = value;

if (ownsSettingsFile())
{
m_settings->setValue(key, value);
}
}

QVariant SConfig::value(const QString& key, const QVariant& defaultValue) const
{
const auto it = m_map.find(key);
if (it != m_map.cend())
{
return it->second;
}
return m_settings->value(key, defaultValue);
}
17 changes: 14 additions & 3 deletions Source/GUI/Settings/SConfig.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
#pragma once

#include <memory>

#include <QByteArray>
#include <QLockFile>
#include <QSettings>

#include "../../Common/CommonTypes.h"
Expand All @@ -9,6 +12,10 @@ class SConfig
{
public:
static SConfig& getInstance();

SConfig();
~SConfig();

SConfig(SConfig const&) = delete;
void operator=(SConfig const&) = delete;

Expand Down Expand Up @@ -50,9 +57,13 @@ class SConfig

void setViewerNbrBytesSeparator(const int viewerNbrBytesSeparator);

bool ownsSettingsFile() const;

private:
SConfig();
~SConfig();
void setValue(const QString& key, const QVariant& value);
QVariant value(const QString& key, const QVariant& defaultValue) const;

QSettings* m_settings;
std::unique_ptr<QLockFile> m_lockFile;
std::unordered_map<QString, QVariant> m_map;
QSettings* m_settings{};
};
17 changes: 17 additions & 0 deletions Source/main.cpp
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
#include <QApplication>
#include <QCommandLineParser>
#include <QMessageBox>

#include "GUI/MainWindow.h"
#include "GUI/Settings/SConfig.h"

int main(int argc, char** argv)
{
QApplication app(argc, argv);
QApplication::setApplicationName("Dolphin Memory Engine");
QApplication::setApplicationVersion("1.1.1");

SConfig config; // Initialize global settings object

QCommandLineParser parser;
parser.setApplicationDescription(
QObject::tr("A RAM search made specifically to search, monitor and edit "
Expand All @@ -34,6 +38,19 @@ int main(int argc, char** argv)
}

MainWindow window;

if (!config.ownsSettingsFile())
{
QMessageBox box(
QMessageBox::Warning, QObject::tr("Another instance is already running"),
QObject::tr(
"Changes made to settings will not be preserved in this session. This includes changes "
"to the watch list, which will need to be saved manually into a file."),
QMessageBox::Ok);
box.setWindowIcon(window.windowIcon());
box.exec();
}

window.show();
return app.exec();
}

0 comments on commit 9f75a21

Please sign in to comment.