From fee774db4591337ba27c98d96f69a93f55e32fa9 Mon Sep 17 00:00:00 2001 From: Tannin Date: Sun, 15 Sep 2013 15:20:00 +0200 Subject: [PATCH 1/5] - added support for mod page plugins - re-introduced the integrated browser - added a plugin to download from the tes alliance page - the download list now contains the file description - nexus interface now stores cookies persistently to reduce number of required log-ins --- src/proxy/embedrunner.rc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/proxy/embedrunner.rc b/src/proxy/embedrunner.rc index df824ca..2def6dc 100644 --- a/src/proxy/embedrunner.rc +++ b/src/proxy/embedrunner.rc @@ -1,3 +1,7 @@ #include "resource.h" +#ifdef _DEBUG +IDR_LOADER_DLL BINARY MOVEABLE PURE "..\\..\\pythonRunner\\debug\\pythonRunner.dll" +#else IDR_LOADER_DLL BINARY MOVEABLE PURE "..\\..\\pythonRunner\\release\\pythonRunner.dll" +#endif From 617c76e9f8c333b329cd76fff5f6321575ded050 Mon Sep 17 00:00:00 2001 From: Tannin Date: Sun, 4 May 2014 14:50:01 +0200 Subject: [PATCH 2/5] - main window now has a small view displaying log messages - mod list will now be highlighted when grouping is active is active - download tooltip now supports bbcode markup in the description - bbcode translator will now translate some named colors - algorithm for detection of mod order problems is now more sophisticated - exposed more functionality to python plugins - updated to qt 4.8.6 dlls - bugfix: plugin list wasn't - bugfix: state changes in mod list wasn't always reported - bugfix: loot client will now create necessary directory - bugfix: NCC sometimes used wrong source path for extracting - bugfix: removed noisy debug message --- src/proxy/proxypython.cpp | 2 +- src/runner/pythonRunner.pro | 7 ++ src/runner/pythonpluginwrapper.h | 1 + src/runner/pythonrunner.cpp | 152 ++++++++++++++++++++++++++++++- src/runner/uibasewrappers.h | 43 ++++++++- 5 files changed, 200 insertions(+), 5 deletions(-) diff --git a/src/proxy/proxypython.cpp b/src/proxy/proxypython.cpp index 08d7c5b..8fc330d 100644 --- a/src/proxy/proxypython.cpp +++ b/src/proxy/proxypython.cpp @@ -178,7 +178,7 @@ QString ProxyPython::description() const VersionInfo ProxyPython::version() const { - return VersionInfo(1, 2, 1, VersionInfo::RELEASE_FINAL); + return VersionInfo(1, 3, 0, VersionInfo::RELEASE_FINAL); } bool ProxyPython::isActive() const diff --git a/src/runner/pythonRunner.pro b/src/runner/pythonRunner.pro index 83341cd..1aee80a 100644 --- a/src/runner/pythonRunner.pro +++ b/src/runner/pythonRunner.pro @@ -31,14 +31,21 @@ HEADERS += pythonrunner.h \ CONFIG(debug, debug|release) { + SRCDIR = $$OUT_PWD/debug + DSTDIR = $$PWD/../../outputd LIBS += -L$$OUT_PWD/../uibase/debug } else { + SRCDIR = $$OUT_PWD/release + DSTDIR = $$PWD/../../output LIBS += -L$$OUT_PWD/../uibase/release QMAKE_CXXFLAGS += /Zi QMAKE_LFLAGS += /DEBUG } +SRCDIR ~= s,/,$$QMAKE_DIR_SEP,g +DSTDIR ~= s,/,$$QMAKE_DIR_SEP,g + INCLUDEPATH += "$(BOOSTPATH)" "$$(PYTHONPATH)/include" "$$(PYTHONPATH)/Lib/site-packages/PyQt4/include" LIBS += -L"$$(PYTHONPATH)/libs" -L"$(BOOSTPATH)/stage/lib" LIBS += -lpython27 diff --git a/src/runner/pythonpluginwrapper.h b/src/runner/pythonpluginwrapper.h index 19c93a9..f797d90 100644 --- a/src/runner/pythonpluginwrapper.h +++ b/src/runner/pythonpluginwrapper.h @@ -27,6 +27,7 @@ public: virtual QList settings() const; protected: + void reportPythonError() const; private: diff --git a/src/runner/pythonrunner.cpp b/src/runner/pythonrunner.cpp index 6dc90f2..e4c6a5b 100644 --- a/src/runner/pythonrunner.cpp +++ b/src/runner/pythonrunner.cpp @@ -86,6 +86,15 @@ struct QString_to_python_str } }; +template +struct QFlags_to_int +{ + static PyObject *convert(const QFlags &flags) { + return bpy::incref(bpy::object(static_cast(flags)).ptr()); + } +}; + + struct QString_from_python_str { QString_from_python_str() { @@ -111,7 +120,6 @@ struct QString_from_python_str }; - template struct GuessedValue_converters { @@ -141,7 +149,6 @@ struct GuessedValue_converters } } - static void construct(PyObject *objPtr, bpy::converter::rvalue_from_python_stage1_data* data) { void *storage = ((bpy::converter::rvalue_from_python_storage >*)data)->storage.bytes; GuessedValue *result = new (storage) GuessedValue(); @@ -258,6 +265,7 @@ struct QVariant_from_python_obj } }; + template struct QList_to_python_list { @@ -381,6 +389,7 @@ PyObject *toPyQt(T *objPtr) return bpy::incref(sipObj); } + template struct QClass_converters { @@ -508,6 +517,105 @@ struct QInterface_converters }; +int getArgCount(PyObject *object) { + int result = 0; + PyObject *funcCode = PyObject_GetAttrString(object, "func_code"); + if (funcCode) { + PyObject *argCount = PyObject_GetAttrString(funcCode, "co_argcount"); + if(argCount) { + result = PyInt_AsLong(argCount); + Py_DECREF(argCount); + } + Py_DECREF(funcCode); + } + return result; +} + +struct Functor0_converter +{ + + struct FunctorWrapper + { + FunctorWrapper(boost::python::object callable) : m_Callable(callable) { + } + + void operator()() { + // These GIL calls make it thread safe, may or may not be needed depending on your use case + PyGILState_STATE gstate = PyGILState_Ensure(); + m_Callable(); + PyGILState_Release(gstate); + } + + boost::python::object m_Callable; + }; + + Functor0_converter() + { + bpy::converter::registry::push_back(&convertible, &construct, bpy::type_id>()); + } + + static void *convertible(PyObject *object) + { + if (!PyCallable_Check(object) + || (getArgCount(object) != 0)) { + return NULL; + } + return object; + } + + static void construct(PyObject *object, bpy::converter::rvalue_from_python_stage1_data *data) + { + bpy::object callable(bpy::handle<>(bpy::borrowed(object))); + void *storage = ((bpy::converter::rvalue_from_python_storage>*)data)->storage.bytes; + new (storage) std::function(FunctorWrapper(callable)); + data->convertible = storage; + } +}; + + +template +struct Functor2_converter +{ + + struct FunctorWrapper + { + FunctorWrapper(boost::python::object callable) : m_Callable(callable) { + } + + void operator()(const PAR1 ¶m1, const PAR2 ¶m2) { + // These GIL calls make it thread safe, may or may not be needed depending on your use case + PyGILState_STATE gstate = PyGILState_Ensure(); + m_Callable(param1, param2); + PyGILState_Release(gstate); + } + + boost::python::object m_Callable; + }; + + Functor2_converter() + { + bpy::converter::registry::push_back(&convertible, &construct, bpy::type_id>()); + } + + static void *convertible(PyObject *object) + { + if (!PyCallable_Check(object) + || (getArgCount(object) != 2)) { + return NULL; + } + return object; + } + + static void construct(PyObject *object, bpy::converter::rvalue_from_python_stage1_data *data) + { + bpy::object callable(bpy::handle<>(bpy::borrowed(object))); + void *storage = ((bpy::converter::rvalue_from_python_storage>*)data)->storage.bytes; + new (storage) std::function(FunctorWrapper(callable)); + data->convertible = storage; + } +}; + + BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS(updateWithQuality, MOBase::GuessedValue::update, 2, 2) @@ -578,9 +686,10 @@ BOOST_PYTHON_MODULE(mobase) .def("persistent", bpy::pure_virtual(&IOrganizer::persistent)) .def("setPersistent", bpy::pure_virtual(&IOrganizer::setPersistent)) .def("pluginDataPath", bpy::pure_virtual(&IOrganizer::pluginDataPath)) - .def("installMod", bpy::pure_virtual(&IOrganizer::installMod)) + .def("installMod", bpy::pure_virtual(&IOrganizer::installMod), bpy::return_value_policy()) .def("downloadManager", bpy::pure_virtual(&IOrganizer::downloadManager), bpy::return_value_policy()) .def("pluginList", bpy::pure_virtual(&IOrganizer::pluginList), bpy::return_value_policy()) + .def("modList", bpy::pure_virtual(&IOrganizer::modList), bpy::return_value_policy()) .def("startApplication", bpy::pure_virtual(&IOrganizer::startApplication), bpy::return_value_policy()) .def("onAboutToRun", bpy::pure_virtual(&IOrganizer::onAboutToRun)) .def("refreshModList", bpy::pure_virtual(&IOrganizer::refreshModList)) @@ -600,6 +709,24 @@ BOOST_PYTHON_MODULE(mobase) .def("startDownloadNexusFile", bpy::pure_virtual(&IDownloadManager::startDownloadNexusFile)) .def("downloadPath", bpy::pure_virtual(&IDownloadManager::downloadPath)); + bpy::class_("IInstallationManager") + .def("extractFile", bpy::pure_virtual(&IInstallationManager::extractFile)) + .def("extractFiles", bpy::pure_virtual(&IInstallationManager::extractFiles)) + .def("installArchive", bpy::pure_virtual(&IInstallationManager::installArchive)) + ; + + bpy::class_("IModInterface") + .def("name", bpy::pure_virtual(&IModInterface::name)) + .def("absolutePath", bpy::pure_virtual(&IModInterface::absolutePath)) + .def("setVersion", bpy::pure_virtual(&IModInterface::setVersion)) + .def("setNewestVersion", bpy::pure_virtual(&IModInterface::setNewestVersion)) + .def("setIsEndorsed", bpy::pure_virtual(&IModInterface::setIsEndorsed)) + .def("setNexusID", bpy::pure_virtual(&IModInterface::setNexusID)) + .def("addNexusCategory", bpy::pure_virtual(&IModInterface::addNexusCategory)) + .def("setName", bpy::pure_virtual(&IModInterface::setName)) + .def("remove", bpy::pure_virtual(&IModInterface::remove)) + ; + bpy::enum_("GuessQuality") .value("invalid", MOBase::GUESS_INVALID) .value("fallback", MOBase::GUESS_FALLBACK) @@ -619,6 +746,25 @@ BOOST_PYTHON_MODULE(mobase) bpy::class_("IPluginInstallerCustom") .def("setParentWidget", bpy::pure_virtual(&MOBase::IPluginInstallerCustom::setParentWidget)); + Functor0_converter(); // converter for the onRefreshed-callback + bpy::class_("IPluginList") + .def("state", bpy::pure_virtual(&MOBase::IPluginList::state)) + .def("priority", bpy::pure_virtual(&MOBase::IPluginList::priority)) + .def("loadOrder", bpy::pure_virtual(&MOBase::IPluginList::loadOrder)) + .def("isMaster", bpy::pure_virtual(&MOBase::IPluginList::isMaster)) + .def("origin", bpy::pure_virtual(&MOBase::IPluginList::origin)) + .def("onRefreshed", bpy::pure_virtual(&MOBase::IPluginList::onRefreshed)) + ; + + bpy::to_python_converter>(); + Functor2_converter(); // converter for the onModStateChanged-callback + bpy::class_("IModList") + .def("state", bpy::pure_virtual(&MOBase::IModList::state)) + .def("priority", bpy::pure_virtual(&MOBase::IModList::priority)) + .def("setPriority", bpy::pure_virtual(&MOBase::IModList::setPriority)) + .def("onModStateChanged", bpy::pure_virtual(&MOBase::IModList::onModStateChanged)) + ; + GuessedValue_converters(); bpy::to_python_converter(); diff --git a/src/runner/uibasewrappers.h b/src/runner/uibasewrappers.h index 882272e..dac1a44 100644 --- a/src/runner/uibasewrappers.h +++ b/src/runner/uibasewrappers.h @@ -3,7 +3,9 @@ #ifndef Q_MOC_RUN +#pragma warning (push, 0) #include +#pragma warning (pop) #endif #include @@ -11,6 +13,8 @@ #include #include #include +#include +#include #include "error.h" #include "gilock.h" @@ -133,7 +137,7 @@ struct IOrganizerWrapper: MOBase::IOrganizer, boost::python::wrapperget_override("persistent")(pluginName, key, def); } virtual void setPersistent(const QString &pluginName, const QString &key, const QVariant &value, bool sync = true) { this->get_override("setPersistent")(pluginName, key, value, sync); } virtual QString pluginDataPath() const { return this->get_override("pluginDataPath")(); } - virtual void installMod(const QString &fileName) { this->get_override("installMod")(fileName); } + virtual MOBase::IModInterface *installMod(const QString &fileName) { return this->get_override("installMod")(fileName); } virtual MOBase::IDownloadManager *downloadManager() { return this->get_override("downloadManager")(); } virtual MOBase::IPluginList *pluginList() { return this->get_override("pluginList")(); } virtual MOBase::IModList *modList() { return this->get_override("modList")(); } @@ -155,6 +159,13 @@ private: boost::python::object m_DownloadCompleteHandler; }; +struct IInstallationManagerWrapper: MOBase::IInstallationManager, boost::python::wrapper +{ + virtual QString extractFile(const QString &fileName) { return this->get_override("extractFile")(fileName); } + virtual QStringList extractFiles(const QStringList &files, bool flatten) { return this->get_override("extractFiles")(files, flatten); } + virtual MOBase::IPluginInstaller::EInstallResult installArchive(MOBase::GuessedValue &modName, const QString &archiveFile) { return this->get_override("installArchive")(modName, archiveFile); } +}; + struct IGameInfoWrapper: MOBase::IGameInfo, boost::python::wrapper { virtual Type type() const { return this->get_override("type")(); } @@ -162,5 +173,35 @@ struct IGameInfoWrapper: MOBase::IGameInfo, boost::python::wrapperget_override("binaryName")(); } }; +struct IModInterfaceWrapper: MOBase::IModInterface, boost::python::wrapper +{ + virtual QString name() const { return this->get_override("name")(); } + virtual QString absolutePath() const { return this->get_override("absolutePath")(); } + virtual void setVersion(const MOBase::VersionInfo &version) { this->get_override("setVersion")(version); } + virtual void setNewestVersion(const MOBase::VersionInfo &version) { this->get_override("setNewestVersion")(version); } + virtual void setIsEndorsed(bool endorsed) { this->get_override("setIsEndorsed")(endorsed); } + virtual void setNexusID(int nexusID) { this->get_override("setNexusID")(nexusID); } + virtual void addNexusCategory(int categoryID) { this->get_override("addNexusCategory")(categoryID); } + virtual bool setName(const QString &name) { return this->get_override("setName")(name); } + virtual bool remove() { return this->get_override("remove")(); } +}; + + +struct IPluginListWrapper: MOBase::IPluginList, boost::python::wrapper { + virtual PluginState state(const QString &name) const { return this->get_override("state")(name); } + virtual int priority(const QString &name) const { return this->get_override("priority")(name); } + virtual int loadOrder(const QString &name) const { return this->get_override("loadOrder")(name); } + virtual bool isMaster(const QString &name) const { return this->get_override("isMaster")(name); } + virtual QString origin(const QString &name) const { return this->get_override("origin")(name); } + virtual bool onRefreshed(const std::function &callback) { return this->get_override("onRefreshed")(callback); } +}; + + +struct IModListWrapper: MOBase::IModList, boost::python::wrapper { + virtual ModStates state(const QString &name) const{ return this->get_override("state")(name); } + virtual int priority(const QString &name) const{ return this->get_override("priority")(name); } + virtual bool setPriority(const QString &name, int newPriority){ return this->get_override("setPriority")(name, newPriority); } + virtual bool onModStateChanged(const std::function &func) { return this->get_override("onModStateChanged")(func); } +}; #endif // UIBASEWRAPPERS_H From f56c6054799bc491073ad39d77b208534e078bcc Mon Sep 17 00:00:00 2001 From: Tannin Date: Sun, 4 May 2014 16:13:35 +0200 Subject: [PATCH 3/5] - bugfix: endless loop in detection of mod order problems --- src/runner/pythonpluginwrapper.cpp | 1 - src/runner/pythonrunner.cpp | 41 +++++++++++++++++------------- src/runner/uibasewrappers.h | 8 +++--- 3 files changed, 28 insertions(+), 22 deletions(-) diff --git a/src/runner/pythonpluginwrapper.cpp b/src/runner/pythonpluginwrapper.cpp index 2208f46..1c0677e 100644 --- a/src/runner/pythonpluginwrapper.cpp +++ b/src/runner/pythonpluginwrapper.cpp @@ -99,7 +99,6 @@ QList PythonPluginWrapper::settings() const try { boost::python::object l = m_SettingsFunction(); if (!l.is_none()) { -// boost::python::list l = extract(temp); for (int i = 0; i < boost::python::len(l); ++i) { result.append(extract(l[i])); } diff --git a/src/runner/pythonrunner.cpp b/src/runner/pythonrunner.cpp index e4c6a5b..d5c3bbd 100644 --- a/src/runner/pythonrunner.cpp +++ b/src/runner/pythonrunner.cpp @@ -540,10 +540,8 @@ struct Functor0_converter } void operator()() { - // These GIL calls make it thread safe, may or may not be needed depending on your use case - PyGILState_STATE gstate = PyGILState_Ensure(); + GILock lock; m_Callable(); - PyGILState_Release(gstate); } boost::python::object m_Callable; @@ -583,10 +581,8 @@ struct Functor2_converter } void operator()(const PAR1 ¶m1, const PAR2 ¶m2) { - // These GIL calls make it thread safe, may or may not be needed depending on your use case - PyGILState_STATE gstate = PyGILState_Ensure(); + GILock lock; m_Callable(param1, param2); - PyGILState_Release(gstate); } boost::python::object m_Callable; @@ -643,32 +639,37 @@ BOOST_PYTHON_MODULE(mobase) .value("candidate", MOBase::VersionInfo::RELEASE_CANDIDATE) .value("beta", MOBase::VersionInfo::RELEASE_BETA) .value("alpha", MOBase::VersionInfo::RELEASE_ALPHA) - .value("prealpha", MOBase::VersionInfo::RELEASE_PREALPHA); + .value("prealpha", MOBase::VersionInfo::RELEASE_PREALPHA) + ; bpy::enum_("InstallResult") .value("success", MOBase::IPluginInstaller::RESULT_SUCCESS) .value("failed", MOBase::IPluginInstaller::RESULT_FAILED) .value("canceled", MOBase::IPluginInstaller::RESULT_CANCELED) .value("manualRequested", MOBase::IPluginInstaller::RESULT_MANUALREQUESTED) - .value("notAttempted", MOBase::IPluginInstaller::RESULT_NOTATTEMPTED); + .value("notAttempted", MOBase::IPluginInstaller::RESULT_NOTATTEMPTED) + ; bpy::enum_("GameType") .value("oblivion", MOBase::IGameInfo::TYPE_OBLIVION) .value("fallout3", MOBase::IGameInfo::TYPE_FALLOUT3) .value("falloutnv", MOBase::IGameInfo::TYPE_FALLOUTNV) - .value("skyrim", MOBase::IGameInfo::TYPE_SKYRIM); + .value("skyrim", MOBase::IGameInfo::TYPE_SKYRIM) + ; bpy::class_("VersionInfo") .def(bpy::init()) .def("parse", &MOBase::VersionInfo::parse) - .def("canonicalString", &MOBase::VersionInfo::canonicalString); + .def("canonicalString", &MOBase::VersionInfo::canonicalString) + ; bpy::class_("PluginSetting", bpy::init()); bpy::class_("GameInfo") .def("type", bpy::pure_virtual(&MOBase::IGameInfo::type)) .def("path", bpy::pure_virtual(&MOBase::IGameInfo::path)) - .def("binaryName", bpy::pure_virtual(&MOBase::IGameInfo::binaryName)); + .def("binaryName", bpy::pure_virtual(&MOBase::IGameInfo::binaryName)) + ; bpy::class_("IOrganizer") .def("gameInfo", bpy::pure_virtual(&MOBase::IOrganizer::gameInfo), bpy::return_value_policy()) @@ -702,12 +703,14 @@ BOOST_PYTHON_MODULE(mobase) .def("requestDownloadURL", &ModRepositoryBridgeWrapper::requestDownloadURL) .def("requestToggleEndorsement", &ModRepositoryBridgeWrapper::requestToggleEndorsement) .def("onFilesAvailable", &ModRepositoryBridgeWrapper::onFilesAvailable) - .def("onRequestFailed", &ModRepositoryBridgeWrapper::onRequestFailed); + .def("onRequestFailed", &ModRepositoryBridgeWrapper::onRequestFailed) + ; bpy::class_("IDownloadManager") .def("startDownloadURLs", bpy::pure_virtual(&IDownloadManager::startDownloadURLs)) .def("startDownloadNexusFile", bpy::pure_virtual(&IDownloadManager::startDownloadNexusFile)) - .def("downloadPath", bpy::pure_virtual(&IDownloadManager::downloadPath)); + .def("downloadPath", bpy::pure_virtual(&IDownloadManager::downloadPath)) + ; bpy::class_("IInstallationManager") .def("extractFile", bpy::pure_virtual(&IInstallationManager::extractFile)) @@ -733,18 +736,22 @@ BOOST_PYTHON_MODULE(mobase) .value("good", MOBase::GUESS_GOOD) .value("meta", MOBase::GUESS_META) .value("preset", MOBase::GUESS_PRESET) - .value("user", MOBase::GUESS_USER); + .value("user", MOBase::GUESS_USER) + ; bpy::class_, boost::noncopyable>("GuessedString") .def("update", static_cast &(GuessedValue::*)(const QString&, EGuessQuality)>(&GuessedValue::update), bpy::return_value_policy(), updateWithQuality()) - .def("variants", &MOBase::GuessedValue::variants, bpy::return_value_policy()); + .def("variants", &MOBase::GuessedValue::variants, bpy::return_value_policy()) + ; bpy::class_("IPluginTool") - .def("setParentWidget", bpy::pure_virtual(&MOBase::IPluginTool::setParentWidget)); + .def("setParentWidget", bpy::pure_virtual(&MOBase::IPluginTool::setParentWidget)) + ; bpy::class_("IPluginInstallerCustom") - .def("setParentWidget", bpy::pure_virtual(&MOBase::IPluginInstallerCustom::setParentWidget)); + .def("setParentWidget", bpy::pure_virtual(&MOBase::IPluginInstallerCustom::setParentWidget)) + ; Functor0_converter(); // converter for the onRefreshed-callback bpy::class_("IPluginList") diff --git a/src/runner/uibasewrappers.h b/src/runner/uibasewrappers.h index dac1a44..8f96647 100644 --- a/src/runner/uibasewrappers.h +++ b/src/runner/uibasewrappers.h @@ -86,13 +86,13 @@ private slots: qCritical("no handler connected"); return; } -// try { + try { GILock lock; m_FilesAvailableHandler(modID, userData, resultData); -// } catch (const boost::python::error_already_set&) { + } catch (const boost::python::error_already_set&) { // qDebug("error"); - //reportPythonError(); -// } + reportPythonError(); + } } void requestFailed(int modID, int fileID, QVariant userData, const QString &errorMessage) From 15e635741c8c115dbb3a8f4cbdc57e57de168431 Mon Sep 17 00:00:00 2001 From: Tannin Date: Tue, 13 May 2014 21:20:44 +0200 Subject: [PATCH 4/5] - mod list context menu split into two menus (one for whole list, one for selected mods) - added option to combine category filters using "or" - added context menu option for deselecting category filters - slightly changed ui on the category filters - added a sample plugin for cpp that can be built without building the rest of MO - simple installer can now be configured to run without any user interaction - extended interface for python plugins - iorganizer implementation moved out of the main window - nexus requests from plugins will now be identified in the user agent - bugfix: shortcuts created from MO used the wrong working directory - bugfix: deactivation of bsas didn't stick - bugfix: file hiding mechanism wasn't active - bugfix: executables linked on the toolbar couldn't be removed if the executable was removed first - bugfix: the endorsement-filter couldn't be combined with other filters - bugfix: python interface to repository bridge was broken --- src/proxy/proxypython.cpp | 2 +- src/runner/pythonrunner.cpp | 93 +++++++++++++++++++++++--------- src/runner/uibasewrappers.h | 103 ++++++++++++++++++++++++++++++++---- 3 files changed, 162 insertions(+), 36 deletions(-) diff --git a/src/proxy/proxypython.cpp b/src/proxy/proxypython.cpp index 8fc330d..5d72e29 100644 --- a/src/proxy/proxypython.cpp +++ b/src/proxy/proxypython.cpp @@ -178,7 +178,7 @@ QString ProxyPython::description() const VersionInfo ProxyPython::version() const { - return VersionInfo(1, 3, 0, VersionInfo::RELEASE_FINAL); + return VersionInfo(1, 3, 1, VersionInfo::RELEASE_FINAL); } bool ProxyPython::isActive() const diff --git a/src/runner/pythonrunner.cpp b/src/runner/pythonrunner.cpp index d5c3bbd..16e6dda 100644 --- a/src/runner/pythonrunner.cpp +++ b/src/runner/pythonrunner.cpp @@ -177,6 +177,7 @@ struct QVariant_to_python_obj static PyObject *convert(const QVariant &var) { switch (var.type()) { case QVariant::Int: return PyLong_FromLong(var.toInt()); + case QVariant::UInt: return PyLong_FromUnsignedLong(var.toUInt()); case QVariant::Bool: return PyBool_FromLong(var.toBool()); case QVariant::String: return bpy::incref(bpy::object(var.toString().toUtf8().constData()).ptr()); case QVariant::List: { @@ -187,8 +188,18 @@ struct QVariant_to_python_obj } return result; } break; + case QVariant::Map: { + QVariantMap map = var.toMap(); + PyObject *result = PyDict_New(); + QMapIterator iter(map); + while (iter.hasNext()) { + iter.next(); + PyDict_SetItem(result, convert(iter.key()), convert(iter.value())); + } + return result; + } break; default: { - PyErr_SetString(PyExc_TypeError, "type unsupported"); + PyErr_Format(PyExc_TypeError, "type unsupported: %d", var.type()); throw bpy::error_already_set(); } break; } @@ -356,9 +367,21 @@ static const sipAPIDef *sipAPI() } +struct IModRepositoryBridge_to_python +{ + static PyObject *convert(IModRepositoryBridge *bridge) + { + ModRepositoryBridgeWrapper wrapper(bridge); + + return bpy::incref(bpy::object(wrapper).ptr()); + } +}; + + + template struct MetaData; -template <> struct MetaData { static const char *className() { return "MOBase::INexusBridge"; } }; +template <> struct MetaData { static const char *className() { return "QObject"; } }; template <> struct MetaData { static const char *className() { return "QObject"; } }; template <> struct MetaData { static const char *className() { return "QObject"; } }; template <> struct MetaData { static const char *className() { return "QWidget"; } }; @@ -373,7 +396,6 @@ PyObject *toPyQt(T *objPtr) qDebug("no input object"); return bpy::incref(Py_None); } - const sipTypeDef *type = sipAPI()->api_find_type(MetaData::className()); if (type == NULL) { @@ -624,13 +646,12 @@ BOOST_PYTHON_MODULE(mobase) bpy::to_python_converter(); QString_from_python_str(); - QClass_converters(); + //QClass_converters(); QClass_converters(); - //QClass_converters(); QClass_converters(); - QInterface_converters(); QInterface_converters(); + bpy::def("toPyQt", &toPyQt); bpy::def("toPyQt", &toPyQt); @@ -642,6 +663,14 @@ BOOST_PYTHON_MODULE(mobase) .value("prealpha", MOBase::VersionInfo::RELEASE_PREALPHA) ; + bpy::enum_("VersionScheme") + .value("discover", MOBase::VersionInfo::SCHEME_DISCOVER) + .value("regular", MOBase::VersionInfo::SCHEME_REGULAR) + .value("decimalmark", MOBase::VersionInfo::SCHEME_DECIMALMARK) + .value("numbersandletters", MOBase::VersionInfo::SCHEME_NUMBERSANDLETTERS) + .value("date", MOBase::VersionInfo::SCHEME_DATE) + ; + bpy::enum_("InstallResult") .value("success", MOBase::IPluginInstaller::RESULT_SUCCESS) .value("failed", MOBase::IPluginInstaller::RESULT_FAILED) @@ -657,31 +686,34 @@ BOOST_PYTHON_MODULE(mobase) .value("skyrim", MOBase::IGameInfo::TYPE_SKYRIM) ; - bpy::class_("VersionInfo") - .def(bpy::init()) - .def("parse", &MOBase::VersionInfo::parse) - .def("canonicalString", &MOBase::VersionInfo::canonicalString) + bpy::class_("VersionInfo") + .def(bpy::init()) + .def(bpy::init()) + .def(bpy::init()) + .def(bpy::init()) + .def("parse", &VersionInfo::parse) + .def("canonicalString", &VersionInfo::canonicalString) ; - bpy::class_("PluginSetting", bpy::init()); + bpy::class_("PluginSetting", bpy::init()); bpy::class_("GameInfo") - .def("type", bpy::pure_virtual(&MOBase::IGameInfo::type)) - .def("path", bpy::pure_virtual(&MOBase::IGameInfo::path)) - .def("binaryName", bpy::pure_virtual(&MOBase::IGameInfo::binaryName)) + .def("type", bpy::pure_virtual(&IGameInfo::type)) + .def("path", bpy::pure_virtual(&IGameInfo::path)) + .def("binaryName", bpy::pure_virtual(&IGameInfo::binaryName)) ; bpy::class_("IOrganizer") - .def("gameInfo", bpy::pure_virtual(&MOBase::IOrganizer::gameInfo), bpy::return_value_policy()) - //.def("createNexusBridge", bpy::pure_virtual(&MOBase::IOrganizer::createNexusBridge), bpy::return_value_policy()) - .def("profileName", bpy::pure_virtual(&MOBase::IOrganizer::profileName)) - .def("profilePath", bpy::pure_virtual(&MOBase::IOrganizer::profilePath)) - .def("downloadsPath", bpy::pure_virtual(&MOBase::IOrganizer::downloadsPath)) - .def("appVersion", bpy::pure_virtual(&MOBase::IOrganizer::appVersion)) - .def("getMod", bpy::pure_virtual(&MOBase::IOrganizer::getMod), bpy::return_value_policy()) - .def("createMod", bpy::pure_virtual(&MOBase::IOrganizer::createMod), bpy::return_value_policy()) - .def("removeMod", bpy::pure_virtual(&MOBase::IOrganizer::removeMod)) - .def("modDataChanged", bpy::pure_virtual(&MOBase::IOrganizer::modDataChanged)) + .def("gameInfo", bpy::pure_virtual(&IOrganizer::gameInfo), bpy::return_value_policy()) + .def("createNexusBridge", bpy::pure_virtual(&IOrganizer::createNexusBridge), bpy::return_value_policy()) + .def("profileName", bpy::pure_virtual(&IOrganizer::profileName)) + .def("profilePath", bpy::pure_virtual(&IOrganizer::profilePath)) + .def("downloadsPath", bpy::pure_virtual(&IOrganizer::downloadsPath)) + .def("appVersion", bpy::pure_virtual(&IOrganizer::appVersion)) + .def("getMod", bpy::pure_virtual(&IOrganizer::getMod), bpy::return_value_policy()) + .def("createMod", bpy::pure_virtual(&IOrganizer::createMod), bpy::return_value_policy()) + .def("removeMod", bpy::pure_virtual(&IOrganizer::removeMod)) + .def("modDataChanged", bpy::pure_virtual(&IOrganizer::modDataChanged)) .def("pluginSetting", bpy::pure_virtual(&IOrganizer::pluginSetting)) .def("setPluginSetting", bpy::pure_virtual(&IOrganizer::pluginSetting)) .def("persistent", bpy::pure_virtual(&IOrganizer::persistent)) @@ -697,15 +729,26 @@ BOOST_PYTHON_MODULE(mobase) ; bpy::class_("ModRepositoryBridge") + .def(bpy::init()) .def("requestDescription", &ModRepositoryBridgeWrapper::requestDescription) .def("requestFiles", &ModRepositoryBridgeWrapper::requestFiles) .def("requestFileInfo", &ModRepositoryBridgeWrapper::requestFileInfo) - .def("requestDownloadURL", &ModRepositoryBridgeWrapper::requestDownloadURL) .def("requestToggleEndorsement", &ModRepositoryBridgeWrapper::requestToggleEndorsement) .def("onFilesAvailable", &ModRepositoryBridgeWrapper::onFilesAvailable) + .def("onFileInfoAvailable", &ModRepositoryBridgeWrapper::onFileInfoAvailable) + .def("onDescriptionAvailable", &ModRepositoryBridgeWrapper::onDescriptionAvailable) + .def("onEndorsementToggled", &ModRepositoryBridgeWrapper::onEndorsementToggled) .def("onRequestFailed", &ModRepositoryBridgeWrapper::onRequestFailed) ; + bpy::class_("IModRepositoryBridge") + .def("requestDescription", bpy::pure_virtual(&IModRepositoryBridge::requestDescription)) + .def("requestFiles", bpy::pure_virtual(&IModRepositoryBridge::requestFiles)) + .def("requestFileInfo", bpy::pure_virtual(&IModRepositoryBridge::requestFileInfo)) + .def("requestDownloadURL", bpy::pure_virtual(&IModRepositoryBridge::requestDownloadURL)) + .def("requestToggleEndorsement", bpy::pure_virtual(&IModRepositoryBridge::requestToggleEndorsement)) + ; + bpy::class_("IDownloadManager") .def("startDownloadURLs", bpy::pure_virtual(&IDownloadManager::startDownloadURLs)) .def("startDownloadNexusFile", bpy::pure_virtual(&IDownloadManager::startDownloadNexusFile)) diff --git a/src/runner/uibasewrappers.h b/src/runner/uibasewrappers.h index 8f96647..2999e4f 100644 --- a/src/runner/uibasewrappers.h +++ b/src/runner/uibasewrappers.h @@ -55,8 +55,6 @@ public: { m_Wrapped->requestFiles(modID, userData); } void requestFileInfo(int modID, int fileID, QVariant userData) { m_Wrapped->requestFileInfo(modID, fileID, userData); } - void requestDownloadURL(int modID, int fileID, QVariant userData) - { m_Wrapped->requestDownloadURL(modID, fileID, userData); } void requestToggleEndorsement(int modID, bool endorse, QVariant userData) { m_Wrapped->requestToggleEndorsement(modID, endorse, userData); } @@ -67,6 +65,27 @@ public: Qt::UniqueConnection); } + void onDescriptionAvailable(boost::python::object callback) { + m_DescriptionAvailableHandler = callback; + connect(m_Wrapped, SIGNAL(descriptionAvailable(int,QVariant,QVariant)), + this, SLOT(descriptionAvailable(int,QVariant,QVariant)), + Qt::UniqueConnection); + } + + void onFileInfoAvailable(boost::python::object callback) { + m_FileInfoHandler = callback; + connect(m_Wrapped, SIGNAL(fileInfoAvailable(int,int,QVariant,QVariant)), + this, SLOT(fileInfoAvailable(int,int,QVariant,QVariant)), + Qt::UniqueConnection); + } + + void onEndorsementToggled(boost::python::object callback) { + m_EndorsementToggledHandler = callback; + connect(m_Wrapped, SIGNAL(endorsementToggled(int,QVariant,QVariant)), + this, SLOT(endorsementToggled(int,QVariant,QVariant)), + Qt::UniqueConnection); + } + void onRequestFailed(boost::python::object callback) { m_FailedHandler = callback; connect(m_Wrapped, SIGNAL(requestFailed(int,int,QVariant,QString)), @@ -90,11 +109,69 @@ private slots: GILock lock; m_FilesAvailableHandler(modID, userData, resultData); } catch (const boost::python::error_already_set&) { -// qDebug("error"); reportPythonError(); } } + void descriptionAvailable(int modID, QVariant userData, const QVariant resultData) + { + try { + if (m_DescriptionAvailableHandler.is_none()) { + qCritical("no handler connected"); + return; + } + try { + GILock lock; + m_DescriptionAvailableHandler(modID, userData, resultData); + } catch (const boost::python::error_already_set&) { + reportPythonError(); + } + } catch (const std::exception &e) { + qCritical("failed to report event: %s", e.what()); + } catch (...) { + qCritical("failed to report event"); + } + } + + void fileInfoAvailable(int modID, int fileID, QVariant userData, const QVariant resultData) { + try { + if (m_FileInfoHandler.is_none()) { + qCritical("no handler connected"); + return; + } + try { + GILock lock; + m_FileInfoHandler(modID, fileID, userData, resultData); + } catch (const boost::python::error_already_set&) { + reportPythonError(); + } + } catch (const std::exception &e) { + qCritical("failed to report event: %s", e.what()); + } catch (...) { + qCritical("failed to report event"); + } + } + + void endorsementToggled(int modID, QVariant userData, const QVariant resultData) + { + try { + if (m_EndorsementToggledHandler.is_none()) { + qCritical("no handler connected"); + return; + } + try { + GILock lock; + m_EndorsementToggledHandler(modID, userData, resultData); + } catch (const boost::python::error_already_set&) { + reportPythonError(); + } + } catch (const std::exception &e) { + qCritical("failed to report event: %s", e.what()); + } catch (...) { + qCritical("failed to report event"); + } + } + void requestFailed(int modID, int fileID, QVariant userData, const QString &errorMessage) { try { @@ -109,21 +186,20 @@ private: MOBase::IModRepositoryBridge *m_Wrapped; boost::python::object m_FilesAvailableHandler; + boost::python::object m_DescriptionAvailableHandler; + boost::python::object m_FileInfoHandler; + boost::python::object m_EndorsementToggledHandler; boost::python::object m_FailedHandler; }; - struct IOrganizerWrapper: MOBase::IOrganizer, boost::python::wrapper { virtual MOBase::IGameInfo &gameInfo() const { MOBase::IGameInfo *result = this->get_override("gameInfo")(); return *result; } - virtual MOBase::IModRepositoryBridge *createNexusBridge() const { - return this->get_override("createNexusBridge")(); - } - + virtual MOBase::IModRepositoryBridge *createNexusBridge() const { return this->get_override("createNexusBridge")(); } virtual QString profileName() const { return this->get_override("profileName")(); } virtual QString profilePath() const { return this->get_override("profilePath")(); } virtual QString downloadsPath() const { return this->get_override("downloadsPath")(); } @@ -155,8 +231,15 @@ struct IDownloadManagerWrapper: MOBase::IDownloadManager, boost::python::wrapper virtual int startDownloadURLs(const QStringList &urls) { return this->get_override("downloadURLs")(urls); } virtual int startDownloadNexusFile(int modID, int fileID) { return this->get_override("downloadNexusFile")(modID, fileID); } virtual QString downloadPath(int id) { return this->get_override("downloadPath")(id); } -private: - boost::python::object m_DownloadCompleteHandler; +}; + +struct IModRepositoryBridgeWrapper: MOBase::IModRepositoryBridge, boost::python::wrapper +{ + virtual void requestDescription(int modID, QVariant userData) { this->get_override("requestDescription")(modID, userData); } + virtual void requestFiles(int modID, QVariant userData) { this->get_override("requestFiles")(modID, userData); } + virtual void requestFileInfo(int modID, int fileID, QVariant userData) { this->get_override("requestFileInfo")(modID, fileID, userData); } + virtual void requestDownloadURL(int modID, int fileID, QVariant userData) { this->get_override("requestDownloadURL")(modID, fileID, userData); } + virtual void requestToggleEndorsement(int modID, bool endorse, QVariant userData) { this->get_override("requestToggleEndorsement")(modID, endorse, userData); } }; struct IInstallationManagerWrapper: MOBase::IInstallationManager, boost::python::wrapper From 46a0ce9e386172014756d50d3dc5b925faa4be03 Mon Sep 17 00:00:00 2001 From: Tannin Date: Sun, 25 May 2014 15:39:45 +0200 Subject: [PATCH 5/5] - files in bsas are now only displayed in the data tab if they are managed by mo - number of problems detected by MO is now displayed as a badge on the icon - rephrased the explanation text on the Archives tab. unchecked plugin-loaded bsas no longer prompt a warning - bsa extraction is now handled in a plugin - added a way for plugins to react to mod installation - re-enabled the automatic fix for asset order problems - bugfix: In some cases when a download wasn't started successfully the download urls weren't stored in the meta file so no resume was possible - bugfix: MO tried to resume downloads when it didn't have and download urls - bugfix: downloads couldn't be paused if the download was already broken on the network layer - bugfix: download managear did not recognize a file as downloaded if the download completed before signals were hooked up - bugfix: in-place file replacement was re-broken --- src/runner/pythonrunner.cpp | 1 + src/runner/uibasewrappers.h | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/runner/pythonrunner.cpp b/src/runner/pythonrunner.cpp index 16e6dda..bcd00e0 100644 --- a/src/runner/pythonrunner.cpp +++ b/src/runner/pythonrunner.cpp @@ -725,6 +725,7 @@ BOOST_PYTHON_MODULE(mobase) .def("modList", bpy::pure_virtual(&IOrganizer::modList), bpy::return_value_policy()) .def("startApplication", bpy::pure_virtual(&IOrganizer::startApplication), bpy::return_value_policy()) .def("onAboutToRun", bpy::pure_virtual(&IOrganizer::onAboutToRun)) + .def("onModInstalled", bpy::pure_virtual(&IOrganizer::onModInstalled)) .def("refreshModList", bpy::pure_virtual(&IOrganizer::refreshModList)) ; diff --git a/src/runner/uibasewrappers.h b/src/runner/uibasewrappers.h index 2999e4f..31df36d 100644 --- a/src/runner/uibasewrappers.h +++ b/src/runner/uibasewrappers.h @@ -222,8 +222,9 @@ struct IOrganizerWrapper: MOBase::IOrganizer, boost::python::wrapper &filter) const { return this->get_override("findFiles")(path, filter); } virtual QList findFileInfos(const QString &path, const std::function &filter) const { return this->get_override("findFileInfos")(path, filter); } virtual HANDLE startApplication(const QString &executable, const QStringList &args = QStringList(), const QString &cwd = "", const QString &profile = "") { return this->get_override("startApplication")(executable, args, cwd, profile); } - virtual bool onAboutToRun(const std::function &func) { return this->get_override("onAboutToRun")(func); } virtual void refreshModList(bool saveChanges = true) { this->get_override("refreshModList")(saveChanges); } + virtual bool onAboutToRun(const std::function &func) { return this->get_override("onAboutToRun")(func); } + virtual bool onModInstalled(const std::function &func) { return this->get_override("onModInstalled")(func); } }; struct IDownloadManagerWrapper: MOBase::IDownloadManager, boost::python::wrapper