Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Warn user about other instances owning the settings file. #135

Merged
merged 1 commit into from
May 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions Source/GUI/MainWindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -572,6 +572,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,17 +30,29 @@ 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::getSettingsFilepath() const
Expand All @@ -38,140 +62,165 @@ QString SConfig::getSettingsFilepath() const

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 @@ -52,9 +59,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 QApplication::exec();
}