summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorBrian Munro <brian.alexander.munro@gmail.com>2017-12-21 08:31:25 +0200
committerGitHub <noreply@github.com>2017-12-21 08:31:25 +0200
commit28a68cf51bab172e0efc460a94821692100dad00 (patch)
tree548012644d64aa6e450fafe0f812e0830239333a /src
parentb157ebb28c38a5ade879c0736a9dcb7fe0367a89 (diff)
parenta24a1cbf257f49fa31263e2f6115c378714a64d3 (diff)
Merge pull request #156 from erasmux/vs_usvfs_plus_various_fixes
Vs usvfs plus various fixes
Diffstat (limited to 'src')
-rw-r--r--src/CMakeLists.txt17
-rw-r--r--src/executableslist.cpp2
-rw-r--r--src/instancemanager.cpp20
-rw-r--r--src/instancemanager.h8
-rw-r--r--src/main.cpp30
-rw-r--r--src/mainwindow.cpp75
-rw-r--r--src/mainwindow.h2
-rw-r--r--src/modlist.cpp14
-rw-r--r--src/moshortcut.cpp39
-rw-r--r--src/moshortcut.h46
-rw-r--r--src/organizercore.cpp25
-rw-r--r--src/organizercore.h6
-rw-r--r--src/profile.cpp99
-rw-r--r--src/profile.h14
14 files changed, 274 insertions, 123 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 914bdd12..7a339180 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -88,6 +88,7 @@ SET(organizer_SRCS
instancemanager.cpp
usvfsconnector.cpp
eventfilter.cpp
+ moshortcut.cpp
shared/windows_error.cpp
shared/error_report.cpp
@@ -179,6 +180,7 @@ SET(organizer_HDRS
usvfsconnector.h
eventfilter.h
descriptionpage.h
+ moshortcut.h
shared/windows_error.h
shared/error_report.h
@@ -282,7 +284,7 @@ INCLUDE_DIRECTORIES(${project_path}/uibase/src
${project_path}/bsatk/src
${project_path}/esptk/src
${project_path}/archive/src
- ${project_path}/../usvfs/usvfs
+ ${project_path}/../usvfs/include
${project_path}/game_gamebryo/src
${project_path}/game_features/src
${project_path}/githubpp/src)
@@ -299,7 +301,7 @@ EXECUTE_PROCESS(
OUTPUT_STRIP_TRAILING_WHITESPACE
)
-ADD_DEFINITIONS(-D_UNICODE -DUNICODE -DNOMINMAX -D_CRT_SECURE_NO_WARNINGS -DGITID="${GIT_COMMIT_HASH}")
+ADD_DEFINITIONS(-D_UNICODE -DUNICODE -DNOMINMAX -D_CRT_SECURE_NO_WARNINGS -DBOOST_CONFIG_SUPPRESS_OUTDATED_MESSAGE -DGITID="${GIT_COMMIT_HASH}")
IF("${CMAKE_SIZEOF_VOID_P}" EQUAL "8")
SET(usvfs_name usvfs_x64)
@@ -335,14 +337,14 @@ FIND_PROGRAM(WINDEPLOYQT_COMMAND windeployqt PATHS ${qt5bin} NO_DEFAULT_PATH)
INSTALL(TARGETS ModOrganizer
RUNTIME DESTINATION bin)
-INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/ModOrganizer.pdb
+INSTALL(FILES $<TARGET_PDB_FILE:ModOrganizer>
DESTINATION pdb)
INSTALL(FILES ${CMAKE_CURRENT_SOURCE_DIR}/dlls.manifest.qt5
DESTINATION bin/dlls
RENAME dlls.manifest)
# use windeployqt.exe to install all required libraries
-SET(windeploy_parameters --no-translations --no-plugins --libdir dlls --release-with-debug-info --no-compiler-runtime)
+SET(windeploy_parameters "--no-translations --plugindir qtplugins --libdir dlls --release-with-debug-info --no-compiler-runtime")
INSTALL(
CODE
"EXECUTE_PROCESS(
@@ -351,7 +353,12 @@ INSTALL(
COMMAND
${qt5bin}/windeployqt.exe uibase.dll ${windeploy_parameters}
WORKING_DIRECTORY ${CMAKE_INSTALL_PREFIX}/bin
- )"
+ )
+ file(REMOVE_RECURSE ${CMAKE_INSTALL_PREFIX}/bin/platforms)
+ file(REMOVE_RECURSE ${CMAKE_INSTALL_PREFIX}/bin/dlls/imageformats)
+ file(RENAME ${CMAKE_INSTALL_PREFIX}/bin/qtplugins/platforms ${CMAKE_INSTALL_PREFIX}/bin/platforms)
+ file(RENAME ${CMAKE_INSTALL_PREFIX}/bin/qtplugins/imageformats ${CMAKE_INSTALL_PREFIX}/bin/dlls/imageformats)
+ file(REMOVE_RECURSE ${CMAKE_INSTALL_PREFIX}/bin/qtplugins)"
)
INSTALL(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/stylesheets
diff --git a/src/executableslist.cpp b/src/executableslist.cpp
index b1890ac9..21cb6ed4 100644
--- a/src/executableslist.cpp
+++ b/src/executableslist.cpp
@@ -86,7 +86,7 @@ Executable &ExecutablesList::find(const QString &title)
return exe;
}
}
- throw std::runtime_error(QString("invalid name %1").arg(title).toLocal8Bit().constData());
+ throw std::runtime_error(QString("invalid executable name %1").arg(title).toLocal8Bit().constData());
}
diff --git a/src/instancemanager.cpp b/src/instancemanager.cpp
index 1b0d0e5b..34e23d7b 100644
--- a/src/instancemanager.cpp
+++ b/src/instancemanager.cpp
@@ -47,9 +47,19 @@ InstanceManager &InstanceManager::instance()
return s_Instance;
}
+void InstanceManager::overrideInstance(const QString& instanceName)
+{
+ m_overrideInstanceName = instanceName;
+ m_overrideInstance = true;
+}
+
+
QString InstanceManager::currentInstance() const
{
- return m_AppSettings.value(INSTANCE_KEY, "").toString();
+ if (m_overrideInstance)
+ return m_overrideInstanceName;
+ else
+ return m_AppSettings.value(INSTANCE_KEY, "").toString();
}
void InstanceManager::clearCurrentInstance()
@@ -177,7 +187,8 @@ void InstanceManager::createDataPath(const QString &dataPath) const
QString InstanceManager::determineDataPath()
{
QString instanceId = currentInstance();
- if (instanceId.isEmpty() && portableInstall() && !m_Reset) {
+ if (instanceId.isEmpty() && !m_Reset && (m_overrideInstance || portableInstall()))
+ {
// startup, apparently using portable mode before
return qApp->applicationDirPath();
}
@@ -187,8 +198,9 @@ QString InstanceManager::determineDataPath()
+ "/" + instanceId);
- if (instanceId.isEmpty() || !QFileInfo::exists(dataPath)) {
+ if (!m_overrideInstance && (instanceId.isEmpty() || !QFileInfo::exists(dataPath))) {
instanceId = chooseInstance(instances());
+ setCurrentInstance(instanceId);
if (!instanceId.isEmpty()) {
dataPath = QDir::fromNativeSeparators(
QStandardPaths::writableLocation(QStandardPaths::DataLocation)
@@ -199,8 +211,6 @@ QString InstanceManager::determineDataPath()
if (instanceId.isEmpty()) {
return qApp->applicationDirPath();
} else {
- setCurrentInstance(instanceId);
-
createDataPath(dataPath);
return dataPath;
diff --git a/src/instancemanager.h b/src/instancemanager.h
index a0a5df09..2782c71d 100644
--- a/src/instancemanager.h
+++ b/src/instancemanager.h
@@ -34,11 +34,14 @@ public:
QString determineDataPath();
void clearCurrentInstance();
+ void overrideInstance(const QString& instanceName);
+
+ QString currentInstance() const;
+
private:
InstanceManager();
- QString currentInstance() const;
QString instancePath() const;
QStringList instances() const;
@@ -55,5 +58,6 @@ private:
QSettings m_AppSettings;
bool m_Reset {false};
-
+ bool m_overrideInstance{false};
+ QString m_overrideInstanceName;
};
diff --git a/src/main.cpp b/src/main.cpp
index 90c15ccd..3d315f1d 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -45,6 +45,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include "tutorialmanager.h"
#include "nxmaccessmanager.h"
#include "instancemanager.h"
+#include "moshortcut.h"
#include <eh.h>
#include <windows_error.h>
@@ -367,8 +368,6 @@ int runApplication(MOApplication &application, SingleInstance &instance,
const QString &splashPath)
{
qDebug("start main application");
- QPixmap pixmap(splashPath);
- QSplashScreen splash(pixmap);
QString dataPath = application.property("dataPath").toString();
qDebug("data path: %s", qPrintable(dataPath));
@@ -382,13 +381,7 @@ int runApplication(MOApplication &application, SingleInstance &instance,
try {
qDebug("Working directory: %s", qPrintable(QDir::toNativeSeparators(QDir::currentPath())));
- splash.show();
- } catch (const std::exception &e) {
- reportError(e.what());
- return 1;
- }
- try {
QSettings settings(dataPath + "/"
+ QString::fromStdWString(AppConfig::iniFileName()),
QSettings::IniFormat);
@@ -458,9 +451,9 @@ int runApplication(MOApplication &application, SingleInstance &instance,
// if we have a command line parameter, it is either a nxm link or
// a binary to start
if (arguments.size() > 1) {
- if (OrganizerCore::isMoShortcut(arguments.at(1))) {
+ if (MOShortcut shortcut{ arguments.at(1) }) {
try {
- organizer.runShortcut(OrganizerCore::moShortcutName(arguments.at(1)));
+ organizer.runShortcut(shortcut);
return 0;
} catch (const std::exception &e) {
reportError(
@@ -488,6 +481,10 @@ int runApplication(MOApplication &application, SingleInstance &instance,
}
}
+ QPixmap pixmap(splashPath);
+ QSplashScreen splash(pixmap);
+ splash.show();
+
NexusInterface::instance()->getAccessManager()->startLoginCheck();
qDebug("initializing tutorials");
@@ -556,10 +553,12 @@ int main(int argc, char *argv[])
forcePrimary = true;
}
+ MOShortcut moshortcut{ arguments.size() > 1 ? arguments.at(1) : "" };
+
SingleInstance instance(forcePrimary);
if (!instance.primaryInstance()) {
- if ((arguments.size() == 2)
- && (OrganizerCore::isMoShortcut(arguments.at(1)) || OrganizerCore::isNxmLink(arguments.at(1))))
+ if (moshortcut ||
+ arguments.size() > 1 && OrganizerCore::isNxmLink(arguments.at(1)))
{
qDebug("not primary instance, sending shortcut/download message");
instance.sendMessage(arguments.at(1));
@@ -576,7 +575,10 @@ int main(int argc, char *argv[])
QString dataPath;
try {
- dataPath = InstanceManager::instance().determineDataPath();
+ InstanceManager& instanceManager = InstanceManager::instance();
+ if (moshortcut && moshortcut.hasInstance())
+ instanceManager.overrideInstance(moshortcut.instance());
+ dataPath = instanceManager.determineDataPath();
} catch (const std::exception &e) {
if (strcmp(e.what(),"Canceled"))
QMessageBox::critical(nullptr, QObject::tr("Failed to set up instance"), e.what());
@@ -587,7 +589,7 @@ int main(int argc, char *argv[])
// initialize dump collection only after "dataPath" since the crashes are stored under it
prevUnhandledExceptionFilter = SetUnhandledExceptionFilter(MyUnhandledExceptionFilter);
- LogBuffer::init(100, QtDebugMsg, qApp->property("dataPath").toString() + "/logs/mo_interface.log");
+ LogBuffer::init(1000, QtDebugMsg, qApp->property("dataPath").toString() + "/logs/mo_interface.log");
QString splash = dataPath + "/splash.png";
if (!QFile::exists(dataPath + "/splash.png")) {
diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp
index 22e99e37..1c5e321d 100644
--- a/src/mainwindow.cpp
+++ b/src/mainwindow.cpp
@@ -1958,7 +1958,7 @@ void MainWindow::modorder_changed()
}
}
m_OrganizerCore.refreshBSAList();
- m_OrganizerCore.currentProfile()->modlistWriter().write();
+ m_OrganizerCore.currentProfile()->writeModlist();
m_ArchiveListWriter.write();
m_OrganizerCore.directoryStructure()->getFileRegister()->sortOrigins();
@@ -2019,71 +2019,9 @@ void MainWindow::installMod_clicked()
installMod();
}
-void MainWindow::renameModInList(QFile &modList, const QString &oldName, const QString &newName)
-{
- //TODO this code needs to be merged with ModList::readFrom
- if (!modList.open(QIODevice::ReadWrite)) {
- reportError(tr("failed to open %1").arg(modList.fileName()));
- return;
- }
-
- QBuffer outBuffer;
- outBuffer.open(QIODevice::WriteOnly);
-
- while (!modList.atEnd()) {
- QByteArray line = modList.readLine();
-
- if (line.length() == 0) {
- // ignore empty lines
- qWarning("mod list contained invalid data: empty line");
- continue;
- }
-
- char spec = line.at(0);
- if (spec == '#') {
- // don't touch comments
- outBuffer.write(line);
- continue;
- }
-
- QString modName = QString::fromUtf8(line).mid(1).trimmed();
-
- if (modName.isEmpty()) {
- // file broken?
- qWarning("mod list contained invalid data: missing mod name");
- continue;
- }
-
- outBuffer.write(QByteArray(1, spec));
- if (modName == oldName) {
- modName = newName;
- }
- outBuffer.write(modName.toUtf8().constData());
- outBuffer.write("\r\n");
- }
-
- modList.resize(0);
- modList.write(outBuffer.buffer());
- modList.close();
-}
-
-
void MainWindow::modRenamed(const QString &oldName, const QString &newName)
{
- // fix the profiles directly on disc
- for (int i = 0; i < ui->profileBox->count(); ++i) {
- QString profileName = ui->profileBox->itemText(i);
-
- //TODO this functionality should be in the Profile class
- QString modlistName = QString("%1/%2/modlist.txt")
- .arg(qApp->property("dataPath").toString() + "/" + QString::fromStdWString(AppConfig::profilesPath()))
- .arg(profileName);
-
- QFile modList(modlistName);
- if (modList.exists()) {
- renameModInList(modList, oldName, newName);
- }
- }
+ Profile::renameModInAllProfiles(oldName, newName);
// immediately refresh the active profile because the data in memory is invalid
m_OrganizerCore.currentProfile()->refreshModStatus();
@@ -2264,7 +2202,7 @@ void MainWindow::restoreBackup_clicked()
void MainWindow::modlistChanged(const QModelIndex&, int)
{
- m_OrganizerCore.currentProfile()->modlistWriter().write();
+ m_OrganizerCore.currentProfile()->writeModlist();
}
void MainWindow::modlistSelectionChanged(const QModelIndex &current, const QModelIndex&)
@@ -3307,7 +3245,7 @@ void MainWindow::fixMods_clicked(SaveGameInfo::MissingAssets const &missingAsset
}
}
- m_OrganizerCore.currentProfile()->modlistWriter().write();
+ m_OrganizerCore.currentProfile()->writeModlist();
m_OrganizerCore.refreshLists();
std::set<QString> espsToActivate = dialog.getESPsToActivate();
@@ -3393,7 +3331,8 @@ void MainWindow::addWindowsLink(const ShortcutType mapping)
QString executable = QDir::toNativeSeparators(selectedExecutable.m_BinaryInfo.absoluteFilePath());
std::wstring targetFile = ToWString(exeInfo.absoluteFilePath());
- std::wstring parameter = ToWString(QString("\"moshortcut://%1\"").arg(selectedExecutable.m_Title));
+ std::wstring parameter = ToWString(
+ QString("\"moshortcut://%1:%2\"").arg(InstanceManager::instance().currentInstance(),selectedExecutable.m_Title));
std::wstring description = ToWString(QString("Run %1 with ModOrganizer").arg(selectedExecutable.m_Title));
std::wstring iconFile = ToWString(executable);
std::wstring currentDirectory = ToWString(QDir::toNativeSeparators(qApp->applicationDirPath()));
@@ -4678,7 +4617,7 @@ void MainWindow::on_restoreButton_clicked()
void MainWindow::on_saveModsButton_clicked()
{
- m_OrganizerCore.currentProfile()->modlistWriter().writeImmediately(true);
+ m_OrganizerCore.currentProfile()->writeModlistNow(true);
QDateTime now = QDateTime::currentDateTime();
if (createBackup(m_OrganizerCore.currentProfile()->getModlistFileName(), now)) {
MessageDialog::showMessage(tr("Backup of modlist created"), this);
diff --git a/src/mainwindow.h b/src/mainwindow.h
index 45a41137..a4ded688 100644
--- a/src/mainwindow.h
+++ b/src/mainwindow.h
@@ -220,8 +220,6 @@ private:
void writeDataToFile(QFile &file, const QString &directory, const MOShared::DirectoryEntry &directoryEntry);
- void renameModInList(QFile &modList, const QString &oldName, const QString &newName);
-
void refreshFilters();
/**
diff --git a/src/modlist.cpp b/src/modlist.cpp
index 3afa94b5..046e2280 100644
--- a/src/modlist.cpp
+++ b/src/modlist.cpp
@@ -424,13 +424,13 @@ bool ModList::renameMod(int index, const QString &newName)
return false;
}
- // before we rename, write back the current profile so we don't lose changes and to ensure
- // there is no scheduled asynchronous rewrite anytime soon
- m_Profile->writeModlistNow();
-
ModInfo::Ptr modInfo = ModInfo::getByIndex(index);
QString oldName = modInfo->name();
- if (modInfo->setName(nameFixed)) {
+ if (newName != oldName && modInfo->setName(nameFixed)) {
+ // before we rename, write back the current profile so we don't lose changes and to ensure
+ // there is no scheduled asynchronous rewrite anytime soon
+ m_Profile->writeModlistNow();
+
// this just disabled the mod in all profiles. The recipient of modRenamed must fix that
emit modRenamed(oldName, nameFixed);
}
@@ -940,12 +940,12 @@ void ModList::removeRowForce(int row, const QModelIndex &parent)
m_Profile->setModEnabled(row, false);
- m_Profile->modlistWriter().cancel();
+ m_Profile->cancelModlistWrite();
beginRemoveRows(parent, row, row);
ModInfo::removeMod(row);
endRemoveRows();
m_Profile->refreshModStatus(); // removes the mod from the status list
- m_Profile->modlistWriter().write(); // this ensures the modified list gets written back before new mods can be installed
+ m_Profile->writeModlist(); // this ensures the modified list gets written back before new mods can be installed
if (wasEnabled) {
emit removeOrigin(modInfo->name());
diff --git a/src/moshortcut.cpp b/src/moshortcut.cpp
new file mode 100644
index 00000000..c8c2ef6f
--- /dev/null
+++ b/src/moshortcut.cpp
@@ -0,0 +1,39 @@
+/*
+Copyright (C) 2016 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 <http://www.gnu.org/licenses/>.
+*/
+
+
+#include "moshortcut.h"
+
+
+MOShortcut::MOShortcut(const QString& link)
+ : m_valid(link.startsWith("moshortcut://"))
+ , m_hasInstance(false)
+{
+ if (m_valid) {
+ int start = (int)strlen("moshortcut://");
+ int sep = link.indexOf(':', start);
+ if (sep >= 0) {
+ m_hasInstance = true;
+ m_instance = link.mid(start, sep - start);
+ m_executable = link.mid(sep + 1);
+ }
+ else
+ m_executable = link.mid(start);
+ }
+}
diff --git a/src/moshortcut.h b/src/moshortcut.h
new file mode 100644
index 00000000..7b7574b9
--- /dev/null
+++ b/src/moshortcut.h
@@ -0,0 +1,46 @@
+/*
+Copyright (C) 2016 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 <http://www.gnu.org/licenses/>.
+*/
+
+
+#pragma once
+
+
+#include <QString>
+
+
+class MOShortcut {
+
+public:
+ MOShortcut(const QString& link);
+
+ /// true iff intialized using a valid moshortcut link
+ operator bool() const { return m_valid; }
+
+ bool hasInstance() const { return m_hasInstance; }
+
+ const QString& instance() const { return m_instance; }
+
+ const QString& executable() const { return m_executable; }
+
+private:
+ QString m_instance;
+ QString m_executable;
+ bool m_valid;
+ bool m_hasInstance;
+};
diff --git a/src/organizercore.cpp b/src/organizercore.cpp
index 98920479..7668485c 100644
--- a/src/organizercore.cpp
+++ b/src/organizercore.cpp
@@ -32,6 +32,7 @@
#include <report.h>
#include <questionboxmemory.h>
#include "lockeddialog.h"
+#include "instancemanager.h"
#include <QApplication>
#include <QCoreApplication>
@@ -578,8 +579,8 @@ void OrganizerCore::downloadRequestedNXM(const QString &url)
void OrganizerCore::externalMessage(const QString &message)
{
- if (isMoShortcut(message)) {
- runShortcut(moShortcutName(message));
+ if (MOShortcut moshortcut{ message }) {
+ runShortcut(moshortcut);
}
else if (isNxmLink(message)) {
MessageDialog::showMessage(tr("Download started"), qApp->activeWindow());
@@ -1181,7 +1182,7 @@ HANDLE OrganizerCore::spawnBinaryProcess(const QFileInfo &binary,
// need to make sure all data is saved before we start the application
if (m_CurrentProfile != nullptr) {
- m_CurrentProfile->modlistWriter().writeImmediately(true);
+ m_CurrentProfile->writeModlistNow(true);
}
// TODO: should also pass arguments
@@ -1228,9 +1229,15 @@ HANDLE OrganizerCore::spawnBinaryProcess(const QFileInfo &binary,
}
}
-HANDLE OrganizerCore::runShortcut(const QString &title)
+HANDLE OrganizerCore::runShortcut(const MOShortcut& shortcut)
{
- Executable& exe = m_ExecutablesList.find(title);
+ if (shortcut.hasInstance() && shortcut.instance() != InstanceManager::instance().currentInstance())
+ throw std::runtime_error(
+ QString("Refusing to run executable from different instance %1:%2")
+ .arg(shortcut.instance(),shortcut.executable())
+ .toLocal8Bit().constData());
+
+ Executable& exe = m_ExecutablesList.find(shortcut.executable());
return spawnBinaryDirect(
exe.m_BinaryInfo, exe.m_Arguments,
@@ -1438,7 +1445,7 @@ void OrganizerCore::refreshModList(bool saveChanges)
{
// don't lose changes!
if (saveChanges) {
- m_CurrentProfile->modlistWriter().writeImmediately(true);
+ m_CurrentProfile->writeModlistNow(true);
}
ModInfo::updateFromDisc(m_Settings.getModDirectory(), &m_DirectoryStructure,
m_Settings.displayForeign(), managedGame());
@@ -1460,7 +1467,7 @@ void OrganizerCore::refreshESPList()
});
return;
}
- m_CurrentProfile->modlistWriter().write();
+ m_CurrentProfile->writeModlist();
// clear list
try {
@@ -1670,7 +1677,7 @@ std::vector<QString> OrganizerCore::enabledArchives()
void OrganizerCore::refreshDirectoryStructure()
{
if (!m_DirectoryUpdate) {
- m_CurrentProfile->modlistWriter().writeImmediately(true);
+ m_CurrentProfile->writeModlistNow(true);
m_DirectoryUpdate = true;
std::vector<std::tuple<QString, QString, int>> activeModList
@@ -1925,7 +1932,7 @@ void OrganizerCore::prepareStart()
if (m_CurrentProfile == nullptr) {
return;
}
- m_CurrentProfile->modlistWriter().write();
+ m_CurrentProfile->writeModlist();
m_CurrentProfile->createTweakedIniFile();
saveCurrentLists();
m_Settings.setupLoadMechanism();
diff --git a/src/organizercore.h b/src/organizercore.h
index decb3d01..f98e9d0a 100644
--- a/src/organizercore.h
+++ b/src/organizercore.h
@@ -13,6 +13,7 @@
#include "downloadmanager.h"
#include "executableslist.h"
#include "usvfsconnector.h"
+#include "moshortcut.h"
#include <directoryentry.h>
#include <imoinfo.h>
#include <iplugindiagnose.h>
@@ -86,9 +87,6 @@ private:
public:
static bool isNxmLink(const QString &link) { return link.startsWith("nxm://", Qt::CaseInsensitive); }
- static bool isMoShortcut(const QString &link) { return link.startsWith("moshortcut://", Qt::CaseInsensitive); }
- static QString moShortcutName(const QString &link) { return link.mid(strlen("moshortcut://")); }
-
OrganizerCore(const QSettings &initSettings);
@@ -203,7 +201,7 @@ public:
DownloadManager *downloadManager();
PluginList *pluginList();
ModList *modList();
- HANDLE runShortcut(const QString &title);
+ HANDLE runShortcut(const MOShortcut& shortcut);
HANDLE startApplication(const QString &executable, const QStringList &args, const QString &cwd, const QString &profile);
bool waitForApplication(HANDLE processHandle, LPDWORD exitCode = nullptr);
HANDLE findAndOpenAUSVFSProcess();
diff --git a/src/profile.cpp b/src/profile.cpp
index 6382a15c..00509ef7 100644
--- a/src/profile.cpp
+++ b/src/profile.cpp
@@ -39,6 +39,8 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include <QStringList> // for QStringList
#include <QtDebug> // for qDebug, qWarning, etc
#include <QtGlobal> // for qPrintable
+#include <QBuffer>
+#include <QDirIterator>
#include <Windows.h>
@@ -66,7 +68,7 @@ void Profile::touchFile(QString fileName)
}
Profile::Profile(const QString &name, IPluginGame const *gamePlugin, bool useDefaultSettings)
- : m_ModListWriter(std::bind(&Profile::writeModlistNow, this))
+ : m_ModListWriter(std::bind(&Profile::doWriteModlist, this))
, m_GamePlugin(gamePlugin)
{
QString profilesDir = Settings::instance().getProfileDirectory();
@@ -111,7 +113,7 @@ Profile::Profile(const QString &name, IPluginGame const *gamePlugin, bool useDef
Profile::Profile(const QDir &directory, IPluginGame const *gamePlugin)
: m_Directory(directory)
, m_GamePlugin(gamePlugin)
- , m_ModListWriter(std::bind(&Profile::writeModlistNow, this))
+ , m_ModListWriter(std::bind(&Profile::doWriteModlist, this))
{
assert(gamePlugin != nullptr);
@@ -133,7 +135,7 @@ Profile::Profile(const QDir &directory, IPluginGame const *gamePlugin)
Profile::Profile(const Profile &reference)
: m_Directory(reference.m_Directory)
- , m_ModListWriter(std::bind(&Profile::writeModlistNow, this))
+ , m_ModListWriter(std::bind(&Profile::doWriteModlist, this))
, m_GamePlugin(reference.m_GamePlugin)
{
@@ -154,7 +156,22 @@ bool Profile::exists() const
return m_Directory.exists();
}
-void Profile::writeModlistNow()
+void Profile::writeModlist()
+{
+ m_ModListWriter.write();
+}
+
+void Profile::writeModlistNow(bool onlyIfPending)
+{
+ m_ModListWriter.writeImmediately(onlyIfPending);
+}
+
+void Profile::cancelModlistWrite()
+{
+ m_ModListWriter.cancel();
+}
+
+void Profile::doWriteModlist()
{
if (!m_Directory.exists()) return;
@@ -227,9 +244,83 @@ void Profile::createTweakedIniFile()
qDebug("%s saved", qPrintable(QDir::toNativeSeparators(tweakedIni)));
}
+// static
+void Profile::renameModInAllProfiles(const QString& oldName, const QString& newName)
+{
+ QDir profilesDir(Settings::instance().getProfileDirectory());
+ profilesDir.setFilter(QDir::AllDirs | QDir::NoDotAndDotDot);
+ QDirIterator profileIter(profilesDir);
+ while (profileIter.hasNext()) {
+ profileIter.next();
+ QFile modList(profileIter.filePath() + "/modlist.txt");
+ if (modList.exists())
+ renameModInList(modList, oldName, newName);
+ else
+ qWarning("Profile has no modlist.txt : %s", qPrintable(profileIter.filePath()));
+ }
+}
+
+// static
+void Profile::renameModInList(QFile &modList, const QString &oldName, const QString &newName)
+{
+ if (!modList.open(QIODevice::ReadOnly)) {
+ reportError(tr("failed to open %1").arg(modList.fileName()));
+ return;
+ }
+
+ QBuffer outBuffer;
+ outBuffer.open(QIODevice::WriteOnly);
+
+ int renamed = 0;
+ while (!modList.atEnd()) {
+ QByteArray line = modList.readLine();
+
+ if (line.length() == 0) {
+ // ignore empty lines
+ qWarning("mod list contained invalid data: empty line");
+ continue;
+ }
+
+ char spec = line.at(0);
+ if (spec == '#') {
+ // don't touch comments
+ outBuffer.write(line);
+ continue;
+ }
+
+ QString modName = QString::fromUtf8(line).mid(1).trimmed();
+
+ if (modName.isEmpty()) {
+ // file broken?
+ qWarning("mod list contained invalid data: missing mod name");
+ continue;
+ }
+
+ outBuffer.write(QByteArray(1, spec));
+ if (modName == oldName) {
+ modName = newName;
+ ++renamed;
+ }
+ outBuffer.write(modName.toUtf8().constData());
+ outBuffer.write("\r\n");
+ }
+ modList.close();
+
+ if (renamed) {
+ modList.open(QIODevice::WriteOnly);
+ modList.write(outBuffer.buffer());
+ modList.close();
+ }
+
+ if (renamed)
+ qDebug("Renamed %d \"%s\" mod to \"%s\" in %s",
+ renamed, qPrintable(oldName), qPrintable(newName), qPrintable(modList.fileName()));
+}
void Profile::refreshModStatus()
{
+ writeModlistNow(true); // if there are pending changes write them first
+
QFile file(getModlistFileName());
if (!file.open(QIODevice::ReadOnly)) {
throw MyException(tr("\"%1\" is missing or inaccessible").arg(getModlistFileName()));
diff --git a/src/profile.h b/src/profile.h
index 996e561f..3e620b3c 100644
--- a/src/profile.h
+++ b/src/profile.h
@@ -89,7 +89,14 @@ public:
**/
static Profile *createPtrFrom(const QString &name, const Profile &reference, MOBase::IPluginGame const *gamePlugin);
- MOBase::DelayedFileWriter &modlistWriter() { return m_ModListWriter; }
+
+ static void renameModInAllProfiles(const QString& oldName, const QString& newName);
+
+ void writeModlist();
+
+ void writeModlistNow(bool onlyIfPending=false);
+
+ void cancelModlistWrite();
/**
* @brief test if this profile uses archive invalidation
@@ -297,7 +304,8 @@ signals:
public slots:
- void writeModlistNow();
+ // should only be called by DelayedFileWriter, use writeModlist() and writeModlistNow() instead
+ void doWriteModlist();
private:
@@ -326,6 +334,8 @@ private:
void touchFile(QString fileName);
void finishChangeStatus() const;
+ static void renameModInList(QFile &modList, const QString &oldName, const QString &newName);
+
private:
QDir m_Directory;