From b7cb63ddb1e2b263d5e485c97faea527c7c0af44 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 6 Nov 2020 11:37:12 -0500 Subject: removed some redundant functions in InstanceManager, made them all non-static documentation --- src/main.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) (limited to 'src/main.cpp') diff --git a/src/main.cpp b/src/main.cpp index e56197c9..7f0886f6 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -280,7 +280,7 @@ std::optional selectInstance() PluginContainer pc(nullptr); pc.loadPlugins(); - if (m.instancePaths().empty() && !m.portableInstanceExists()) { + if (!m.hasAnyInstances()) { // no instances configured CreateInstanceDialog dlg(pc, nullptr); if (dlg.exec() != QDialog::Accepted) { @@ -454,10 +454,6 @@ int runApplication( log::info("data path: {}", dataPath); - if (InstanceManager::isPortablePath(dataPath)) { - log::debug("this is a portable instance"); - } - log::info("working directory: {}", QDir::currentPath()); if (!instance.secondary()) { @@ -529,6 +525,10 @@ int runApplication( } } + if (currentInstance.isPortable()) { + log::debug("this is a portable instance"); + } + checkPathsForSanity(*currentInstance.gamePlugin(), settings); organizer.setManagedGame(currentInstance.gamePlugin()); @@ -665,13 +665,13 @@ int doOneRun( if (!currentInstance->directory().exists()) { // the previously used instance doesn't exist anymore - if (m.instanceNames().empty() && !m.portableInstanceExists()) { + if (m.hasAnyInstances()) { criticalOnTop(QObject::tr( - "Instance at '%1' not found. You must create a new instance") - .arg(currentInstance->directory().absolutePath())); + "Instance at '%1' not found. Select another instance.") + .arg(currentInstance->directory().absolutePath())); } else { criticalOnTop(QObject::tr( - "Instance at '%1' not found. Select another instance.") + "Instance at '%1' not found. You must create a new instance") .arg(currentInstance->directory().absolutePath())); } -- cgit v1.3.1 From dc2ada2a4249917f538938298deb193ed1dd6bb9 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 6 Nov 2020 13:56:04 -0500 Subject: added ... to the "Delete Instance" button to make it less scary merged InstanceInfo into Instance, most of it was redundant added logging before deleting instance added Instance::readFromIni(), contains stuff that used to be in setup(), used by instance dialog replaced QDir with QString in a few places, I hate QDir --- src/instancemanager.cpp | 278 +++++++++++++++++++++++++++++---- src/instancemanager.h | 109 ++++++++++--- src/instancemanagerdialog.cpp | 347 +++++------------------------------------- src/instancemanagerdialog.h | 80 +++++++++- src/instancemanagerdialog.ui | 2 +- src/main.cpp | 8 +- 6 files changed, 456 insertions(+), 368 deletions(-) (limited to 'src/main.cpp') diff --git a/src/instancemanager.cpp b/src/instancemanager.cpp index e7c2a611..fbafc6e8 100644 --- a/src/instancemanager.cpp +++ b/src/instancemanager.cpp @@ -38,7 +38,7 @@ along with Mod Organizer. If not, see . using namespace MOBase; -Instance::Instance(QDir dir, bool portable, QString profileName) : +Instance::Instance(QString dir, bool portable, QString profileName) : m_dir(std::move(dir)), m_portable(portable), m_plugin(nullptr), m_profile(std::move(profileName)) { @@ -49,7 +49,7 @@ QString Instance::name() const if (isPortable()) return QObject::tr("Portable"); else - return m_dir.dirName(); + return QDir(m_dir).dirName(); } QString Instance::gameName() const @@ -62,11 +62,16 @@ QString Instance::gameDirectory() const return m_gameDir; } -QDir Instance::directory() const +QString Instance::directory() const { return m_dir; } +QString Instance::baseDirectory() const +{ + return m_baseDir; +} + MOBase::IPluginGame* Instance::gamePlugin() const { return m_plugin; @@ -87,13 +92,29 @@ bool Instance::isPortable() const return m_portable; } -Instance::SetupResults Instance::setup(PluginContainer& plugins) +bool Instance::isActive() const +{ + auto& m = InstanceManager::singleton(); + + if (auto i=m.currentInstance()) + { + if (m_portable) { + return i->isPortable(); + } else { + return (i->name() == name()); + } + } + + return false; +} + +bool Instance::readFromIni() { Settings s(iniPath()); if (s.iniStatus() != QSettings::NoError) { log::error("can't read ini {}", iniPath()); - return SetupResults::BadIni; + return false; } // game name and directory are from ini unless overridden by setGame() @@ -107,27 +128,61 @@ Instance::SetupResults Instance::setup(PluginContainer& plugins) m_gameDir = *v; } - // getting game plugin - const auto r = getGamePlugin(plugins); - if (r != SetupResults::Ok) { - return r; - } - - // getting game variant, error if it's missing and required by the plugin if (m_gameVariant.isEmpty()) { if (auto v=s.game().edition()) { m_gameVariant = *v; } } + if (m_baseDir.isEmpty()) { + m_baseDir = s.paths().base(); + } + + // figuring out profile from ini if it's missing + getProfile(s); + + return true; +} + +Instance::SetupResults Instance::setup(PluginContainer& plugins) +{ + // read initial values from the ini + if (!readFromIni()) { + return SetupResults::BadIni; + } + + // getting game plugin + const auto r = getGamePlugin(plugins); + if (r != SetupResults::Ok) { + return r; + } + + // error if the variant missing and required by the plugin if (m_gameVariant.isEmpty() && m_plugin->gameVariants().size() > 1) { return SetupResults::MissingVariant; } else { m_plugin->setGameVariant(m_gameVariant); } - // figuring out profile from ini if it's missing - getProfile(s); + // update the ini in case anything was missing + updateIni(); + + // the game directory may be different than what the plugin detected, the user + // can change it in the settings and might have multiple versions of the game + // installed + m_plugin->setGamePath(m_gameDir); + + return SetupResults::Ok; +} + +void Instance::updateIni() +{ + Settings s(iniPath()); + + if (s.iniStatus() != QSettings::NoError) { + log::error("can't open ini {}", iniPath()); + return; + } // updating the settings since some of these values might have been missing s.game().setName(m_gameName); @@ -138,13 +193,6 @@ Instance::SetupResults Instance::setup(PluginContainer& plugins) // don't write a variant to the ini if the plugin doesn't require one s.game().setEdition(m_gameVariant); } - - // the game directory may be different than what the plugin detected, the user - // can change it in the settings and might have multiple versions of the game - // installed - m_plugin->setGamePath(m_gameDir); - - return SetupResults::Ok; } void Instance::setGame(const QString& name, const QString& dir) @@ -272,7 +320,6 @@ Instance::SetupResults Instance::getGamePlugin(PluginContainer& plugins) } } - void Instance::getProfile(const Settings& s) { if (!m_profile.isEmpty()) { @@ -294,6 +341,176 @@ void Instance::getProfile(const Settings& s) iniPath(), m_profile); } +// returns a list of files and folders that must be deleted when deleting +// this instance +// +std::vector Instance::objectsForDeletion() const +{ + // native separators and ending slash + auto prettyDir = [](auto s) { + if (!s.endsWith("/") || !s.endsWith("\\")) { + s += "/"; + } + + return QDir::toNativeSeparators(s); + }; + + // native separators + auto prettyFile = [](auto s) { + return QDir::toNativeSeparators(s); + }; + + + // lowercase, native separators and ending slash + auto canonicalDir = [](QString s) { + s = s.toLower(); + + if (!s.endsWith("/") || !s.endsWith("\\")) { + s += "/"; + } + + return QDir::toNativeSeparators(s); + }; + + // lower and native separators + auto canonicalFile = [](auto s) { + return QDir::toNativeSeparators(s.toLower()); + }; + + + + // whether the given directory is contained in the root + auto dirInRoot = [&](const QString& root, const QString& dir) { + return canonicalDir(dir).startsWith(canonicalDir(root)); + }; + + // whether the given file is contained in the root + auto fileInRoot = [&](const QString& root, const QString& file) { + return canonicalFile(file).startsWith(canonicalDir(root)); + }; + + + Settings settings(iniPath()); + + if (settings.iniStatus() != QSettings::NoError) { + log::error("can't read ini {}", iniPath()); + return {}; + } + + + + const QString loc = directory(); + const QString base = settings.paths().base(); + + + // directories that might contain the individual files and directories + // set in the path settings + std::vector roots; + + // a portable instance has its location in the installation directory, + // don't delete that + if (!isPortable()) { + if (QDir(loc).exists()) { + roots.push_back({loc, true}); + } + } + + // the base directory is the location directory by default, don't add it + // if it's the same + if (canonicalDir(base) != canonicalDir(loc)) { + if (QDir(base).exists()) { + roots.push_back({base, false}); + } + } + + + // all the directories that are part of an instance; none of them are + // mandatory for deletion + const std::vector dirs = { + settings.paths().downloads(), + settings.paths().mods(), + settings.paths().cache(), + settings.paths().profiles(), + settings.paths().overwrite(), + QDir(m_dir).filePath(QString::fromStdWString(AppConfig::dumpsDir())), + QDir(m_dir).filePath(QString::fromStdWString(AppConfig::logPath())), + }; + + // all the files that are part of an instance + const std::vector files = { + {iniPath(), true}, // the ini file must be deleted + }; + + + // this will contain the root directories, plus all the individual + // directories that are not inside these roots + std::vector cleanDirs; + + for (const auto& f : dirs) { + bool inRoots = false; + + for (const auto& root : roots) { + if (dirInRoot(root.path, f.path)) { + inRoots = true; + break; + } + } + + if (!inRoots) { + // not in roots, this is a path that was changed by the user; make + // sure it exists + if (QDir(f.path).exists()) { + cleanDirs.push_back({prettyDir(f.path), f.mandatoryDelete}); + } + } + } + + // prepending the roots + for (auto itor=roots.rbegin(); itor!=roots.rend(); ++itor) { + cleanDirs.insert( + cleanDirs.begin(), + {prettyDir(itor->path), itor->mandatoryDelete}); + } + + + + // this will contain the individual files that are not inside the roots; + // not that this only contains the INI file for now, so most of this is + // useless + std::vector cleanFiles; + + for (const auto& f : files) { + bool inRoots = false; + + for (const auto& root : roots) { + if (fileInRoot(root.path, f.path)) { + inRoots = true; + break; + } + } + + if (!inRoots) { + // not in roots, this is a path that was changed by the user; make + // sure it exists + if (QFileInfo(f.path).exists()) { + cleanFiles.push_back({prettyFile(f.path), f.mandatoryDelete}); + } + } + } + + + // contains all the directories and files to be deleted + std::vector all; + all.insert(all.end(), cleanDirs.begin(), cleanDirs.end()); + all.insert(all.end(), cleanFiles.begin(), cleanFiles.end()); + + // mandatory on top + std::stable_sort(all.begin(), all.end()); + + return all; +} + + InstanceManager::InstanceManager() { @@ -327,20 +544,20 @@ std::optional InstanceManager::currentInstance() const if (!allowedToChangeInstance()) { // force portable instance - return Instance(QDir(portablePath()), true, profile); + return Instance(portablePath(), true, profile); } if (name.isEmpty()) { if (portableInstanceExists()) { // use portable - return Instance(QDir(portablePath()), true, profile); + return Instance(portablePath(), true, profile); } else { // no instance set return {}; } } - return Instance(QDir(instancePath(name)), false, profile); + return Instance(instancePath(name), false, profile); } void InstanceManager::clearCurrentInstance() @@ -365,12 +582,13 @@ QString InstanceManager::globalInstancesRootPath() const QStandardPaths::writableLocation(QStandardPaths::DataLocation)); } -QString InstanceManager::iniPath(const QDir& instanceDir) const +QString InstanceManager::iniPath(const QString& instanceDir) const { - return instanceDir.filePath(QString::fromStdWString(AppConfig::iniFileName())); + return QDir(instanceDir).filePath( + QString::fromStdWString(AppConfig::iniFileName())); } -std::vector InstanceManager::globalInstancePaths() const +std::vector InstanceManager::globalInstancePaths() const { const std::set ignore = { "cache", "qtwebengine", @@ -379,7 +597,7 @@ std::vector InstanceManager::globalInstancePaths() const const QDir root(globalInstancesRootPath()); const auto dirs = root.entryList(QDir::Dirs | QDir::NoDotAndDotDot); - std::vector list; + std::vector list; for (auto&& d : dirs) { if (!ignore.contains(QFileInfo(d).fileName().toLower())) { @@ -416,7 +634,7 @@ bool InstanceManager::allowedToChangeInstance() const } const MOBase::IPluginGame* InstanceManager::gamePluginForDirectory( - const QDir& instanceDir, const PluginContainer& plugins) const + const QString& instanceDir, const PluginContainer& plugins) const { const QString ini = iniPath(instanceDir); diff --git a/src/instancemanager.h b/src/instancemanager.h index e2ceb743..161df739 100644 --- a/src/instancemanager.h +++ b/src/instancemanager.h @@ -55,15 +55,64 @@ public: }; + // a file or directory owned by this instance, used by objectsForDeletion() + // + struct Object + { + // path to the file or directory + QString path; + + // whether this object must be deleted to properly delete the instance; + // typically true only for the instance directory itself, but not for the + // base directory, etc. + bool mandatoryDelete; + + + Object(QString p, bool d=false) + : path(std::move(p)), mandatoryDelete(d) + { + } + + // puts mandatory delete on top + // + bool operator<(const Object& o) const + { + if (mandatoryDelete && !o.mandatoryDelete) { + return true; + } else if (!mandatoryDelete && o.mandatoryDelete) { + return false; + } + + return false; + } + }; + + + // an instance that lives in the given directory; `portable` must be `true` // if this is a portable instance // // `profileName` can be given to override what's in the INI; this typically // happens when the profile is overriden on the command line // - Instance(QDir dir, bool portable, QString profileName={}); + Instance(QString dir, bool portable, QString profileName={}); + + + // reads in values from the INI if they were not given yet: + // - game name + // - game directory + // - game variant + // - profile name + // + // note that setup() already calls this + // + // returns false if the ini couldn't be read from + // + bool readFromIni(); + - // finds the appropriate game plugin and sets it up so MO can use it + // finds the appropriate game plugin and sets it up so MO can use it; this + // calls readFromIni() first // // setup() tries to recover from some errors, but can fail for a variety of // reasons, see SetupResults @@ -88,22 +137,32 @@ public: QString name() const; // returns either: - // 1) the game name from the INI, - // 2) gameName() from the game plugin if it was missing, or - // 3) whatever was given in setGame() + // 1) the game name from the INI, if readFromIni() was called; + // 2) gameName() from the game plugin if it was missing and setup() was + // called; or + // 3) whatever was given in setGame() // QString gameName() const; // returns either: - // 1) the game directory from the INI, - // 2) gameDirectory() from the game plugin if it was missing, or - // 3) whatever was given in setGame() + // 1) the game directory from the INI, if readFromIni() was called; + // 2) gameDirectory() from the game plugin if it was missing and setup() + // was called; or + // 3) whatever was given in setGame() // QString gameDirectory() const; - // returns the instance directory; can be called without setup() + // returns the instance directory as given in the constructor // - QDir directory() const; + QString directory() const; + + // returns the base directory; empty if readFromIni() hasn't been called + // + QString baseDirectory() const; + + // returns whether this is a portable instance, as given in the constructor + // + bool isPortable() const; // returns the selected game plugin; will return null if setup() hasn't been // called, or if it failed @@ -111,8 +170,8 @@ public: MOBase::IPluginGame* gamePlugin() const; // returns either: - // 1) the profile name given in the constructor, - // 2) the profile name from the INI, or + // 1) the profile name given in the constructor if not empty; + // 2) the profile name from the INI if readFromIni() was called, or // 3) the default profile name if it's missing (see // AppConfig::defaultProfileName()) // @@ -123,15 +182,21 @@ public: // QString iniPath() const; - // returns whether this is a portable instance; this is the flag given in the - // constructor + // whether this is the currently active instance in MO // - bool isPortable() const; + bool isActive() const; + + // returns a list of files and directories that must be deleted when deleting + // this instance; this will read the INI and fail if it's not accessible + // + // returns an empty list on failure + // + std::vector objectsForDeletion() const; private: - QDir m_dir; + QString m_dir; bool m_portable; - QString m_gameName, m_gameDir, m_gameVariant; + QString m_gameName, m_gameDir, m_gameVariant, m_baseDir; MOBase::IPluginGame* m_plugin; QString m_profile; @@ -142,6 +207,10 @@ private: // figures out the profile name for this instance // void getProfile(const Settings& s); + + // updates the ini with the given values and the ones found by setup() + // + void updateIni(); }; @@ -175,7 +244,7 @@ public: // returns null if all of this fails // const MOBase::IPluginGame* gamePluginForDirectory( - const QDir& dir, const PluginContainer& plugins) const; + const QString& dir, const PluginContainer& plugins) const; // clears the instance name from the registry; on restart, this will make MO // either select the portable instance if it exists, or display the instance @@ -221,7 +290,7 @@ public: // returns the list of absolute path to all existing global instances; this // does not include the portable instance // - std::vector globalInstancePaths() const; + std::vector globalInstancePaths() const; // returns `name` modified so that it is a valid instance name // @@ -253,7 +322,7 @@ public: // returns the absolute path to the INI file for the given instance directory; // the file may not exist // - QString iniPath(const QDir& instanceDir) const; + QString iniPath(const QString& instanceDir) const; private: InstanceManager(); diff --git a/src/instancemanagerdialog.cpp b/src/instancemanagerdialog.cpp index c2108f9d..c199fa7b 100644 --- a/src/instancemanagerdialog.cpp +++ b/src/instancemanagerdialog.cpp @@ -13,292 +13,19 @@ using namespace MOBase; -class InstanceInfo -{ -public: - struct Object - { - QString path; - bool mandatoryDelete; - - Object(QString p, bool d=false) - : path(std::move(p)), mandatoryDelete(d) - { - } - - bool operator<(const Object& o) const - { - if (mandatoryDelete && !o.mandatoryDelete) { - return true; - } else if (!mandatoryDelete && o.mandatoryDelete) { - return false; - } - - return false; - } - }; - - - InstanceInfo(QDir dir, bool isPortable) - : m_portable(isPortable) - { - setDir(dir); - } - - void setDir(const QDir& dir) - { - m_dir = dir; - m_settings.reset(new Settings(InstanceManager::singleton().iniPath(dir))); - } - - QString name() const - { - if (m_portable) { - return QObject::tr("Portable"); - } else { - return m_dir.dirName(); - } - } - - QString gameName() const - { - if (auto n=m_settings->game().name()) { - if (auto e=m_settings->game().edition()) { - if (!e->isEmpty()) { - return *n + " (" + *e + ")"; - } - } - - return *n; - } else { - return {}; - } - } - - QString gamePath() const - { - if (auto n=m_settings->game().directory()) { - return QDir::toNativeSeparators(*n); - } else { - return {}; - } - } - - QString location() const - { - return QDir::toNativeSeparators(m_dir.path()); - } - - QString baseDirectory() const - { - return QDir::toNativeSeparators(m_settings->paths().base()); - } - - QString iniFile() const - { - return InstanceManager::singleton().iniPath(m_dir); - } - - QIcon icon(const PluginContainer& plugins) const - { - const auto* game = InstanceManager::singleton().gamePluginForDirectory( - m_dir, plugins); - - if (game) - return game->gameIcon(); - - QPixmap empty(32, 32); - empty.fill(QColor(0, 0, 0, 0)); - return QIcon(empty); - } - - bool isPortable() const - { - return m_portable; - } - - bool isActive() const - { - auto& m = InstanceManager::singleton(); - - if (auto i=m.currentInstance()) - { - if (m_portable) { - return i->isPortable(); - } else { - return (i->name() == name()); - } - } - - return false; - } - - // returns a list of files and folders that must be deleted when deleting - // this instance - // - std::vector objectsForDeletion() const - { - // native separators and ending slash - auto prettyDir = [](auto s) { - if (!s.endsWith("/") || !s.endsWith("\\")) { - s += "/"; - } - - return QDir::toNativeSeparators(s); - }; - - // native separators - auto prettyFile = [](auto s) { - return QDir::toNativeSeparators(s); - }; - - - // lowercase, native separators and ending slash - auto canonicalDir = [](auto s) { - s = s.toLower(); - if (!s.endsWith("/") || !s.endsWith("\\")) { - s += "/"; - } - - return QDir::toNativeSeparators(s); - }; - - // lower and native separators - auto canonicalFile = [](auto s) { - return QDir::toNativeSeparators(s.toLower()); - }; - - - - // whether the given directory is contained in the root - auto dirInRoot = [&](auto root, auto dir) { - return canonicalDir(dir).startsWith(canonicalDir(root)); - }; - - // whether the given file is contained in the root - auto fileInRoot = [&](auto root, auto file) { - return canonicalFile(file).startsWith(canonicalDir(root)); - }; - +QIcon instanceIcon(const PluginContainer& pc, const Instance& i) +{ + const auto* game = InstanceManager::singleton() + .gamePluginForDirectory(i.directory(), pc); + if (game) + return game->gameIcon(); - const auto loc = location(); - const auto base = m_settings->paths().base(); - - - // directories that might contain the individual files and directories - // set in the path settings - std::vector roots; - - // a portable instance has its location in the installation directory, - // don't delete that - if (!isPortable()) { - if (QDir(loc).exists()) { - roots.push_back({loc, true}); - } - } - - // the base directory is the location directory by default, don't add it - // if it's the same - if (canonicalDir(base) != canonicalDir(loc)) { - if (QDir(base).exists()) { - roots.push_back({base, false}); - } - } - - - // all the directories that are part of an instance; none of them are - // mandatory for deletion - const std::vector dirs = { - m_settings->paths().downloads(), - m_settings->paths().mods(), - m_settings->paths().cache(), - m_settings->paths().profiles(), - m_settings->paths().overwrite(), - m_dir.filePath(QString::fromStdWString(AppConfig::dumpsDir())), - m_dir.filePath(QString::fromStdWString(AppConfig::logPath())), - }; - - // all the files that are part of an instance - const std::vector files = { - {iniFile(), true}, // the ini file must be deleted - }; - - - // this will contain the root directories, plus all the individual - // directories that are not inside these roots - std::vector cleanDirs; - - for (const auto& f : dirs) { - bool inRoots = false; - - for (const auto& root : roots) { - if (dirInRoot(root.path, f.path)) { - inRoots = true; - break; - } - } - - if (!inRoots) { - // not in roots, this is a path that was changed by the user; make - // sure it exists - if (QDir(f.path).exists()) { - cleanDirs.push_back({prettyDir(f.path), f.mandatoryDelete}); - } - } - } - - // prepending the roots - for (auto itor=roots.rbegin(); itor!=roots.rend(); ++itor) { - cleanDirs.insert( - cleanDirs.begin(), - {prettyDir(itor->path), itor->mandatoryDelete}); - } - - - - // this will contain the individual files that are not inside the roots; - // not that this only contains the INI file for now, so most of this is - // useless - std::vector cleanFiles; - - for (const auto& f : files) { - bool inRoots = false; - - for (const auto& root : roots) { - if (fileInRoot(root.path, f.path)) { - inRoots = true; - break; - } - } - - if (!inRoots) { - // not in roots, this is a path that was changed by the user; make - // sure it exists - if (QFileInfo(f.path).exists()) { - cleanFiles.push_back({prettyFile(f.path), f.mandatoryDelete}); - } - } - } - - - // contains all the directories and files to be deleted - std::vector all; - all.insert(all.end(), cleanDirs.begin(), cleanDirs.end()); - all.insert(all.end(), cleanFiles.begin(), cleanFiles.end()); - - // mandatory on top - std::stable_sort(all.begin(), all.end()); - - return all; - } - -private: - const bool m_portable; - QDir m_dir; - std::unique_ptr m_settings; -}; + QPixmap empty(32, 32); + empty.fill(QColor(0, 0, 0, 0)); + return QIcon(empty); +} InstanceManagerDialog::~InstanceManagerDialog() = default; @@ -352,7 +79,7 @@ void InstanceManagerDialog::updateInstances() m_instances.clear(); for (auto&& d : m.globalInstancePaths()) { - m_instances.push_back(std::make_unique(d, false)); + m_instances.push_back(std::make_unique(d, false)); } // sort first, prepend portable after so it's always on top @@ -363,7 +90,12 @@ void InstanceManagerDialog::updateInstances() if (m.portableInstanceExists()) { m_instances.insert( m_instances.begin(), - std::make_unique(m.portablePath(), true)); + std::make_unique(m.portablePath(), true)); + } + + // read all inis, ignore errors + for (auto&& i : m_instances) { + i->readFromIni(); } } @@ -381,7 +113,7 @@ void InstanceManagerDialog::updateList() const auto& ii = *m_instances[i]; auto* item = new QStandardItem(ii.name()); - item->setIcon(ii.icon(m_pc)); + item->setIcon(instanceIcon(m_pc, ii)); m_model->appendRow(item); @@ -568,9 +300,9 @@ void InstanceManagerDialog::rename() return; } - const QString src = i->location(); + const QString src = i->directory(); const QString dest = QDir::toNativeSeparators( - QFileInfo(i->location()).dir().path() + "/" + newName); + QFileInfo(src).dir().path() + "/" + newName); const auto r = shell::Rename(src, dest, false); if (!r) { @@ -582,15 +314,19 @@ void InstanceManagerDialog::rename() return; } + auto newInstance = std::make_unique(dest, false); + i = newInstance.get(); + m_model->item(selIndex)->setText(newName); - i->setDir(dest); + m_instances[selIndex] = std::move(newInstance); + fillData(*i); } void InstanceManagerDialog::exploreLocation() { if (const auto* i=singleSelection()) { - shell::Explore(i->location()); + shell::Explore(i->directory()); } } @@ -604,14 +340,14 @@ void InstanceManagerDialog::exploreBaseDirectory() void InstanceManagerDialog::exploreGame() { if (const auto* i=singleSelection()) { - shell::Explore(i->gamePath()); + shell::Explore(i->gameDirectory()); } } void InstanceManagerDialog::openINI() { if (const auto* i=singleSelection()) { - shell::Open(i->iniFile()); + shell::Open(i->iniPath()); } } @@ -707,6 +443,15 @@ void InstanceManagerDialog::setRestartOnSelect(bool b) bool InstanceManagerDialog::doDelete(const QStringList& files, bool recycle) { + // logging + for (auto&& f : files) { + if (recycle) { + log::info("will recycle {}", f); + } else { + log::info("will delete {}", f); + } + } + if (MOBase::shellDelete(files, recycle, this)) { return true; } @@ -771,17 +516,7 @@ std::size_t InstanceManagerDialog::singleSelectionIndex() const return static_cast(sel.indexes()[0].row()); } -InstanceInfo* InstanceManagerDialog::singleSelection() -{ - const auto i = singleSelectionIndex(); - if (i == NoSelection) { - return nullptr; - } - - return m_instances[i].get(); -} - -const InstanceInfo* InstanceManagerDialog::singleSelection() const +const Instance* InstanceManagerDialog::singleSelection() const { const auto i = singleSelectionIndex(); if (i == NoSelection) { @@ -791,13 +526,13 @@ const InstanceInfo* InstanceManagerDialog::singleSelection() const return m_instances[i].get(); } -void InstanceManagerDialog::fillData(const InstanceInfo& ii) +void InstanceManagerDialog::fillData(const Instance& ii) { ui->name->setText(ii.name()); - ui->location->setText(ii.location()); + ui->location->setText(ii.directory()); ui->baseDirectory->setText(ii.baseDirectory()); ui->gameName->setText(ii.gameName()); - ui->gameDir->setText(ii.gamePath()); + ui->gameDir->setText(ii.gameDirectory()); setButtonsEnabled(true); const auto& m = InstanceManager::singleton(); diff --git a/src/instancemanagerdialog.h b/src/instancemanagerdialog.h index 5b08ffc2..94f379ca 100644 --- a/src/instancemanagerdialog.h +++ b/src/instancemanagerdialog.h @@ -6,9 +6,11 @@ namespace Ui { class InstanceManagerDialog; }; -class InstanceInfo; +class Instance; class PluginContainer; +// a dialog to manage existing instances +// class InstanceManagerDialog : public QDialog { Q_OBJECT @@ -19,21 +21,61 @@ public: ~InstanceManagerDialog(); + // selects the instance having the given index in the list + // void select(std::size_t i); + + // selects the instance by name + // void select(const QString& name); + + // select the instance that is currently in use in MO + // void selectActiveInstance(); + + // switches to the selected instance; restarts MO, unless + // was called setRestartOnSelect(false) + // void openSelectedInstance(); + + // renames the currently selected instance + // void rename(); + + // explores the directory of the selected instance + // void exploreLocation(); + + // explores the base directory of the selected instance + // void exploreBaseDirectory(); + + // explores the game directory of the selected instance + // void exploreGame(); + + // converts the selected, portable instance to a global one; not implemented + // void convertToGlobal(); + + // converts the selected, global instance to a portable one; not implemented + // void convertToPortable(); + + // opens the ini of the selected instance in the shell + // void openINI(); + + // deletes the selected instance + // void deleteInstance(); + + // sets whether the dialog should restart MO when selecting an instance; this + // is false on startup when no instances exist + // void setRestartOnSelect(bool b); private: @@ -41,27 +83,51 @@ private: std::unique_ptr ui; const PluginContainer& m_pc; - std::vector> m_instances; + std::vector> m_instances; MOBase::FilterWidget m_filter; QStandardItemModel* m_model; bool m_restartOnSelect; + // refreshes the list instances from disk + // void updateInstances(); + // updates the ui for the selected instance + // void onSelection(); + + // opens the create instance dialog + // void createNew(); + + // returns the index of selected instance, NoSelection if none + // std::size_t singleSelectionIndex() const; - InstanceInfo* singleSelection(); - const InstanceInfo* singleSelection() const; + // returns the InstanceInfo associated with the selected instance, null if + // none + // + const Instance* singleSelection() const; + + // fills the instance list on the ui + // void updateList(); - void fillData(const InstanceInfo& ii); + + // fills the ui for the selected instance + // + void fillData(const Instance& ii); + + // clears the ui when there's no selection + // void clearData(); + + // enables/disables buttons like rename, explore... + // void setButtonsEnabled(bool b); - bool deletePortable(const InstanceInfo& ii); - bool deleteGlobal(const InstanceInfo& ii); + // deletes the given files, returns false on error + // bool doDelete(const QStringList& files, bool recycle); }; diff --git a/src/instancemanagerdialog.ui b/src/instancemanagerdialog.ui index 410b58c1..45b99e21 100644 --- a/src/instancemanagerdialog.ui +++ b/src/instancemanagerdialog.ui @@ -330,7 +330,7 @@ - Delete instance + Delete instance... diff --git a/src/main.cpp b/src/main.cpp index 7f0886f6..a2653685 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -662,17 +662,17 @@ int doOneRun( } else { - if (!currentInstance->directory().exists()) { + if (!QDir(currentInstance->directory()).exists()) { // the previously used instance doesn't exist anymore if (m.hasAnyInstances()) { criticalOnTop(QObject::tr( "Instance at '%1' not found. Select another instance.") - .arg(currentInstance->directory().absolutePath())); + .arg(currentInstance->directory())); } else { criticalOnTop(QObject::tr( "Instance at '%1' not found. You must create a new instance") - .arg(currentInstance->directory().absolutePath())); + .arg(currentInstance->directory())); } currentInstance = selectInstance(); @@ -682,7 +682,7 @@ int doOneRun( } } - const QString dataPath = currentInstance->directory().path(); + const QString dataPath = currentInstance->directory(); application.setProperty("dataPath", dataPath); setExceptionHandlers(); -- cgit v1.3.1 From 90ea45328d72f9664ace3cfbdce700dbe7a1d016 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 7 Nov 2020 16:16:47 -0500 Subject: moved splash stuff to MOSplash comments --- src/commandline.cpp | 3 +++ src/main.cpp | 71 ++----------------------------------------------- src/moapplication.cpp | 73 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/moapplication.h | 19 ++++++++++++++ src/settings.h | 18 +++++++++++++ 5 files changed, 115 insertions(+), 69 deletions(-) (limited to 'src/main.cpp') diff --git a/src/commandline.cpp b/src/commandline.cpp index ff1be764..dc8cf53f 100644 --- a/src/commandline.cpp +++ b/src/commandline.cpp @@ -592,6 +592,8 @@ std::optional ExeCommand::doRun() const auto args = vm()["arguments"].as(); const auto cwd = vm()["cwd"].as(); + std::cout << "not implemented\n"; + return 0; } @@ -608,6 +610,7 @@ Command::Meta RunCommand::meta() const std::optional RunCommand::doRun() { + std::cout << "not implemented\n"; return {}; } diff --git a/src/main.cpp b/src/main.cpp index a2653685..9d6ed7b0 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -160,69 +160,6 @@ void addDllsToPath() env::prependToPath(dllsPath); } -QString getSplashPath( - const Settings& settings, const QString& dataPath, - const MOBase::IPluginGame* game) -{ - if (!settings.useSplash()) { - return {}; - } - - // try splash from instance directory - const QString splashPath = dataPath + "/splash.png"; - if (QFile::exists(dataPath + "/splash.png")) { - QImage image(splashPath); - if (!image.isNull()) { - return splashPath; - } - } - - // try splash from plugin - QString pluginSplash = QString(":/%1/splash").arg(game->gameShortName()); - if (QFile::exists(pluginSplash)) { - QImage image(pluginSplash); - if (!image.isNull()) { - image.save(splashPath); - return pluginSplash; - } - } - - // try default splash from resource - QString defaultSplash = ":/MO/gui/splash"; - if (QFile::exists(defaultSplash)) { - QImage image(defaultSplash); - if (!image.isNull()) { - return defaultSplash; - } - } - - return splashPath; -} - -std::unique_ptr createSplash( - const Settings& settings, const QString& dataPath, - const MOBase::IPluginGame* game) -{ - const auto splashPath = getSplashPath(settings, dataPath, game); - if (splashPath.isEmpty()) { - return {}; - } - - QPixmap image(splashPath); - if (image.isNull()) { - log::error("failed to load splash from {}", splashPath); - return {}; - } - - auto splash = std::make_unique(image); - settings.geometry().centerOnMainWindowMonitor(splash.get()); - - splash->show(); - splash->activateWindow(); - - return splash; -} - std::optional handleCommandLine( const cl::CommandLine& cl, OrganizerCore& organizer) { @@ -554,7 +491,7 @@ int runApplication( return *r; } - auto splash = createSplash(settings, dataPath, currentInstance.gamePlugin()); + MOSplash splash(settings, dataPath, currentInstance.gamePlugin()); QString apiKey; if (GlobalSettings::nexusApiKey(apiKey)) { @@ -589,11 +526,7 @@ int runApplication( mainWindow.show(); mainWindow.activateWindow(); - if (splash) { - // don't pass mainwindow as it just waits half a second for it - // instead of proceding - splash->finish(nullptr); - } + splash.close(); tt.stop(); diff --git a/src/moapplication.cpp b/src/moapplication.cpp index 290666af..659b5a12 100644 --- a/src/moapplication.cpp +++ b/src/moapplication.cpp @@ -18,6 +18,8 @@ along with Mod Organizer. If not, see . */ #include "moapplication.h" +#include "settings.h" +#include #include #include #include @@ -147,3 +149,74 @@ void MOApplication::updateStyle(const QString& fileName) } } } + + +MOSplash::MOSplash( + const Settings& settings, const QString& dataPath, + const MOBase::IPluginGame* game) +{ + const auto splashPath = getSplashPath(settings, dataPath, game); + if (splashPath.isEmpty()) { + return; + } + + QPixmap image(splashPath); + if (image.isNull()) { + log::error("failed to load splash from {}", splashPath); + return; + } + + ss_.reset(new QSplashScreen(image)); + settings.geometry().centerOnMainWindowMonitor(ss_.get()); + + ss_->show(); + ss_->activateWindow(); +} + +void MOSplash::close() +{ + if (ss_) { + // don't pass mainwindow as it just waits half a second for it + // instead of proceding + ss_->finish(nullptr); + } +} + +QString MOSplash::getSplashPath( + const Settings& settings, const QString& dataPath, + const MOBase::IPluginGame* game) const +{ + if (!settings.useSplash()) { + return {}; + } + + // try splash from instance directory + const QString splashPath = dataPath + "/splash.png"; + if (QFile::exists(dataPath + "/splash.png")) { + QImage image(splashPath); + if (!image.isNull()) { + return splashPath; + } + } + + // try splash from plugin + QString pluginSplash = QString(":/%1/splash").arg(game->gameShortName()); + if (QFile::exists(pluginSplash)) { + QImage image(pluginSplash); + if (!image.isNull()) { + image.save(splashPath); + return pluginSplash; + } + } + + // try default splash from resource + QString defaultSplash = ":/MO/gui/splash"; + if (QFile::exists(defaultSplash)) { + QImage image(defaultSplash); + if (!image.isNull()) { + return defaultSplash; + } + } + + return splashPath; +} diff --git a/src/moapplication.h b/src/moapplication.h index c67c4622..0ee04492 100644 --- a/src/moapplication.h +++ b/src/moapplication.h @@ -23,6 +23,8 @@ along with Mod Organizer. If not, see . #include #include +class Settings; +namespace MOBase { class IPluginGame; } class MOApplication : public QApplication { @@ -46,4 +48,21 @@ private: }; +class MOSplash +{ +public: + MOSplash( + const Settings& settings, const QString& dataPath, + const MOBase::IPluginGame* game); + + void close(); + +private: + std::unique_ptr ss_; + + QString getSplashPath( + const Settings& settings, const QString& dataPath, + const MOBase::IPluginGame* game) const; +}; + #endif // MOAPPLICATION_H diff --git a/src/settings.h b/src/settings.h index c8325ba2..fc7789f0 100644 --- a/src/settings.h +++ b/src/settings.h @@ -195,6 +195,10 @@ private: class WidgetSettings { public: + // globalInstance is forwarded from the Settings constructor; WidgetSettings + // has the callbacks used by QuestionBoxMemory and those are global, so they + // should only be set by the global instance + // WidgetSettings(QSettings& s, bool globalInstance); // selected index for a combobox @@ -671,6 +675,20 @@ class Settings : public QObject Q_OBJECT; public: + // there is one Settings global object for MO when an instance is loaded, but + // other Settings objects are required in several places, such as in the + // Instance class, the instance dialogs, etc. + // + // any Settings object created with globalInstance==false won't set the + // singleton + // + // only WidgetSettings need to know whether it created from a globalInstance, + // see its constructor + // + // @param path path to an ini file + // @param globalInsance whether this is the global instance; creates the + // singleton and asserts if it already exists + // Settings(const QString& path, bool globalInstance=false); ~Settings(); -- cgit v1.3.1 From a8187e7dc47cd344fd309a2be3675e4687f95aaa Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 7 Nov 2020 16:31:35 -0500 Subject: moved dlls stuff to MOApplication --- src/main.cpp | 40 +--------------------------------------- src/moapplication.cpp | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 39 deletions(-) (limited to 'src/main.cpp') diff --git a/src/main.cpp b/src/main.cpp index 9d6ed7b0..a8ea2601 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -122,44 +122,6 @@ void setExceptionHandlers() g_prevTerminateHandler = std::set_terminate(onTerminate); } -// This adds the `dlls` directory to the path so the dlls can be found. How -// MO is able to find dlls in there is a bit convoluted: -// -// Dependencies on DLLs can be baked into an executable by passing a -// `manifestdependency` option to the linker. This can be done on the command -// line or with a pragma. Typically, the dependency will not be a hardcoded -// filename, but an assembly name, such as Microsoft.Windows.Common-Controls. -// -// When Windows loads the exe, it will look for this assembly in a variety of -// places, such as in the WinSxS folder, but also in the program's folder. It -// will look for `assemblyname.dll` or `assemblyname/assemblyname.dll` and try -// to load that. -// -// If these files don't exist, then the loader gets creative and looks for -// `assemblyname.manifest` and `assemblyname/assemblyname.manifest`. A manifest -// file is just an XML file that can contain a list of DLLs to load for this -// assembly. -// -// In MO's case, there's a `pragma` at the beginning of this file which adds -// `dlls` as an "assembly" dependency. This is a bit of a hack to just force -// the loader to eventually find `dlls/dlls.manifest`, which contains the list -// of all the DLLs MO requires to load. -// -// This file was handwritten in `modorganizer/src/dlls.manifest.qt5` and -// is copied and renamed in CMakeLists.txt into `bin/dlls/dlls.manifest`. Note -// that the useless and incorrect .qt5 extension is removed. -// -void addDllsToPath() -{ - const auto dllsPath = QDir::toNativeSeparators( - QCoreApplication::applicationDirPath() + "/dlls"); - - QCoreApplication::setLibraryPaths( - QStringList(dllsPath) + QCoreApplication::libraryPaths()); - - env::prependToPath(dllsPath); -} - std::optional handleCommandLine( const cl::CommandLine& cl, OrganizerCore& organizer) { @@ -646,8 +608,8 @@ int main(int argc, char *argv[]) SetThisThreadName("main"); initLogging(); + auto application = MOApplication::create(argc, argv); - addDllsToPath(); SingleInstance instance(cl.multiple()); if (instance.ephemeral()) { diff --git a/src/moapplication.cpp b/src/moapplication.cpp index 659b5a12..f514050f 100644 --- a/src/moapplication.cpp +++ b/src/moapplication.cpp @@ -19,6 +19,7 @@ along with Mod Organizer. If not, see . #include "moapplication.h" #include "settings.h" +#include "env.h" #include #include #include @@ -32,6 +33,12 @@ along with Mod Organizer. If not, see . #include #include +// see addDllsToPath() below +#pragma comment(linker, "/manifestDependency:\"" \ + "name='dlls' " \ + "processorArchitecture='x86' " \ + "version='1.0.0.0' " \ + "type='win32' \"") using namespace MOBase; @@ -73,6 +80,45 @@ public: }; +// This adds the `dlls` directory to the path so the dlls can be found. How +// MO is able to find dlls in there is a bit convoluted: +// +// Dependencies on DLLs can be baked into an executable by passing a +// `manifestdependency` option to the linker. This can be done on the command +// line or with a pragma. Typically, the dependency will not be a hardcoded +// filename, but an assembly name, such as Microsoft.Windows.Common-Controls. +// +// When Windows loads the exe, it will look for this assembly in a variety of +// places, such as in the WinSxS folder, but also in the program's folder. It +// will look for `assemblyname.dll` or `assemblyname/assemblyname.dll` and try +// to load that. +// +// If these files don't exist, then the loader gets creative and looks for +// `assemblyname.manifest` and `assemblyname/assemblyname.manifest`. A manifest +// file is just an XML file that can contain a list of DLLs to load for this +// assembly. +// +// In MO's case, there's a `pragma` at the beginning of this file which adds +// `dlls` as an "assembly" dependency. This is a bit of a hack to just force +// the loader to eventually find `dlls/dlls.manifest`, which contains the list +// of all the DLLs MO requires to load. +// +// This file was handwritten in `modorganizer/src/dlls.manifest.qt5` and +// is copied and renamed in CMakeLists.txt into `bin/dlls/dlls.manifest`. Note +// that the useless and incorrect .qt5 extension is removed. +// +void addDllsToPath() +{ + const auto dllsPath = QDir::toNativeSeparators( + QCoreApplication::applicationDirPath() + "/dlls"); + + QCoreApplication::setLibraryPaths( + QStringList(dllsPath) + QCoreApplication::libraryPaths()); + + env::prependToPath(dllsPath); +} + + MOApplication::MOApplication(int& argc, char** argv) : QApplication(argc, argv) { @@ -83,11 +129,13 @@ MOApplication::MOApplication(int& argc, char** argv) m_DefaultStyle = style()->objectName(); setStyle(new ProxyStyle(style())); + addDllsToPath(); } MOApplication MOApplication::create(int& argc, char** argv) { QApplication::setAttribute(Qt::AA_EnableHighDpiScaling); + return MOApplication(argc, argv); } -- cgit v1.3.1 From d288587b002b19c54dc08d08ae267d301aa2ac81 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 7 Nov 2020 16:51:55 -0500 Subject: moved instance setup/selection to instancemanager.cpp comments --- src/instancemanager.cpp | 157 +++++++++++++++++++++++++++++++++++++- src/instancemanager.h | 37 +++++++++ src/main.cpp | 181 +------------------------------------------- src/shared/error_report.cpp | 10 +++ src/shared/error_report.h | 8 ++ 5 files changed, 214 insertions(+), 179 deletions(-) (limited to 'src/main.cpp') diff --git a/src/instancemanager.cpp b/src/instancemanager.cpp index fbafc6e8..b68d715b 100644 --- a/src/instancemanager.cpp +++ b/src/instancemanager.cpp @@ -21,9 +21,14 @@ along with Mod Organizer. If not, see . #include "instancemanager.h" #include "selectiondialog.h" #include "settings.h" -#include "shared/appconfig.h" #include "plugincontainer.h" +#include "nexusinterface.h" +#include "createinstancedialog.h" +#include "instancemanagerdialog.h" +#include "createinstancedialogpages.h" +#include "shared/appconfig.h" #include "shared/util.h" +#include "shared/error_report.h" #include #include #include @@ -751,3 +756,153 @@ bool InstanceManager::validInstanceName(const QString& instanceName) const return (instanceName == sanitizeInstanceName(instanceName)); } + + + +std::optional selectInstance() +{ + auto& m = InstanceManager::singleton(); + + NexusInterface ni(nullptr); + PluginContainer pc(nullptr); + pc.loadPlugins(); + + if (!m.hasAnyInstances()) { + // no instances configured + CreateInstanceDialog dlg(pc, nullptr); + if (dlg.exec() != QDialog::Accepted) { + return {}; + } + + return m.currentInstance(); + } + + + InstanceManagerDialog dlg(pc); + dlg.setRestartOnSelect(false); + + dlg.show(); + dlg.activateWindow(); + dlg.raise(); + + if (dlg.exec() != QDialog::Accepted) { + return {}; + } + + return m.currentInstance(); +} + +SetupInstanceResults setupInstance(Instance& instance, PluginContainer& pc) +{ + const auto setupResult = instance.setup(pc); + + switch (setupResult) + { + case Instance::SetupResults::Ok: + { + return SetupInstanceResults::Ok; + } + + case Instance::SetupResults::BadIni: + { + MOShared::criticalOnTop( + QObject::tr("Cannot open instance '%1', failed to read INI file %2.") + .arg(instance.name()).arg(instance.iniPath())); + + return SetupInstanceResults::SelectAnother; + } + + case Instance::SetupResults::IniMissingGame: + { + MOShared::criticalOnTop( + QObject::tr( + "Cannot open instance '%1', the managed game was not found in the INI " + "file %2. Select the game managed by this instance.") + .arg(instance.name()).arg(instance.iniPath())); + + CreateInstanceDialog dlg(pc, nullptr); + dlg.setSinglePage(instance.name()); + + dlg.show(); + dlg.activateWindow(); + dlg.raise(); + + if (dlg.exec() != QDialog::Accepted) { + return SetupInstanceResults::Exit; + } + + instance.setGame( + dlg.creationInfo().game->gameName(), + dlg.creationInfo().gameLocation); + + return SetupInstanceResults::TryAgain; + } + + case Instance::SetupResults::PluginGone: + { + MOShared::criticalOnTop( + QObject::tr( + "Cannot open instance '%1', the game plugin '%2' doesn't exist. It " + "may have been deleted by an antivirus. Select another instance.") + .arg(instance.name()).arg(instance.gameName())); + + return SetupInstanceResults::SelectAnother; + } + + case Instance::SetupResults::GameGone: + { + MOShared::criticalOnTop( + QObject::tr( + "Cannot open instance '%1', the game directory '%2' doesn't exist or " + "the game plugin '%3' doesn't recognize it. Select the game managed " + "by this instance.") + .arg(instance.name()) + .arg(instance.gameDirectory()) + .arg(instance.gameName())); + + CreateInstanceDialog dlg(pc, nullptr); + dlg.setSinglePage(instance.name()); + + dlg.show(); + dlg.activateWindow(); + dlg.raise(); + + if (dlg.exec() != QDialog::Accepted) { + return SetupInstanceResults::Exit; + } + + instance.setGame( + dlg.creationInfo().game->gameName(), + dlg.creationInfo().gameLocation); + + return SetupInstanceResults::TryAgain; + } + + case Instance::SetupResults::MissingVariant: + { + CreateInstanceDialog dlg(pc, nullptr); + + dlg.getPage()->select( + instance.gamePlugin(), instance.gameDirectory()); + + dlg.setSinglePage(instance.name()); + + dlg.show(); + dlg.activateWindow(); + dlg.raise(); + + if (dlg.exec() != QDialog::Accepted) { + return SetupInstanceResults::Exit; + } + + instance.setVariant(dlg.creationInfo().gameVariant); + + return SetupInstanceResults::TryAgain; + } + + default: + { + return SetupInstanceResults::Exit; + } + } +} diff --git a/src/instancemanager.h b/src/instancemanager.h index 161df739..d75a46f4 100644 --- a/src/instancemanager.h +++ b/src/instancemanager.h @@ -332,4 +332,41 @@ private: std::optional m_overrideProfileName; }; + +// see setupInstance() +// +enum class SetupInstanceResults +{ + Ok, + TryAgain, + SelectAnother, + Exit +}; + +// if there are no instances configured, global or portable, shows the +// create instance dialog and returns the new instance or empty if the user +// cancelled +// +// if there is at least one instance available, unconditionally show the +// instance manager dialog and returns the selected instnace or empty if the +// user cancelled +// +std::optional selectInstance(); + +// calls instance.setup() tries to handle problems by itself: +// +// - if the ini is missing some information, will show dialogs and ask the user +// to fill in what's required (such as the game directory, variant, etc.); +// if successful, returns TryAgain and setupInstance() can be called again +// with the same instance +// +// - if the instance cannot be used (no game plugin found for it, ini can't +// be read, etc.), returns SelectAnother +// +// - if the user cancels at any point, returns Exit +// +// - if the instance has been set up correctly, returns Ok +// +SetupInstanceResults setupInstance(Instance& instance, PluginContainer& pc); + #endif // MODORGANIZER_INSTANCEMANAGER_INCLUDED diff --git a/src/main.cpp b/src/main.cpp index a8ea2601..fdd09872 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -34,20 +34,13 @@ along with Mod Organizer. If not, see . #include "commandline.h" #include "shared/util.h" #include "shared/appconfig.h" - +#include "shared/error_report.h" #include #include #include #include #include -// see addDllsToPath() below -#pragma comment(linker, "/manifestDependency:\"" \ - "name='dlls' " \ - "processorArchitecture='x86' " \ - "version='1.0.0.0' " \ - "type='win32' \"") - using namespace MOBase; using namespace MOShared; @@ -171,174 +164,6 @@ std::optional handleCommandLine( return {}; } -std::optional selectInstance() -{ - auto& m = InstanceManager::singleton(); - - NexusInterface ni(nullptr); - PluginContainer pc(nullptr); - pc.loadPlugins(); - - if (!m.hasAnyInstances()) { - // no instances configured - CreateInstanceDialog dlg(pc, nullptr); - if (dlg.exec() != QDialog::Accepted) { - return {}; - } - - return m.currentInstance(); - } - - - InstanceManagerDialog dlg(pc); - dlg.setRestartOnSelect(false); - - dlg.show(); - dlg.activateWindow(); - dlg.raise(); - - if (dlg.exec() != QDialog::Accepted) { - return {}; - } - - return m.currentInstance(); -} - -enum class SetupInstanceResults -{ - Ok, - TryAgain, - SelectAnother, - Exit -}; - - -void criticalOnTop(const QString& message) -{ - QMessageBox mb(QMessageBox::Critical, QObject::tr("Mod Organizer"), message); - - mb.show(); - mb.activateWindow(); - mb.raise(); - mb.exec(); -} - - -SetupInstanceResults setupInstance(Instance& instance, PluginContainer& pc) -{ - const auto setupResult = instance.setup(pc); - - switch (setupResult) - { - case Instance::SetupResults::Ok: - { - return SetupInstanceResults::Ok; - } - - case Instance::SetupResults::BadIni: - { - criticalOnTop( - QObject::tr("Cannot open instance '%1', failed to read INI file %2.") - .arg(instance.name()).arg(instance.iniPath())); - - return SetupInstanceResults::SelectAnother; - } - - case Instance::SetupResults::IniMissingGame: - { - criticalOnTop( - QObject::tr( - "Cannot open instance '%1', the managed game was not found in the INI " - "file %2. Select the game managed by this instance.") - .arg(instance.name()).arg(instance.iniPath())); - - CreateInstanceDialog dlg(pc, nullptr); - dlg.setSinglePage(instance.name()); - - dlg.show(); - dlg.activateWindow(); - dlg.raise(); - - if (dlg.exec() != QDialog::Accepted) { - return SetupInstanceResults::Exit; - } - - instance.setGame( - dlg.creationInfo().game->gameName(), - dlg.creationInfo().gameLocation); - - return SetupInstanceResults::TryAgain; - } - - case Instance::SetupResults::PluginGone: - { - criticalOnTop( - QObject::tr( - "Cannot open instance '%1', the game plugin '%2' doesn't exist. It " - "may have been deleted by an antivirus. Select another instance.") - .arg(instance.name()).arg(instance.gameName())); - - return SetupInstanceResults::SelectAnother; - } - - case Instance::SetupResults::GameGone: - { - criticalOnTop( - QObject::tr( - "Cannot open instance '%1', the game directory '%2' doesn't exist or " - "the game plugin '%3' doesn't recognize it. Select the game managed " - "by this instance.") - .arg(instance.name()) - .arg(instance.gameDirectory()) - .arg(instance.gameName())); - - CreateInstanceDialog dlg(pc, nullptr); - dlg.setSinglePage(instance.name()); - - dlg.show(); - dlg.activateWindow(); - dlg.raise(); - - if (dlg.exec() != QDialog::Accepted) { - return SetupInstanceResults::Exit; - } - - instance.setGame( - dlg.creationInfo().game->gameName(), - dlg.creationInfo().gameLocation); - - return SetupInstanceResults::TryAgain; - } - - case Instance::SetupResults::MissingVariant: - { - CreateInstanceDialog dlg(pc, nullptr); - - dlg.getPage()->select( - instance.gamePlugin(), instance.gameDirectory()); - - dlg.setSinglePage(instance.name()); - - dlg.show(); - dlg.activateWindow(); - dlg.raise(); - - if (dlg.exec() != QDialog::Accepted) { - return SetupInstanceResults::Exit; - } - - instance.setVariant(dlg.creationInfo().gameVariant); - - return SetupInstanceResults::TryAgain; - } - - default: - { - return SetupInstanceResults::Exit; - } - } -} - int runApplication( MOApplication &application, const cl::CommandLine& cl, SingleInstance &instance, const QString &dataPath, @@ -561,11 +386,11 @@ int doOneRun( // the previously used instance doesn't exist anymore if (m.hasAnyInstances()) { - criticalOnTop(QObject::tr( + MOShared::criticalOnTop(QObject::tr( "Instance at '%1' not found. Select another instance.") .arg(currentInstance->directory())); } else { - criticalOnTop(QObject::tr( + MOShared::criticalOnTop(QObject::tr( "Instance at '%1' not found. You must create a new instance") .arg(currentInstance->directory())); } diff --git a/src/shared/error_report.cpp b/src/shared/error_report.cpp index 4185b544..09fdcb49 100644 --- a/src/shared/error_report.cpp +++ b/src/shared/error_report.cpp @@ -51,4 +51,14 @@ void reportError(LPCWSTR format, ...) MessageBoxW(nullptr, buffer, L"Error", MB_OK | MB_ICONERROR); } +void criticalOnTop(const QString& message) +{ + QMessageBox mb(QMessageBox::Critical, QObject::tr("Mod Organizer"), message); + + mb.show(); + mb.activateWindow(); + mb.raise(); + mb.exec(); +} + } // namespace MOShared diff --git a/src/shared/error_report.h b/src/shared/error_report.h index da07c728..9343d3da 100644 --- a/src/shared/error_report.h +++ b/src/shared/error_report.h @@ -31,6 +31,14 @@ namespace MOShared void reportError(LPCSTR format, ...); void reportError(LPCWSTR format, ...); +// shows a critical message box that's raised to the top of the zorder, useful +// for messages without a main window, which sometimes makes them pop up behind +// all other windows +// +// the dialog is not topmost, it's just raised once when shown +// +void criticalOnTop(const QString& message); + } // namespace MOShared #endif // MODORGANIZER_SHARED_ERROR_REPORT_INCLUDED -- cgit v1.3.1 From 1bf24c7daad1eb954dc160784802de58f9809fa1 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 7 Nov 2020 17:05:29 -0500 Subject: refactored setupInstance(), comments ok -> okay --- src/instancemanager.cpp | 173 ++++++++++++++++++++++++++++++------------------ src/instancemanager.h | 8 +-- src/main.cpp | 2 +- 3 files changed, 114 insertions(+), 69 deletions(-) (limited to 'src/main.cpp') diff --git a/src/instancemanager.cpp b/src/instancemanager.cpp index b68d715b..4191f850 100644 --- a/src/instancemanager.cpp +++ b/src/instancemanager.cpp @@ -158,7 +158,7 @@ Instance::SetupResults Instance::setup(PluginContainer& plugins) // getting game plugin const auto r = getGamePlugin(plugins); - if (r != SetupResults::Ok) { + if (r != SetupResults::Okay) { return r; } @@ -177,7 +177,7 @@ Instance::SetupResults Instance::setup(PluginContainer& plugins) // installed m_plugin->setGamePath(m_gameDir); - return SetupResults::Ok; + return SetupResults::Okay; } void Instance::updateIni() @@ -248,7 +248,7 @@ Instance::SetupResults Instance::getGamePlugin(PluginContainer& plugins) } m_plugin = game; - return SetupResults::Ok; + return SetupResults::Okay; } } @@ -272,7 +272,7 @@ Instance::SetupResults Instance::getGamePlugin(PluginContainer& plugins) m_plugin = game; m_gameName = game->gameName(); - return SetupResults::Ok; + return SetupResults::Okay; } } @@ -301,7 +301,7 @@ Instance::SetupResults Instance::getGamePlugin(PluginContainer& plugins) m_plugin = game; m_gameDir = game->gameDirectory().absolutePath(); - return SetupResults::Ok; + return SetupResults::Okay; } else { log::warn( "found plugin {} that matches name in ini {}, but no game install " @@ -763,48 +763,126 @@ std::optional selectInstance() { auto& m = InstanceManager::singleton(); + // since there is no instance currently active, load plugins with a null + // OrganizerCore; see PluginContainer::initPlugin() NexusInterface ni(nullptr); PluginContainer pc(nullptr); pc.loadPlugins(); - if (!m.hasAnyInstances()) { - // no instances configured + if (m.hasAnyInstances()) { + // there is at least one instance available, show the instance manager + // dialog + InstanceManagerDialog dlg(pc); + + // the dialog normally restarts MO when an instance is selected, but this + // is not necessary here since MO hasn't really started yet + dlg.setRestartOnSelect(false); + + dlg.show(); + dlg.activateWindow(); + dlg.raise(); + + if (dlg.exec() != QDialog::Accepted) { + return {}; + } + + } else { + // no instances configured, ask the user to create one CreateInstanceDialog dlg(pc, nullptr); + if (dlg.exec() != QDialog::Accepted) { return {}; } + } - return m.currentInstance(); + // return the new instance or the selection + return m.currentInstance(); +} + +// shows the game selection page of the create instance dialog so the user can +// pick which game is managed by this instance +// +// this is used below in setupInstance() when the game directory is gone or +// no plugins can recognize it +// +SetupInstanceResults selectGame(Instance& instance, PluginContainer& pc) +{ + CreateInstanceDialog dlg(pc, nullptr); + + // only show the game page + dlg.setSinglePage(instance.name()); + + dlg.show(); + dlg.activateWindow(); + dlg.raise(); + + if (dlg.exec() != QDialog::Accepted) { + // cancelled + return SetupInstanceResults::Exit; } + // this info will be used instead of the ini, which should fix this + // particular problem + instance.setGame( + dlg.creationInfo().game->gameName(), + dlg.creationInfo().gameLocation); + + return SetupInstanceResults::TryAgain; +} + +// shows the game variant page of the create instance dialog so the user can +// pick which game variant is installed +// +// this is used below in setupInstance() when there is no variant in the ini +// but the game plugin requires one; this can happen when the ini is broken +// or when a new variant has become supported by the plugin for a game the +// user already has an instance for +// +SetupInstanceResults selectVariant(Instance& instance, PluginContainer& pc) +{ + CreateInstanceDialog dlg(pc, nullptr); + + // the variant page uses the game page to know which game was selected, so + // set it manually + dlg.getPage()->select( + instance.gamePlugin(), instance.gameDirectory()); - InstanceManagerDialog dlg(pc); - dlg.setRestartOnSelect(false); + // only show the variant page + dlg.setSinglePage(instance.name()); dlg.show(); dlg.activateWindow(); dlg.raise(); if (dlg.exec() != QDialog::Accepted) { - return {}; + return SetupInstanceResults::Exit; } - return m.currentInstance(); + // this info will be used instead of the ini, which should fix this + // particular problem + instance.setVariant(dlg.creationInfo().gameVariant); + + return SetupInstanceResults::TryAgain; } SetupInstanceResults setupInstance(Instance& instance, PluginContainer& pc) { + // set up the instance const auto setupResult = instance.setup(pc); switch (setupResult) { - case Instance::SetupResults::Ok: + case Instance::SetupResults::Okay: { - return SetupInstanceResults::Ok; + // all good + return SetupInstanceResults::Okay; } case Instance::SetupResults::BadIni: { + // unreadable ini, there's not much that can be done, select another + // instance + MOShared::criticalOnTop( QObject::tr("Cannot open instance '%1', failed to read INI file %2.") .arg(instance.name()).arg(instance.iniPath())); @@ -814,32 +892,26 @@ SetupInstanceResults setupInstance(Instance& instance, PluginContainer& pc) case Instance::SetupResults::IniMissingGame: { + // both the game name and directory are missing from the ini; although + // this shouldn't happen, setup() is able to handle when either is + // missing, but not both + // + // ask the user for the game managed by this instance + MOShared::criticalOnTop( QObject::tr( "Cannot open instance '%1', the managed game was not found in the INI " "file %2. Select the game managed by this instance.") .arg(instance.name()).arg(instance.iniPath())); - CreateInstanceDialog dlg(pc, nullptr); - dlg.setSinglePage(instance.name()); - - dlg.show(); - dlg.activateWindow(); - dlg.raise(); - - if (dlg.exec() != QDialog::Accepted) { - return SetupInstanceResults::Exit; - } - - instance.setGame( - dlg.creationInfo().game->gameName(), - dlg.creationInfo().gameLocation); - - return SetupInstanceResults::TryAgain; + return selectGame(instance, pc); } case Instance::SetupResults::PluginGone: { + // there is no plugin that can handle the game name/directory from the + // ini, so this instance is unusable + MOShared::criticalOnTop( QObject::tr( "Cannot open instance '%1', the game plugin '%2' doesn't exist. It " @@ -851,6 +923,9 @@ SetupInstanceResults setupInstance(Instance& instance, PluginContainer& pc) case Instance::SetupResults::GameGone: { + // the game directory doesn't exist or the plugin doesn't recognize it; + // ask the user for the game managed by this instance + MOShared::criticalOnTop( QObject::tr( "Cannot open instance '%1', the game directory '%2' doesn't exist or " @@ -860,48 +935,18 @@ SetupInstanceResults setupInstance(Instance& instance, PluginContainer& pc) .arg(instance.gameDirectory()) .arg(instance.gameName())); - CreateInstanceDialog dlg(pc, nullptr); - dlg.setSinglePage(instance.name()); - - dlg.show(); - dlg.activateWindow(); - dlg.raise(); - - if (dlg.exec() != QDialog::Accepted) { - return SetupInstanceResults::Exit; - } - - instance.setGame( - dlg.creationInfo().game->gameName(), - dlg.creationInfo().gameLocation); - - return SetupInstanceResults::TryAgain; + return selectGame(instance, pc); } case Instance::SetupResults::MissingVariant: { - CreateInstanceDialog dlg(pc, nullptr); - - dlg.getPage()->select( - instance.gamePlugin(), instance.gameDirectory()); - - dlg.setSinglePage(instance.name()); - - dlg.show(); - dlg.activateWindow(); - dlg.raise(); - - if (dlg.exec() != QDialog::Accepted) { - return SetupInstanceResults::Exit; - } - - instance.setVariant(dlg.creationInfo().gameVariant); - - return SetupInstanceResults::TryAgain; + // the game variant is missing from the ini, ask the user for it + return selectVariant(instance, pc); } default: { + // shouldn't happen return SetupInstanceResults::Exit; } } diff --git a/src/instancemanager.h b/src/instancemanager.h index d75a46f4..4f8b01c7 100644 --- a/src/instancemanager.h +++ b/src/instancemanager.h @@ -32,7 +32,7 @@ public: enum class SetupResults { // instance is ready to be used - Ok, + Okay, // error while reading the INI BadIni, @@ -337,7 +337,7 @@ private: // enum class SetupInstanceResults { - Ok, + Okay, TryAgain, SelectAnother, Exit @@ -348,7 +348,7 @@ enum class SetupInstanceResults // cancelled // // if there is at least one instance available, unconditionally show the -// instance manager dialog and returns the selected instnace or empty if the +// instance manager dialog and returns the selected instance or empty if the // user cancelled // std::optional selectInstance(); @@ -365,7 +365,7 @@ std::optional selectInstance(); // // - if the user cancels at any point, returns Exit // -// - if the instance has been set up correctly, returns Ok +// - if the instance has been set up correctly, returns Okay // SetupInstanceResults setupInstance(Instance& instance, PluginContainer& pc); diff --git a/src/main.cpp b/src/main.cpp index fdd09872..e0326b27 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -237,7 +237,7 @@ int runApplication( { const auto setupResult = setupInstance(currentInstance, *pluginContainer); - if (setupResult == SetupInstanceResults::Ok) { + if (setupResult == SetupInstanceResults::Okay) { break; } else if (setupResult == SetupInstanceResults::TryAgain) { continue; -- cgit v1.3.1 From ba7d24faef49858b9721c2ade0f3ea4a7ba4d96d Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 7 Nov 2020 17:21:13 -0500 Subject: moved handleCommandLine() to CommandLine::setupCore() --- src/commandline.cpp | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++++ src/commandline.h | 22 ++++++++++++++++++++++ src/main.cpp | 51 +-------------------------------------------------- 3 files changed, 76 insertions(+), 50 deletions(-) (limited to 'src/main.cpp') diff --git a/src/commandline.cpp b/src/commandline.cpp index dc8cf53f..3d0e901d 100644 --- a/src/commandline.cpp +++ b/src/commandline.cpp @@ -1,10 +1,15 @@ #include "commandline.h" #include "env.h" +#include "organizercore.h" #include "shared/util.h" +#include +#include namespace cl { +using namespace MOBase; + std::string pad_right(std::string s, std::size_t n, char c=' ') { if (s.size() < n) @@ -195,6 +200,54 @@ std::optional CommandLine::run(const std::wstring& line) } } +std::optional CommandLine::setupCore(OrganizerCore& organizer) const +{ + // if we have a command line parameter, it is either a nxm link or + // a binary to start + if (m_shortcut.isValid()) { + if (m_shortcut.hasExecutable()) { + try { + organizer.processRunner() + .setFromShortcut(m_shortcut) + .setWaitForCompletion() + .run(); + + return 0; + } + catch (const std::exception &e) { + reportError( + QObject::tr("failed to start shortcut: %1").arg(e.what())); + return 1; + } + } + } else if (m_nxmLink) { + log::debug("starting download from command line: {}", *m_nxmLink); + organizer.externalMessage(*m_nxmLink); + } else if (m_executable) { + const QString exeName = *m_executable; + log::debug("starting {} from command line", exeName); + + try + { + // pass the remaining parameters to the binary + organizer.processRunner() + .setFromFileOrExecutable(exeName, m_untouched) + .setWaitForCompletion() + .run(); + + return 0; + } + catch (const std::exception &e) + { + reportError( + QObject::tr("failed to start application: %1").arg(e.what())); + return 1; + } + } + + return {}; +} + void CommandLine::clear() { m_vm.clear(); diff --git a/src/commandline.h b/src/commandline.h index 478ee8ea..baa0bfb9 100644 --- a/src/commandline.h +++ b/src/commandline.h @@ -4,6 +4,8 @@ #include #include +class OrganizerCore; + namespace cl { @@ -193,6 +195,18 @@ protected: // if moshortcut:// is detected and has an instance, it will override -i if both // are given // +// +// the command is used in two phases: +// +// 1) run() is called in main() shortly after startup; it parses the command +// line and runs the given command, if any +// +// 2) if the command did not request to exit, the instance is loaded +// in main() et al. and setupCore() is called; it handles moshortcut, +// nxm links and starting executables/binaries +// +// either can return an exit code, which will make MO exit immediately +// class CommandLine { public: @@ -206,6 +220,14 @@ public: // std::optional run(const std::wstring& line); + // handles moshortcut, nxm links and starting processes + // + // returns an empty optional if execution should continue, or a return code + // if MO must quit + // + std::optional setupCore(OrganizerCore& organizer) const; + + // clears parsed options, used when MO is "restarted" so the options aren't // processed again // diff --git a/src/main.cpp b/src/main.cpp index e0326b27..55fedc7a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -115,55 +115,6 @@ void setExceptionHandlers() g_prevTerminateHandler = std::set_terminate(onTerminate); } -std::optional handleCommandLine( - const cl::CommandLine& cl, OrganizerCore& organizer) -{ - // if we have a command line parameter, it is either a nxm link or - // a binary to start - if (cl.shortcut().isValid()) { - if (cl.shortcut().hasExecutable()) { - try { - organizer.processRunner() - .setFromShortcut(cl.shortcut()) - .setWaitForCompletion() - .run(); - - return 0; - } - catch (const std::exception &e) { - reportError( - QObject::tr("failed to start shortcut: %1").arg(e.what())); - return 1; - } - } - } else if (cl.nxmLink()) { - log::debug("starting download from command line: {}", *cl.nxmLink()); - organizer.externalMessage(*cl.nxmLink()); - } else if (cl.executable()) { - const QString exeName = *cl.executable(); - log::debug("starting {} from command line", exeName); - - try - { - // pass the remaining parameters to the binary - organizer.processRunner() - .setFromFileOrExecutable(exeName, cl.untouched()) - .setWaitForCompletion() - .run(); - - return 0; - } - catch (const std::exception &e) - { - reportError( - QObject::tr("failed to start application: %1").arg(e.what())); - return 1; - } - } - - return {}; -} - int runApplication( MOApplication &application, const cl::CommandLine& cl, SingleInstance &instance, const QString &dataPath, @@ -274,7 +225,7 @@ int runApplication( organizer.setCurrentProfile(currentInstance.profileName()); - if (auto r=handleCommandLine(cl, organizer)) { + if (auto r=cl.setupCore(organizer)) { return *r; } -- cgit v1.3.1 From 079fd33cf18cc901d1a6a4463d8a8ccd1d730786 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 7 Nov 2020 17:32:30 -0500 Subject: added sanitychecks.h, moved to namespace sanity added a set SetThisThreadName() after creating the main window, Qt resets it --- src/main.cpp | 22 +++++++++++----------- src/moapplication.cpp | 1 + src/sanitychecks.cpp | 10 ++++++++-- src/sanitychecks.h | 27 +++++++++++++++++++++++++++ 4 files changed, 47 insertions(+), 13 deletions(-) create mode 100644 src/sanitychecks.h (limited to 'src/main.cpp') diff --git a/src/main.cpp b/src/main.cpp index 55fedc7a..40512006 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -32,6 +32,7 @@ along with Mod Organizer. If not, see . #include "env.h" #include "envmodule.h" #include "commandline.h" +#include "sanitychecks.h" #include "shared/util.h" #include "shared/appconfig.h" #include "shared/error_report.h" @@ -41,14 +42,9 @@ along with Mod Organizer. If not, see . #include #include - using namespace MOBase; using namespace MOShared; -void sanityChecks(const env::Environment& env); -int checkIncompatibleModule(const env::Module& m); -int checkPathsForSanity(MOBase::IPluginGame& game, const Settings& s); - void purgeOldFiles() { // remove the temporary backup directory in case we're restarting after an @@ -159,11 +155,11 @@ int runApplication( env::Environment env; env.dump(settings); settings.dump(); - sanityChecks(env); + sanity::checkEnvironment(env); const auto moduleNotification = env.onModuleLoaded(qApp, [](auto&& m) { log::debug("loaded module {}", m.toString()); - checkIncompatibleModule(m); + sanity::checkIncompatibleModule(m); }); // this must outlive `organizer` @@ -204,7 +200,7 @@ int runApplication( log::debug("this is a portable instance"); } - checkPathsForSanity(*currentInstance.gamePlugin(), settings); + sanity::checkPaths(*currentInstance.gamePlugin(), settings); organizer.setManagedGame(currentInstance.gamePlugin()); organizer.createDefaultProfile(); @@ -249,10 +245,14 @@ int runApplication( int res = 1; - { // scope to control lifetime of mainwindow + { + // scope to control lifetime of mainwindow // set up main window and its data structures MainWindow mainWindow(settings, organizer, *pluginContainer); + // qt resets the thread name somewhere when creating the main window + MOShared::SetThisThreadName("main"); + ni.getAccessManager()->setTopLevelWidget(&mainWindow); QObject::connect(&mainWindow, SIGNAL(styleChanged(QString)), &application, @@ -373,6 +373,8 @@ int doOneRun( int main(int argc, char *argv[]) { + MOShared::SetThisThreadName("main"); + cl::CommandLine cl; if (auto r=cl.run(GetCommandLineW())) { @@ -381,8 +383,6 @@ int main(int argc, char *argv[]) TimeThis tt("main() to doOneRun()"); - SetThisThreadName("main"); - initLogging(); auto application = MOApplication::create(argc, argv); diff --git a/src/moapplication.cpp b/src/moapplication.cpp index f514050f..43d787f5 100644 --- a/src/moapplication.cpp +++ b/src/moapplication.cpp @@ -20,6 +20,7 @@ along with Mod Organizer. If not, see . #include "moapplication.h" #include "settings.h" #include "env.h" +#include "shared/util.h" #include #include #include diff --git a/src/sanitychecks.cpp b/src/sanitychecks.cpp index 7afce7bd..613e4b26 100644 --- a/src/sanitychecks.cpp +++ b/src/sanitychecks.cpp @@ -1,3 +1,4 @@ +#include "sanitychecks.h" #include "env.h" #include "envmodule.h" #include "settings.h" @@ -5,6 +6,9 @@ #include #include +namespace sanity +{ + using namespace MOBase; enum class SecurityZone @@ -368,7 +372,7 @@ int checkProtected(const QDir& d, const QString& what) return 0; } -int checkPathsForSanity(IPluginGame& game, const Settings& s) +int checkPaths(IPluginGame& game, const Settings& s) { log::debug("checking paths"); @@ -390,7 +394,7 @@ int checkPathsForSanity(IPluginGame& game, const Settings& s) return n; } -void sanityChecks(const env::Environment& e) +void checkEnvironment(const env::Environment& e) { log::debug("running sanity checks..."); @@ -404,3 +408,5 @@ void sanityChecks(const env::Environment& e) "sanity checks done, {}", (n > 0 ? "problems were found" : "everything looks okay")); } + +} // namespace diff --git a/src/sanitychecks.h b/src/sanitychecks.h new file mode 100644 index 00000000..8786ee78 --- /dev/null +++ b/src/sanitychecks.h @@ -0,0 +1,27 @@ +#ifndef MODORGANIZER_SANITYCHECKS_INCLUDED +#define MODORGANIZER_SANITYCHECKS_INCLUDED + +namespace env +{ + class Environment; + class Module; +} + +namespace MOBase +{ + class IPluginGame; +} + +class Settings; + + +namespace sanity +{ + +void checkEnvironment(const env::Environment& env); +int checkIncompatibleModule(const env::Module& m); +int checkPaths(MOBase::IPluginGame& game, const Settings& s); + +} // namespace + +#endif // MODORGANIZER_SANITYCHECKS_INCLUDED -- cgit v1.3.1 From 1b7384f7cb3dc32063e588c174265b8c46cfa629 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 7 Nov 2020 17:55:25 -0500 Subject: moved most of the stuff from main.cpp into MOApplication --- src/main.cpp | 288 +---------------------------------------------- src/moapplication.cpp | 303 ++++++++++++++++++++++++++++++++++++++++++++++++-- src/moapplication.h | 20 +++- 3 files changed, 315 insertions(+), 296 deletions(-) (limited to 'src/main.cpp') diff --git a/src/main.cpp b/src/main.cpp index 40512006..72d598a8 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -45,21 +45,6 @@ along with Mod Organizer. If not, see . using namespace MOBase; using namespace MOShared; -void purgeOldFiles() -{ - // remove the temporary backup directory in case we're restarting after an - // update - QString backupDirectory = qApp->applicationDirPath() + "/update_backup"; - if (QDir(backupDirectory).exists()) { - shellDelete(QStringList(backupDirectory)); - } - - // cycle log file - removeOldFiles( - qApp->property("dataPath").toString() + "/" + QString::fromStdWString(AppConfig::logPath()), - "usvfs*.log", 5, QDir::Name); -} - thread_local LPTOP_LEVEL_EXCEPTION_FILTER g_prevExceptionFilter = nullptr; thread_local std::terminate_handler g_prevTerminateHandler = nullptr; @@ -111,180 +96,6 @@ void setExceptionHandlers() g_prevTerminateHandler = std::set_terminate(onTerminate); } -int runApplication( - MOApplication &application, const cl::CommandLine& cl, - SingleInstance &instance, const QString &dataPath, - Instance& currentInstance) -{ - TimeThis tt("runApplication() to exec()"); - - log::info( - "starting Mod Organizer version {} revision {} in {}, usvfs: {}", - createVersionInfo().displayString(3), GITID, - QCoreApplication::applicationDirPath(), MOShared::getUsvfsVersionString()); - - log::info("data path: {}", dataPath); - - log::info("working directory: {}", QDir::currentPath()); - - if (!instance.secondary()) { - purgeOldFiles(); - } - - QWindowsWindowFunctions::setWindowActivationBehavior( - QWindowsWindowFunctions::AlwaysActivateWindow); - - try - { - Settings settings( - dataPath + "/" + QString::fromStdWString(AppConfig::iniFileName()), - true); - - log::getDefault().setLevel(settings.diagnostics().logLevel()); - - log::debug("using ini at '{}'", settings.filename()); - - if (instance.secondary()) { - log::debug("another instance of MO is running but --multiple was given"); - } - - // global crashDumpType sits in OrganizerCore to make a bit less ugly to - // update it when the settings are changed during runtime - OrganizerCore::setGlobalCrashDumpsType(settings.diagnostics().crashDumpsType()); - - env::Environment env; - env.dump(settings); - settings.dump(); - sanity::checkEnvironment(env); - - const auto moduleNotification = env.onModuleLoaded(qApp, [](auto&& m) { - log::debug("loaded module {}", m.toString()); - sanity::checkIncompatibleModule(m); - }); - - // this must outlive `organizer` - std::unique_ptr pluginContainer; - - log::debug("initializing nexus interface"); - NexusInterface ni(&settings); - - log::debug("initializing core"); - OrganizerCore organizer(settings); - if (!organizer.bootstrap()) { - reportError("failed to set up data paths"); - InstanceManager::singleton().clearCurrentInstance(); - return 1; - } - - log::debug("initializing plugins"); - pluginContainer = std::make_unique(&organizer); - pluginContainer->loadPlugins(); - - for (;;) - { - const auto setupResult = setupInstance(currentInstance, *pluginContainer); - - if (setupResult == SetupInstanceResults::Okay) { - break; - } else if (setupResult == SetupInstanceResults::TryAgain) { - continue; - } else if (setupResult == SetupInstanceResults::SelectAnother) { - InstanceManager::singleton().clearCurrentInstance(); - return RestartExitCode; - } else { - return 1; - } - } - - if (currentInstance.isPortable()) { - log::debug("this is a portable instance"); - } - - sanity::checkPaths(*currentInstance.gamePlugin(), settings); - - organizer.setManagedGame(currentInstance.gamePlugin()); - organizer.createDefaultProfile(); - - log::info( - "using game plugin '{}' ('{}', variant {}, steam id '{}') at {}", - currentInstance.gamePlugin()->gameName(), - currentInstance.gamePlugin()->gameShortName(), - (settings.game().edition().value_or("").isEmpty() ? - "(none)" : *settings.game().edition()), - currentInstance.gamePlugin()->steamAPPId(), - currentInstance.gamePlugin()->gameDirectory().absolutePath()); - - - CategoryFactory::instance().loadCategories(); - organizer.updateExecutablesList(); - organizer.updateModInfoFromDisc(); - - organizer.setCurrentProfile(currentInstance.profileName()); - - if (auto r=cl.setupCore(organizer)) { - return *r; - } - - MOSplash splash(settings, dataPath, currentInstance.gamePlugin()); - - QString apiKey; - if (GlobalSettings::nexusApiKey(apiKey)) { - ni.getAccessManager()->apiCheck(apiKey); - } - - log::debug("initializing tutorials"); - TutorialManager::init( - qApp->applicationDirPath() + "/" - + QString::fromStdWString(AppConfig::tutorialsPath()) + "/", - &organizer); - - if (!application.setStyleFile(settings.interface().styleName().value_or(""))) { - // disable invalid stylesheet - settings.interface().setStyleName(""); - } - - int res = 1; - - { - // scope to control lifetime of mainwindow - // set up main window and its data structures - MainWindow mainWindow(settings, organizer, *pluginContainer); - - // qt resets the thread name somewhere when creating the main window - MOShared::SetThisThreadName("main"); - - ni.getAccessManager()->setTopLevelWidget(&mainWindow); - - QObject::connect(&mainWindow, SIGNAL(styleChanged(QString)), &application, - SLOT(setStyleFile(QString))); - QObject::connect(&instance, SIGNAL(messageSent(QString)), &organizer, - SLOT(externalMessage(QString))); - - log::debug("displaying main window"); - mainWindow.show(); - mainWindow.activateWindow(); - - splash.close(); - - tt.stop(); - - res = application.exec(); - mainWindow.close(); - - ni.getAccessManager()->setTopLevelWidget(nullptr); - } - - settings.geometry().resetIfNeeded(); - return res; - } - catch (const std::exception &e) - { - reportError(e.what()); - } - - return 1; -} - int forwardToPrimary(SingleInstance& instance, const cl::CommandLine& cl) { if (cl.shortcut().isValid()) { @@ -300,118 +111,25 @@ int forwardToPrimary(SingleInstance& instance, const cl::CommandLine& cl) return 0; } -void resetForRestart(cl::CommandLine& cl) -{ - LogModel::instance().clear(); - ResetExitFlag(); - - // make sure the log file isn't locked in case MO was restarted and - // the previous instance gets deleted - log::getDefault().setFile({}); - - // don't reprocess command line - cl.clear(); -} - -int doOneRun( - cl::CommandLine& cl, MOApplication& application, SingleInstance& instance) -{ - TimeThis tt("doOneRun() to runApplication()"); - - // resets things when MO is "restarted" - resetForRestart(cl); - - auto& m = InstanceManager::singleton(); - auto currentInstance = m.currentInstance(); - - if (!currentInstance) - { - currentInstance = selectInstance(); - if (!currentInstance) { - return 1; - } - } - else - { - if (!QDir(currentInstance->directory()).exists()) { - // the previously used instance doesn't exist anymore - - if (m.hasAnyInstances()) { - MOShared::criticalOnTop(QObject::tr( - "Instance at '%1' not found. Select another instance.") - .arg(currentInstance->directory())); - } else { - MOShared::criticalOnTop(QObject::tr( - "Instance at '%1' not found. You must create a new instance") - .arg(currentInstance->directory())); - } - - currentInstance = selectInstance(); - if (!currentInstance) { - return 1; - } - } - } - - const QString dataPath = currentInstance->directory(); - application.setProperty("dataPath", dataPath); - - setExceptionHandlers(); - - if (!setLogDirectory(dataPath)) { - reportError("Failed to create log folder"); - InstanceManager::singleton().clearCurrentInstance(); - return 1; - } - - log::debug("command line: '{}'", QString::fromWCharArray(GetCommandLineW())); - - tt.stop(); - - return runApplication(application, cl, instance, dataPath, *currentInstance); -} int main(int argc, char *argv[]) { MOShared::SetThisThreadName("main"); cl::CommandLine cl; - if (auto r=cl.run(GetCommandLineW())) { return *r; } - TimeThis tt("main() to doOneRun()"); - initLogging(); - auto application = MOApplication::create(argc, argv); + QApplication::setAttribute(Qt::AA_EnableHighDpiScaling); + MOApplication app(cl, argc, argv); SingleInstance instance(cl.multiple()); if (instance.ephemeral()) { return forwardToPrimary(instance, cl); } - tt.stop(); - - if (cl.instance()) - InstanceManager::singleton().overrideInstance(*cl.instance()); - - if (cl.profile()) { - InstanceManager::singleton().overrideProfile(*cl.profile()); - } - - // makes plugin data path available to plugins, see - // IOrganizer::getPluginDataPath() - MOBase::details::setPluginDataPath(OrganizerCore::pluginDataPath()); - - for (;;) - { - const auto r = doOneRun(cl, application, instance); - if (r == RestartExitCode) { - continue; - } - - return r; - } + return app.run(instance); } diff --git a/src/moapplication.cpp b/src/moapplication.cpp index 43d787f5..b4ba7da6 100644 --- a/src/moapplication.cpp +++ b/src/moapplication.cpp @@ -20,6 +20,18 @@ along with Mod Organizer. If not, see . #include "moapplication.h" #include "settings.h" #include "env.h" +#include "commandline.h" +#include "instancemanager.h" +#include "organizercore.h" +#include "thread_utils.h" +#include "loglist.h" +#include "singleinstance.h" +#include "nexusinterface.h" +#include "nxmaccessmanager.h" +#include "tutorialmanager.h" +#include "sanitychecks.h" +#include "mainwindow.h" +#include "shared/error_report.h" #include "shared/util.h" #include #include @@ -42,7 +54,7 @@ along with Mod Organizer. If not, see . "type='win32' \"") using namespace MOBase; - +using namespace MOShared; class ProxyStyle : public QProxyStyle { public: @@ -120,8 +132,8 @@ void addDllsToPath() } -MOApplication::MOApplication(int& argc, char** argv) - : QApplication(argc, argv) +MOApplication::MOApplication(cl::CommandLine& cl, int& argc, char** argv) + : QApplication(argc, argv), m_cl(cl) { connect(&m_StyleWatcher, &QFileSystemWatcher::fileChanged, [&](auto&& file){ log::debug("style file '{}' changed, reloading", file); @@ -133,11 +145,288 @@ MOApplication::MOApplication(int& argc, char** argv) addDllsToPath(); } -MOApplication MOApplication::create(int& argc, char** argv) +int MOApplication::run(SingleInstance& singleInstance) +{ + auto& m = InstanceManager::singleton(); + + if (m_cl.instance()) + m.overrideInstance(*m_cl.instance()); + + if (m_cl.profile()) { + m.overrideProfile(*m_cl.profile()); + } + + // makes plugin data path available to plugins, see + // IOrganizer::getPluginDataPath() + MOBase::details::setPluginDataPath(OrganizerCore::pluginDataPath()); + + for (;;) + { + const auto r = doOneRun(singleInstance); + if (r == RestartExitCode) { + continue; + } + + return r; + } +} + +int MOApplication::doOneRun(SingleInstance& singleInstance) { - QApplication::setAttribute(Qt::AA_EnableHighDpiScaling); + TimeThis tt("doOneRun() to runApplication()"); + + // resets things when MO is "restarted" + resetForRestart(); + + auto& m = InstanceManager::singleton(); + auto currentInstance = m.currentInstance(); + + if (!currentInstance) + { + currentInstance = selectInstance(); + if (!currentInstance) { + return 1; + } + } + else + { + if (!QDir(currentInstance->directory()).exists()) { + // the previously used instance doesn't exist anymore + + if (m.hasAnyInstances()) { + MOShared::criticalOnTop(QObject::tr( + "Instance at '%1' not found. Select another instance.") + .arg(currentInstance->directory())); + } else { + MOShared::criticalOnTop(QObject::tr( + "Instance at '%1' not found. You must create a new instance") + .arg(currentInstance->directory())); + } + + currentInstance = selectInstance(); + if (!currentInstance) { + return 1; + } + } + } + + const QString dataPath = currentInstance->directory(); + setProperty("dataPath", dataPath); + + setExceptionHandlers(); + + if (!setLogDirectory(dataPath)) { + reportError("Failed to create log folder"); + InstanceManager::singleton().clearCurrentInstance(); + return 1; + } + + log::debug("command line: '{}'", QString::fromWCharArray(GetCommandLineW())); + + tt.stop(); - return MOApplication(argc, argv); + return runApplication(singleInstance, dataPath, *currentInstance); +} + +int MOApplication::runApplication( + SingleInstance& singleInstance, + const QString &dataPath, Instance& currentInstance) +{ + TimeThis tt("runApplication() to exec()"); + + log::info( + "starting Mod Organizer version {} revision {} in {}, usvfs: {}", + createVersionInfo().displayString(3), GITID, + QCoreApplication::applicationDirPath(), MOShared::getUsvfsVersionString()); + + log::info("data path: {}", dataPath); + + log::info("working directory: {}", QDir::currentPath()); + + if (!singleInstance.secondary()) { + purgeOldFiles(); + } + + QWindowsWindowFunctions::setWindowActivationBehavior( + QWindowsWindowFunctions::AlwaysActivateWindow); + + try + { + Settings settings( + dataPath + "/" + QString::fromStdWString(AppConfig::iniFileName()), + true); + + log::getDefault().setLevel(settings.diagnostics().logLevel()); + + log::debug("using ini at '{}'", settings.filename()); + + if (singleInstance.secondary()) { + log::debug("another instance of MO is running but --multiple was given"); + } + + // global crashDumpType sits in OrganizerCore to make a bit less ugly to + // update it when the settings are changed during runtime + OrganizerCore::setGlobalCrashDumpsType(settings.diagnostics().crashDumpsType()); + + env::Environment env; + env.dump(settings); + settings.dump(); + sanity::checkEnvironment(env); + + const auto moduleNotification = env.onModuleLoaded(qApp, [](auto&& m) { + log::debug("loaded module {}", m.toString()); + sanity::checkIncompatibleModule(m); + }); + + // this must outlive `organizer` + std::unique_ptr pluginContainer; + + log::debug("initializing nexus interface"); + NexusInterface ni(&settings); + + log::debug("initializing core"); + OrganizerCore organizer(settings); + if (!organizer.bootstrap()) { + reportError("failed to set up data paths"); + InstanceManager::singleton().clearCurrentInstance(); + return 1; + } + + log::debug("initializing plugins"); + pluginContainer = std::make_unique(&organizer); + pluginContainer->loadPlugins(); + + for (;;) + { + const auto setupResult = setupInstance(currentInstance, *pluginContainer); + + if (setupResult == SetupInstanceResults::Okay) { + break; + } else if (setupResult == SetupInstanceResults::TryAgain) { + continue; + } else if (setupResult == SetupInstanceResults::SelectAnother) { + InstanceManager::singleton().clearCurrentInstance(); + return RestartExitCode; + } else { + return 1; + } + } + + if (currentInstance.isPortable()) { + log::debug("this is a portable instance"); + } + + sanity::checkPaths(*currentInstance.gamePlugin(), settings); + + organizer.setManagedGame(currentInstance.gamePlugin()); + organizer.createDefaultProfile(); + + log::info( + "using game plugin '{}' ('{}', variant {}, steam id '{}') at {}", + currentInstance.gamePlugin()->gameName(), + currentInstance.gamePlugin()->gameShortName(), + (settings.game().edition().value_or("").isEmpty() ? + "(none)" : *settings.game().edition()), + currentInstance.gamePlugin()->steamAPPId(), + currentInstance.gamePlugin()->gameDirectory().absolutePath()); + + + CategoryFactory::instance().loadCategories(); + organizer.updateExecutablesList(); + organizer.updateModInfoFromDisc(); + + organizer.setCurrentProfile(currentInstance.profileName()); + + if (auto r=m_cl.setupCore(organizer)) { + return *r; + } + + MOSplash splash(settings, dataPath, currentInstance.gamePlugin()); + + QString apiKey; + if (GlobalSettings::nexusApiKey(apiKey)) { + ni.getAccessManager()->apiCheck(apiKey); + } + + log::debug("initializing tutorials"); + TutorialManager::init( + qApp->applicationDirPath() + "/" + + QString::fromStdWString(AppConfig::tutorialsPath()) + "/", + &organizer); + + if (!setStyleFile(settings.interface().styleName().value_or(""))) { + // disable invalid stylesheet + settings.interface().setStyleName(""); + } + + int res = 1; + + { + // scope to control lifetime of mainwindow + // set up main window and its data structures + MainWindow mainWindow(settings, organizer, *pluginContainer); + + // qt resets the thread name somewhere when creating the main window + MOShared::SetThisThreadName("main"); + + ni.getAccessManager()->setTopLevelWidget(&mainWindow); + + QObject::connect(&mainWindow, SIGNAL(styleChanged(QString)), this, + SLOT(setStyleFile(QString))); + QObject::connect(&singleInstance, SIGNAL(messageSent(QString)), &organizer, + SLOT(externalMessage(QString))); + + log::debug("displaying main window"); + mainWindow.show(); + mainWindow.activateWindow(); + + splash.close(); + + tt.stop(); + + res = exec(); + mainWindow.close(); + + ni.getAccessManager()->setTopLevelWidget(nullptr); + } + + settings.geometry().resetIfNeeded(); + return res; + } + catch (const std::exception &e) + { + reportError(e.what()); + } + + return 1; +} + +void MOApplication::purgeOldFiles() +{ + // remove the temporary backup directory in case we're restarting after an + // update + QString backupDirectory = qApp->applicationDirPath() + "/update_backup"; + if (QDir(backupDirectory).exists()) { + shellDelete(QStringList(backupDirectory)); + } + + // cycle log file + removeOldFiles( + qApp->property("dataPath").toString() + "/" + QString::fromStdWString(AppConfig::logPath()), + "usvfs*.log", 5, QDir::Name); +} + +void MOApplication::resetForRestart() +{ + LogModel::instance().clear(); + ResetExitFlag(); + + // make sure the log file isn't locked in case MO was restarted and + // the previous instance gets deleted + log::getDefault().setFile({}); + + // don't reprocess command line + m_cl.clear(); } bool MOApplication::setStyleFile(const QString& styleName) @@ -163,7 +452,6 @@ bool MOApplication::setStyleFile(const QString& styleName) return true; } - bool MOApplication::notify(QObject* receiver, QEvent* event) { try { @@ -183,7 +471,6 @@ bool MOApplication::notify(QObject* receiver, QEvent* event) } } - void MOApplication::updateStyle(const QString& fileName) { if (QStyleFactory::keys().contains(fileName)) { diff --git a/src/moapplication.h b/src/moapplication.h index 0ee04492..c1512d9b 100644 --- a/src/moapplication.h +++ b/src/moapplication.h @@ -24,15 +24,21 @@ along with Mod Organizer. If not, see . #include class Settings; +class SingleInstance; +class Instance; + namespace MOBase { class IPluginGame; } +namespace cl { class CommandLine; } class MOApplication : public QApplication { Q_OBJECT public: - static MOApplication create(int& argc, char** argv); - virtual bool notify (QObject* receiver, QEvent* event); + MOApplication(cl::CommandLine& cl, int& argc, char** argv); + + int run(SingleInstance& si); + virtual bool notify(QObject* receiver, QEvent* event); public slots: bool setStyleFile(const QString& style); @@ -43,8 +49,16 @@ private slots: private: QFileSystemWatcher m_StyleWatcher; QString m_DefaultStyle; + cl::CommandLine& m_cl; + + int doOneRun(SingleInstance& singleInstance); + + int runApplication( + SingleInstance& singleInstance, + const QString &dataPath, Instance& currentInstance); - MOApplication(int& argc, char** argv); + void purgeOldFiles(); + void resetForRestart(); }; -- cgit v1.3.1 From 98b782caff4d1b5c316f5773d7a07494912bb2d8 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 7 Nov 2020 17:58:07 -0500 Subject: reordered functions, removed unused headers --- src/main.cpp | 113 +++++++++++++++++++++-------------------------------------- 1 file changed, 39 insertions(+), 74 deletions(-) (limited to 'src/main.cpp') diff --git a/src/main.cpp b/src/main.cpp index 72d598a8..a2e16cc0 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,53 +1,56 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free 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. - -Mod Organizer 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 Mod Organizer. If not, see . -*/ - -#include "mainwindow.h" #include "singleinstance.h" #include "loglist.h" -#include "selectiondialog.h" #include "moapplication.h" -#include "tutorialmanager.h" -#include "nxmaccessmanager.h" -#include "instancemanager.h" -#include "instancemanagerdialog.h" -#include "createinstancedialog.h" -#include "createinstancedialogpages.h" #include "organizercore.h" -#include "env.h" -#include "envmodule.h" #include "commandline.h" -#include "sanitychecks.h" #include "shared/util.h" -#include "shared/appconfig.h" -#include "shared/error_report.h" -#include -#include #include #include -#include using namespace MOBase; -using namespace MOShared; thread_local LPTOP_LEVEL_EXCEPTION_FILTER g_prevExceptionFilter = nullptr; thread_local std::terminate_handler g_prevTerminateHandler = nullptr; +int forwardToPrimary(SingleInstance& instance, const cl::CommandLine& cl); + +int main(int argc, char *argv[]) +{ + MOShared::SetThisThreadName("main"); + + cl::CommandLine cl; + if (auto r=cl.run(GetCommandLineW())) { + return *r; + } + + initLogging(); + + QApplication::setAttribute(Qt::AA_EnableHighDpiScaling); + MOApplication app(cl, argc, argv); + + SingleInstance instance(cl.multiple()); + if (instance.ephemeral()) { + return forwardToPrimary(instance, cl); + } + + return app.run(instance); +} + +int forwardToPrimary(SingleInstance& instance, const cl::CommandLine& cl) +{ + if (cl.shortcut().isValid()) { + instance.sendMessage(cl.shortcut().toString()); + } else if (cl.nxmLink()) { + instance.sendMessage(*cl.nxmLink()); + } else { + QMessageBox::information( + nullptr, QObject::tr("Mod Organizer"), + QObject::tr("An instance of Mod Organizer is already running")); + } + + return 0; +} + LONG WINAPI onUnhandledException(_EXCEPTION_POINTERS* ptrs) { const std::wstring& dumpPath = OrganizerCore::crashDumpsPath(); @@ -95,41 +98,3 @@ void setExceptionHandlers() g_prevExceptionFilter = SetUnhandledExceptionFilter(onUnhandledException); g_prevTerminateHandler = std::set_terminate(onTerminate); } - -int forwardToPrimary(SingleInstance& instance, const cl::CommandLine& cl) -{ - if (cl.shortcut().isValid()) { - instance.sendMessage(cl.shortcut().toString()); - } else if (cl.nxmLink()) { - instance.sendMessage(*cl.nxmLink()); - } else { - QMessageBox::information( - nullptr, QObject::tr("Mod Organizer"), - QObject::tr("An instance of Mod Organizer is already running")); - } - - return 0; -} - - -int main(int argc, char *argv[]) -{ - MOShared::SetThisThreadName("main"); - - cl::CommandLine cl; - if (auto r=cl.run(GetCommandLineW())) { - return *r; - } - - initLogging(); - - QApplication::setAttribute(Qt::AA_EnableHighDpiScaling); - MOApplication app(cl, argc, argv); - - SingleInstance instance(cl.multiple()); - if (instance.ephemeral()) { - return forwardToPrimary(instance, cl); - } - - return app.run(instance); -} -- cgit v1.3.1 From 08c952e53a4efcd5b50c0ec947bf216101c027ef Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 7 Nov 2020 19:05:15 -0500 Subject: stopped using core dump stuff from usvfs, mo has its own set exception handler at the start, it can handle not having qt or data paths hopefully fixed infinite crash dumps --- src/CMakeLists.txt | 1 + src/env.cpp | 27 ++++++++++++++-------- src/env.h | 21 ----------------- src/envdump.h | 30 ++++++++++++++++++++++++ src/main.cpp | 27 ++++++++++++++-------- src/mainwindow.cpp | 4 ++-- src/moapplication.cpp | 4 +--- src/organizercore.cpp | 48 +++++++++++++++++++++++---------------- src/organizercore.h | 13 +++++------ src/settings.cpp | 12 +++++----- src/settings.h | 9 ++++---- src/settingsdialogdiagnostics.cpp | 20 ++++++++-------- src/usvfsconnector.cpp | 33 +++++++++++++++------------ src/usvfsconnector.h | 3 ++- 14 files changed, 146 insertions(+), 106 deletions(-) create mode 100644 src/envdump.h (limited to 'src/main.cpp') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index eec33460..30614e59 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -70,6 +70,7 @@ add_filter(NAME src/downloads GROUPS add_filter(NAME src/env GROUPS env + envdump envfs envmetrics envmodule diff --git a/src/env.cpp b/src/env.cpp index 5bed84c1..98d2e6ea 100644 --- a/src/env.cpp +++ b/src/env.cpp @@ -4,6 +4,7 @@ #include "envsecurity.h" #include "envshortcut.h" #include "envwindows.h" +#include "envdump.h" #include "settings.h" #include #include @@ -1182,8 +1183,16 @@ HandlePtr tempFile(const std::wstring dir) return {}; } -HandlePtr dumpFile() +HandlePtr dumpFile(const wchar_t* dir) { + // try the given directory, if any + if (dir) { + HandlePtr h = tempFile(dir); + if (h.get() != INVALID_HANDLE_VALUE) { + return h; + } + } + // try the current directory HandlePtr h = tempFile(L"."); if (h.get() != INVALID_HANDLE_VALUE) { @@ -1193,10 +1202,10 @@ HandlePtr dumpFile() std::wclog << L"cannot write dump file in current directory\n"; // try the temp directory - const auto dir = tempDir(); + const auto temp = tempDir(); - if (!dir.empty()) { - h = tempFile(dir.c_str()); + if (!temp.empty()) { + h = tempFile(temp.c_str()); if (h.get() != INVALID_HANDLE_VALUE) { return h; } @@ -1235,11 +1244,11 @@ std::string toString(CoreDumpTypes type) } -bool createMiniDump(HANDLE process, CoreDumpTypes type) +bool createMiniDump(const wchar_t* dir, HANDLE process, CoreDumpTypes type) { const DWORD pid = GetProcessId(process); - const HandlePtr file = dumpFile(); + const HandlePtr file = dumpFile(dir); if (!file) { std::wcerr << L"nowhere to write the dump file\n"; return false; @@ -1278,10 +1287,10 @@ bool createMiniDump(HANDLE process, CoreDumpTypes type) } -bool coredump(CoreDumpTypes type) +bool coredump(const wchar_t* dir, CoreDumpTypes type) { std::wclog << L"creating minidump for the current process\n"; - return createMiniDump(GetCurrentProcess(), type); + return createMiniDump(dir, GetCurrentProcess(), type); } bool coredumpOther(CoreDumpTypes type) @@ -1309,7 +1318,7 @@ bool coredumpOther(CoreDumpTypes type) return false; } - return createMiniDump(handle.get(), type); + return createMiniDump(nullptr, handle.get(), type); } } // namespace diff --git a/src/env.h b/src/env.h index dbbd5cf4..e9cb7a36 100644 --- a/src/env.h +++ b/src/env.h @@ -309,27 +309,6 @@ bool registryValueExists(const QString& key, const QString& value); // void deleteRegistryKeyIfEmpty(const QString& name); - -enum class CoreDumpTypes -{ - Mini = 1, - Data, - Full -}; - - -CoreDumpTypes coreDumpTypeFromString(const std::string& s); -std::string toString(CoreDumpTypes type); - -// creates a minidump file for this process -// -bool coredump(CoreDumpTypes type); - -// finds another process with the same name as this one and creates a minidump -// file for it -// -bool coredumpOther(CoreDumpTypes type); - } // namespace env #endif // ENV_ENV_H diff --git a/src/envdump.h b/src/envdump.h new file mode 100644 index 00000000..355df783 --- /dev/null +++ b/src/envdump.h @@ -0,0 +1,30 @@ +#ifndef MODORGANIZER_ENVDUMP_INCLUDED +#define MODORGANIZER_ENVDUMP_INCLUDED + +namespace env +{ + +enum class CoreDumpTypes +{ + None, + Mini, + Data, + Full +}; + + +CoreDumpTypes coreDumpTypeFromString(const std::string& s); +std::string toString(CoreDumpTypes type); + +// creates a minidump file for this process +// +bool coredump(const wchar_t* dir, CoreDumpTypes type); + +// finds another process with the same name as this one and creates a minidump +// file for it +// +bool coredumpOther(CoreDumpTypes type); + +} // namespace + +#endif // MODORGANIZER_ENVDUMP_INCLUDED diff --git a/src/main.cpp b/src/main.cpp index a2e16cc0..ad074501 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -3,8 +3,9 @@ #include "moapplication.h" #include "organizercore.h" #include "commandline.h" +#include "env.h" +#include "thread_utils.h" #include "shared/util.h" -#include #include using namespace MOBase; @@ -17,6 +18,7 @@ int forwardToPrimary(SingleInstance& instance, const cl::CommandLine& cl); int main(int argc, char *argv[]) { MOShared::SetThisThreadName("main"); + setExceptionHandlers(); cl::CommandLine cl; if (auto r=cl.run(GetCommandLineW())) { @@ -53,20 +55,20 @@ int forwardToPrimary(SingleInstance& instance, const cl::CommandLine& cl) LONG WINAPI onUnhandledException(_EXCEPTION_POINTERS* ptrs) { - const std::wstring& dumpPath = OrganizerCore::crashDumpsPath(); + const auto path = OrganizerCore::getGlobalCoreDumpPath(); + const auto type = OrganizerCore::getGlobalCoreDumpType(); - const int r = CreateMiniDump( - ptrs, OrganizerCore::getGlobalCrashDumpsType(), dumpPath.c_str()); + const auto r = env::coredump(path.empty() ? nullptr : path.c_str(), type); - if (r == 0) { - log::error("ModOrganizer has crashed, crash dump created."); + if (r) { + log::error("ModOrganizer has crashed, core dump created."); } else { - log::error( - "ModOrganizer has crashed, CreateMiniDump failed ({}, error {}).", - r, GetLastError()); + log::error("ModOrganizer has crashed, core dump failed"); } - if (g_prevExceptionFilter && ptrs) + // g_prevExceptionFilter somehow sometimes point to this function, making this + // recurse and create hundreds of core dump, not sure why + if (g_prevExceptionFilter && ptrs && g_prevExceptionFilter != onUnhandledException) return g_prevExceptionFilter(ptrs); else return EXCEPTION_CONTINUE_SEARCH; @@ -95,6 +97,11 @@ void onTerminate() noexcept void setExceptionHandlers() { + if (g_prevExceptionFilter) { + // already called + return; + } + g_prevExceptionFilter = SetUnhandledExceptionFilter(onUnhandledException); g_prevTerminateHandler = std::set_terminate(onTerminate); } diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index ea8a0efe..41958b80 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -5084,7 +5084,7 @@ void MainWindow::on_actionSettings_triggered() bool proxy = settings.network().useProxy(); DownloadManager *dlManager = m_OrganizerCore.downloadManager(); const bool oldCheckForUpdates = settings.checkForUpdates(); - const int oldMaxDumps = settings.diagnostics().crashDumpsMax(); + const int oldMaxDumps = settings.diagnostics().maxCoreDumps(); SettingsDialog dialog(&m_PluginContainer, settings, this); @@ -5171,7 +5171,7 @@ void MainWindow::on_actionSettings_triggered() m_OrganizerCore.setLogLevel(settings.diagnostics().logLevel()); - if (settings.diagnostics().crashDumpsMax() != oldMaxDumps) { + if (settings.diagnostics().maxCoreDumps() != oldMaxDumps) { m_OrganizerCore.cycleDiagnostics(); } diff --git a/src/moapplication.cpp b/src/moapplication.cpp index 24cafd05..bb7b4922 100644 --- a/src/moapplication.cpp +++ b/src/moapplication.cpp @@ -188,8 +188,6 @@ int MOApplication::doOneRun(SingleInstance& singleInstance) const QString dataPath = currentInstance->directory(); setProperty("dataPath", dataPath); - setExceptionHandlers(); - if (!setLogDirectory(dataPath)) { reportError("Failed to create log folder"); InstanceManager::singleton().clearCurrentInstance(); @@ -272,7 +270,7 @@ int MOApplication::runApplication( // global crashDumpType sits in OrganizerCore to make a bit less ugly to // update it when the settings are changed during runtime - OrganizerCore::setGlobalCrashDumpsType(settings.diagnostics().crashDumpsType()); + OrganizerCore::setGlobalCoreDumpType(settings.diagnostics().coreDumpType()); env::Environment env; env.dump(settings); diff --git a/src/organizercore.cpp b/src/organizercore.cpp index d01aab96..f57903e2 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -76,8 +76,7 @@ using namespace MOShared; using namespace MOBase; -//static -CrashDumpsType OrganizerCore::m_globalCrashDumpsType = CrashDumpsType::None; +static env::CoreDumpTypes g_coreDumpType = env::CoreDumpTypes::Mini; template QStringList toStringList(InputIterator current, InputIterator end) @@ -478,7 +477,7 @@ bool OrganizerCore::bootstrap() m_Settings.paths().mods(), m_Settings.paths().downloads(), m_Settings.paths().overwrite(), - QString::fromStdWString(crashDumpsPath()) + QString::fromStdWString(getGlobalCoreDumpPath()) }; for (auto&& dir : dirs) { @@ -497,14 +496,14 @@ bool OrganizerCore::bootstrap() // log if there are any dmp files const auto hasCrashDumps = - !QDir(QString::fromStdWString(crashDumpsPath())) + !QDir(QString::fromStdWString(getGlobalCoreDumpPath())) .entryList({"*.dmp"}, QDir::Files) .empty(); if (hasCrashDumps) { log::debug( "there are crash dumps in '{}'", - QString::fromStdWString(crashDumpsPath())); + QString::fromStdWString(getGlobalCoreDumpPath())); } return true; @@ -528,13 +527,14 @@ void OrganizerCore::prepareVFS() } void OrganizerCore::updateVFSParams( - log::Levels logLevel, CrashDumpsType crashDumpsType, + log::Levels logLevel, env::CoreDumpTypes coreDumpType, const QString& crashDumpsPath, std::chrono::seconds spawnDelay, QString executableBlacklist) { - setGlobalCrashDumpsType(crashDumpsType); + setGlobalCoreDumpType(coreDumpType); + m_USVFS.updateParams( - logLevel, crashDumpsType, crashDumpsPath, spawnDelay, executableBlacklist); + logLevel, coreDumpType, crashDumpsPath, spawnDelay, executableBlacklist); } void OrganizerCore::setLogLevel(log::Levels level) @@ -543,8 +543,8 @@ void OrganizerCore::setLogLevel(log::Levels level) updateVFSParams( m_Settings.diagnostics().logLevel(), - m_Settings.diagnostics().crashDumpsType(), - QString::fromStdWString(crashDumpsPath()), + m_Settings.diagnostics().coreDumpType(), + QString::fromStdWString(getGlobalCoreDumpPath()), m_Settings.diagnostics().spawnDelay(), m_Settings.executablesBlacklist()); @@ -553,8 +553,8 @@ void OrganizerCore::setLogLevel(log::Levels level) bool OrganizerCore::cycleDiagnostics() { - const auto maxDumps = settings().diagnostics().crashDumpsMax(); - const auto path = QString::fromStdWString(crashDumpsPath()); + const auto maxDumps = settings().diagnostics().maxCoreDumps(); + const auto path = QString::fromStdWString(getGlobalCoreDumpPath()); if (maxDumps > 0) { removeOldFiles(path, "*.dmp", maxDumps, QDir::Time|QDir::Reversed); @@ -563,16 +563,26 @@ bool OrganizerCore::cycleDiagnostics() return true; } -void OrganizerCore::setGlobalCrashDumpsType(CrashDumpsType type) +env::CoreDumpTypes OrganizerCore::getGlobalCoreDumpType() +{ + return g_coreDumpType; +} + +void OrganizerCore::setGlobalCoreDumpType(env::CoreDumpTypes type) { - m_globalCrashDumpsType = type; + g_coreDumpType = type; } -std::wstring OrganizerCore::crashDumpsPath() { - return ( - qApp->property("dataPath").toString() + "/" - + QString::fromStdWString(AppConfig::dumpsDir()) - ).toStdWString(); +std::wstring OrganizerCore::getGlobalCoreDumpPath() +{ + if (qApp) { + const auto dp = qApp->property("dataPath"); + if (!dp.isNull()) { + return dp.toString().toStdWString() + L"/" + AppConfig::dumpsDir(); + } + } + + return {}; } void OrganizerCore::setCurrentProfile(const QString &profileName) diff --git a/src/organizercore.h b/src/organizercore.h index 80b89a43..b566e626 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -13,6 +13,7 @@ #include "moshortcut.h" #include "processrunner.h" #include "uilocker.h" +#include "envdump.h" #include #include #include @@ -277,17 +278,17 @@ public: void prepareVFS(); void updateVFSParams( - MOBase::log::Levels logLevel, CrashDumpsType crashDumpsType, - const QString& crashDumpsPath, std::chrono::seconds spawnDelay, + MOBase::log::Levels logLevel, env::CoreDumpTypes coreDumpType, + const QString& coreDumpsPath, std::chrono::seconds spawnDelay, QString executableBlacklist); void setLogLevel(MOBase::log::Levels level); bool cycleDiagnostics(); - static CrashDumpsType getGlobalCrashDumpsType() { return m_globalCrashDumpsType; } - static void setGlobalCrashDumpsType(CrashDumpsType crashDumpsType); - static std::wstring crashDumpsPath(); + static env::CoreDumpTypes getGlobalCoreDumpType(); + static void setGlobalCoreDumpType(env::CoreDumpTypes type); + static std::wstring getGlobalCoreDumpPath(); /** * @brief Returns the name of all the mods in the priority order of the given profile. @@ -487,8 +488,6 @@ private: UsvfsConnector m_USVFS; UILocker m_UILocker; - - static CrashDumpsType m_globalCrashDumpsType; }; #endif // ORGANIZERCORE_H diff --git a/src/settings.cpp b/src/settings.cpp index 99b41e1f..a8ada6ba 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -2110,23 +2110,23 @@ void DiagnosticsSettings::setLootLogLevel(lootcli::LogLevels level) set(m_Settings, "Settings", "loot_log_level", level); } -CrashDumpsType DiagnosticsSettings::crashDumpsType() const +env::CoreDumpTypes DiagnosticsSettings::coreDumpType() const { - return get(m_Settings, - "Settings", "crash_dumps_type", CrashDumpsType::Mini); + return get(m_Settings, + "Settings", "crash_dumps_type", env::CoreDumpTypes::Mini); } -void DiagnosticsSettings::setCrashDumpsType(CrashDumpsType type) +void DiagnosticsSettings::setCoreDumpType(env::CoreDumpTypes type) { set(m_Settings, "Settings", "crash_dumps_type", type); } -int DiagnosticsSettings::crashDumpsMax() const +int DiagnosticsSettings::maxCoreDumps() const { return get(m_Settings, "Settings", "crash_dumps_max", 5); } -void DiagnosticsSettings::setCrashDumpsMax(int n) +void DiagnosticsSettings::setMaxCoreDumps(int n) { set(m_Settings, "Settings", "crash_dumps_max", n); } diff --git a/src/settings.h b/src/settings.h index fc7789f0..df081c5c 100644 --- a/src/settings.h +++ b/src/settings.h @@ -21,6 +21,7 @@ along with Mod Organizer. If not, see . #define SETTINGS_H #include "loadmechanism.h" +#include "envdump.h" #include #include #include @@ -651,13 +652,13 @@ public: // crash dump type for both MO and usvfs // - CrashDumpsType crashDumpsType() const; - void setCrashDumpsType(CrashDumpsType type); + env::CoreDumpTypes coreDumpType() const; + void setCoreDumpType(env::CoreDumpTypes type); // maximum number of dump files keps, for both MO and usvfs // - int crashDumpsMax() const; - void setCrashDumpsMax(int n); + int maxCoreDumps() const; + void setMaxCoreDumps(int n); std::chrono::seconds spawnDelay() const; void setSpawnDelay(std::chrono::seconds t); diff --git a/src/settingsdialogdiagnostics.cpp b/src/settingsdialogdiagnostics.cpp index 4ade6900..33175a66 100644 --- a/src/settingsdialogdiagnostics.cpp +++ b/src/settingsdialogdiagnostics.cpp @@ -13,7 +13,7 @@ DiagnosticsSettingsTab::DiagnosticsSettingsTab(Settings& s, SettingsDialog& d) setLootLogLevel(); setCrashDumpTypesBox(); - ui->dumpsMaxEdit->setValue(settings().diagnostics().crashDumpsMax()); + ui->dumpsMaxEdit->setValue(settings().diagnostics().maxCoreDumps()); QString logsPath = qApp->property("dataPath").toString() + "/" + QString::fromStdWString(AppConfig::logPath()); @@ -22,7 +22,7 @@ DiagnosticsSettingsTab::DiagnosticsSettingsTab(Settings& s, SettingsDialog& d) ui->diagnosticsExplainedLabel->text() .replace("LOGS_FULL_PATH", logsPath) .replace("LOGS_DIR", QString::fromStdWString(AppConfig::logPath())) - .replace("DUMPS_FULL_PATH", QString::fromStdWString(OrganizerCore::crashDumpsPath())) + .replace("DUMPS_FULL_PATH", QString::fromStdWString(OrganizerCore::getGlobalCoreDumpPath())) .replace("DUMPS_DIR", QString::fromStdWString(AppConfig::dumpsDir())) ); } @@ -78,13 +78,13 @@ void DiagnosticsSettingsTab::setCrashDumpTypesBox() ui->dumpsTypeBox->addItem(text, static_cast(type)); }; - add(QObject::tr("None"), CrashDumpsType::None); - add(QObject::tr("Mini (recommended)"), CrashDumpsType::Mini); - add(QObject::tr("Data"), CrashDumpsType::Data); - add(QObject::tr("Full"), CrashDumpsType::Full); + add(QObject::tr("None"), env::CoreDumpTypes::None); + add(QObject::tr("Mini (recommended)"), env::CoreDumpTypes::Mini); + add(QObject::tr("Data"), env::CoreDumpTypes::Data); + add(QObject::tr("Full"), env::CoreDumpTypes::Full); const auto current = static_cast( - settings().diagnostics().crashDumpsType()); + settings().diagnostics().coreDumpType()); for (int i=0; idumpsTypeBox->count(); ++i) { if (ui->dumpsTypeBox->itemData(i) == current) { @@ -99,10 +99,10 @@ void DiagnosticsSettingsTab::update() settings().diagnostics().setLogLevel( static_cast(ui->logLevelBox->currentData().toInt())); - settings().diagnostics().setCrashDumpsType( - static_cast(ui->dumpsTypeBox->currentData().toInt())); + settings().diagnostics().setCoreDumpType( + static_cast(ui->dumpsTypeBox->currentData().toInt())); - settings().diagnostics().setCrashDumpsMax(ui->dumpsMaxEdit->value()); + settings().diagnostics().setMaxCoreDumps(ui->dumpsMaxEdit->value()); settings().diagnostics().setLootLogLevel( static_cast(ui->lootLogLevel->currentData().toInt())); diff --git a/src/usvfsconnector.cpp b/src/usvfsconnector.cpp index ed6e31ce..c8f2f1c4 100644 --- a/src/usvfsconnector.cpp +++ b/src/usvfsconnector.cpp @@ -107,17 +107,22 @@ LogLevel toUsvfsLogLevel(log::Levels level) } } -CrashDumpsType crashDumpsType(int type) +CrashDumpsType toUsvfsCrashDumpsType(env::CoreDumpTypes type) { - switch (static_cast(type)) { - case CrashDumpsType::Mini: - return CrashDumpsType::Mini; - case CrashDumpsType::Data: - return CrashDumpsType::Data; - case CrashDumpsType::Full: - return CrashDumpsType::Full; - default: - return CrashDumpsType::None; + switch (type) + { + case env::CoreDumpTypes::None: + return CrashDumpsType::None; + + case env::CoreDumpTypes::Data: + return CrashDumpsType::Data; + + case env::CoreDumpTypes::Full: + return CrashDumpsType::Full; + + case env::CoreDumpTypes::Mini: + default: + return CrashDumpsType::Mini; } } @@ -128,9 +133,9 @@ UsvfsConnector::UsvfsConnector() const auto& s = Settings::instance(); const LogLevel logLevel = toUsvfsLogLevel(s.diagnostics().logLevel()); - const CrashDumpsType dumpType = s.diagnostics().crashDumpsType(); + const auto dumpType = toUsvfsCrashDumpsType(s.diagnostics().coreDumpType()); const auto delay = duration_cast(s.diagnostics().spawnDelay()); - std::string dumpPath = MOShared::ToString(OrganizerCore::crashDumpsPath(), true); + std::string dumpPath = MOShared::ToString(OrganizerCore::getGlobalCoreDumpPath(), true); usvfsParameters* params = usvfsCreateParameters(); @@ -224,7 +229,7 @@ void UsvfsConnector::updateMapping(const MappingType &mapping) } void UsvfsConnector::updateParams( - MOBase::log::Levels logLevel, CrashDumpsType crashDumpsType, + MOBase::log::Levels logLevel, env::CoreDumpTypes coreDumpType, const QString& crashDumpsPath, std::chrono::seconds spawnDelay, QString executableBlacklist) { @@ -234,7 +239,7 @@ void UsvfsConnector::updateParams( usvfsSetDebugMode(p, FALSE); usvfsSetLogLevel(p, toUsvfsLogLevel(logLevel)); - usvfsSetCrashDumpType(p, crashDumpsType); + usvfsSetCrashDumpType(p, toUsvfsCrashDumpsType(coreDumpType)); usvfsSetCrashDumpPath(p, crashDumpsPath.toStdString().c_str()); usvfsSetProcessDelay(p, duration_cast(spawnDelay).count()); diff --git a/src/usvfsconnector.h b/src/usvfsconnector.h index 5982778b..bf5d49ce 100644 --- a/src/usvfsconnector.h +++ b/src/usvfsconnector.h @@ -31,6 +31,7 @@ along with Mod Organizer. If not, see . #include #include #include "executableinfo.h" +#include "envdump.h" class LogWorker : public QThread { @@ -87,7 +88,7 @@ public: void updateMapping(const MappingType &mapping); void updateParams( - MOBase::log::Levels logLevel, CrashDumpsType crashDumpsType, + MOBase::log::Levels logLevel, env::CoreDumpTypes coreDumpType, const QString& crashDumpsPath, std::chrono::seconds spawnDelay, QString executableBlacklist); -- cgit v1.3.1 From 54bf7e1fa230c78e98f429559b51a6be42f29e4a Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 8 Nov 2020 21:58:02 -0500 Subject: timings --- src/main.cpp | 4 ++++ src/moapplication.cpp | 26 ++++++++++++++++++++++++++ 2 files changed, 30 insertions(+) (limited to 'src/main.cpp') diff --git a/src/main.cpp b/src/main.cpp index ad074501..8f99cf1d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -17,6 +17,8 @@ int forwardToPrimary(SingleInstance& instance, const cl::CommandLine& cl); int main(int argc, char *argv[]) { + TimeThis tt("main()"); + MOShared::SetThisThreadName("main"); setExceptionHandlers(); @@ -35,6 +37,8 @@ int main(int argc, char *argv[]) return forwardToPrimary(instance, cl); } + tt.stop(); + return app.run(instance); } diff --git a/src/moapplication.cpp b/src/moapplication.cpp index 885abc37..3f17e436 100644 --- a/src/moapplication.cpp +++ b/src/moapplication.cpp @@ -135,6 +135,8 @@ void addDllsToPath() MOApplication::MOApplication(cl::CommandLine& cl, int& argc, char** argv) : QApplication(argc, argv), m_cl(cl) { + TimeThis tt("MOApplication()"); + connect(&m_StyleWatcher, &QFileSystemWatcher::fileChanged, [&](auto&& file){ log::debug("style file '{}' changed, reloading", file); updateStyle(file); @@ -147,6 +149,8 @@ MOApplication::MOApplication(cl::CommandLine& cl, int& argc, char** argv) int MOApplication::run(SingleInstance& singleInstance) { + TimeThis tt("MOApplication run() to doOneRun()"); + auto& m = InstanceManager::singleton(); if (m_cl.instance()) @@ -169,6 +173,7 @@ int MOApplication::run(SingleInstance& singleInstance) // resets things when MO is "restarted" resetForRestart(); + tt.stop(); const auto r = doOneRun(singleInstance); if (r == RestartExitCode) { continue; @@ -186,6 +191,8 @@ int MOApplication::run(SingleInstance& singleInstance) int MOApplication::doOneRun(SingleInstance& singleInstance) { + TimeThis tt("MOApplication::doOneRun() instances"); + // figuring out the current instance auto currentInstance = getCurrentInstance(); if (!currentInstance) { @@ -218,6 +225,8 @@ int MOApplication::doOneRun(SingleInstance& singleInstance) log::info("working directory: {}", QDir::currentPath()); + tt.start("MOApplication::doOneRun() settings"); + // deleting old files, only for the main instance if (!singleInstance.secondary()) { purgeOldFiles(); @@ -235,6 +244,8 @@ int MOApplication::doOneRun(SingleInstance& singleInstance) OrganizerCore::setGlobalCoreDumpType(settings.diagnostics().coreDumpType()); + tt.start("MOApplication::doOneRun() log and checks"); + // logging and checking env::Environment env; env.dump(settings); @@ -251,11 +262,14 @@ int MOApplication::doOneRun(SingleInstance& singleInstance) std::unique_ptr pluginContainer; // nexus interface + tt.start("MOApplication::doOneRun() NexusInterface"); log::debug("initializing nexus interface"); NexusInterface ni(&settings); // organizer core + tt.start("MOApplication::doOneRun() OrganizerCore"); log::debug("initializing core"); + OrganizerCore organizer(settings); if (!organizer.bootstrap()) { reportError("failed to set up data paths"); @@ -264,7 +278,9 @@ int MOApplication::doOneRun(SingleInstance& singleInstance) } // plugins + tt.start("MOApplication::doOneRun() plugins"); log::debug("initializing plugins"); + pluginContainer = std::make_unique(&organizer); pluginContainer->loadPlugins(); @@ -277,6 +293,8 @@ int MOApplication::doOneRun(SingleInstance& singleInstance) log::debug("this is a portable instance"); } + tt.start("MOApplication::doOneRun() OrganizerCore setup"); + sanity::checkPaths(*currentInstance->gamePlugin(), settings); // setting up organizer core @@ -298,14 +316,19 @@ int MOApplication::doOneRun(SingleInstance& singleInstance) organizer.setCurrentProfile(currentInstance->profileName()); // checking command line + tt.start("MOApplication::doOneRun() command line"); if (auto r=m_cl.setupCore(organizer)) { return *r; } // show splash + tt.start("MOApplication::doOneRun() splash"); + MOSplash splash( settings, currentInstance->directory(), currentInstance->gamePlugin()); + tt.start("MOApplication::doOneRun() finishing"); + // start an api check QString apiKey; if (GlobalSettings::nexusApiKey(apiKey)) { @@ -329,6 +352,7 @@ int MOApplication::doOneRun(SingleInstance& singleInstance) int res = 1; { + tt.start("MOApplication::doOneRun() MainWindow setup"); MainWindow mainWindow(settings, organizer, *pluginContainer); // qt resets the thread name somewhere when creating the main window @@ -350,6 +374,8 @@ int MOApplication::doOneRun(SingleInstance& singleInstance) mainWindow.activateWindow(); splash.close(); + tt.stop(); + res = exec(); mainWindow.close(); -- cgit v1.3.1 From a5469ae4e0668c36e4bf36bf7395fa25073b0bc6 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 8 Nov 2020 22:07:15 -0500 Subject: renamed SingleInstance to MOMultiProcess rewrote some comments to avoid using "instance" --- src/commandline.cpp | 5 ++--- src/main.cpp | 16 ++++++++-------- src/moapplication.cpp | 12 ++++++------ src/moapplication.h | 6 +++--- src/singleinstance.cpp | 36 ++++++++---------------------------- src/singleinstance.h | 48 ++++++++++++++---------------------------------- 6 files changed, 41 insertions(+), 82 deletions(-) (limited to 'src/main.cpp') diff --git a/src/commandline.cpp b/src/commandline.cpp index 3d0e901d..fd2fcb51 100644 --- a/src/commandline.cpp +++ b/src/commandline.cpp @@ -144,7 +144,8 @@ std::optional CommandLine::run(const std::wstring& line) // the first word on the command line is not a valid command, try the other - // stuff; this is handled in main.cpp + // stuff; this is used in setupCore() below when called from + // MOApplication::doOneRun() // look for help if (m_vm.count("help")) { @@ -202,8 +203,6 @@ std::optional CommandLine::run(const std::wstring& line) std::optional CommandLine::setupCore(OrganizerCore& organizer) const { - // if we have a command line parameter, it is either a nxm link or - // a binary to start if (m_shortcut.isValid()) { if (m_shortcut.hasExecutable()) { try { diff --git a/src/main.cpp b/src/main.cpp index 8f99cf1d..440489d3 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -13,7 +13,7 @@ using namespace MOBase; thread_local LPTOP_LEVEL_EXCEPTION_FILTER g_prevExceptionFilter = nullptr; thread_local std::terminate_handler g_prevTerminateHandler = nullptr; -int forwardToPrimary(SingleInstance& instance, const cl::CommandLine& cl); +int forwardToPrimary(MOMultiProcess& multiProcess, const cl::CommandLine& cl); int main(int argc, char *argv[]) { @@ -32,22 +32,22 @@ int main(int argc, char *argv[]) QApplication::setAttribute(Qt::AA_EnableHighDpiScaling); MOApplication app(cl, argc, argv); - SingleInstance instance(cl.multiple()); - if (instance.ephemeral()) { - return forwardToPrimary(instance, cl); + MOMultiProcess multiProcess(cl.multiple()); + if (multiProcess.ephemeral()) { + return forwardToPrimary(multiProcess, cl); } tt.stop(); - return app.run(instance); + return app.run(multiProcess); } -int forwardToPrimary(SingleInstance& instance, const cl::CommandLine& cl) +int forwardToPrimary(MOMultiProcess& multiProcess, const cl::CommandLine& cl) { if (cl.shortcut().isValid()) { - instance.sendMessage(cl.shortcut().toString()); + multiProcess.sendMessage(cl.shortcut().toString()); } else if (cl.nxmLink()) { - instance.sendMessage(*cl.nxmLink()); + multiProcess.sendMessage(*cl.nxmLink()); } else { QMessageBox::information( nullptr, QObject::tr("Mod Organizer"), diff --git a/src/moapplication.cpp b/src/moapplication.cpp index 3f17e436..db559421 100644 --- a/src/moapplication.cpp +++ b/src/moapplication.cpp @@ -147,7 +147,7 @@ MOApplication::MOApplication(cl::CommandLine& cl, int& argc, char** argv) addDllsToPath(); } -int MOApplication::run(SingleInstance& singleInstance) +int MOApplication::run(MOMultiProcess& multiProcess) { TimeThis tt("MOApplication run() to doOneRun()"); @@ -174,7 +174,7 @@ int MOApplication::run(SingleInstance& singleInstance) resetForRestart(); tt.stop(); - const auto r = doOneRun(singleInstance); + const auto r = doOneRun(multiProcess); if (r == RestartExitCode) { continue; } @@ -189,7 +189,7 @@ int MOApplication::run(SingleInstance& singleInstance) } } -int MOApplication::doOneRun(SingleInstance& singleInstance) +int MOApplication::doOneRun(MOMultiProcess& multiProcess) { TimeThis tt("MOApplication::doOneRun() instances"); @@ -217,7 +217,7 @@ int MOApplication::doOneRun(SingleInstance& singleInstance) createVersionInfo().displayString(3), GITID, QCoreApplication::applicationDirPath(), MOShared::getUsvfsVersionString()); - if (singleInstance.secondary()) { + if (multiProcess.secondary()) { log::debug("another instance of MO is running but --multiple was given"); } @@ -228,7 +228,7 @@ int MOApplication::doOneRun(SingleInstance& singleInstance) tt.start("MOApplication::doOneRun() settings"); // deleting old files, only for the main instance - if (!singleInstance.secondary()) { + if (!multiProcess.secondary()) { purgeOldFiles(); } @@ -365,7 +365,7 @@ int MOApplication::doOneRun(SingleInstance& singleInstance) QObject::connect(&mainWindow, SIGNAL(styleChanged(QString)), this, SLOT(setStyleFile(QString))); - QObject::connect(&singleInstance, SIGNAL(messageSent(QString)), &organizer, + QObject::connect(&multiProcess, SIGNAL(messageSent(QString)), &organizer, SLOT(externalMessage(QString))); diff --git a/src/moapplication.h b/src/moapplication.h index 7423c897..91b8023a 100644 --- a/src/moapplication.h +++ b/src/moapplication.h @@ -24,7 +24,7 @@ along with Mod Organizer. If not, see . #include class Settings; -class SingleInstance; +class MOMultiProcess; class Instance; class PluginContainer; @@ -40,7 +40,7 @@ public: // sets up everything, creates the main window and runs it // - int run(SingleInstance& si); + int run(MOMultiProcess& multiProcess); virtual bool notify(QObject* receiver, QEvent* event); @@ -55,7 +55,7 @@ private: QString m_DefaultStyle; cl::CommandLine& m_cl; - int doOneRun(SingleInstance& singleInstance); + int doOneRun(MOMultiProcess& multiProcess); std::optional getCurrentInstance(); std::optional setupInstanceLoop(Instance& currentInstance, PluginContainer& pc); diff --git a/src/singleinstance.cpp b/src/singleinstance.cpp index bd7ccc43..401381fd 100644 --- a/src/singleinstance.cpp +++ b/src/singleinstance.cpp @@ -1,22 +1,3 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free 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. - -Mod Organizer 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 Mod Organizer. If not, see . -*/ - #include "singleinstance.h" #include "utility.h" #include @@ -28,7 +9,7 @@ static const int s_Timeout = 5000; using MOBase::reportError; -SingleInstance::SingleInstance(bool allowMultiple, QObject *parent) : +MOMultiProcess::MOMultiProcess(bool allowMultiple, QObject *parent) : QObject(parent), m_Ephemeral(false), m_OwnsSM(false) { m_SharedMem.setKey(s_Key); @@ -58,7 +39,7 @@ SingleInstance::SingleInstance(bool allowMultiple, QObject *parent) : } -void SingleInstance::sendMessage(const QString &message) +void MOMultiProcess::sendMessage(const QString &message) { if (m_OwnsSM) { // nobody there to receive the message @@ -72,19 +53,19 @@ void SingleInstance::sendMessage(const QString &message) Sleep(250); } - // other instance may be just starting up + // other process may be just starting up socket.connectToServer(s_Key, QIODevice::WriteOnly); connected = socket.waitForConnected(s_Timeout); } if (!connected) { - reportError(tr("failed to connect to running instance: %1").arg(socket.errorString())); + reportError(tr("failed to connect to running process: %1").arg(socket.errorString())); return; } socket.write(message.toUtf8()); if (!socket.waitForBytesWritten(s_Timeout)) { - reportError(tr("failed to communicate with running instance: %1").arg(socket.errorString())); + reportError(tr("failed to communicate with running process: %1").arg(socket.errorString())); return; } @@ -92,8 +73,7 @@ void SingleInstance::sendMessage(const QString &message) socket.waitForDisconnected(); } - -void SingleInstance::receiveMessage() +void MOMultiProcess::receiveMessage() { QLocalSocket *socket = m_Server.nextPendingConnection(); if (!socket) { @@ -108,10 +88,10 @@ void SingleInstance::receiveMessage() if (av <= 0) { MOBase::log::error( - "failed to receive data from secondary instance: {}", + "failed to receive data from secondary process: {}", socket->errorString()); - reportError(tr("failed to receive data from secondary instance: %1").arg(socket->errorString())); + reportError(tr("failed to receive data from secondary process: %1").arg(socket->errorString())); return; } } diff --git a/src/singleinstance.h b/src/singleinstance.h index 5f6c3633..810e63d2 100644 --- a/src/singleinstance.h +++ b/src/singleinstance.h @@ -1,24 +1,5 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free 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. - -Mod Organizer 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 Mod Organizer. If not, see . -*/ - -#ifndef SINGLEINSTANCE_H -#define SINGLEINSTANCE_H +#ifndef MODORGANIZER_MOMULTIPROCESS_INCLUDED +#define MODORGANIZER_MOMULTIPROCESS_INCLUDED #include #include @@ -26,30 +7,29 @@ along with Mod Organizer. If not, see . /** - * used to ensure only a single instance of Mod Organizer is started and to - * allow ephemeral instances to send messages to the primary (visible) one. - * This way, other instances can start a download in the primary one + * used to ensure only a single process of Mod Organizer is started and to + * allow ephemeral processes to send messages to the primary (visible) one. + * This way, other processes can start a download in the primary one **/ -class SingleInstance : public QObject +class MOMultiProcess : public QObject { - Q_OBJECT public: - // `allowMultiple`: if another instance is running, run this one + // `allowMultiple`: if another process is running, run this one // disconnected from the shared memory - explicit SingleInstance(bool allowMultiple, QObject *parent = 0); + explicit MOMultiProcess(bool allowMultiple, QObject *parent = 0); /** - * @return true if this instance's job is to forward data to the primary - * instance through shared memory + * @return true if this process's job is to forward data to the primary + * process through shared memory **/ bool ephemeral() const { return m_Ephemeral; } - // returns true if this is not the primary instance, but was allowed because + // returns true if this is not the primary process, but was allowed because // of the AllowMultiple flag // bool secondary() const @@ -58,7 +38,7 @@ public: } /** - * send a message to the primary instance. This can be used to transmit download urls + * send a message to the primary process. This can be used to transmit download urls * * @param message message to send **/ @@ -67,7 +47,7 @@ public: signals: /** - * @brief emitted when an ephemeral instance has sent a message (to us) + * @brief emitted when an ephemeral process has sent a message (to us) * * @param message the message we received **/ @@ -87,4 +67,4 @@ private: }; -#endif // SINGLEINSTANCE_H +#endif // MODORGANIZER_MOMULTIPROCESS_INCLUDED -- cgit v1.3.1 From d7ae42d3775f88d5cd63fa2f8860bd31e18f981e Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 8 Nov 2020 22:09:42 -0500 Subject: renamed singleinstance.h/cpp to multiprocess.h/cpp --- src/CMakeLists.txt | 2 +- src/main.cpp | 2 +- src/moapplication.cpp | 2 +- src/multiprocess.cpp | 102 +++++++++++++++++++++++++++++++++++++++++++++++++ src/multiprocess.h | 70 +++++++++++++++++++++++++++++++++ src/singleinstance.cpp | 102 ------------------------------------------------- src/singleinstance.h | 70 --------------------------------- 7 files changed, 175 insertions(+), 175 deletions(-) create mode 100644 src/multiprocess.cpp create mode 100644 src/multiprocess.h delete mode 100644 src/singleinstance.cpp delete mode 100644 src/singleinstance.h (limited to 'src/main.cpp') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 30614e59..bb4b151f 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -15,9 +15,9 @@ add_filter(NAME src/application GROUPS main moapplication moshortcut + multiprocess sanitychecks selfupdater - singleinstance ) add_filter(NAME src/browser GROUPS diff --git a/src/main.cpp b/src/main.cpp index 440489d3..554ed006 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,4 +1,4 @@ -#include "singleinstance.h" +#include "multiprocess.h" #include "loglist.h" #include "moapplication.h" #include "organizercore.h" diff --git a/src/moapplication.cpp b/src/moapplication.cpp index db559421..9139acbb 100644 --- a/src/moapplication.cpp +++ b/src/moapplication.cpp @@ -25,7 +25,7 @@ along with Mod Organizer. If not, see . #include "organizercore.h" #include "thread_utils.h" #include "loglist.h" -#include "singleinstance.h" +#include "multiprocess.h" #include "nexusinterface.h" #include "nxmaccessmanager.h" #include "tutorialmanager.h" diff --git a/src/multiprocess.cpp b/src/multiprocess.cpp new file mode 100644 index 00000000..4ce58718 --- /dev/null +++ b/src/multiprocess.cpp @@ -0,0 +1,102 @@ +#include "multiprocess.h" +#include "utility.h" +#include +#include +#include + +static const char s_Key[] = "mo-43d1a3ad-eeb0-4818-97c9-eda5216c29b5"; +static const int s_Timeout = 5000; + +using MOBase::reportError; + +MOMultiProcess::MOMultiProcess(bool allowMultiple, QObject *parent) : + QObject(parent), m_Ephemeral(false), m_OwnsSM(false) +{ + m_SharedMem.setKey(s_Key); + + if (!m_SharedMem.create(1)) { + if (m_SharedMem.error() == QSharedMemory::AlreadyExists) { + if (!allowMultiple) { + m_SharedMem.attach(); + m_Ephemeral = true; + } + } + + if ((m_SharedMem.error() != QSharedMemory::NoError) && + (m_SharedMem.error() != QSharedMemory::AlreadyExists)) { + throw MOBase::MyException(tr("SHM error: %1").arg(m_SharedMem.errorString())); + } + } else { + m_OwnsSM = true; + } + + if (m_OwnsSM) { + connect(&m_Server, SIGNAL(newConnection()), this, SLOT(receiveMessage()), Qt::QueuedConnection); + // has to be called before listen + m_Server.setSocketOptions(QLocalServer::WorldAccessOption); + m_Server.listen(s_Key); + } +} + + +void MOMultiProcess::sendMessage(const QString &message) +{ + if (m_OwnsSM) { + // nobody there to receive the message + return; + } + QLocalSocket socket(this); + + bool connected = false; + for(int i = 0; i < 2 && !connected; ++i) { + if (i > 0) { + Sleep(250); + } + + // other process may be just starting up + socket.connectToServer(s_Key, QIODevice::WriteOnly); + connected = socket.waitForConnected(s_Timeout); + } + + if (!connected) { + reportError(tr("failed to connect to running process: %1").arg(socket.errorString())); + return; + } + + socket.write(message.toUtf8()); + if (!socket.waitForBytesWritten(s_Timeout)) { + reportError(tr("failed to communicate with running process: %1").arg(socket.errorString())); + return; + } + + socket.disconnectFromServer(); + socket.waitForDisconnected(); +} + +void MOMultiProcess::receiveMessage() +{ + QLocalSocket *socket = m_Server.nextPendingConnection(); + if (!socket) { + return; + } + + if (!socket->waitForReadyRead(s_Timeout)) { + // check if there are bytes available; if so, it probably means the data was + // already received by the time waitForReadyRead() was called and the + // connection has been closed + const auto av = socket->bytesAvailable(); + + if (av <= 0) { + MOBase::log::error( + "failed to receive data from secondary process: {}", + socket->errorString()); + + reportError(tr("failed to receive data from secondary process: %1").arg(socket->errorString())); + return; + } + } + + QString message = QString::fromUtf8(socket->readAll().constData()); + emit messageSent(message); + socket->disconnectFromServer(); +} diff --git a/src/multiprocess.h b/src/multiprocess.h new file mode 100644 index 00000000..810e63d2 --- /dev/null +++ b/src/multiprocess.h @@ -0,0 +1,70 @@ +#ifndef MODORGANIZER_MOMULTIPROCESS_INCLUDED +#define MODORGANIZER_MOMULTIPROCESS_INCLUDED + +#include +#include +#include + + +/** + * used to ensure only a single process of Mod Organizer is started and to + * allow ephemeral processes to send messages to the primary (visible) one. + * This way, other processes can start a download in the primary one + **/ +class MOMultiProcess : public QObject +{ + Q_OBJECT + +public: + // `allowMultiple`: if another process is running, run this one + // disconnected from the shared memory + explicit MOMultiProcess(bool allowMultiple, QObject *parent = 0); + + /** + * @return true if this process's job is to forward data to the primary + * process through shared memory + **/ + bool ephemeral() const + { + return m_Ephemeral; + } + + // returns true if this is not the primary process, but was allowed because + // of the AllowMultiple flag + // + bool secondary() const + { + return !m_Ephemeral && !m_OwnsSM; + } + + /** + * send a message to the primary process. This can be used to transmit download urls + * + * @param message message to send + **/ + void sendMessage(const QString &message); + +signals: + + /** + * @brief emitted when an ephemeral process has sent a message (to us) + * + * @param message the message we received + **/ + void messageSent(const QString &message); + +public slots: + +private slots: + + void receiveMessage(); + +private: + bool m_Ephemeral; + bool m_OwnsSM; + QSharedMemory m_SharedMem; + QLocalServer m_Server; + +}; + +#endif // MODORGANIZER_MOMULTIPROCESS_INCLUDED diff --git a/src/singleinstance.cpp b/src/singleinstance.cpp deleted file mode 100644 index 401381fd..00000000 --- a/src/singleinstance.cpp +++ /dev/null @@ -1,102 +0,0 @@ -#include "singleinstance.h" -#include "utility.h" -#include -#include -#include - -static const char s_Key[] = "mo-43d1a3ad-eeb0-4818-97c9-eda5216c29b5"; -static const int s_Timeout = 5000; - -using MOBase::reportError; - -MOMultiProcess::MOMultiProcess(bool allowMultiple, QObject *parent) : - QObject(parent), m_Ephemeral(false), m_OwnsSM(false) -{ - m_SharedMem.setKey(s_Key); - - if (!m_SharedMem.create(1)) { - if (m_SharedMem.error() == QSharedMemory::AlreadyExists) { - if (!allowMultiple) { - m_SharedMem.attach(); - m_Ephemeral = true; - } - } - - if ((m_SharedMem.error() != QSharedMemory::NoError) && - (m_SharedMem.error() != QSharedMemory::AlreadyExists)) { - throw MOBase::MyException(tr("SHM error: %1").arg(m_SharedMem.errorString())); - } - } else { - m_OwnsSM = true; - } - - if (m_OwnsSM) { - connect(&m_Server, SIGNAL(newConnection()), this, SLOT(receiveMessage()), Qt::QueuedConnection); - // has to be called before listen - m_Server.setSocketOptions(QLocalServer::WorldAccessOption); - m_Server.listen(s_Key); - } -} - - -void MOMultiProcess::sendMessage(const QString &message) -{ - if (m_OwnsSM) { - // nobody there to receive the message - return; - } - QLocalSocket socket(this); - - bool connected = false; - for(int i = 0; i < 2 && !connected; ++i) { - if (i > 0) { - Sleep(250); - } - - // other process may be just starting up - socket.connectToServer(s_Key, QIODevice::WriteOnly); - connected = socket.waitForConnected(s_Timeout); - } - - if (!connected) { - reportError(tr("failed to connect to running process: %1").arg(socket.errorString())); - return; - } - - socket.write(message.toUtf8()); - if (!socket.waitForBytesWritten(s_Timeout)) { - reportError(tr("failed to communicate with running process: %1").arg(socket.errorString())); - return; - } - - socket.disconnectFromServer(); - socket.waitForDisconnected(); -} - -void MOMultiProcess::receiveMessage() -{ - QLocalSocket *socket = m_Server.nextPendingConnection(); - if (!socket) { - return; - } - - if (!socket->waitForReadyRead(s_Timeout)) { - // check if there are bytes available; if so, it probably means the data was - // already received by the time waitForReadyRead() was called and the - // connection has been closed - const auto av = socket->bytesAvailable(); - - if (av <= 0) { - MOBase::log::error( - "failed to receive data from secondary process: {}", - socket->errorString()); - - reportError(tr("failed to receive data from secondary process: %1").arg(socket->errorString())); - return; - } - } - - QString message = QString::fromUtf8(socket->readAll().constData()); - emit messageSent(message); - socket->disconnectFromServer(); -} diff --git a/src/singleinstance.h b/src/singleinstance.h deleted file mode 100644 index 810e63d2..00000000 --- a/src/singleinstance.h +++ /dev/null @@ -1,70 +0,0 @@ -#ifndef MODORGANIZER_MOMULTIPROCESS_INCLUDED -#define MODORGANIZER_MOMULTIPROCESS_INCLUDED - -#include -#include -#include - - -/** - * used to ensure only a single process of Mod Organizer is started and to - * allow ephemeral processes to send messages to the primary (visible) one. - * This way, other processes can start a download in the primary one - **/ -class MOMultiProcess : public QObject -{ - Q_OBJECT - -public: - // `allowMultiple`: if another process is running, run this one - // disconnected from the shared memory - explicit MOMultiProcess(bool allowMultiple, QObject *parent = 0); - - /** - * @return true if this process's job is to forward data to the primary - * process through shared memory - **/ - bool ephemeral() const - { - return m_Ephemeral; - } - - // returns true if this is not the primary process, but was allowed because - // of the AllowMultiple flag - // - bool secondary() const - { - return !m_Ephemeral && !m_OwnsSM; - } - - /** - * send a message to the primary process. This can be used to transmit download urls - * - * @param message message to send - **/ - void sendMessage(const QString &message); - -signals: - - /** - * @brief emitted when an ephemeral process has sent a message (to us) - * - * @param message the message we received - **/ - void messageSent(const QString &message); - -public slots: - -private slots: - - void receiveMessage(); - -private: - bool m_Ephemeral; - bool m_OwnsSM; - QSharedMemory m_SharedMem; - QLocalServer m_Server; - -}; - -#endif // MODORGANIZER_MOMULTIPROCESS_INCLUDED -- cgit v1.3.1