Copyright (C) 2012 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 "executableslist.h"
#include "iplugingame.h"
#include "settings.h"
#include "utility.h"
#include <log.h>
#include <QCoreApplication>
#include <QDebug>
#include <QDir>
#include <QFileInfo>
#include <algorithm>
using namespace MOBase;
ExecutablesList::iterator ExecutablesList::begin()
{
return m_Executables.begin();
}
ExecutablesList::const_iterator ExecutablesList::begin() const
{
return m_Executables.begin();
}
ExecutablesList::iterator ExecutablesList::end()
{
return m_Executables.end();
}
ExecutablesList::const_iterator ExecutablesList::end() const
{
return m_Executables.end();
}
std::size_t ExecutablesList::size() const
{
return m_Executables.size();
}
bool ExecutablesList::empty() const
{
return m_Executables.empty();
}
void ExecutablesList::load(const MOBase::IPluginGame* game, const Settings& s)
{
log::debug("loading executables");
m_Executables.clear();
bool needsUpgrade = false;
for (auto& map : s.executables()) {
Executable::Flags flags;
if (map["toolbar"].toBool())
flags |= Executable::ShowInToolbar;
if (map["ownicon"].toBool())
flags |= Executable::UseApplicationIcon;
if (map["minimizeToSystemTray"].toBool())
flags |= Executable::MinimizeToSystemTray;
if (map["hide"].toBool())
flags |= Executable::Hide;
if (map.contains("custom")) {
needsUpgrade = true;
}
setExecutable(Executable()
.title(map["title"].toString())
.binaryInfo(QFileInfo(map["binary"].toString()))
.arguments(map["arguments"].toString())
.steamAppID(map["steamAppID"].toString())
.workingDirectory(map["workingDirectory"].toString())
.flags(flags));
}
addFromPlugin(game, IgnoreExisting);
if (needsUpgrade)
upgradeFromCustom(game);
dump();
}
void ExecutablesList::store(Settings& s)
{
std::vector<std::map<QString, QVariant>> v;
for (const auto& item : *this) {
std::map<QString, QVariant> map;
map["title"] = item.title();
map["toolbar"] = item.isShownOnToolbar();
map["ownicon"] = item.usesOwnIcon();
map["hide"] = item.hide();
map["binary"] = item.binaryInfo().filePath();
map["arguments"] = item.arguments();
map["workingDirectory"] = item.workingDirectory();
map["steamAppID"] = item.steamAppID();
map["minimizeToSystemTray"] = item.minimizeToSystemTray();
v.push_back(std::move(map));
}
s.setExecutables(v);
}
std::vector<Executable>
ExecutablesList::getPluginExecutables(MOBase::IPluginGame const* game) const
{
Q_ASSERT(game != nullptr);
std::vector<Executable> v;
for (const ExecutableInfo& info : game->executables()) {
if (!info.isValid()) {
continue;
}
v.push_back({info, Executable::UseApplicationIcon});
}
const QFileInfo eppBin(QCoreApplication::applicationDirPath() +
"/explorer++/Explorer++.exe");
if (eppBin.exists()) {
const auto args = QString("\"%1\"").arg(
QDir::toNativeSeparators(game->dataDirectory().absolutePath()));
const auto exe = Executable()
.title("Explore Virtual Folder")
.binaryInfo(eppBin)
.arguments(args)
.workingDirectory(eppBin.absolutePath())
.flags(Executable::UseApplicationIcon);
v.push_back(exe);
}
return v;
}
void ExecutablesList::resetFromPlugin(MOBase::IPluginGame const* game)
{
log::debug("resetting plugin executables");
Q_ASSERT(game != nullptr);
for (const auto& exe : getPluginExecutables(game)) {
setExecutable(exe, MoveExisting);
}
}
void ExecutablesList::addFromPlugin(IPluginGame const* game, SetFlags flags)
{
Q_ASSERT(game != nullptr);
for (const auto& exe : getPluginExecutables(game)) {
setExecutable(exe, flags);
}
}
const Executable& ExecutablesList::get(const QString& title) const
{
for (const auto& exe : m_Executables) {
if (exe.title() == title) {
return exe;
}
}
throw std::runtime_error(
QString("executable not found: %1").arg(title).toLocal8Bit().constData());
}
Executable& ExecutablesList::get(const QString& title)
{
return const_cast<Executable&>(std::as_const(*this).get(title));
}
Executable& ExecutablesList::getByBinary(const QFileInfo& info)
{
for (Executable& exe : m_Executables) {
if (exe.binaryInfo() == info) {
return exe;
}
}
throw std::runtime_error("invalid info");
}
ExecutablesList::iterator ExecutablesList::find(const QString& title, bool ci)
{
const auto cif = ci ? Qt::CaseInsensitive : Qt::CaseSensitive;
return std::find_if(begin(), end(), [&](auto&& e) {
return (e.title().compare(title, cif) == 0);
});
}
ExecutablesList::const_iterator ExecutablesList::find(const QString& title,
bool ci) const
{
const auto cif = ci ? Qt::CaseInsensitive : Qt::CaseSensitive;
return std::find_if(begin(), end(), [&](auto&& e) {
return (e.title().compare(title, cif) == 0);
});
}
bool ExecutablesList::titleExists(const QString& title) const
{
auto test = [&](const Executable& exe) {
return exe.title() == title;
};
return std::find_if(m_Executables.begin(), m_Executables.end(), test) !=
m_Executables.end();
}
void ExecutablesList::setExecutable(const Executable& exe)
{
setExecutable(exe, MergeExisting);
}
void ExecutablesList::setExecutable(const Executable& exe, SetFlags flags)
{
auto itor = find(exe.title());
if (itor != end()) {
if (flags == IgnoreExisting) {
return;
}
if (flags == MoveExisting) {
const auto newTitle = makeNonConflictingTitle(exe.title());
if (!newTitle) {
log::error("executable '{}' was in the way but could not be renamed",
exe.title());
return;
}
log::warn("executable '{}' was in the way and was renamed to '{}'", itor->title(),
*newTitle);
itor->title(*newTitle);
itor = end();
}
}
if (itor == m_Executables.end()) {
m_Executables.push_back(exe);
} else {
itor->mergeFrom(exe);
}
}
void ExecutablesList::remove(const QString& title)
{
auto itor = find(title);
if (itor != m_Executables.end()) {
m_Executables.erase(itor);
}
}
std::optional<QString> ExecutablesList::makeNonConflictingTitle(const QString& prefix)
{
const int max = 100;
QString title = prefix;
for (int i = 1; i < max; ++i) {
if (!titleExists(title)) {
return title;
}
title = prefix + QString(" (%1)").arg(i);
}
log::error("ran out of executable titles for prefix '{}'", prefix);
return {};
}
void ExecutablesList::upgradeFromCustom(MOBase::IPluginGame const* game)
{
log::debug("upgrading executables list");
Q_ASSERT(game != nullptr);
for (const auto& exe : getPluginExecutables(game)) {
auto itor = find(exe.title());
if (itor == end()) {
continue;
}
if (!itor->binaryInfo().exists()) {
itor->binaryInfo(exe.binaryInfo());
}
if (itor->workingDirectory().isEmpty()) {
itor->workingDirectory(exe.workingDirectory());
}
}
}
void ExecutablesList::dump() const
{
for (const auto& e : m_Executables) {
QStringList flags;
if (e.flags() & Executable::ShowInToolbar) {
flags.push_back("toolbar");
}
if (e.flags() & Executable::UseApplicationIcon) {
flags.push_back("icon");
}
if (e.flags() & Executable::Hide) {
flags.push_back("hide");
}
if (e.flags() & Executable::MinimizeToSystemTray) {
flags.push_back("minimizeToSystemTray");
}
log::debug(" . executable '{}'\n"
" binary: {}\n"
" arguments: {}\n"
" steam ID: {}\n"
" directory: {}\n"
" flags: {} ({})",
e.title(), e.binaryInfo().filePath(), e.arguments(), e.steamAppID(),
e.workingDirectory(), flags.join("|"), e.flags());
}
}
Executable::Executable(QString title) : m_title(title) {}
Executable::Executable(const MOBase::ExecutableInfo& info, Flags flags)
: m_title(info.title()), m_binaryInfo(info.binary()),
m_arguments(info.arguments().join(" ")), m_steamAppID(info.steamAppID()),
m_workingDirectory(info.workingDirectory().path()), m_flags(flags)
{}
const QString& Executable::title() const
{
return m_title;
}
const QFileInfo& Executable::binaryInfo() const
{
return m_binaryInfo;
}
const QString& Executable::arguments() const
{
return m_arguments;
}
const QString& Executable::steamAppID() const
{
return m_steamAppID;
}
const QString& Executable::workingDirectory() const
{
return m_workingDirectory;
}
Executable::Flags Executable::flags() const
{
return m_flags;
}
Executable& Executable::title(const QString& s)
{
m_title = s;
return *this;
}
Executable& Executable::binaryInfo(const QFileInfo& fi)
{
m_binaryInfo = fi;
return *this;
}
Executable& Executable::arguments(const QString& s)
{
m_arguments = s;
return *this;
}
Executable& Executable::steamAppID(const QString& s)
{
m_steamAppID = s;
return *this;
}
Executable& Executable::workingDirectory(const QString& s)
{
m_workingDirectory = s;
return *this;
}
Executable& Executable::flags(Flags f)
{
m_flags = f;
return *this;
}
bool Executable::isShownOnToolbar() const
{
return m_flags.testFlag(ShowInToolbar);
}
void Executable::setShownOnToolbar(bool state)
{
if (state) {
m_flags |= ShowInToolbar;
} else {
m_flags &= ~ShowInToolbar;
}
}
bool Executable::usesOwnIcon() const
{
return m_flags.testFlag(UseApplicationIcon);
}
bool Executable::minimizeToSystemTray() const
{
return m_flags.testFlag(MinimizeToSystemTray);
}
bool Executable::hide() const
{
return m_flags.testFlag(Hide);
}
void Executable::mergeFrom(const Executable& other)
{
m_title = other.title();
m_binaryInfo = other.binaryInfo();
m_arguments = other.arguments();
m_steamAppID = other.steamAppID();
m_workingDirectory = other.workingDirectory();
m_flags = other.flags();
}