From 9166bd9c5bf02484bd7486374e508ca304111f5a Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 6 Jul 2019 18:50:28 -0400 Subject: added new Shortcut class, moved stuff that was in MainWindow into it added more error handling and logging, some was missing removed some redundancy of setting the menu icons for add/remove, they're all set when clicking the button anyway --- src/mainwindow.cpp | 177 ++++------------------------- src/mainwindow.h | 12 +- src/shared/util.cpp | 320 +++++++++++++++++++++++++++++++++++++++++++++++++++- src/shared/util.h | 96 ++++++++++++++++ 4 files changed, 442 insertions(+), 163 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 7681b482..f9e4138e 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -220,6 +220,9 @@ MainWindow::MainWindow(QSettings &initSettings , m_PluginContainer(pluginContainer) , m_DidUpdateMasterList(false) , m_ArchiveListWriter(std::bind(&MainWindow::saveArchiveList, this)) + , m_LinkToolbar(nullptr) + , m_LinkDesktop(nullptr) + , m_LinkStartMenu(nullptr) { QWebEngineProfile::defaultProfile()->setPersistentCookiesPolicy(QWebEngineProfile::NoPersistentCookies); QWebEngineProfile::defaultProfile()->setHttpCacheMaximumSize(52428800); @@ -335,9 +338,9 @@ MainWindow::MainWindow(QSettings &initSettings resizeLists(modListAdjusted, pluginListAdjusted); QMenu *linkMenu = new QMenu(this); - linkMenu->addAction(QIcon(":/MO/gui/link"), tr("Toolbar and Menu"), this, SLOT(linkToolbar())); - linkMenu->addAction(QIcon(":/MO/gui/link"), tr("Desktop"), this, SLOT(linkDesktop())); - linkMenu->addAction(QIcon(":/MO/gui/link"), tr("Start Menu"), this, SLOT(linkMenu())); + m_LinkToolbar = linkMenu->addAction(QIcon(":/MO/gui/link"), tr("Toolbar and Menu"), this, SLOT(linkToolbar())); + m_LinkDesktop = linkMenu->addAction(QIcon(":/MO/gui/link"), tr("Desktop"), this, SLOT(linkDesktop())); + m_LinkStartMenu = linkMenu->addAction(QIcon(":/MO/gui/link"), tr("Start Menu"), this, SLOT(linkMenu())); ui->linkButton->setMenu(linkMenu); QMenu *listOptionsMenu = new QMenu(ui->listOptionsBtn); @@ -2397,88 +2400,6 @@ void MainWindow::on_startButton_clicked() { ui->startButton->setEnabled(true); } -static HRESULT CreateShortcut(LPCWSTR targetFileName, LPCWSTR arguments, - LPCSTR linkFileName, LPCWSTR description, - LPCTSTR iconFileName, int iconNumber, - LPCWSTR currentDirectory) -{ - HRESULT result = E_INVALIDARG; - if ((targetFileName != nullptr) && (wcslen(targetFileName) > 0) && - (arguments != nullptr) && - (linkFileName != nullptr) && (strlen(linkFileName) > 0) && - (description != nullptr) && - (currentDirectory != nullptr)) { - - IShellLink* shellLink; - result = CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, - IID_IShellLink, (LPVOID*)&shellLink); - - if (!SUCCEEDED(result)) { - qCritical("failed to create IShellLink instance"); - return result; - } - - result = shellLink->SetPath(targetFileName); - if (!SUCCEEDED(result)) { - qCritical("failed to set target path %ls", targetFileName); - shellLink->Release(); - return result; - } - - result = shellLink->SetArguments(arguments); - if (!SUCCEEDED(result)) { - qCritical("failed to set arguments: %ls", arguments); - shellLink->Release(); - return result; - } - - if (wcslen(description) > 0) { - result = shellLink->SetDescription(description); - if (!SUCCEEDED(result)) { - qCritical("failed to set description: %ls", description); - shellLink->Release(); - return result; - } - } - - if (wcslen(currentDirectory) > 0) { - result = shellLink->SetWorkingDirectory(currentDirectory); - if (!SUCCEEDED(result)) { - qCritical("failed to set working directory: %ls", currentDirectory); - shellLink->Release(); - return result; - } - } - - if (iconFileName != nullptr) { - result = shellLink->SetIconLocation(iconFileName, iconNumber); - if (!SUCCEEDED(result)) { - qCritical("failed to load program icon: %ls %d", iconFileName, iconNumber); - shellLink->Release(); - return result; - } - } - - IPersistFile *persistFile; - result = shellLink->QueryInterface(IID_IPersistFile, (LPVOID*)&persistFile); - if (SUCCEEDED(result)) { - wchar_t linkFileNameW[MAX_PATH]; - if (MultiByteToWideChar(CP_ACP, 0, linkFileName, -1, linkFileNameW, MAX_PATH) > 0) { - result = persistFile->Save(linkFileNameW, TRUE); - } else { - qCritical("failed to create link: %s", linkFileName); - } - persistFile->Release(); - } else { - qCritical("failed to create IPersistFile instance"); - } - - shellLink->Release(); - } - return result; -} - - bool MainWindow::modifyExecutablesDialog() { bool result = false; @@ -5144,74 +5065,39 @@ void MainWindow::on_savegameList_customContextMenuRequested(const QPoint &pos) void MainWindow::linkToolbar() { - Executable &exe(getSelectedExecutable()); + Executable& exe = getSelectedExecutable(); + exe.setShownOnToolbar(!exe.isShownOnToolbar()); - ui->linkButton->menu()->actions().at(static_cast(ShortcutType::Toolbar))->setIcon(exe.isShownOnToolbar() ? QIcon(":/MO/gui/remove") : QIcon(":/MO/gui/link")); updatePinnedExecutables(); } -namespace { -QString getLinkfile(const QString &dir, const Executable &exec) +void MainWindow::linkDesktop() { - return QDir::fromNativeSeparators(dir) + "/" + exec.title() + ".lnk"; + env::Shortcut(getSelectedExecutable()).toggle(env::Shortcut::Desktop); } -QString getDesktopLinkfile(const Executable &exec) +void MainWindow::linkMenu() { - return getLinkfile(getDesktopDirectory(), exec); + env::Shortcut(getSelectedExecutable()).toggle(env::Shortcut::StartMenu); } -QString getStartMenuLinkfile(const Executable &exec) +void MainWindow::on_linkButton_pressed() { - return getLinkfile(getStartMenuDirectory(), exec); -} -} + const Executable& exe = getSelectedExecutable(); -void MainWindow::addWindowsLink(const ShortcutType mapping) -{ - const Executable &selectedExecutable(getSelectedExecutable()); - QString const linkName = getLinkfile(mapping == ShortcutType::Desktop ? getDesktopDirectory() : getStartMenuDirectory(), - selectedExecutable); + const QIcon addIcon(":/MO/gui/link"); + const QIcon removeIcon(":/MO/gui/remove"); - if (QFile::exists(linkName)) { - if (QFile::remove(linkName)) { - ui->linkButton->menu()->actions().at(static_cast(mapping))->setIcon(QIcon(":/MO/gui/link")); - } else { - reportError(tr("failed to remove %1").arg(linkName)); - } - } else { - QFileInfo const exeInfo(qApp->applicationFilePath()); - // create link - QString executable = QDir::toNativeSeparators(selectedExecutable.binaryInfo().absoluteFilePath()); - - std::wstring targetFile = ToWString(exeInfo.absoluteFilePath()); - std::wstring parameter = ToWString( - QString("\"moshortcut://%1:%2\"").arg(InstanceManager::instance().currentInstance(),selectedExecutable.title())); - std::wstring description = ToWString(QString("Run %1 with ModOrganizer").arg(selectedExecutable.title())); - std::wstring iconFile = ToWString(executable); - std::wstring currentDirectory = ToWString(QDir::toNativeSeparators(qApp->applicationDirPath())); - - if (CreateShortcut(targetFile.c_str() - , parameter.c_str() - , QDir::toNativeSeparators(linkName).toUtf8().constData() - , description.c_str() - , (selectedExecutable.usesOwnIcon() ? iconFile.c_str() : nullptr), 0 - , currentDirectory.c_str()) == 0) { - ui->linkButton->menu()->actions().at(static_cast(mapping))->setIcon(QIcon(":/MO/gui/remove")); - } else { - reportError(tr("failed to create %1").arg(linkName)); - } - } -} + env::Shortcut shortcut(exe); -void MainWindow::linkDesktop() -{ - addWindowsLink(ShortcutType::Desktop); -} + m_LinkToolbar->setIcon( + exe.isShownOnToolbar() ? removeIcon : addIcon); -void MainWindow::linkMenu() -{ - addWindowsLink(ShortcutType::StartMenu); + m_LinkDesktop->setIcon( + shortcut.exists(env::Shortcut::Desktop) ? removeIcon : addIcon); + + m_LinkStartMenu->setIcon( + shortcut.exists(env::Shortcut::StartMenu) ? removeIcon : addIcon); } void MainWindow::on_actionSettings_triggered() @@ -6476,21 +6362,6 @@ Executable &MainWindow::getSelectedExecutable() return m_OrganizerCore.executablesList()->get(name); } -void MainWindow::on_linkButton_pressed() -{ - const Executable &selectedExecutable(getSelectedExecutable()); - - const QIcon addIcon(":/MO/gui/link"); - const QIcon removeIcon(":/MO/gui/remove"); - - const QFileInfo linkDesktopFile(getDesktopLinkfile(selectedExecutable)); - const QFileInfo linkMenuFile(getStartMenuLinkfile(selectedExecutable)); - - ui->linkButton->menu()->actions().at(static_cast(ShortcutType::Toolbar))->setIcon(selectedExecutable.isShownOnToolbar() ? removeIcon : addIcon); - ui->linkButton->menu()->actions().at(static_cast(ShortcutType::Desktop))->setIcon(linkDesktopFile.exists() ? removeIcon : addIcon); - ui->linkButton->menu()->actions().at(static_cast(ShortcutType::StartMenu))->setIcon(linkMenuFile.exists() ? removeIcon : addIcon); -} - void MainWindow::on_showHiddenBox_toggled(bool checked) { m_OrganizerCore.downloadManager()->setShowHidden(checked); diff --git a/src/mainwindow.h b/src/mainwindow.h index 00f15a2b..eee269cf 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -403,18 +403,14 @@ private: MOBase::DelayedFileWriter m_ArchiveListWriter; + QAction* m_LinkToolbar; + QAction* m_LinkDesktop; + QAction* m_LinkStartMenu; + // icon set by the stylesheet, used to remember its original appearance // when painting the count QIcon m_originalNotificationIcon; - enum class ShortcutType { - Toolbar, - Desktop, - StartMenu - }; - - void addWindowsLink(ShortcutType const); - Executable const &getSelectedExecutable() const; Executable &getSelectedExecutable(); diff --git a/src/shared/util.cpp b/src/shared/util.cpp index 072cee2d..2bf1dba6 100644 --- a/src/shared/util.cpp +++ b/src/shared/util.cpp @@ -20,6 +20,8 @@ along with Mod Organizer. If not, see . #include "util.h" #include "windows_error.h" #include "error_report.h" +#include "executableslist.h" +#include "instancemanager.h" #include #include @@ -307,10 +309,324 @@ struct COMReleaser } }; + template using COMPtr = std::unique_ptr; +class ShellLinkException {}; + +// just a wrapper around IShellLink operations that throws ShellLinkException +// on errors +// +class ShellLinkWrapper +{ +public: + ShellLinkWrapper() + { + m_link = createShellLink(); + m_file = createPersistFile(); + } + + void setPath(const QString& s) + { + if (s.isEmpty()) { + critical() << "path cannot be empty"; + throw ShellLinkException(); + } + + const auto r = m_link->SetPath(s.toStdWString().c_str()); + throwOnFail(r, QString("failed to set target path '%1'").arg(s)); + } + + void setArguments(const QString& s) + { + const auto r = m_link->SetArguments(s.toStdWString().c_str()); + throwOnFail(r, QString("failed to set arguments '%1'").arg(s)); + } + + void setDescription(const QString& s) + { + if (s.isEmpty()) { + return; + } + + const auto r = m_link->SetDescription(s.toStdWString().c_str()); + throwOnFail(r, QString("failed to set description '%1'").arg(s)); + } + + void setIcon(const QString& file, int i) + { + if (file.isEmpty()) { + return; + } + + const auto r = m_link->SetIconLocation(file.toStdWString().c_str(), i); + throwOnFail(r, QString("failed to set icon '%1' @ %2").arg(file).arg(i)); + } + + void setWorkingDirectory(const QString& s) + { + if (s.isEmpty()) { + return; + } + + const auto r = m_link->SetWorkingDirectory(s.toStdWString().c_str()); + throwOnFail(r, QString("failed to set working directory '%1'").arg(s)); + } + + void save(const QString& path) + { + const auto r = m_file->Save(path.toStdWString().c_str(), TRUE); + throwOnFail(r, QString("failed to save link '%1'").arg(path)); + } + +private: + COMPtr m_link; + COMPtr m_file; + + QDebug critical() + { + return qCritical().noquote().nospace() << "system shortcut: "; + } + + void throwOnFail(HRESULT r, const QString& s) + { + if (FAILED(r)) { + critical() << s << ", " << formatSystemMessageQ(r); + throw ShellLinkException(); + } + } + + COMPtr createShellLink() + { + void* link = nullptr; + + const auto r = CoCreateInstance( + CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, + IID_IShellLink, &link); + + throwOnFail(r, "failed to create IShellLink instance"); + + if (!link) { + critical() << "creating IShellLink worked, but pointer is null"; + throw ShellLinkException(); + } + + return COMPtr(static_cast(link)); + } + + COMPtr createPersistFile() + { + void* file = nullptr; + + const auto r = m_link->QueryInterface(IID_IPersistFile, &file); + throwOnFail(r, "failed to get IPersistFile interface"); + + if (!file) { + critical() << "querying IPersistFile worked, but pointer is null"; + throw ShellLinkException(); + } + + return COMPtr(static_cast(file)); + } +}; + + +Shortcut::Shortcut() + : m_iconIndex(0) +{ +} + +Shortcut::Shortcut(const Executable& exe) + : Shortcut() +{ + m_name = exe.title(); + m_target = QFileInfo(qApp->applicationFilePath()).absoluteFilePath(); + + m_arguments = QString("\"moshortcut://%1:%2\"") + .arg(InstanceManager::instance().currentInstance()) + .arg(exe.title()); + + m_description = QString("Run %1 with ModOrganizer").arg(exe.title()); + + if (exe.usesOwnIcon()) { + m_icon = exe.binaryInfo().absoluteFilePath(); + } + + m_workingDirectory = qApp->applicationDirPath(); +} + +Shortcut& Shortcut::name(const QString& s) +{ + m_name = s; + return *this; +} + +Shortcut& Shortcut::target(const QString& s) +{ + m_target = s; + return *this; +} + +Shortcut& Shortcut::arguments(const QString& s) +{ + m_arguments = s; + return *this; +} + +Shortcut& Shortcut::description(const QString& s) +{ + m_description = s; + return *this; +} + +Shortcut& Shortcut::icon(const QString& s, int index) +{ + m_icon = s; + m_iconIndex = index; + return *this; +} + +Shortcut& Shortcut::workingDirectory(const QString& s) +{ + m_workingDirectory = s; + return *this; +} + +bool Shortcut::exists(Locations loc) const +{ + const auto path = shortcutPath(loc); + if (path.isEmpty()) { + return false; + } + + return QFileInfo(path).exists(); +} + +bool Shortcut::toggle(Locations loc) +{ + if (exists(loc)) { + return remove(loc); + } else { + return add(loc); + } +} + +bool Shortcut::add(Locations loc) +{ + const auto path = shortcutPath(loc); + if (path.isEmpty()) { + return false; + } + + if (m_target.isEmpty()) { + qCritical() << "system shortcut: target is empty"; + return false; + } + + try + { + ShellLinkWrapper link; + + link.setPath(m_target); + link.setArguments(m_arguments); + link.setDescription(m_description); + link.setIcon(m_icon, m_iconIndex); + link.setWorkingDirectory(m_workingDirectory); + + link.save(path); + + return true; + } + catch(ShellLinkException&) + { + } + + return false; +} + +bool Shortcut::remove(Locations loc) +{ + const auto path = shortcutPath(loc); + if (path.isEmpty()) { + return false; + } + + if (!QFile::exists(path)) { + qCritical().nospace().noquote() + << "system shortcut: can't remove '" << path << "', file not found"; + + return false; + } + + if (!QFile::remove(path)) { + qCritical().nospace().noquote() + << "system shortcut: failed to remove '" << path << "'"; + + return false; + } + + return true; +} + +QString Shortcut::shortcutPath(Locations loc) const +{ + const auto dir = shortcutDirectory(loc); + if (dir.isEmpty()) { + return {}; + } + + const auto file = shortcutFilename(); + if (file.isEmpty()) { + return {}; + } + + return dir + QDir::separator() + file; +} + +QString Shortcut::shortcutDirectory(Locations loc) const +{ + QString dir; + + try + { + switch (loc) + { + case Desktop: + dir = MOBase::getDesktopDirectory(); + break; + + case StartMenu: + dir = MOBase::getStartMenuDirectory(); + break; + + case None: + default: + qCritical() << "system shortcut: bad location " << loc; + return {}; + } + } + catch(std::exception&) + { + return {}; + } + + return QDir::toNativeSeparators(dir); +} + +QString Shortcut::shortcutFilename() const +{ + if (m_name.isEmpty()) { + qCritical() << "system shortcut: name is empty"; + return {}; + } + + return m_name + ".lnk"; +} + + + class WMI { public: @@ -674,7 +990,7 @@ std::optional Environment::getWindowsFirewall() const if (FAILED(hr) || !rawPolicy) { qCritical() << "CoCreateInstance for NetFwPolicy2 failed, " - << formatSystemMessage(hr); + << formatSystemMessageQ(hr); return {}; } @@ -690,7 +1006,7 @@ std::optional Environment::getWindowsFirewall() const { qCritical() << "get_FirewallEnabled failed, " - << formatSystemMessage(hr); + << formatSystemMessageQ(hr); return {}; } diff --git a/src/shared/util.h b/src/shared/util.h index 4df1d13c..a40fa201 100644 --- a/src/shared/util.h +++ b/src/shared/util.h @@ -29,6 +29,8 @@ along with Mod Organizer. If not, see . #include +class Executable; + namespace MOShared { /// Test if a file (or directory) by the specified name exists @@ -52,6 +54,100 @@ bool CaseInsensitiveEqual(const std::wstring &lhs, const std::wstring &rhs); namespace env { +// an application shortcut that can be either on the desktop or the start menu +// +class Shortcut +{ +public: + // location of a shortcut + // + enum Locations + { + None = 0, + + // on the desktop + Desktop, + + // in the start menu + StartMenu + }; + + + // empty shortcut + // + Shortcut(); + + // shortcut from an executable + // + explicit Shortcut(const Executable& exe); + + // sets the name of the shortcut, shown on icons and start menu entries + // + Shortcut& name(const QString& s); + + // the program to start + // + Shortcut& target(const QString& s); + + // arguments to pass + // + Shortcut& arguments(const QString& s); + + // shows in the status bar of explorer, for example + // + Shortcut& description(const QString& s); + + // path to a binary that contains the icon and its index + // + Shortcut& icon(const QString& s, int index=0); + + // "start in" option for this shortcut + // + Shortcut& workingDirectory(const QString& s); + + + // returns whether this shortcut already exists at the given location; this + // does not check whether the shortcut parameters are different, it merely if + // the .lnk file exists + // + bool exists(Locations loc) const; + + // calls remove() if exists(), or add() + // + bool toggle(Locations loc); + + // adds the shortcut to the given location + // + bool add(Locations loc); + + // removes the shortcut from the given location + // + bool remove(Locations loc); + +private: + QString m_name; + QString m_target; + QString m_arguments; + QString m_description; + QString m_icon; + int m_iconIndex; + QString m_workingDirectory; + + + // returns the path where the shortcut file should be saved + // + QString shortcutPath(Locations loc) const; + + // returns the directory where the shortcut file should be saved + // + QString shortcutDirectory(Locations loc) const; + + // returns the filename of the shortcut file that should be used when saving + // + QString shortcutFilename() const; +}; + + // represents one module // class Module -- cgit v1.3.1 From f7c844fcf7684cbde0a3290b1950c52f88236be3 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 6 Jul 2019 19:23:52 -0400 Subject: put the error message in the ShellLinkException instead added more debug logging when creating and deleting shortcuts --- src/shared/util.cpp | 111 ++++++++++++++++++++++++++++++++++++++-------------- src/shared/util.h | 13 ++++++ 2 files changed, 95 insertions(+), 29 deletions(-) (limited to 'src') diff --git a/src/shared/util.cpp b/src/shared/util.cpp index 2bf1dba6..17df3b92 100644 --- a/src/shared/util.cpp +++ b/src/shared/util.cpp @@ -314,7 +314,22 @@ template using COMPtr = std::unique_ptr; -class ShellLinkException {}; +class ShellLinkException +{ +public: + ShellLinkException(QString s) + : m_what(std::move(s)) + { + } + + const QString& what() const + { + return m_what; + } + +private: + QString m_what; +}; // just a wrapper around IShellLink operations that throws ShellLinkException // on errors @@ -331,8 +346,7 @@ public: void setPath(const QString& s) { if (s.isEmpty()) { - critical() << "path cannot be empty"; - throw ShellLinkException(); + throw ShellLinkException("path cannot be empty"); } const auto r = m_link->SetPath(s.toStdWString().c_str()); @@ -385,16 +399,12 @@ private: COMPtr m_link; COMPtr m_file; - QDebug critical() - { - return qCritical().noquote().nospace() << "system shortcut: "; - } - void throwOnFail(HRESULT r, const QString& s) { if (FAILED(r)) { - critical() << s << ", " << formatSystemMessageQ(r); - throw ShellLinkException(); + throw ShellLinkException(QString("%1, %2") + .arg(s) + .arg(formatSystemMessageQ(r))); } } @@ -409,8 +419,7 @@ private: throwOnFail(r, "failed to create IShellLink instance"); if (!link) { - critical() << "creating IShellLink worked, but pointer is null"; - throw ShellLinkException(); + throw ShellLinkException("creating IShellLink worked, pointer is null"); } return COMPtr(static_cast(link)); @@ -424,8 +433,7 @@ private: throwOnFail(r, "failed to get IPersistFile interface"); if (!file) { - critical() << "querying IPersistFile worked, but pointer is null"; - throw ShellLinkException(); + throw ShellLinkException("querying IPersistFile worked, pointer is null"); } return COMPtr(static_cast(file)); @@ -515,16 +523,27 @@ bool Shortcut::toggle(Locations loc) bool Shortcut::add(Locations loc) { - const auto path = shortcutPath(loc); - if (path.isEmpty()) { + debug() + << "adding shortcut to " << toString(loc) << ":\n" + << " . name: '" << m_name << "'\n" + << " . target: '" << m_target << "'\n" + << " . arguments: '" << m_arguments << "'\n" + << " . description: '" << m_description << "'\n" + << " . icon: '" << m_icon << "' @ " << m_iconIndex << "\n" + << " . working directory: '" << m_workingDirectory << "'"; + + if (m_target.isEmpty()) { + critical() << "target is empty"; return false; } - if (m_target.isEmpty()) { - qCritical() << "system shortcut: target is empty"; + const auto path = shortcutPath(loc); + if (path.isEmpty()) { return false; } + debug() << "shorcut file will be saved at '" << path << "'"; + try { ShellLinkWrapper link; @@ -539,8 +558,9 @@ bool Shortcut::add(Locations loc) return true; } - catch(ShellLinkException&) + catch(ShellLinkException& e) { + critical() << e.what() << "\nshortcut file was not saved"; } return false; @@ -548,21 +568,26 @@ bool Shortcut::add(Locations loc) bool Shortcut::remove(Locations loc) { + debug() << "removing shortcut for '" << m_name << "' from " << toString(loc); + const auto path = shortcutPath(loc); if (path.isEmpty()) { return false; } - if (!QFile::exists(path)) { - qCritical().nospace().noquote() - << "system shortcut: can't remove '" << path << "', file not found"; + debug() << "path to shortcut file is '" << path << "'"; + if (!QFile::exists(path)) { + critical() << "can't remove '" << path << "', file not found"; return false; } - if (!QFile::remove(path)) { - qCritical().nospace().noquote() - << "system shortcut: failed to remove '" << path << "'"; + if (!MOBase::shellDelete({path})) { + const auto e = ::GetLastError(); + + critical() + << "failed to remove '" << path << "', " + << formatSystemMessageQ(e); return false; } @@ -603,13 +628,12 @@ QString Shortcut::shortcutDirectory(Locations loc) const case None: default: - qCritical() << "system shortcut: bad location " << loc; - return {}; + critical() << "bad location " << loc; + break; } } catch(std::exception&) { - return {}; } return QDir::toNativeSeparators(dir); @@ -618,13 +642,42 @@ QString Shortcut::shortcutDirectory(Locations loc) const QString Shortcut::shortcutFilename() const { if (m_name.isEmpty()) { - qCritical() << "system shortcut: name is empty"; + critical() << "name is empty"; return {}; } return m_name + ".lnk"; } +QDebug Shortcut::debug() const +{ + return qDebug().noquote().nospace() << "system shortcut: "; +} + +QDebug Shortcut::critical() const +{ + return qCritical().noquote().nospace() << "system shortcut: "; +} + + +QString toString(Shortcut::Locations loc) +{ + switch (loc) + { + case Shortcut::None: + return "none"; + + case Shortcut::Desktop: + return "desktop"; + + case Shortcut::StartMenu: + return "start menu"; + + default: + return QString("? (%1)").arg(static_cast(loc)); + } +} + class WMI diff --git a/src/shared/util.h b/src/shared/util.h index a40fa201..c4a2ed7d 100644 --- a/src/shared/util.h +++ b/src/shared/util.h @@ -133,6 +133,14 @@ private: int m_iconIndex; QString m_workingDirectory; + // returns a qCritical() logger with a prefix already logged + // + QDebug critical() const; + + // returns a qDebug() logger with a prefix already logged + // + QDebug debug() const; + // returns the path where the shortcut file should be saved // @@ -148,6 +156,11 @@ private: }; +// returns a string representation of the given location +// +QString toString(Shortcut::Locations loc); + + // represents one module // class Module -- cgit v1.3.1 From 37aa5e07f473e0377ef59d6e2f53f7373b11aecd Mon Sep 17 00:00:00 2001 From: Silarn Date: Mon, 8 Jul 2019 13:19:52 -0500 Subject: Add some basic checks for symlinks --- src/main.cpp | 18 +++++++++++++++++- src/organizercore.cpp | 17 ++++++++++++++++- src/organizercore.h | 1 + src/selectiondialog.cpp | 8 ++++++++ src/selectiondialog.h | 1 + 5 files changed, 43 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/main.cpp b/src/main.cpp index 4a2f6035..f2571685 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -303,6 +303,11 @@ MOBase::IPluginGame *determineCurrentGame(QString const &moPath, QSettings &sett gamePath = game->gameDirectory().absolutePath(); } QDir gameDir(gamePath); + QFileInfo directoryInfo(gameDir.path()); + if (directoryInfo.isSymLink()) { + reportError(QObject::tr("The configured path to the game directory (%1) appears to be a symbolic (or other) link. " + "This setup is incompatible with MO2's VFS and will not run correctly.").arg(gamePath)); + } if (game->looksValid(gameDir)) { return selectGame(settings, gameDir, game); } @@ -335,15 +340,26 @@ MOBase::IPluginGame *determineCurrentGame(QString const &moPath, QSettings &sett while (selection.exec() != QDialog::Rejected) { IPluginGame * game = selection.getChoiceData().value(); + QString gamePath = selection.getChoiceDescription(); + QFileInfo directoryInfo(gamePath); + if (directoryInfo.isSymLink()) { + reportError(QObject::tr("The configured path to the game directory (%1) appears to be a symbolic (or other) link. " + "This setup is incompatible with MO2's VFS and will not run correctly.").arg(gamePath)); + } if (game != nullptr) { return selectGame(settings, game->gameDirectory(), game); } - QString gamePath = QFileDialog::getExistingDirectory(nullptr, gameConfigured ? QObject::tr("Please select the installation of %1 to manage").arg(gameName) + gamePath = QFileDialog::getExistingDirectory(nullptr, gameConfigured ? QObject::tr("Please select the installation of %1 to manage").arg(gameName) : QObject::tr("Please select the game to manage"), QString(), QFileDialog::ShowDirsOnly); if (!gamePath.isEmpty()) { QDir gameDir(gamePath); + QFileInfo directoryInfo(gamePath); + if (directoryInfo.isSymLink()) { + reportError(QObject::tr("The configured path to the game directory (%1) appears to be a symbolic (or other) link. " + "This setup is incompatible with MO2's VFS and will not run correctly.").arg(gamePath)); + } QList possibleGames; for (IPluginGame * const game : plugins.plugins()) { //If a game is already configured, skip any plugins that are not for that game diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 99ddda1d..81ec7f43 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -691,12 +691,27 @@ bool OrganizerCore::createDirectory(const QString &path) { } } +bool OrganizerCore::checkPathSymlinks() { + bool hasSymlink = (QFileInfo(m_Settings.getProfileDirectory()).isSymLink() || + QFileInfo(m_Settings.getModDirectory()).isSymLink() || + QFileInfo(m_Settings.getOverwriteDirectory()).isSymLink()); + if (hasSymlink) { + QMessageBox::critical(nullptr, QObject::tr("Error"), + QObject::tr("One of the configured MO2 directories (profiles, mods, or overwrite) " + "is on a path containing a symbolic (or other) link. This is incompatible " + "with MO2's VFS system.")); + return false; + } + return true; +} + bool OrganizerCore::bootstrap() { return createDirectory(m_Settings.getProfileDirectory()) && createDirectory(m_Settings.getModDirectory()) && createDirectory(m_Settings.getDownloadDirectory()) && createDirectory(m_Settings.getOverwriteDirectory()) && - createDirectory(QString::fromStdWString(crashDumpsPath())) && cycleDiagnostics(); + createDirectory(QString::fromStdWString(crashDumpsPath())) && + checkPathSymlinks() && cycleDiagnostics(); } void OrganizerCore::createDefaultProfile() diff --git a/src/organizercore.h b/src/organizercore.h index 4dd11831..99b1c5f2 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -183,6 +183,7 @@ public: void loginFailedUpdate(const QString &message); static bool createAndMakeWritable(const QString &path); + bool checkPathSymlinks(); bool bootstrap(); void createDefaultProfile(); diff --git a/src/selectiondialog.cpp b/src/selectiondialog.cpp index 55728751..089760f9 100644 --- a/src/selectiondialog.cpp +++ b/src/selectiondialog.cpp @@ -79,6 +79,14 @@ QString SelectionDialog::getChoiceString() } } +QString SelectionDialog::getChoiceDescription() +{ + if (m_Choice == nullptr) + return QString(); + else + return m_Choice->accessibleDescription(); +} + void SelectionDialog::disableCancel() { ui->cancelButton->setEnabled(false); diff --git a/src/selectiondialog.h b/src/selectiondialog.h index 3c4b25df..5a70aa5e 100644 --- a/src/selectiondialog.h +++ b/src/selectiondialog.h @@ -52,6 +52,7 @@ public: QVariant getChoiceData(); QString getChoiceString(); + QString getChoiceDescription(); void disableCancel(); -- cgit v1.3.1 From 4bfb1a0d7666cbde7b10f59feaccde0af723a9e1 Mon Sep 17 00:00:00 2001 From: Silarn Date: Mon, 8 Jul 2019 13:20:41 -0500 Subject: Fix missing spaces in tutorial message --- src/tutorials/tutorial_firststeps_settings.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/tutorials/tutorial_firststeps_settings.js b/src/tutorials/tutorial_firststeps_settings.js index 792712be..b0e0c3c3 100644 --- a/src/tutorials/tutorial_firststeps_settings.js +++ b/src/tutorials/tutorial_firststeps_settings.js @@ -16,10 +16,10 @@ function getTutorialSteps() function() { highlightItem("nexusBox", false) - tutorial.text = qsTr("Use this interface to obtain an API key from NexusMods." - +"This is used for all API connections - downloads, updates" - +"etc. MO2 uses the Windows Credential Manager to store" - +"this data securely. If the SSO page on Nexus is failing," + tutorial.text = qsTr("Use this interface to obtain an API key from NexusMods. " + +"This is used for all API connections - downloads, updates " + +"etc. MO2 uses the Windows Credential Manager to store " + +"this data securely. If the SSO page on Nexus is failing, " +"use the manual entry and copy the API key from your profile.") waitForClick() } -- cgit v1.3.1