Extended MODL / direct download handling (#2384)

* Extended MODL / direct download handling
- name, modname, version, and source options added to download command
- nxmhandler init adds schemas and MODL entry with default launch args
- Add MODL register button to general tab
- On window display, call meta function to trigger both registrations
This commit is contained in:
Jeremy Rimpo
2026-05-03 03:04:49 -05:00
committed by GitHub
parent 2e393aa3cc
commit ef7499aade
11 changed files with 162 additions and 59 deletions
+30 -1
View File
@@ -853,6 +853,18 @@ Command::Meta DownloadFileCommand::meta() const
return {"download", "downloads a file", "URL", ""};
}
po::options_description DownloadFileCommand::getVisibleOptions() const
{
po::options_description d;
d.add_options()("name,n", po::value<std::string>(), "(optional) the download name")(
"modname,m", po::value<std::string>(), "(optional) the mod name")(
"version,v", po::value<std::string>(), "(optional) the download / mod version")(
"source,s", po::value<std::string>(), "(optional) the download source");
return d;
}
po::options_description DownloadFileCommand::getInternalOptions() const
{
po::options_description d;
@@ -879,16 +891,33 @@ bool DownloadFileCommand::canForwardToPrimary() const
std::optional<int> DownloadFileCommand::runPostOrganizer(OrganizerCore& core)
{
const QString url = QString::fromStdString(vm()["URL"].as<std::string>());
QString name, modName, version, source;
if (!url.startsWith("https://")) {
reportError(QObject::tr("Download URL must start with https://"));
return 1;
}
if (vm().count("name")) {
name = QString::fromStdString(vm()["name"].as<std::string>());
}
if (vm().count("modname")) {
modName = QString::fromStdString(vm()["modname"].as<std::string>());
}
if (vm().count("version")) {
version = QString::fromStdString(vm()["version"].as<std::string>());
}
if (vm().count("source")) {
source = QString::fromStdString(vm()["source"].as<std::string>());
}
log::debug("starting direct download from command line: {}", url.toStdString());
MessageDialog::showMessage(QObject::tr("Download started"), qApp->activeWindow(),
false);
core.downloadManager()->startDownloadURLs(QStringList() << url);
core.downloadManager()->startDownloadURLWithMeta(url, name, modName, version, source);
return {};
}
+1
View File
@@ -212,6 +212,7 @@ class DownloadFileCommand : public Command
protected:
Meta meta() const override;
po::options_description getVisibleOptions() const override;
po::options_description getInternalOptions() const override;
po::positional_options_description getPositional() const override;
+16
View File
@@ -1986,6 +1986,22 @@ int DownloadManager::startDownloadURLs(const QStringList& urls)
return m_ActiveDownloads.size() - 1;
}
int DownloadManager::startDownloadURLWithMeta(const QString& url, const QString& name,
const QString& modName,
const QString& version,
const QString& source)
{
ModRepositoryFileInfo info;
info.name = name;
info.modName = modName;
info.version = version;
info.repository = source;
if (!addDownload(QStringList(url), "", -1, -1, &info)) {
return 0;
}
return m_ActiveDownloads.size() - 1;
}
int DownloadManager::startDownloadNexusFile(const QString& gameName, int modID,
int fileID)
{
+3
View File
@@ -410,6 +410,9 @@ public:
public: // IDownloadManager interface:
int startDownloadURLs(const QStringList& urls);
int startDownloadURLWithMeta(const QString& url, const QString& name,
const QString& modName, const QString& version,
const QString& source);
int startDownloadNexusFile(const QString& gameName, int modID, int fileID);
QString downloadPath(int id);
+1 -1
View File
@@ -1356,7 +1356,7 @@ void MainWindow::showEvent(QShowEvent* event)
m_OrganizerCore.settings().widgets().restoreIndex(ui->groupCombo);
m_OrganizerCore.settings().nexus().registerAsNXMHandler(false);
m_OrganizerCore.settings().registerDownloadHandlers(false);
m_WasVisible = true;
updateProblemsButton();
+30 -2
View File
@@ -417,6 +417,35 @@ void Settings::setKeepBackupOnInstall(bool b)
set(m_Settings, "General", "backup_install", b);
}
void Settings::registerDownloadHandlers(bool force)
{
m_Nexus.registerAsNXMHandler(force);
registerAsMODLHandler(force);
}
void Settings::registerAsMODLHandler(bool force)
{
const auto nxmPath = QCoreApplication::applicationDirPath() + "/" +
QString::fromStdWString(AppConfig::nxmHandlerExe());
const auto executable = QCoreApplication::applicationFilePath();
QString mode = force ? "forcereg" : "reg";
QString parameters = mode + " modl " + m_Game.plugin()->gameShortName();
for (const QString& altGame : m_Game.plugin()->validShortNames()) {
parameters += "," + altGame;
}
parameters +=
" \"" + executable + "\" \"-n %name% -m %modname% -v %version% -s %source%\"";
const auto r = shell::Execute(nxmPath, parameters);
if (!r.success()) {
QMessageBox::critical(
nullptr, QObject::tr("Failed"),
QObject::tr("Failed to start the helper application: %1").arg(r.toString()));
}
}
GameSettings& Settings::game()
{
return m_Game;
@@ -1992,14 +2021,13 @@ void NexusSettings::registerAsNXMHandler(bool force)
const auto executable = QCoreApplication::applicationFilePath();
QString mode = force ? "forcereg" : "reg";
QString parameters = mode + " " + m_Parent.game().plugin()->gameShortName();
QString parameters = mode + " nxm " + m_Parent.game().plugin()->gameShortName();
for (const QString& altGame : m_Parent.game().plugin()->validShortNames()) {
parameters += "," + altGame;
}
parameters += " \"" + executable + "\"";
const auto r = shell::Execute(nxmPath, parameters);
if (!r.success()) {
QMessageBox::critical(
nullptr, QObject::tr("Failed"),
+14
View File
@@ -809,6 +809,20 @@ public:
unsigned int motdHash() const;
void setMotdHash(unsigned int hash);
// registers MO as the handler for download links
//
// if 'force' is true, the registration dialog will be shown even if the user
// said earlier not to
//
void registerDownloadHandlers(bool force);
// registers MO as the handler for modl links
//
// if 'force' is true, the registration dialog will be shown even if the user
// said earlier not to
//
void registerAsMODLHandler(bool force);
// whether archives should be parsed to show conflicts and contents
//
bool archiveParsing() const;
+61 -48
View File
@@ -31,10 +31,10 @@
#generalScrollAreaWidgetContents { background-color: transparent; }</string>
</property>
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
<enum>QFrame::Shape::NoFrame</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Plain</enum>
<enum>QFrame::Shadow::Plain</enum>
</property>
<property name="widgetResizable">
<bool>true</bool>
@@ -85,7 +85,7 @@
<item row="0" column="2">
<spacer name="horizontalSpacer_4">
<property name="orientation">
<enum>Qt::Horizontal</enum>
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
@@ -166,6 +166,19 @@
</property>
</widget>
</item>
<item alignment="Qt::AlignmentFlag::AlignLeft">
<widget class="QPushButton" name="associateModlButton">
<property name="maximumSize">
<size>
<width>300</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>Associate MODL Download Links</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
@@ -348,7 +361,7 @@
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
@@ -364,7 +377,7 @@
<item>
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
<enum>Qt::Orientation::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
@@ -391,7 +404,7 @@
<string>Style</string>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
<set>Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignTop</set>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_13">
<item>
@@ -414,7 +427,7 @@
<item>
<spacer name="horizontalSpacer_5">
<property name="orientation">
<enum>Qt::Horizontal</enum>
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
@@ -438,16 +451,16 @@
<item>
<widget class="ColorTable" name="colorTable">
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
<set>QAbstractItemView::EditTrigger::NoEditTriggers</set>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::SingleSelection</enum>
<enum>QAbstractItemView::SelectionMode::SingleSelection</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
<enum>QAbstractItemView::SelectionBehavior::SelectRows</enum>
</property>
<property name="verticalScrollMode">
<enum>QAbstractItemView::ScrollPerPixel</enum>
<enum>QAbstractItemView::ScrollMode::ScrollPerPixel</enum>
</property>
<property name="cornerButtonEnabled">
<bool>false</bool>
@@ -610,10 +623,10 @@ If you disable this feature, MO will only display official DLCs this way. Please
<item>
<spacer name="verticalSpacer_5">
<property name="orientation">
<enum>Qt::Vertical</enum>
<enum>Qt::Orientation::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
<enum>QSizePolicy::Policy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
@@ -771,7 +784,7 @@ If you disable this feature, MO will only display official DLCs this way. Please
<item>
<spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
@@ -805,7 +818,7 @@ If you disable this feature, MO will only display official DLCs this way. Please
<item>
<spacer name="verticalSpacer_13">
<property name="orientation">
<enum>Qt::Vertical</enum>
<enum>Qt::Orientation::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
@@ -895,7 +908,7 @@ If you disable this feature, MO will only display official DLCs this way. Please
<item row="1" column="1">
<spacer name="verticalSpacer_8">
<property name="orientation">
<enum>Qt::Vertical</enum>
<enum>Qt::Orientation::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
@@ -965,7 +978,7 @@ If you disable this feature, MO will only display official DLCs this way. Please
<item row="10" column="1">
<spacer name="verticalSpacer_7">
<property name="orientation">
<enum>Qt::Vertical</enum>
<enum>Qt::Orientation::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
@@ -1008,7 +1021,7 @@ If you disable this feature, MO will only display official DLCs this way. Please
<item>
<spacer name="verticalSpacer_6">
<property name="orientation">
<enum>Qt::Vertical</enum>
<enum>Qt::Orientation::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
@@ -1039,10 +1052,10 @@ If you disable this feature, MO will only display official DLCs this way. Please
#nexusScrollAreaWidgetContents { background-color: transparent; }</string>
</property>
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
<enum>QFrame::Shape::NoFrame</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Plain</enum>
<enum>QFrame::Shadow::Plain</enum>
</property>
<property name="widgetResizable">
<bool>true</bool>
@@ -1089,7 +1102,7 @@ If you disable this feature, MO will only display official DLCs this way. Please
<string>User ID:</string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
<set>Qt::TextInteractionFlag::LinksAccessibleByMouse|Qt::TextInteractionFlag::TextSelectableByKeyboard|Qt::TextInteractionFlag::TextSelectableByMouse</set>
</property>
</widget>
</item>
@@ -1228,7 +1241,7 @@ If you disable this feature, MO will only display official DLCs this way. Please
<item>
<spacer name="verticalSpacer_4">
<property name="orientation">
<enum>Qt::Vertical</enum>
<enum>Qt::Orientation::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
@@ -1259,7 +1272,7 @@ If you disable this feature, MO will only display official DLCs this way. Please
<item>
<widget class="QListWidget" name="nexusLog">
<property name="sizeAdjustPolicy">
<enum>QAbstractScrollArea::AdjustToContents</enum>
<enum>QAbstractScrollArea::SizeAdjustPolicy::AdjustToContents</enum>
</property>
</widget>
</item>
@@ -1437,10 +1450,10 @@ If you disable this feature, MO will only display official DLCs this way. Please
<item>
<widget class="QListWidget" name="knownServersList">
<property name="dragDropMode">
<enum>QAbstractItemView::DragDrop</enum>
<enum>QAbstractItemView::DragDropMode::DragDrop</enum>
</property>
<property name="defaultDropAction">
<enum>Qt::MoveAction</enum>
<enum>Qt::DropAction::MoveAction</enum>
</property>
</widget>
</item>
@@ -1458,10 +1471,10 @@ If you disable this feature, MO will only display official DLCs this way. Please
<item>
<widget class="QListWidget" name="preferredServersList">
<property name="dragDropMode">
<enum>QAbstractItemView::DragDrop</enum>
<enum>QAbstractItemView::DragDropMode::DragDrop</enum>
</property>
<property name="defaultDropAction">
<enum>Qt::MoveAction</enum>
<enum>Qt::DropAction::MoveAction</enum>
</property>
</widget>
</item>
@@ -1484,7 +1497,7 @@ If you disable this feature, MO will only display official DLCs this way. Please
<item>
<widget class="QSplitter" name="splitter">
<property name="orientation">
<enum>Qt::Vertical</enum>
<enum>Qt::Orientation::Vertical</enum>
</property>
<widget class="QWidget" name="widget" native="true">
<property name="sizePolicy">
@@ -1515,7 +1528,7 @@ If you disable this feature, MO will only display official DLCs this way. Please
</sizepolicy>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
<enum>Qt::Orientation::Horizontal</enum>
</property>
<widget class="QWidget" name="widget1" native="true">
<layout class="QVBoxLayout" name="verticalLayout_15">
@@ -1571,7 +1584,7 @@ If you disable this feature, MO will only display official DLCs this way. Please
<widget class="QWidget" name="pluginDescription" native="true">
<layout class="QFormLayout" name="pluginDescriptionLayout">
<property name="labelAlignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
<set>Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignTop</set>
</property>
<property name="leftMargin">
<number>6</number>
@@ -1582,7 +1595,7 @@ If you disable this feature, MO will only display official DLCs this way. Please
<string>Author:</string>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
<set>Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignTop</set>
</property>
</widget>
</item>
@@ -1592,7 +1605,7 @@ If you disable this feature, MO will only display official DLCs this way. Please
<string/>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
<set>Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignTop</set>
</property>
</widget>
</item>
@@ -1602,7 +1615,7 @@ If you disable this feature, MO will only display official DLCs this way. Please
<string>Version:</string>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
<set>Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignTop</set>
</property>
</widget>
</item>
@@ -1612,7 +1625,7 @@ If you disable this feature, MO will only display official DLCs this way. Please
<string/>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
<set>Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignTop</set>
</property>
</widget>
</item>
@@ -1622,7 +1635,7 @@ If you disable this feature, MO will only display official DLCs this way. Please
<string>Description:</string>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
<set>Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignTop</set>
</property>
</widget>
</item>
@@ -1632,7 +1645,7 @@ If you disable this feature, MO will only display official DLCs this way. Please
<string/>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
<set>Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignTop</set>
</property>
<property name="wordWrap">
<bool>true</bool>
@@ -1687,7 +1700,7 @@ If you disable this feature, MO will only display official DLCs this way. Please
<string>No plugin found.</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
<set>Qt::AlignmentFlag::AlignCenter</set>
</property>
</widget>
</item>
@@ -1729,10 +1742,10 @@ If you disable this feature, MO will only display official DLCs this way. Please
#workaroundScrollAreaWidgetContents { background-color: transparent; }</string>
</property>
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
<enum>QFrame::Shape::NoFrame</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Plain</enum>
<enum>QFrame::Shadow::Plain</enum>
</property>
<property name="widgetResizable">
<bool>true</bool>
@@ -1851,7 +1864,7 @@ Uncheck this if you want to use Mod Organizer with total conversions (like Nehri
<item row="1" column="3">
<widget class="QLineEdit" name="steamPassEdit">
<property name="echoMode">
<enum>QLineEdit::Password</enum>
<enum>QLineEdit::EchoMode::Password</enum>
</property>
</widget>
</item>
@@ -2125,7 +2138,7 @@ programs you are intentionally running.</string>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
@@ -2141,7 +2154,7 @@ programs you are intentionally running.</string>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
<enum>Qt::Orientation::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
@@ -2182,7 +2195,7 @@ programs you are intentionally running.</string>
</property>
<layout class="QFormLayout" name="formLayout_4">
<property name="fieldGrowthPolicy">
<enum>QFormLayout::FieldsStayAtSizeHint</enum>
<enum>QFormLayout::FieldGrowthPolicy::FieldsStayAtSizeHint</enum>
</property>
<item row="0" column="0">
<widget class="QLabel" name="label_12">
@@ -2257,7 +2270,7 @@ programs you are intentionally running.</string>
</property>
<layout class="QFormLayout" name="formLayout_2">
<property name="fieldGrowthPolicy">
<enum>QFormLayout::FieldsStayAtSizeHint</enum>
<enum>QFormLayout::FieldGrowthPolicy::FieldsStayAtSizeHint</enum>
</property>
<item row="0" column="0">
<widget class="QLabel" name="label_32">
@@ -2296,10 +2309,10 @@ programs you are intentionally running.</string>
<item>
<spacer name="verticalSpacer_10">
<property name="orientation">
<enum>Qt::Vertical</enum>
<enum>Qt::Orientation::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Expanding</enum>
<enum>QSizePolicy::Policy::Expanding</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
@@ -2316,10 +2329,10 @@ programs you are intentionally running.</string>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
<set>QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok</set>
</property>
</widget>
</item>
+5
View File
@@ -21,6 +21,11 @@ GeneralSettingsTab::GeneralSettingsTab(Settings& s, SettingsDialog& d)
ui->hideDownloadInstallBox->setChecked(
settings().interface().hideDownloadsAfterInstallation());
// connect MODL button
QObject::connect(ui->associateModlButton, &QPushButton::clicked, [&] {
Settings::instance().registerAsMODLHandler(true);
});
// updates
ui->checkForUpdates->setChecked(settings().checkForUpdates());
ui->usePrereleaseBox->setChecked(settings().usePrereleases());
+1 -6
View File
@@ -343,7 +343,7 @@ NexusSettingsTab::NexusSettingsTab(Settings& s, SettingsDialog& d) : SettingsTab
clearCache();
});
QObject::connect(ui->associateButton, &QPushButton::clicked, [&] {
associate();
Settings::instance().nexus().registerAsNXMHandler(true);
});
QObject::connect(ui->useCustomBrowser, &QCheckBox::clicked, [&] {
updateCustomBrowser();
@@ -417,11 +417,6 @@ void NexusSettingsTab::clearCache()
NexusInterface::instance().clearCache();
}
void NexusSettingsTab::associate()
{
Settings::instance().nexus().registerAsNXMHandler(true);
}
void NexusSettingsTab::updateNexusData()
{
const auto user = NexusInterface::instance().getAPIUserAccount();
-1
View File
@@ -60,7 +60,6 @@ private:
std::unique_ptr<NexusConnectionUI> m_connectionUI;
void clearCache();
void associate();
void updateNexusData();
void updateCustomBrowser();