#include "organizerproxy.h" #include "downloadmanagerproxy.h" #include "executableslistproxy.h" #include "gamefeaturesproxy.h" #include "glob_matching.h" #include "instancemanager.h" #include "instancemanagerproxy.h" #include "modlistproxy.h" #include "organizercore.h" #include "plugincontainer.h" #include "pluginlistproxy.h" #include "proxyutils.h" #include "settings.h" #include "shared/appconfig.h" #include "shared/util.h" #include #include #include using namespace MOBase; using namespace MOShared; OrganizerProxy::OrganizerProxy(OrganizerCore* organizer, PluginContainer* pluginContainer, MOBase::IPlugin* plugin) : m_Proxied(organizer), m_PluginContainer(pluginContainer), m_Plugin(plugin), m_InstanceManagerProxy( std::make_unique(&InstanceManager::singleton())), m_DownloadManagerProxy( std::make_unique(this, organizer->downloadManager())), m_ModListProxy(std::make_unique(this, organizer->modList())), m_ExecutablesListProxy( std::make_unique(organizer->executablesList())), m_PluginListProxy( std::make_unique(this, organizer->pluginList())), m_GameFeaturesProxy( std::make_unique(this, pluginContainer->gameFeatures())) {} OrganizerProxy::~OrganizerProxy() { disconnectSignals(); } void OrganizerProxy::connectSignals() { m_Connections.push_back( m_Proxied->onAboutToRun(callSignalIfPluginActive(this, m_AboutToRun, true))); m_Connections.push_back( m_Proxied->onFinishedRun(callSignalIfPluginActive(this, m_FinishedRun))); m_Connections.push_back( m_Proxied->onProfileCreated(callSignalIfPluginActive(this, m_ProfileCreated))); m_Connections.push_back( m_Proxied->onProfileRenamed(callSignalIfPluginActive(this, m_ProfileRenamed))); m_Connections.push_back( m_Proxied->onProfileRemoved(callSignalIfPluginActive(this, m_ProfileRemoved))); m_Connections.push_back( m_Proxied->onProfileChanged(callSignalIfPluginActive(this, m_ProfileChanged))); m_Connections.push_back(m_Proxied->onUserInterfaceInitialized( callSignalAlways(m_UserInterfaceInitialized))); m_Connections.push_back( m_Proxied->onPluginSettingChanged(callSignalAlways(m_PluginSettingChanged))); m_Connections.push_back( m_Proxied->onPluginEnabled(callSignalAlways(m_PluginEnabled))); m_Connections.push_back( m_Proxied->onPluginDisabled(callSignalAlways(m_PluginDisabled))); // Connect the child proxies. m_DownloadManagerProxy->connectSignals(); m_ModListProxy->connectSignals(); m_PluginListProxy->connectSignals(); } void OrganizerProxy::disconnectSignals() { // Disconnect the child proxies. m_DownloadManagerProxy->disconnectSignals(); m_ModListProxy->disconnectSignals(); m_PluginListProxy->disconnectSignals(); for (auto& conn : m_Connections) { conn.disconnect(); } m_Connections.clear(); } IModRepositoryBridge* OrganizerProxy::createNexusBridge() const { return new NexusBridge(m_PluginContainer, m_Plugin->name()); } QString OrganizerProxy::instanceName() const { return InstanceManager::singleton().currentInstance()->displayName(); } QString OrganizerProxy::profileName() const { return m_Proxied->profileName(); } QString OrganizerProxy::profilePath() const { return m_Proxied->profilePath(); } QString OrganizerProxy::downloadsPath() const { return m_Proxied->downloadsPath(); } QString OrganizerProxy::overwritePath() const { return m_Proxied->overwritePath(); } QString OrganizerProxy::basePath() const { return m_Proxied->basePath(); } QString OrganizerProxy::modsPath() const { return m_Proxied->modsPath(); } Version OrganizerProxy::version() const { return m_Proxied->version(); } VersionInfo OrganizerProxy::appVersion() const { const auto version = m_Proxied->version(); const int major = version.major(), minor = version.minor(), subminor = version.patch(); int subsubminor = 0; VersionInfo::ReleaseType infoReleaseType = VersionInfo::RELEASE_FINAL; // make a copy auto prereleases = version.preReleases(); if (!prereleases.empty()) { // check if the first pre-release entry is a number if (prereleases.front().index() == 0) { subsubminor = std::get(prereleases.front()); prereleases.erase(prereleases.begin()); } if (!prereleases.empty()) { const auto releaseType = std::get(prereleases.front()); switch (releaseType) { case Version::Development: infoReleaseType = VersionInfo::RELEASE_PREALPHA; break; case Version::Alpha: infoReleaseType = VersionInfo::RELEASE_ALPHA; break; case Version::Beta: infoReleaseType = VersionInfo::RELEASE_BETA; break; case Version::ReleaseCandidate: infoReleaseType = VersionInfo::RELEASE_CANDIDATE; break; default: infoReleaseType = VersionInfo::RELEASE_PREALPHA; } } // there is no way to differentiate two pre-releases? } return VersionInfo(major, minor, subminor, subsubminor, infoReleaseType); } IPluginGame* OrganizerProxy::getGame(const QString& gameName) const { return m_Proxied->getGame(gameName); } IModInterface* OrganizerProxy::createMod(MOBase::GuessedValue& name) { return m_Proxied->createMod(name); } void OrganizerProxy::modDataChanged(IModInterface* mod) { m_Proxied->modDataChanged(mod); } bool OrganizerProxy::isPluginEnabled(QString const& pluginName) const { return m_PluginContainer->isEnabled(pluginName); } bool OrganizerProxy::isPluginEnabled(IPlugin* plugin) const { return m_PluginContainer->isEnabled(plugin); } QVariant OrganizerProxy::pluginSetting(const QString& pluginName, const QString& key) const { return m_Proxied->pluginSetting(pluginName, key); } void OrganizerProxy::setPluginSetting(const QString& pluginName, const QString& key, const QVariant& value) { m_Proxied->setPluginSetting(pluginName, key, value); } QVariant OrganizerProxy::persistent(const QString& pluginName, const QString& key, const QVariant& def) const { return m_Proxied->persistent(pluginName, key, def); } void OrganizerProxy::setPersistent(const QString& pluginName, const QString& key, const QVariant& value, bool sync) { m_Proxied->setPersistent(pluginName, key, value, sync); } QString OrganizerProxy::pluginDataPath() const { return OrganizerCore::pluginDataPath(); } HANDLE OrganizerProxy::startApplication(const QString& exe, const QStringList& args, const QString& cwd, const QString& profile, const QString& overwrite, bool ignoreOverwrite) { log::debug("a plugin has requested to start an application:\n" " . executable: '{}'\n" " . args: '{}'\n" " . cwd: '{}'\n" " . profile: '{}'\n" " . overwrite: '{}'\n" " . ignore overwrite: {}", exe, args.join(" "), cwd, profile, overwrite, ignoreOverwrite); auto runner = m_Proxied->processRunner(); // don't wait for completion runner.setFromFileOrExecutable(exe, args, cwd, profile, overwrite, ignoreOverwrite) .run(); // the plugin is in charge of closing the handle, unless waitForApplication() // is called on it return runner.stealProcessHandle().release(); } bool OrganizerProxy::waitForApplication(HANDLE handle, bool refresh, LPDWORD exitCode) const { const auto pid = ::GetProcessId(handle); log::debug("a plugin wants to wait for an application to complete, pid {}{}", pid, (pid == 0 ? "unknown (probably already completed)" : "")); auto runner = m_Proxied->processRunner(); ProcessRunner::WaitFlags waitFlags = ProcessRunner::ForceWait; if (refresh) { waitFlags |= ProcessRunner::TriggerRefresh | ProcessRunner::WaitForRefresh; } const auto r = runner.setWaitForCompletion(waitFlags, UILocker::OutputRequired) .attachToProcess(handle); if (exitCode) { *exitCode = runner.exitCode(); } switch (r) { case ProcessRunner::Completed: return true; case ProcessRunner::Cancelled: // fall-through case ProcessRunner::ForceUnlocked: // this is always an error because the application should have run to // completion return false; case ProcessRunner::Error: // fall-through default: return false; } } void OrganizerProxy::refresh(bool saveChanges) { m_Proxied->refresh(saveChanges); } IModInterface* OrganizerProxy::installMod(const QString& fileName, const QString& nameSuggestion) { return m_Proxied->installMod(fileName, -1, false, nullptr, nameSuggestion); } QString OrganizerProxy::resolvePath(const QString& fileName) const { return m_Proxied->resolvePath(fileName); } QStringList OrganizerProxy::listDirectories(const QString& directoryName) const { return m_Proxied->listDirectories(directoryName); } QStringList OrganizerProxy::findFiles(const QString& path, const std::function& filter) const { return m_Proxied->findFiles(path, filter); } QStringList OrganizerProxy::findFiles(const QString& path, const QStringList& globFilters) const { QList> patterns; for (auto& gfilter : globFilters) { patterns.append(GlobPattern(gfilter)); } return findFiles(path, [&patterns](const QString& filename) { for (auto& p : patterns) { if (p.match(filename)) { return true; } } return false; }); } QStringList OrganizerProxy::getFileOrigins(const QString& fileName) const { return m_Proxied->getFileOrigins(fileName); } QList OrganizerProxy::findFileInfos( const QString& path, const std::function& filter) const { return m_Proxied->findFileInfos(path, filter); } std::shared_ptr OrganizerProxy::virtualFileTree() const { return m_Proxied->m_VirtualFileTree.value(); } MOBase::IInstanceManager* OrganizerProxy::instanceManager() const { return m_InstanceManagerProxy.get(); } MOBase::IDownloadManager* OrganizerProxy::downloadManager() const { return m_DownloadManagerProxy.get(); } MOBase::IPluginList* OrganizerProxy::pluginList() const { return m_PluginListProxy.get(); } MOBase::IModList* OrganizerProxy::modList() const { return m_ModListProxy.get(); } MOBase::IExecutablesList* OrganizerProxy::executablesList() const { return m_ExecutablesListProxy.get(); } MOBase::IGameFeatures* OrganizerProxy::gameFeatures() const { return m_GameFeaturesProxy.get(); } std::shared_ptr OrganizerProxy::profile() const { return m_Proxied->currentProfile(); } QStringList OrganizerProxy::profileNames() const { return m_Proxied->profileNames(); } std::shared_ptr OrganizerProxy::getProfile(const QString& name) const { return m_Proxied->getProfile(name); } MOBase::IPluginGame const* OrganizerProxy::managedGame() const { return m_Proxied->managedGame(); } // CALLBACKS bool OrganizerProxy::onAboutToRun(const std::function& func) { return m_Proxied ->onAboutToRun(MOShared::callIfPluginActive( this, [func](const QString& binary, const QDir&, const QString&) { return func(binary); }, true)) .connected(); } bool OrganizerProxy::onAboutToRun( const std::function& func) { return m_Proxied->onAboutToRun(MOShared::callIfPluginActive(this, func, true)) .connected(); } bool OrganizerProxy::onFinishedRun( const std::function& func) { return m_Proxied->onFinishedRun(MOShared::callIfPluginActive(this, func)).connected(); } bool OrganizerProxy::onUserInterfaceInitialized( std::function const& func) { // Always call this one to allow plugin to initialize themselves even when not active: return m_UserInterfaceInitialized.connect(func).connected(); } bool OrganizerProxy::onNextRefresh(const std::function& func, bool immediateIfPossible) { using enum OrganizerCore::RefreshCallbackMode; return m_Proxied ->onNextRefresh(MOShared::callIfPluginActive(this, func), OrganizerCore::RefreshCallbackGroup::EXTERNAL, immediateIfPossible ? RUN_NOW_IF_POSSIBLE : FORCE_WAIT_FOR_REFRESH) .connected(); } bool OrganizerProxy::onProfileCreated(std::function const& func) { return m_ProfileCreated.connect(func).connected(); } bool OrganizerProxy::onProfileRenamed( std::function const& func) { return m_ProfileRenamed.connect(func).connected(); } bool OrganizerProxy::onProfileRemoved(std::function const& func) { return m_ProfileRemoved.connect(func).connected(); } bool OrganizerProxy::onProfileChanged( std::function const& func) { return m_ProfileChanged.connect(func).connected(); } // Always call these one, otherwise plugin cannot detect they are being enabled / // disabled: bool OrganizerProxy::onPluginSettingChanged( std::function const& func) { return m_PluginSettingChanged.connect(func).connected(); } bool OrganizerProxy::onPluginEnabled(std::function const& func) { return m_PluginEnabled.connect(func).connected(); } bool OrganizerProxy::onPluginEnabled(const QString& pluginName, std::function const& func) { return onPluginEnabled([=](const IPlugin* plugin) { if (plugin->name().compare(pluginName, Qt::CaseInsensitive) == 0) { func(); } }); } bool OrganizerProxy::onPluginDisabled(std::function const& func) { return m_PluginDisabled.connect(func).connected(); } bool OrganizerProxy::onPluginDisabled(const QString& pluginName, std::function const& func) { return onPluginDisabled([=](const IPlugin* plugin) { if (plugin->name().compare(pluginName, Qt::CaseInsensitive) == 0) { func(); } }); }