diff --git a/src/plugin_python_en.ts b/src/plugin_python_en.ts
index 9ef7836..158fcc7 100644
--- a/src/plugin_python_en.ts
+++ b/src/plugin_python_en.ts
@@ -4,78 +4,35 @@
ProxyPython
-
- Python Initialization failed
+
+ The path to Mod Organizer (%1) contains a semicolon. <br>While this is legal on NTFS drives, many softwares do not handle it correctly.<br>Unfortunately MO depends on libraries that seem to fall into that group.<br>As a result the python plugin cannot be loaded, and the only solution we canoffer is to remove the semicolon or move MO to a path without a semicolon.
-
- On a previous start the Python Plugin failed to initialize.
-Do you want to try initializing python again (at the risk of another crash)?
- Suggestion: Select "no", and click the warning sign for further help.Afterwards you have to re-enable the python plugin.
-
-
-
-
- Python Proxy
-
-
-
-
- Proxy Plugin to allow plugins written in python to be loaded
-
-
-
-
- ModOrganizer path contains a semicolon
-
-
-
-
- Python DLL not found
-
-
-
-
- Invalid Python DLL
-
-
-
-
- Initializing Python failed
-
-
-
-
-
- invalid problem key %1
-
-
-
-
- The path to Mod Organizer (%1) contains a semicolon.<br>While this is legal on NTFS drives, many applications do not handle it correctly.<br>Unfortunately MO depends on libraries that seem to fall into that group.<br>As a result the python plugin cannot be loaded, and the only solution we can offer is to remove the semicolon or move MO to a path without a semicolon.
-
-
-
-
+
The Python plugin DLL was not found, maybe your antivirus deleted it. Re-installing MO2 might fix the problem.
-
+
The Python plugin DLL is invalid, maybe your antivirus is blocking it. Re-installing MO2 and adding exclusions for it to your AV might fix the problem.
-
+
The initialization of the Python plugin DLL failed, unfortunately without any details.
+
+
+ no failure
+
+
QObject
-
+
An unknown exception was thrown in python code.
diff --git a/src/proxy/CMakeLists.txt b/src/proxy/CMakeLists.txt
index c37b219..3ec965b 100644
--- a/src/proxy/CMakeLists.txt
+++ b/src/proxy/CMakeLists.txt
@@ -2,28 +2,33 @@ cmake_minimum_required(VERSION 3.16)
find_package(mo2-uibase CONFIG REQUIRED)
-set(PLUGIN_NAME "plugin_python")
+set(PROXY_NAME "python")
add_library(proxy SHARED proxypython.cpp proxypython.h)
mo2_configure_plugin(proxy
NO_SOURCES
WARNINGS 4
EXTERNAL_WARNINGS 4
- TRANSLATIONS OFF
+ TRANSLATIONS ON
EXTRA_TRANSLATIONS
${CMAKE_CURRENT_SOURCE_DIR}/../runner
${CMAKE_CURRENT_SOURCE_DIR}/../mobase
${CMAKE_CURRENT_SOURCE_DIR}/../pybind11-qt)
mo2_default_source_group()
target_link_libraries(proxy PRIVATE runner mo2::uibase)
-set_target_properties(proxy PROPERTIES OUTPUT_NAME ${PLUGIN_NAME})
-mo2_install_plugin(proxy FOLDER)
+set_target_properties(proxy PROPERTIES OUTPUT_NAME "python_proxy")
-set(PLUGIN_PYTHON_DIR bin/plugins/${PLUGIN_NAME})
+set(PROXY_PYTHON_DIR ${MO2_INSTALL_BIN}/proxies/python)
-# install runner
+# install runner and proxy
+install(FILES $ DESTINATION ${PROXY_PYTHON_DIR}/python)
+install(FILES $ DESTINATION ${PROXY_PYTHON_DIR}/python/dlls)
+
+# install PDB
+install(FILES $ DESTINATION pdb)
+
+# delay loading since the dll is not in the standard folder
target_link_options(proxy PRIVATE "/DELAYLOAD:runner.dll")
-install(FILES $ DESTINATION ${PLUGIN_PYTHON_DIR}/dlls)
# translations (custom location)
mo2_add_translations(proxy
@@ -35,14 +40,14 @@ mo2_add_translations(proxy
${CMAKE_CURRENT_SOURCE_DIR}/../pybind11-qt)
# install DLLs files needed
-set(DLL_DIRS ${PLUGIN_PYTHON_DIR}/dlls)
+set(DLL_DIRS ${PROXY_PYTHON_DIR}/dlls)
file(GLOB dlls_to_install
# ${PYTHON_BUILD_PATH}/libffi*.dll
${Python_HOME}/python${Python_VERSION_MAJOR}*.dll)
install(FILES ${dlls_to_install} DESTINATION ${DLL_DIRS})
# install Python .pyd files
-set(PYLIB_DIR ${PLUGIN_PYTHON_DIR}/libs)
+set(PYLIB_DIR ${PROXY_PYTHON_DIR}/libs)
file(GLOB libs_to_install ${Python_DLL_DIR}/*.pyd)
install(FILES ${libs_to_install} DESTINATION ${PYLIB_DIR})
diff --git a/src/proxy/proxypython.cpp b/src/proxy/proxypython.cpp
index 0ecbfc0..8cfc3e3 100644
--- a/src/proxy/proxypython.cpp
+++ b/src/proxy/proxypython.cpp
@@ -53,59 +53,26 @@ fs::path getPluginFolder()
return fs::path(path).parent_path();
}
-ProxyPython::ProxyPython()
- : m_MOInfo{nullptr}, m_RunnerLib{nullptr}, m_Runner{nullptr},
- m_LoadFailure(FailureType::NONE)
-{
-}
+ProxyPython::ProxyPython() : m_RunnerLib{nullptr}, m_Runner{nullptr} {}
-bool ProxyPython::init(IOrganizer* moInfo)
+bool ProxyPython::initialize(QString& errorMessage)
{
- m_MOInfo = moInfo;
-
- if (m_MOInfo && !m_MOInfo->isPluginEnabled(this)) {
- return false;
- }
+ errorMessage = "";
if (QCoreApplication::applicationDirPath().contains(';')) {
- m_LoadFailure = FailureType::SEMICOLON;
+ errorMessage = failureMessage(FailureType::SEMICOLON);
return true;
}
const auto pluginFolder = getPluginFolder();
-
if (pluginFolder.empty()) {
- DWORD error = ::GetLastError();
- m_LoadFailure = FailureType::DLL_NOT_FOUND;
+ DWORD error = ::GetLastError();
+ errorMessage = failureMessage(FailureType::DLL_NOT_FOUND);
log::error("failed to resolve Python proxy directory ({}): {}", error,
qUtf8Printable(windowsErrorString(::GetLastError())));
return false;
}
- if (m_MOInfo && m_MOInfo->persistent(name(), "tryInit", false).toBool()) {
- m_LoadFailure = FailureType::INITIALIZATION;
- if (QMessageBox::question(
- parentWidget(), tr("Python Initialization failed"),
- tr("On a previous start the Python Plugin failed to initialize.\n"
- "Do you want to try initializing python again (at the risk of "
- "another crash)?\n "
- "Suggestion: Select \"no\", and click the warning sign for further "
- "help.Afterwards you have to re-enable the python plugin."),
- QMessageBox::Yes | QMessageBox::No,
- QMessageBox::No) == QMessageBox::No) {
- // we force enabled here (note: this is a persistent settings since MO2 2.4
- // or something), plugin
- // usually should not handle enabled/disabled themselves but this is a base
- // plugin so...
- m_MOInfo->setPersistent(name(), "enabled", false, true);
- return true;
- }
- }
-
- if (m_MOInfo) {
- m_MOInfo->setPersistent(name(), "tryInit", true);
- }
-
// load the pythonrunner library, this is done in multiple steps:
//
// 1. we set the dlls/ subfolder (from the plugin) as the DLL directory so Windows
@@ -114,8 +81,8 @@ bool ProxyPython::init(IOrganizer* moInfo)
//
const auto dllPaths = pluginFolder / "dlls";
if (SetDllDirectoryW(dllPaths.c_str()) == 0) {
- DWORD error = ::GetLastError();
- m_LoadFailure = FailureType::DLL_NOT_FOUND;
+ DWORD error = ::GetLastError();
+ errorMessage = failureMessage(FailureType::DLL_NOT_FOUND);
log::error("failed to add python DLL directory ({}): {}", error,
qUtf8Printable(windowsErrorString(::GetLastError())));
return false;
@@ -129,21 +96,16 @@ bool ProxyPython::init(IOrganizer* moInfo)
if (m_Runner) {
const auto libpath = pluginFolder / "libs";
- const std::vector paths{
- libpath / "pythoncore.zip", libpath,
- std::filesystem::path{IOrganizer::getPluginDataPath().toStdWString()}};
+ const std::vector paths{libpath / "pythoncore.zip", libpath};
m_Runner->initialize(paths);
}
- if (m_MOInfo) {
- m_MOInfo->setPersistent(name(), "tryInit", false);
- }
-
// reset DLL directory
SetDllDirectoryW(NULL);
if (!m_Runner || !m_Runner->isInitialized()) {
- m_LoadFailure = FailureType::INITIALIZATION;
+ errorMessage = failureMessage(FailureType::INITIALIZATION);
+ return false;
}
else {
m_Runner->addDllSearchPath(pluginFolder / "dlls");
@@ -152,110 +114,57 @@ bool ProxyPython::init(IOrganizer* moInfo)
return true;
}
-QString ProxyPython::name() const
-{
- return "Python Proxy";
-}
-
-QString ProxyPython::localizedName() const
-{
- return tr("Python Proxy");
-}
-
-QString ProxyPython::author() const
-{
- return "AnyOldName3, Holt59, Silarn, Tannin";
-}
-
-QString ProxyPython::description() const
-{
- return tr("Proxy Plugin to allow plugins written in python to be loaded");
-}
-
-VersionInfo ProxyPython::version() const
-{
- return VersionInfo(3, 0, 0, VersionInfo::RELEASE_FINAL);
-}
-
-QList ProxyPython::settings() const
-{
- return {};
-}
-
-QStringList ProxyPython::pluginList(const QDir& pluginPath) const
-{
- QDir dir(pluginPath);
- dir.setFilter(dir.filter() | QDir::NoDotAndDotDot);
- QDirIterator iter(dir);
-
- // Note: We put python script (.py) and directory names, not the __init__.py
- // files in those since it is easier for the runner to import them.
- QStringList result;
- while (iter.hasNext()) {
- QString name = iter.next();
- QFileInfo info = iter.fileInfo();
-
- if (info.isFile() && name.endsWith(".py")) {
- result.append(name);
- }
- else if (info.isDir() && QDir(info.absoluteFilePath()).exists("__init__.py")) {
- result.append(name);
- }
- }
-
- return result;
-}
-
-QList ProxyPython::load(const QString& identifier)
+QList> ProxyPython::load(const PluginExtension& extension)
{
if (!m_Runner) {
return {};
}
- return m_Runner->load(identifier);
+
+ // currently, only handle __init__.py directly in the folder
+ const auto pyIniFile = extension.directory() / "__init__.py";
+ if (!exists(pyIniFile)) {
+ return {};
+ }
+
+ m_ExtensionModules[&extension] = {pyIniFile};
+
+ QList> plugins;
+ for (auto&& pythonModule : m_ExtensionModules[&extension]) {
+ plugins.append(m_Runner->load(pythonModule));
+ }
+
+ return plugins;
}
-void ProxyPython::unload(const QString& identifier)
+void ProxyPython::unload(const PluginExtension& extension)
+{
+ if (!m_Runner) {
+ return;
+ }
+
+ if (auto it = m_ExtensionModules.find(&extension); it != m_ExtensionModules.end()) {
+ for (auto&& pythonModule : it->second) {
+ m_Runner->unload(pythonModule);
+ }
+ m_ExtensionModules.erase(it);
+ }
+}
+
+void ProxyPython::unloadAll()
{
if (m_Runner) {
- return m_Runner->unload(identifier);
+ for (auto& [ext, modules] : m_ExtensionModules) {
+ for (auto& pythonModule : modules) {
+ m_Runner->unload(pythonModule);
+ }
+ }
}
+ m_ExtensionModules.clear();
}
-std::vector ProxyPython::activeProblems() const
+QString ProxyPython::failureMessage(FailureType key)
{
- auto failure = m_LoadFailure;
-
- // don't know how this could happen but wth
- if (m_Runner && !m_Runner->isInitialized()) {
- failure = FailureType::INITIALIZATION;
- }
-
- if (failure != FailureType::NONE) {
- return {static_cast>(failure)};
- }
-
- return {};
-}
-
-QString ProxyPython::shortDescription(unsigned int key) const
-{
- switch (static_cast(key)) {
- case FailureType::SEMICOLON:
- return tr("ModOrganizer path contains a semicolon");
- case FailureType::DLL_NOT_FOUND:
- return tr("Python DLL not found");
- case FailureType::INVALID_DLL:
- return tr("Invalid Python DLL");
- case FailureType::INITIALIZATION:
- return tr("Initializing Python failed");
- default:
- return tr("invalid problem key %1").arg(key);
- }
-}
-
-QString ProxyPython::fullDescription(unsigned int key) const
-{
- switch (static_cast(key)) {
+ switch (key) {
case FailureType::SEMICOLON:
return tr("The path to Mod Organizer (%1) contains a semicolon.
"
"While this is legal on NTFS drives, many applications do not "
@@ -278,13 +187,6 @@ QString ProxyPython::fullDescription(unsigned int key) const
return tr("The initialization of the Python plugin DLL failed, unfortunately "
"without any details.");
default:
- return tr("invalid problem key %1").arg(key);
+ return tr("no failure");
}
}
-
-bool ProxyPython::hasGuidedFix(unsigned int) const
-{
- return false;
-}
-
-void ProxyPython::startGuidedFix(unsigned int) const {}
diff --git a/src/proxy/proxypython.h b/src/proxy/proxypython.h
index 3d52da5..a6f239a 100644
--- a/src/proxy/proxypython.h
+++ b/src/proxy/proxypython.h
@@ -23,45 +23,27 @@ along with python proxy plugin. If not, see .
#include