mirror of
https://github.com/limo-app/limo.git
synced 2025-12-23 14:57:56 -05:00
refactor mod import
Improved mod matching on import. Improved name and version detection on import.
This commit is contained in:
@@ -83,7 +83,6 @@ find_package(ZLIB REQUIRED)
|
||||
|
||||
# Separated for tests
|
||||
set(CORE_SOURCES
|
||||
src/core/addmodinfo.h
|
||||
src/core/appinfo.h
|
||||
src/core/autotag.cpp
|
||||
src/core/autotag.h
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
/*!
|
||||
* \file addmodinfo.h
|
||||
* \brief Contains the AddModInfo struct.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
|
||||
/*!
|
||||
* \brief Stores data needed to install a new mod.
|
||||
*/
|
||||
struct AddModInfo
|
||||
{
|
||||
/*! \brief Name of the new mod. */
|
||||
std::string name;
|
||||
/*! \brief Version of the new mod. */
|
||||
std::string version;
|
||||
/*! \brief Installer type to be used. */
|
||||
std::string installer;
|
||||
/*! \brief Path to the mods files. */
|
||||
std::string source_path;
|
||||
/*! \brief Ids of deployers to which the new mod will be added. */
|
||||
std::vector<int> deployers;
|
||||
/*! \brief Id of the mod the group of which the new mod will be added to, or -1 for no group. */
|
||||
int group;
|
||||
/*! \brief Flags for the installer. */
|
||||
int installer_flags;
|
||||
/*! \brief If > 0: Remove path components with depth < root_level. */
|
||||
int root_level;
|
||||
/*! \brief Contains pairs of source and destination paths for installation files. */
|
||||
std::vector<std::pair<std::filesystem::path, std::filesystem::path>> files;
|
||||
/*! \brief If true: The newly installed mod will replace the mod specified in group. */
|
||||
bool replace_mod = false;
|
||||
/*! \brief Path to the local archive or directory used to install this mod. */
|
||||
std::filesystem::path local_source = "";
|
||||
/*! \brief URL from where the mod was downloaded. */
|
||||
std::string remote_source = "";
|
||||
};
|
||||
@@ -15,30 +15,60 @@
|
||||
*/
|
||||
struct ImportModInfo
|
||||
{
|
||||
/*! \brief Describes what import action should be taken. */
|
||||
enum Type
|
||||
/*! \brief Describes which remote source this mod was retreived from. */
|
||||
enum RemoteType
|
||||
{
|
||||
download = 0,
|
||||
extract = 1
|
||||
/*! \brief No remote. */
|
||||
local = 0,
|
||||
/*! \brief NexusMods. */
|
||||
nexus = 1
|
||||
};
|
||||
|
||||
/*! \brief Describes what import action should be taken. */
|
||||
enum ActionType
|
||||
{
|
||||
/*! \brief Mod is to be downloaded. */
|
||||
download = 0,
|
||||
/*! \brief Mod archive is to be extracted. */
|
||||
extract = 1,
|
||||
/*! \brief Installation dialog is to be shown. */
|
||||
install_dialog = 2,
|
||||
/*! \brief Mod is to be installed. */
|
||||
install = 3
|
||||
};
|
||||
|
||||
/*! \brief Target ModdedApplication */
|
||||
int app_id;
|
||||
/*! \brief Type of action to be performed. */
|
||||
Type type;
|
||||
/*! \brief ActionType of action to be performed. */
|
||||
ActionType action_type;
|
||||
/*! \brief Path to the local file used for extraction or empty if type == download. */
|
||||
std::filesystem::path local_source;
|
||||
/*!
|
||||
* \brief URL used to download the mod. Can be either a URL pointing to the mod itself or
|
||||
* a NexusMods nxm URL.
|
||||
*/
|
||||
/*! \brief Type of remote this mod was retreived from. */
|
||||
RemoteType remote_type = local;
|
||||
/*! \brief Remote URL associated with this mod. */
|
||||
std::string remote_source = "";
|
||||
/*! \brief Remote URL used to request a download URL. */
|
||||
std::string remote_request_url = "";
|
||||
/*! \brief If this was retreived from a remote source: The mod id on the remote. */
|
||||
long remote_mod_id = -1;
|
||||
/*! \brief If this was retreived from a remote source: The file id on the remote. */
|
||||
long remote_file_id = -1;
|
||||
/*! \brief If this was retreived from a remote source: The mod name on the remote. */
|
||||
std::string remote_mod_name = "";
|
||||
/*! \brief If this was retreived from a remote source: The file name on the remote. */
|
||||
std::string remote_file_name = "";
|
||||
/*! \brief If this was retreived from a remote source: The file version on the remote. */
|
||||
std::string remote_file_version = "";
|
||||
/*! \brief URL used to download the mod. Note: This may only be valid for a limited time period. */
|
||||
std::string remote_download_url = "";
|
||||
/*! \brief If !=-1: The mod should be added to this mods group after installation. */
|
||||
int target_group_id = -1;
|
||||
/*! \brief Id assigned to this mod by Limo. */
|
||||
int limo_mod_id = -1;
|
||||
/*! \brief This is where the mod should be stored after extraction/ download. */
|
||||
std::filesystem::path target_path;
|
||||
/*! \brief If remote_source is a NexusMods mod page: The id of the file to be downloaded, else:
|
||||
* Not set. */
|
||||
int nexus_file_id = -1;
|
||||
/*! \brief If !=-1: The mod should be added to this mods group after installation. */
|
||||
int mod_id = -1;
|
||||
/*! \brief Current location of the mod on disk. */
|
||||
std::filesystem::path current_path;
|
||||
/*! \brief Time at which this object was added to the queue. Used for sorting. */
|
||||
std::chrono::time_point<std::chrono::high_resolution_clock> queue_time =
|
||||
std::chrono::high_resolution_clock::now();
|
||||
@@ -46,16 +76,34 @@ struct ImportModInfo
|
||||
std::string version_overwrite = "";
|
||||
/*! \brief If this is not empty: Use this as mod name. */
|
||||
std::string name_overwrite = "";
|
||||
/*! \brief Indicates whether the last import action performed was successful. */
|
||||
bool last_action_was_successful = true;
|
||||
/*! \brief Flags for the installer. */
|
||||
int installer_flags = 0;
|
||||
/*! \brief If > 0: Remove path components with depth < root_level. */
|
||||
int root_level = 0;
|
||||
/*! \brief Contains pairs of source and destination paths for installation files. */
|
||||
std::vector<std::pair<std::filesystem::path, std::filesystem::path>> files{};
|
||||
/*! \brief If true: The newly installed mod will replace the mod specified in group. */
|
||||
bool replace_mod = false;
|
||||
/*! \brief Ids of deployers to which the new mod will be added. */
|
||||
std::vector<int> deployers{};
|
||||
/*! \brief Installer type to be used. */
|
||||
std::string installer;
|
||||
/*! \brief Name of the new mod. */
|
||||
std::string name;
|
||||
/*! \brief Version of the new mod. */
|
||||
std::string version;
|
||||
|
||||
/*!
|
||||
* \brief Compares with another ImportModInfo object by their type.
|
||||
* \brief Compares with another ImportModInfo object by their action_type.
|
||||
* \param other Object to compare to.
|
||||
* \return True if only this object has type extract, else false.
|
||||
* \return True if only this object has action_type extract, else false.
|
||||
*/
|
||||
bool operator<(const ImportModInfo& other) const
|
||||
{
|
||||
if(type == other.type)
|
||||
if(action_type == other.action_type)
|
||||
return queue_time > other.queue_time;
|
||||
return type < other.type;
|
||||
return action_type < other.action_type;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -8,23 +8,55 @@ Mod::Mod(int id,
|
||||
const std::string& source_r,
|
||||
const std::time_t& time_r,
|
||||
unsigned long size,
|
||||
const std::time_t& suppress_time) :
|
||||
const std::time_t& suppress_time,
|
||||
long remote_mod_id,
|
||||
long remote_file_id,
|
||||
ImportModInfo::RemoteType remote_type) :
|
||||
id(id), name(std::move(name)), version(std::move(version)), install_time(time),
|
||||
local_source(source_l), remote_source(source_r), remote_update_time(time_r), size_on_disk(size),
|
||||
suppress_update_time(suppress_time)
|
||||
suppress_update_time(suppress_time), remote_mod_id(remote_mod_id), remote_file_id(remote_file_id),
|
||||
remote_type(remote_type)
|
||||
{}
|
||||
|
||||
Mod::Mod(const Mod& other)
|
||||
{
|
||||
id = other.id;
|
||||
name = other.name;
|
||||
version = other.version;
|
||||
install_time = other.install_time;
|
||||
local_source = other.local_source;
|
||||
remote_source = other.remote_source;
|
||||
remote_update_time = other.remote_update_time;
|
||||
size_on_disk = other.size_on_disk;
|
||||
suppress_update_time = other.suppress_update_time;
|
||||
remote_mod_id = other.remote_mod_id;
|
||||
remote_file_id = other.remote_file_id;
|
||||
remote_type = other.remote_type;
|
||||
}
|
||||
|
||||
Mod::Mod(const Json::Value& json)
|
||||
{
|
||||
Mod(json["id"].asInt(),
|
||||
json["name"].asString(),
|
||||
json["version"].asString(),
|
||||
json["install_time"].asInt64(),
|
||||
json["local_source"].asString(),
|
||||
json["remote_source"].asString(),
|
||||
json["remote_update_time"].asInt64(),
|
||||
json["size_on_disk"].asInt64(),
|
||||
json["suppress_update_time"].asInt64());
|
||||
long remote_mod_id = -1;
|
||||
if(json.isMember("remote_mod_id"))
|
||||
remote_mod_id = json["remote_mod_id"].asInt64();
|
||||
long remote_file_id = -1;
|
||||
if(json.isMember("remote_file_id"))
|
||||
remote_file_id = json["remote_file_id"].asInt64();
|
||||
ImportModInfo::RemoteType remote_type = ImportModInfo::RemoteType::local;
|
||||
if(json.isMember("remote_type"))
|
||||
remote_type = static_cast<ImportModInfo::RemoteType>(json["remote_type"].asInt());
|
||||
id = json["id"].asInt();
|
||||
name = json["name"].asString();
|
||||
version = json["version"].asString();
|
||||
install_time = json["install_time"].asInt64();
|
||||
local_source = json["local_source"].asString();
|
||||
remote_source = json["remote_source"].asString();
|
||||
remote_update_time = json["remote_update_time"].asInt64();
|
||||
size_on_disk = json["size_on_disk"].asInt64();
|
||||
suppress_update_time = json["suppress_update_time"].asInt64();
|
||||
remote_mod_id = remote_mod_id;
|
||||
remote_file_id = remote_file_id;
|
||||
remote_type = remote_type;
|
||||
}
|
||||
|
||||
Json::Value Mod::toJson() const
|
||||
@@ -39,6 +71,9 @@ Json::Value Mod::toJson() const
|
||||
json["remote_update_time"] = remote_update_time;
|
||||
json["size_on_disk"] = size_on_disk;
|
||||
json["suppress_update_time"] = suppress_update_time;
|
||||
json["remote_mod_id"] = remote_mod_id;
|
||||
json["remote_file_id"] = remote_file_id;
|
||||
json["remote_type"] = static_cast<int>(remote_type);
|
||||
return json;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <filesystem>
|
||||
#include <json/json.h>
|
||||
#include <string>
|
||||
#include "importmodinfo.h"
|
||||
|
||||
|
||||
/*!
|
||||
@@ -33,6 +34,12 @@ struct Mod
|
||||
unsigned long size_on_disk;
|
||||
/*! \brief Timestamp for when the user requested to suppress current update notifications. */
|
||||
std::time_t suppress_update_time;
|
||||
/*! \brief If this was retreived from a remote source: The mod id on the remote. */
|
||||
long remote_mod_id = -1;
|
||||
/*! \brief If this was retreived from a remote source: The file id on the remote. */
|
||||
long remote_file_id = -1;
|
||||
/*! \brief Type of remote this mod was retreived from. */
|
||||
ImportModInfo::RemoteType remote_type;
|
||||
|
||||
/*!
|
||||
* \brief Constructor. Simply initializes members.
|
||||
@@ -46,6 +53,9 @@ struct Mod
|
||||
* \param size Total size of the installed mod on disk.
|
||||
* \param suppress_time Timestamp for when the user requested to suppress current update
|
||||
* notifications.
|
||||
* \param remote_mod_id If this was retreived from a remote source: The mod id on the remote.
|
||||
* \param remote_file_id If this was retreived from a remote source: The file id on the remote.
|
||||
* \param remote_type Type of remote this mod was retreived from.
|
||||
*/
|
||||
Mod(int id,
|
||||
const std::string& name,
|
||||
@@ -55,13 +65,24 @@ struct Mod
|
||||
const std::string& source_r,
|
||||
const std::time_t& time_r,
|
||||
unsigned long size,
|
||||
const std::time_t& suppress_time);
|
||||
const std::time_t& suppress_time,
|
||||
long remote_mod_id,
|
||||
long remote_file_id,
|
||||
ImportModInfo::RemoteType remote_type);
|
||||
/*!
|
||||
* \brief Copy constructor. Copies all members.
|
||||
* \param other Copy source
|
||||
*/
|
||||
Mod(const Mod& other);
|
||||
/*!
|
||||
* \brief Initializes all members from a JSON object.
|
||||
* \param json The source for member values.
|
||||
*/
|
||||
Mod(const Json::Value& json);
|
||||
|
||||
/*!
|
||||
* \brief Serializes this struct to a JSON object.
|
||||
* \return The JSON object.
|
||||
*/
|
||||
Json::Value toJson() const;
|
||||
/*!
|
||||
* \brief Compares to another mod by id.
|
||||
|
||||
@@ -116,17 +116,17 @@ void ModdedApplication::unDeployModsFor(std::vector<int> deployers)
|
||||
updateSettings(true);
|
||||
}
|
||||
|
||||
void ModdedApplication::installMod(const AddModInfo& info)
|
||||
void ModdedApplication::installMod(const ImportModInfo& info)
|
||||
{
|
||||
if(info.replace_mod && info.group != -1)
|
||||
if(info.replace_mod && info.target_group_id != -1)
|
||||
{
|
||||
replaceMod(info);
|
||||
return;
|
||||
}
|
||||
ProgressNode progress_node(progress_callback_);
|
||||
if(info.group >= 0 && !info.deployers.empty())
|
||||
if(info.target_group_id >= 0 && !info.deployers.empty())
|
||||
progress_node.addChildren({ 1.0f, 10.0f, info.deployers.size() > 1 ? 10.0f : 1.0f });
|
||||
else if(info.group >= 0 || !info.deployers.empty())
|
||||
else if(info.target_group_id >= 0 || !info.deployers.empty())
|
||||
progress_node.addChildren({ 1, 10 });
|
||||
else
|
||||
progress_node.addChildren({ 1 });
|
||||
@@ -140,7 +140,7 @@ void ModdedApplication::installMod(const AddModInfo& info)
|
||||
if(mod_id == std::numeric_limits<int>().max())
|
||||
throw std::runtime_error("Error: Could not generate new mod id.");
|
||||
last_mod_id_ = mod_id;
|
||||
const auto mod_size = Installer::install(info.source_path,
|
||||
const auto mod_size = Installer::install(info.current_path,
|
||||
staging_dir_ / std::to_string(mod_id),
|
||||
info.installer_flags,
|
||||
info.installer,
|
||||
@@ -155,19 +155,22 @@ void ModdedApplication::installMod(const AddModInfo& info)
|
||||
info.remote_source,
|
||||
time_now,
|
||||
mod_size,
|
||||
time_now);
|
||||
time_now,
|
||||
info.remote_mod_id,
|
||||
info.remote_file_id,
|
||||
info.remote_type);
|
||||
installer_map_[mod_id] = info.installer;
|
||||
progress_node.child(0).advance();
|
||||
if(info.group >= 0)
|
||||
if(info.target_group_id >= 0)
|
||||
{
|
||||
if(modHasGroup(info.group))
|
||||
addModToGroup(mod_id, group_map_[info.group], &progress_node.child(1));
|
||||
if(modHasGroup(info.target_group_id))
|
||||
addModToGroup(mod_id, group_map_[info.target_group_id], &progress_node.child(1));
|
||||
else
|
||||
createGroup(mod_id, info.group, &progress_node.child(1));
|
||||
createGroup(mod_id, info.target_group_id, &progress_node.child(1));
|
||||
}
|
||||
|
||||
for(int deployer : info.deployers)
|
||||
addModToDeployer(deployer, mod_id, true, &progress_node.child(info.group >= 0 ? 2 : 1));
|
||||
addModToDeployer(deployer, mod_id, true, &progress_node.child(info.target_group_id >= 0 ? 2 : 1));
|
||||
|
||||
for(auto& tag : auto_tags_)
|
||||
tag.updateMods(staging_dir_, std::vector<int>{ mod_id });
|
||||
@@ -372,15 +375,7 @@ std::vector<ModInfo> ModdedApplication::getModInfo() const
|
||||
}
|
||||
|
||||
mod_info.emplace_back(
|
||||
mod.id,
|
||||
mod.name,
|
||||
mod.version,
|
||||
mod.install_time,
|
||||
mod.local_source,
|
||||
mod.remote_source,
|
||||
mod.remote_update_time,
|
||||
mod.size_on_disk,
|
||||
mod.suppress_update_time,
|
||||
mod,
|
||||
deployer_names,
|
||||
deployer_ids,
|
||||
statuses,
|
||||
@@ -857,12 +852,6 @@ int ModdedApplication::verifyStagingDir(sfs::path staging_dir)
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ModdedApplication::extractArchive(const sfs::path& source, const sfs::path& target)
|
||||
{
|
||||
ProgressNode node(progress_callback_);
|
||||
Installer::extract(source, target, &node);
|
||||
}
|
||||
|
||||
DeployerInfo ModdedApplication::getDeployerInfo(int deployer)
|
||||
{
|
||||
if(!(deployers_[deployer]->isAutonomous()))
|
||||
@@ -1393,7 +1382,7 @@ void ModdedApplication::deleteAllData()
|
||||
for(const auto& mod : installed_mods_)
|
||||
sfs::remove_all(staging_dir_ / std::to_string(mod.id));
|
||||
sfs::remove(staging_dir_ / CONFIG_FILE_NAME);
|
||||
sfs::remove_all(staging_dir_ / download_dir_);
|
||||
sfs::remove_all(getDownloadDir());
|
||||
}
|
||||
|
||||
void ModdedApplication::setAppVersion(const std::string& app_version)
|
||||
@@ -1461,106 +1450,6 @@ void ModdedApplication::suppressUpdateNotification(const std::vector<int>& mod_i
|
||||
updateSettings(true);
|
||||
}
|
||||
|
||||
std::string ModdedApplication::getDownloadUrl(const std::string& nxm_url)
|
||||
{
|
||||
return nexus::Api::getDownloadUrl(nxm_url);
|
||||
}
|
||||
|
||||
std::string ModdedApplication::getDownloadUrlForFile(int nexus_file_id, const std::string& mod_url)
|
||||
{
|
||||
return nexus::Api::getDownloadUrl(mod_url, nexus_file_id);
|
||||
}
|
||||
|
||||
std::string ModdedApplication::getNexusPageUrl(const std::string& nxm_url)
|
||||
{
|
||||
return nexus::Api::getNexusPageUrl(nxm_url);
|
||||
}
|
||||
|
||||
std::string ModdedApplication::downloadMod(const std::string& url,
|
||||
std::function<void(float)> progress_callback)
|
||||
{
|
||||
log_(Log::LOG_DEBUG, "Download URL: " + url);
|
||||
std::regex url_regex(R"(.*/(.*)\?.*)");
|
||||
std::smatch match;
|
||||
if(!std::regex_match(url, match, url_regex))
|
||||
throw std::runtime_error(std::format("Invalid download URL \"{}\"", url));
|
||||
sfs::path download_path = staging_dir_ / download_dir_;
|
||||
if(!sfs::exists(download_path))
|
||||
sfs::create_directories(download_path);
|
||||
sfs::path file_name = match[1].str();
|
||||
const std::string file_name_prefix = file_name.stem();
|
||||
const std::string extension = file_name.extension();
|
||||
int suffix = 1;
|
||||
while(pu::exists(download_path / file_name))
|
||||
{
|
||||
file_name = file_name_prefix + "(" + std::to_string(suffix) + ")" + extension;
|
||||
suffix++;
|
||||
}
|
||||
std::string file_name_str = file_name.string();
|
||||
auto pos = file_name_str.find("%20");
|
||||
while(pos != std::string::npos)
|
||||
{
|
||||
file_name_str.replace(pos, 3, " ");
|
||||
pos = file_name_str.find("%20");
|
||||
}
|
||||
file_name = file_name_str;
|
||||
|
||||
std::ofstream fstream(download_path / file_name, std::ios::binary);
|
||||
if(!fstream.is_open())
|
||||
throw std::runtime_error("Failed to write to disk.");
|
||||
bool message_sent = false;
|
||||
cpr::Response response = cpr::Download(
|
||||
fstream,
|
||||
cpr::Url(url),
|
||||
cpr::ProgressCallback(
|
||||
[app = this, &message_sent, &file_name, progress_callback](auto download_total,
|
||||
auto download_now,
|
||||
auto upload_total,
|
||||
auto upload_now,
|
||||
intptr_t user_data)
|
||||
{
|
||||
if(!message_sent && download_total > 0)
|
||||
{
|
||||
std::string size_string;
|
||||
long last_size = 0;
|
||||
long size = download_total;
|
||||
int exp = 0;
|
||||
const std::vector<std::string> units{ "B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB" };
|
||||
while(size > 1024 && exp < units.size())
|
||||
{
|
||||
last_size = size;
|
||||
size /= 1024;
|
||||
exp++;
|
||||
}
|
||||
last_size /= 1.024;
|
||||
size_string = std::to_string(size);
|
||||
const int first_digit = (last_size / 100) % 10;
|
||||
const int second_digit = (last_size / 10) % 10;
|
||||
if(first_digit != 0 || second_digit != 0)
|
||||
size_string += "." + std::to_string(first_digit);
|
||||
if(second_digit != 0)
|
||||
size_string += std::to_string(second_digit);
|
||||
size_string += units[exp];
|
||||
|
||||
app->log_(Log::LOG_INFO,
|
||||
("Downloading \"" + file_name.string() + "\" with size: ").c_str() +
|
||||
size_string + "...");
|
||||
message_sent = true;
|
||||
}
|
||||
if(download_total != 0)
|
||||
progress_callback((float)download_now / (float)download_total);
|
||||
return true;
|
||||
}));
|
||||
if(response.status_code != 200)
|
||||
{
|
||||
sfs::remove(download_path / file_name);
|
||||
throw std::runtime_error("Download failed with response: \"" + response.status_line +
|
||||
"\" (code " + std::to_string(response.status_code) + ").");
|
||||
}
|
||||
fstream.close();
|
||||
return (download_path / file_name).string();
|
||||
}
|
||||
|
||||
ExternalChangesInfo ModdedApplication::getExternalChanges(int deployer)
|
||||
{
|
||||
ExternalChangesInfo info;
|
||||
@@ -1668,6 +1557,11 @@ void ModdedApplication::applyModAction(int deployer, int action, int mod_id)
|
||||
updateSettings(true);
|
||||
}
|
||||
|
||||
std::filesystem::path ModdedApplication::getDownloadDir() const
|
||||
{
|
||||
return staging_dir_ / DOWNLOAD_DIR;
|
||||
}
|
||||
|
||||
sfs::path ModdedApplication::iconPath() const
|
||||
{
|
||||
return icon_path_;
|
||||
@@ -1702,18 +1596,8 @@ void ModdedApplication::updateSettings(bool write)
|
||||
|
||||
for(int i = 0; i < installed_mods_.size(); i++)
|
||||
{
|
||||
json_settings_["installed_mods"][i]["id"] = installed_mods_[i].id;
|
||||
json_settings_["installed_mods"][i]["name"] = installed_mods_[i].name;
|
||||
json_settings_["installed_mods"][i]["version"] = installed_mods_[i].version;
|
||||
json_settings_["installed_mods"][i] = installed_mods_[i].toJson();
|
||||
json_settings_["installed_mods"][i]["installer"] = installer_map_[installed_mods_[i].id];
|
||||
json_settings_["installed_mods"][i]["install_time"] = installed_mods_[i].install_time;
|
||||
json_settings_["installed_mods"][i]["local_source"] = installed_mods_[i].local_source.string();
|
||||
json_settings_["installed_mods"][i]["remote_source"] = installed_mods_[i].remote_source;
|
||||
json_settings_["installed_mods"][i]["remote_update_time"] =
|
||||
installed_mods_[i].remote_update_time;
|
||||
json_settings_["installed_mods"][i]["size_on_disk"] = installed_mods_[i].size_on_disk;
|
||||
json_settings_["installed_mods"][i]["suppress_update_time"] =
|
||||
installed_mods_[i].suppress_update_time;
|
||||
}
|
||||
|
||||
for(int depl = 0; depl < deployers_.size(); depl++)
|
||||
@@ -1850,15 +1734,7 @@ void ModdedApplication::updateState(bool read)
|
||||
Json::Value installed_mods = json_settings_["installed_mods"];
|
||||
for(int i = 0; i < installed_mods.size(); i++)
|
||||
{
|
||||
installed_mods_.emplace_back(installed_mods[i]["id"].asInt(),
|
||||
installed_mods[i]["name"].asString(),
|
||||
installed_mods[i]["version"].asString(),
|
||||
installed_mods[i]["install_time"].asInt64(),
|
||||
installed_mods[i]["local_source"].asString(),
|
||||
installed_mods[i]["remote_source"].asString(),
|
||||
installed_mods[i]["remote_update_time"].asInt64(),
|
||||
installed_mods[i]["size_on_disk"].asInt64(),
|
||||
installed_mods[i]["suppress_update_time"].asInt64());
|
||||
installed_mods_.emplace_back(installed_mods[i]);
|
||||
std::string installer = installed_mods[i]["installer"].asString();
|
||||
std::vector<std::string> types = Installer::INSTALLER_TYPES;
|
||||
if(std::find(types.begin(), types.end(), installer) == types.end())
|
||||
@@ -2116,9 +1992,9 @@ void ModdedApplication::splitMod(int mod_id, int deployer)
|
||||
continue;
|
||||
const auto mod_dir = staging_dir_ / std::to_string(mod_id) / mod_dir_optional->string();
|
||||
|
||||
AddModInfo info;
|
||||
ImportModInfo info;
|
||||
info.deployers = { depl };
|
||||
info.group = -1;
|
||||
info.target_group_id = -1;
|
||||
auto iter =
|
||||
str::find_if(installed_mods_, [mod_id](const auto& mod) { return mod.id == mod_id; });
|
||||
if(iter == installed_mods_.end())
|
||||
@@ -2129,7 +2005,13 @@ void ModdedApplication::splitMod(int mod_id, int deployer)
|
||||
info.installer_flags = Installer::Flag::preserve_case | Installer::Flag::preserve_directories;
|
||||
info.files = {};
|
||||
info.root_level = 0;
|
||||
info.source_path = mod_dir;
|
||||
info.current_path = mod_dir;
|
||||
info.local_source = iter->local_source;
|
||||
info.remote_source = iter->remote_source;
|
||||
info.remote_mod_id = iter->remote_mod_id;
|
||||
info.remote_file_id = iter->remote_file_id;
|
||||
info.remote_type = iter->remote_type;
|
||||
|
||||
log_(
|
||||
Log::LOG_WARNING,
|
||||
std::format(
|
||||
@@ -2141,17 +2023,17 @@ void ModdedApplication::splitMod(int mod_id, int deployer)
|
||||
}
|
||||
}
|
||||
|
||||
void ModdedApplication::replaceMod(const AddModInfo& info)
|
||||
void ModdedApplication::replaceMod(const ImportModInfo& info)
|
||||
{
|
||||
if(!info.replace_mod || info.group == -1)
|
||||
if(!info.replace_mod || info.target_group_id == -1)
|
||||
{
|
||||
installMod(info);
|
||||
return;
|
||||
}
|
||||
auto index =
|
||||
str::find_if(installed_mods_, [group = info.group](const Mod& m) { return m.id == group; });
|
||||
str::find_if(installed_mods_, [group = info.target_group_id](const Mod& m) { return m.id == group; });
|
||||
if(index == installed_mods_.end())
|
||||
throw std::runtime_error(std::format("Invalid group '{}' for mod '{}'", info.group, info.name));
|
||||
throw std::runtime_error(std::format("Invalid group '{}' for mod '{}'", info.target_group_id, info.name));
|
||||
|
||||
int mod_id = 0;
|
||||
if(!installed_mods_.empty())
|
||||
@@ -2164,13 +2046,13 @@ void ModdedApplication::replaceMod(const AddModInfo& info)
|
||||
const sfs::path tmp_replace_dir =
|
||||
staging_dir_ / (std::string("tmp_replace_") + std::to_string(mod_id));
|
||||
|
||||
const auto mod_size = Installer::install(info.source_path,
|
||||
const auto mod_size = Installer::install(info.current_path,
|
||||
tmp_replace_dir,
|
||||
info.installer_flags,
|
||||
info.installer,
|
||||
info.root_level,
|
||||
info.files);
|
||||
const sfs::path old_mod_path = staging_dir_ / std::to_string(info.group);
|
||||
const sfs::path old_mod_path = staging_dir_ / std::to_string(info.target_group_id);
|
||||
sfs::remove_all(old_mod_path);
|
||||
sfs::rename(tmp_replace_dir, old_mod_path);
|
||||
|
||||
@@ -2181,6 +2063,9 @@ void ModdedApplication::replaceMod(const AddModInfo& info)
|
||||
index->install_time = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
|
||||
index->remote_update_time = index->install_time;
|
||||
index->size_on_disk = mod_size;
|
||||
index->remote_mod_id = info.remote_mod_id;
|
||||
index->remote_file_id = info.remote_file_id;
|
||||
index->remote_type = info.remote_type;
|
||||
|
||||
std::vector<float> weights_profiles;
|
||||
std::vector<float> weights_mods;
|
||||
@@ -2189,7 +2074,7 @@ void ModdedApplication::replaceMod(const AddModInfo& info)
|
||||
{
|
||||
bool was_split = false;
|
||||
update_targets.push_back({});
|
||||
if(deployers_[depl]->hasMod(info.group))
|
||||
if(deployers_[depl]->hasMod(info.target_group_id))
|
||||
weights_mods.push_back(deployers_[depl]->getNumMods());
|
||||
else
|
||||
weights_mods.push_back(0);
|
||||
@@ -2198,14 +2083,14 @@ void ModdedApplication::replaceMod(const AddModInfo& info)
|
||||
for(int prof = 0; prof < profile_names_.size(); prof++)
|
||||
{
|
||||
deployers_[depl]->setProfile(prof);
|
||||
if(deployers_[depl]->hasMod(info.group))
|
||||
if(deployers_[depl]->hasMod(info.target_group_id))
|
||||
{
|
||||
update_targets[depl].push_back(prof);
|
||||
weights_profiles.push_back(deployers_[depl]->getNumMods());
|
||||
if(!was_split)
|
||||
{
|
||||
was_split = true;
|
||||
splitMod(info.group, depl);
|
||||
splitMod(info.target_group_id, depl);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2218,7 +2103,7 @@ void ModdedApplication::replaceMod(const AddModInfo& info)
|
||||
int i = 0;
|
||||
for(int depl = 0; depl < update_targets.size(); depl++)
|
||||
{
|
||||
deployers_[depl]->updateDeployedFilesForMod(info.group, &node.child(0).child(depl));
|
||||
deployers_[depl]->updateDeployedFilesForMod(info.target_group_id, &node.child(0).child(depl));
|
||||
for(int prof : update_targets[depl])
|
||||
{
|
||||
deployers_[depl]->setProfile(prof);
|
||||
@@ -2229,7 +2114,7 @@ void ModdedApplication::replaceMod(const AddModInfo& info)
|
||||
}
|
||||
|
||||
for(auto& tag : auto_tags_)
|
||||
tag.updateMods(staging_dir_, std::vector<int>{ info.group });
|
||||
tag.updateMods(staging_dir_, std::vector<int>{ info.target_group_id });
|
||||
updateAutoTagMap();
|
||||
|
||||
updateSettings(true);
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "addmodinfo.h"
|
||||
#include "appinfo.h"
|
||||
#include "autotag.h"
|
||||
#include "backupmanager.h"
|
||||
@@ -74,7 +73,7 @@ public:
|
||||
* \brief Installs a new mod using the given Installer type.
|
||||
* \param info Contains all data needed to install the mod.
|
||||
*/
|
||||
void installMod(const AddModInfo& info);
|
||||
void installMod(const ImportModInfo& info);
|
||||
/*!
|
||||
* \brief Uninstalls the given mods, this includes deleting all installed files.
|
||||
* \param mod_id Ids of the mods to be uninstalled.
|
||||
@@ -375,12 +374,6 @@ public:
|
||||
* \return A code indicating success(0), an IO error(1) or an error during JSON parsing(2).
|
||||
*/
|
||||
static int verifyStagingDir(std::filesystem::path staging_dir);
|
||||
/*!
|
||||
* \brief Extracts the given archive to the given location.
|
||||
* \param source Source path.
|
||||
* \param target Extraction target path.
|
||||
*/
|
||||
void extractArchive(const std::filesystem::path& source, const std::filesystem::path& target);
|
||||
/*!
|
||||
* \brief Creates DeployerInfo for one Deployer.
|
||||
* \param deployer Target deployer.
|
||||
@@ -604,31 +597,6 @@ public:
|
||||
* \param mod_ids Ids of the mods for which update notifications are to be disabled.
|
||||
*/
|
||||
void suppressUpdateNotification(const std::vector<int>& mod_ids);
|
||||
/*!
|
||||
* \brief Generates a download URL from the given NexusMods nxm Url.
|
||||
* \param nxm_url The nxm URL used.
|
||||
* \return The download URL.
|
||||
*/
|
||||
std::string getDownloadUrl(const std::string& nxm_url);
|
||||
/*!
|
||||
* \brief Generates a download URL from the given NexusMods mod id and file id.
|
||||
* \param nexus_file_id File id of the mod.
|
||||
* \param mod_url Url to the mod page on NexusMods.
|
||||
* \return The download URL.
|
||||
*/
|
||||
std::string getDownloadUrlForFile(int nexus_file_id, const std::string& mod_url);
|
||||
/*!
|
||||
* \brief Generates a NexusMods mod page URL from the given nxm URL.
|
||||
* \param nxm_url The nxm Url used. This is usually generated through the NexusMods website.
|
||||
* \return The NexusMods mod page URL.
|
||||
*/
|
||||
std::string getNexusPageUrl(const std::string& nxm_url);
|
||||
/*!
|
||||
* \brief Downloads the file from the given url to staging_dir_ / _download.
|
||||
* \param url Url from which to download the file.
|
||||
* \return The path to the downloaded file.
|
||||
*/
|
||||
std::string downloadMod(const std::string& url, std::function<void(float)> progress_callback);
|
||||
/*!
|
||||
* \brief Checks if files deployed by the given deployer have been externally overwritten.
|
||||
* \param deployer Deployer to check.
|
||||
@@ -674,8 +642,16 @@ public:
|
||||
* \param mod_id Target mod.
|
||||
*/
|
||||
void applyModAction(int deployer, int action, int mod_id);
|
||||
/*!
|
||||
* \brief Returns the path used to store downloaded mods.
|
||||
* \return The download path.
|
||||
*/
|
||||
std::filesystem::path getDownloadDir() const;
|
||||
|
||||
private:
|
||||
/*! \brief The subdirectory used to store downloads. */
|
||||
static inline constexpr std::string DOWNLOAD_DIR = "_download";
|
||||
|
||||
/*! \brief The name of this application. */
|
||||
std::string name_;
|
||||
/*! \brief Contains the internal state of this object. */
|
||||
@@ -728,8 +704,6 @@ private:
|
||||
std::vector<std::string> app_versions_;
|
||||
/*! \brief Callback used to inform about the current task's progress. */
|
||||
std::function<void(float)> progress_callback_ = [](float f) {};
|
||||
/*! \brief The subdirectory used to store downloads. */
|
||||
std::string download_dir_ = "_download";
|
||||
/*! \brief File name used to store exported deployers and auto tags. */
|
||||
std::string export_file_name = "exported_config";
|
||||
|
||||
@@ -778,7 +752,7 @@ private:
|
||||
* \brief Replaces an existing mod with the mod specified by the given argument.
|
||||
* \param info Contains all data needed to install the mod.
|
||||
*/
|
||||
void replaceMod(const AddModInfo& info);
|
||||
void replaceMod(const ImportModInfo& info);
|
||||
/*! \brief Updates manual_tag_map_ with the information contained in manual_tags_. */
|
||||
void updateManualTagMap();
|
||||
/*! \brief Updates auto_tag_map_ with the information contained in auto_tags_. */
|
||||
|
||||
@@ -35,16 +35,7 @@ struct ModInfo
|
||||
|
||||
/*!
|
||||
* \brief Constructor. Simply initializes members.
|
||||
* \param id The mod's id.
|
||||
* \param name The mod's name.
|
||||
* \param version The mod's version.
|
||||
* \param install_time Timestamp indicating when the mod was installed.
|
||||
* \param local_source Source archive for the mod.
|
||||
* \param remote_source URL from where the mod was downloaded.
|
||||
* \param remote_update_time Timestamp for when the mod was updated at the remote source.
|
||||
* \param size Total size of the installed mod on disk.
|
||||
* \param suppress_time Timestamp for when the user requested to suppress current update
|
||||
* notifications.
|
||||
* \param mod Mod object wrapped by this struct.
|
||||
* \param deployer_names Names of all \ref Deployer "deployers" the mod belongs to.
|
||||
* \param deployer_ids Ids of all \ref Deployer "deployers" the mod belongs to.
|
||||
* \param statuses The mods activation status for every \ref Deployer "deployer" it belongs to.
|
||||
@@ -52,15 +43,7 @@ struct ModInfo
|
||||
* \param is_active_member If true: Mod is the active member of it's group.
|
||||
* \param man_tags The names of all manual tags for this mod.
|
||||
*/
|
||||
ModInfo(int id,
|
||||
const std::string& name,
|
||||
const std::string& version,
|
||||
const std::time_t& install_time,
|
||||
const std::filesystem::path& local_source,
|
||||
const std::string& remote_source,
|
||||
const std::time_t& remote_update_time,
|
||||
unsigned long size,
|
||||
const std::time_t& suppress_time,
|
||||
ModInfo(const Mod& mod,
|
||||
const std::vector<std::string>& deployer_names,
|
||||
const std::vector<int>& deployer_ids,
|
||||
const std::vector<bool>& statuses,
|
||||
@@ -68,16 +51,8 @@ struct ModInfo
|
||||
bool is_active_member,
|
||||
const std::vector<std::string>& man_tags,
|
||||
const std::vector<std::string>& au_tags) :
|
||||
mod(id,
|
||||
name,
|
||||
version,
|
||||
install_time,
|
||||
local_source,
|
||||
remote_source,
|
||||
remote_update_time,
|
||||
size,
|
||||
suppress_time),
|
||||
deployers(std::move(deployer_names)), deployer_ids(std::move(deployer_ids)),
|
||||
mod(mod),
|
||||
deployers(deployer_names), deployer_ids(deployer_ids),
|
||||
deployer_statuses(statuses), group(group), is_active_group_member(is_active_member),
|
||||
manual_tags(man_tags), auto_tags(au_tags)
|
||||
{}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "api.h"
|
||||
#include "../parseerror.h"
|
||||
#include <iostream>
|
||||
#include <json/json.h>
|
||||
#include <ranges>
|
||||
#include <regex>
|
||||
@@ -150,11 +151,10 @@ std::string Api::getDownloadUrl(const std::string& mod_url, long file_id)
|
||||
|
||||
std::string Api::getDownloadUrl(const std::string& nxm_url)
|
||||
{
|
||||
const std::regex regex(
|
||||
R"(nxm:\/\/(.+)\/mods\/(\d+)\/files\/(\d+)\?key=(.+)&expires=(\d+)&user_id=(\d+))");
|
||||
std::smatch match;
|
||||
if(!std::regex_match(nxm_url, match, regex))
|
||||
const auto match_opt = nxmUrlIsValid(nxm_url);
|
||||
if(!match_opt)
|
||||
throw std::runtime_error(std::format("Invalid NXM URL: \"{}\"", nxm_url));
|
||||
std::smatch match = *match_opt;
|
||||
const std::string domain_name = match[1];
|
||||
const std::string mod_id = match[2];
|
||||
const std::string file_id = match[3];
|
||||
@@ -332,3 +332,52 @@ std::optional<std::pair<std::string, int>> Api::extractDomainAndModId(const std:
|
||||
return { { match[1], std::stoi(match[2]) } };
|
||||
return {};
|
||||
}
|
||||
|
||||
bool Api::initModInfo(ImportModInfo& info)
|
||||
{
|
||||
std::vector<File> files;
|
||||
auto match = nxmUrlIsValid(info.remote_request_url);
|
||||
if(!match)
|
||||
return false;
|
||||
|
||||
if(modUrlIsValid(info.remote_source))
|
||||
files = getModFiles(info.remote_source);
|
||||
else
|
||||
{
|
||||
info.remote_source = getNexusPageUrl(info.remote_request_url);
|
||||
files = getModFiles(info.remote_source);
|
||||
}
|
||||
|
||||
const std::string file_id_str = (*match)[3];
|
||||
if(file_id_str.find_first_not_of("0123456789") != std::string::npos)
|
||||
return false;
|
||||
|
||||
const std::string mod_id_str = (*match)[2];
|
||||
if(mod_id_str.find_first_not_of("0123456789") != std::string::npos)
|
||||
return false;
|
||||
|
||||
long file_id = std::stol(file_id_str);
|
||||
long mod_id = std::stol(mod_id_str);
|
||||
auto iter = str::find_if(files, [file_id](File& f){return f.file_id == file_id;});
|
||||
if(iter == files.end())
|
||||
return false;
|
||||
|
||||
info.remote_mod_id = mod_id;
|
||||
info.remote_file_id = iter->file_id;
|
||||
info.remote_file_name = iter->name;
|
||||
info.remote_file_version = iter->version;
|
||||
info.remote_type = ImportModInfo::RemoteType::nexus;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::optional<std::smatch> Api::nxmUrlIsValid(const std::string& nxm_url)
|
||||
{
|
||||
const std::regex regex(
|
||||
R"(nxm:\/\/(.+)\/mods\/(\d+)\/files\/(\d+)\?key=(.+)&expires=(\d+)&user_id=(\d+))");
|
||||
std::smatch match;
|
||||
std::regex_match(nxm_url, match, regex);
|
||||
if(match.empty())
|
||||
return {};
|
||||
return match;
|
||||
}
|
||||
|
||||
@@ -7,8 +7,10 @@
|
||||
|
||||
#include "file.h"
|
||||
#include "mod.h"
|
||||
#include "../importmodinfo.h"
|
||||
#include <cpr/cpr.h>
|
||||
#include <string>
|
||||
#include <regex>
|
||||
|
||||
|
||||
/*!
|
||||
@@ -136,11 +138,6 @@ public:
|
||||
* \return The API key.
|
||||
*/
|
||||
static std::string getApiKey();
|
||||
|
||||
private:
|
||||
/*! \brief The API key used for all operations. */
|
||||
inline static std::string api_key_ = "";
|
||||
|
||||
/*!
|
||||
* \brief Extracts the NexusMods domain and mod id from the given mod page URL.
|
||||
* \param url URL to the mod on NexusMods.
|
||||
@@ -148,5 +145,26 @@ private:
|
||||
*/
|
||||
static std::optional<std::pair<std::string, int>> extractDomainAndModId(
|
||||
const std::string& mod_url);
|
||||
/*!
|
||||
* \brief Initializes remote members of the given ImportModInfo.
|
||||
*
|
||||
* Uses data retreived for the mod associated with the ImportModInfo::remote_source member.
|
||||
* If remote_source is not valid, uses ImportModInfo::remote_download_url instead.
|
||||
*
|
||||
* \param info Mod info to initialize.
|
||||
* \return True if initialization was successful.
|
||||
*/
|
||||
static bool initModInfo(ImportModInfo& info);
|
||||
/*!
|
||||
* \brief Checks whether the given string is a valid NexusMods nxm URL.
|
||||
* \param nxm_url String to check.
|
||||
* \return A regex match object for the string containing a group for every datum in the
|
||||
* URL. If the URL is invalid: An empty optional.
|
||||
*/
|
||||
static std::optional<std::smatch> nxmUrlIsValid(const std::string& nxm_url);
|
||||
|
||||
private:
|
||||
/*! \brief The API key used for all operations. */
|
||||
inline static std::string api_key_ = "";
|
||||
};
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ public:
|
||||
long uid;
|
||||
/*! \brief The file id. */
|
||||
long file_id;
|
||||
/*! \brief The name of the actual file on disk. */
|
||||
/*! \brief The files display name. */
|
||||
std::string name;
|
||||
/*! \brief The files version. */
|
||||
std::string version;
|
||||
@@ -56,15 +56,15 @@ public:
|
||||
bool is_primary;
|
||||
/*! \brief Size of the file in KibiBytes. */
|
||||
long size;
|
||||
/*! \brief The files display name- */
|
||||
/*! \brief The name of the actual file on disk. */
|
||||
std::string file_name;
|
||||
/*! \brief Timestamp for when the file was uploaded to NexusMods. */
|
||||
std::time_t uploaded_time;
|
||||
/*! \brief Mod version to which the file belongs. */
|
||||
std::string mod_version;
|
||||
/*! \brief Optional: The URL of a virus scanning website (like virustotal.com) for this file. */
|
||||
/*! \brief Optional: The URL of a virus scanning website (e.g. virustotal.com) for this file. */
|
||||
std::string external_virus_scan_url;
|
||||
/*! \brief The description if the file. */
|
||||
/*! \brief The description of the file. */
|
||||
std::string description;
|
||||
/*! \brief Size of the file in KibiBytes. */
|
||||
long size_kb;
|
||||
|
||||
23
src/main.cpp
23
src/main.cpp
@@ -107,6 +107,7 @@ int main(int argc, char* argv[])
|
||||
if(pos_args.empty())
|
||||
{
|
||||
client.sendString("Started");
|
||||
std::cout << "Another instance is already running. Sending arguments..." << std::endl;
|
||||
return 2;
|
||||
}
|
||||
std::regex nxm_regex(R"(nxm:\/\/.*\mods\/\d+\/files\/\d+\?.*)");
|
||||
@@ -116,6 +117,28 @@ int main(int argc, char* argv[])
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// TODO: Remove
|
||||
std::cout << "Info size: " << sizeof(ImportModInfo) << std::endl;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
app.setWindowIcon(QIcon(":/logo.png"));
|
||||
MainWindow w;
|
||||
w.setDebugMode(debug_mode);
|
||||
|
||||
@@ -145,20 +145,13 @@ int AddModDialog::detectRootLevel(int deployer) const
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool AddModDialog::setupDialog(const QString& name,
|
||||
const QStringList& deployers,
|
||||
bool AddModDialog::setupDialog(const QStringList& deployers,
|
||||
int cur_deployer,
|
||||
const QString& path,
|
||||
const QStringList& deployer_paths,
|
||||
int app_id,
|
||||
const std::vector<bool>& autonomous_deployers,
|
||||
const std::vector<bool>& case_invariant_deployers,
|
||||
const QString& app_version,
|
||||
const QString& local_source,
|
||||
const QString& remote_source,
|
||||
int mod_id,
|
||||
const QString& version_overwrite,
|
||||
const QString& name_overwrite)
|
||||
const ImportModInfo& info)
|
||||
{
|
||||
groups_.clear();
|
||||
const auto& mod_infos = mod_list_model_->getModInfo();
|
||||
@@ -170,16 +163,13 @@ bool AddModDialog::setupDialog(const QString& name,
|
||||
groups_ << (prefix + mod_info.mod.name + " [" + std::to_string(mod_info.mod.id) + "]").c_str();
|
||||
}
|
||||
|
||||
import_mod_info_ = info;
|
||||
ui->content_tree->clear();
|
||||
ui->root_level_box->setValue(0);
|
||||
ui->name_text->setFocus();
|
||||
app_id_ = app_id;
|
||||
mod_path_ = path;
|
||||
deployer_paths_ = deployer_paths;
|
||||
case_invariant_deployers_ = case_invariant_deployers;
|
||||
app_version_ = app_version;
|
||||
local_source_ = local_source;
|
||||
remote_source_ = remote_source;
|
||||
ui->group_combo_box->setCurrentIndex(ADD_TO_GROUP_INDEX);
|
||||
ui->deployer_list->setEnabled(true);
|
||||
ui->group_field->clear();
|
||||
@@ -192,10 +182,11 @@ bool AddModDialog::setupDialog(const QString& name,
|
||||
ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true);
|
||||
ui->group_check->setCheckState(Qt::Unchecked);
|
||||
int mod_index = -1;
|
||||
if(mod_id != -1)
|
||||
if(info.target_group_id != -1)
|
||||
{
|
||||
auto iter =
|
||||
str::find_if(mod_infos, [mod_id](const ModInfo& info) { return mod_id == info.mod.id; });
|
||||
auto iter = str::find_if(mod_infos,
|
||||
[mod_id = info.target_group_id](const ModInfo& info)
|
||||
{ return mod_id == info.mod.id; });
|
||||
if(iter != mod_infos.end())
|
||||
{
|
||||
mod_index = iter - mod_infos.begin();
|
||||
@@ -207,10 +198,8 @@ bool AddModDialog::setupDialog(const QString& name,
|
||||
|
||||
std::regex name_regex(R"(-\d+((?:-[\dA-Za-z]+)+)-\d+(?:\(\d+\))?\.(?:zip|7z|rar)$)");
|
||||
std::smatch match;
|
||||
std::string name_str = name.toStdString();
|
||||
if(!name_overwrite.isEmpty())
|
||||
ui->name_text->setText(name_overwrite);
|
||||
else if(mod_index >= 0 && mod_index < mod_infos.size())
|
||||
std::string name_str = info.local_source.filename().string();
|
||||
if(mod_index >= 0 && mod_index < mod_infos.size())
|
||||
{
|
||||
ui->name_text->setText(mod_infos[mod_index].mod.name.c_str());
|
||||
ui->version_text->setText(mod_infos[mod_index].mod.version.c_str());
|
||||
@@ -227,11 +216,18 @@ bool AddModDialog::setupDialog(const QString& name,
|
||||
else
|
||||
{
|
||||
ui->version_text->setText("1.0");
|
||||
ui->name_text->setText(name);
|
||||
ui->name_text->setText(name_str.c_str());
|
||||
}
|
||||
|
||||
if(!version_overwrite.isEmpty())
|
||||
ui->version_text->setText(version_overwrite);
|
||||
if(!info.remote_file_name.empty())
|
||||
ui->name_text->setText(info.remote_file_name.c_str());
|
||||
if(!info.remote_file_version.empty())
|
||||
ui->version_text->setText(info.remote_file_version.c_str());
|
||||
|
||||
if(!info.name_overwrite.empty())
|
||||
ui->name_text->setText(info.name_overwrite.c_str());
|
||||
if(!info.version_overwrite.empty())
|
||||
ui->version_text->setText(info.version_overwrite.c_str());
|
||||
|
||||
ui->installer_box->clear();
|
||||
int root_level = 0;
|
||||
@@ -239,53 +235,65 @@ bool AddModDialog::setupDialog(const QString& name,
|
||||
std::string detected_type;
|
||||
try
|
||||
{
|
||||
auto signature = Installer::detectInstallerSignature(path.toStdString());
|
||||
auto signature = Installer::detectInstallerSignature(info.current_path);
|
||||
std::tie(root_level, prefix, detected_type) = signature;
|
||||
}
|
||||
catch(std::runtime_error& error)
|
||||
{
|
||||
showError(error);
|
||||
emit addModAborted(mod_path_);
|
||||
emit addModAborted(info.current_path.c_str());
|
||||
return false;
|
||||
}
|
||||
if(detected_type == Installer::FOMODINSTALLER)
|
||||
{
|
||||
auto [name, version] =
|
||||
fomod::FomodInstaller::getMetaData(sfs::path(mod_path_.toStdString()) / prefix);
|
||||
if(!name.empty() && name_overwrite.isEmpty())
|
||||
fomod::FomodInstaller::getMetaData(info.current_path / prefix);
|
||||
if(!name.empty() && info.name_overwrite.empty())
|
||||
ui->name_text->setText(name.c_str());
|
||||
if(!version.empty() && version_overwrite.isEmpty())
|
||||
if(!version.empty() && info.version_overwrite.empty())
|
||||
ui->version_text->setText(version.c_str());
|
||||
}
|
||||
|
||||
const std::string mod_name = ui->name_text->text().toStdString();
|
||||
const std::string remote_source_str = remote_source.toStdString();
|
||||
auto remote_source_or_name_matches = [&remote_source_str, &mod_name](const auto& pair)
|
||||
auto remote_source_or_name_matches =
|
||||
[&mod_info = std::as_const(info), &mod_name](const auto& pair)
|
||||
{
|
||||
const Mod mod = std::get<1>(pair).mod;
|
||||
return !remote_source_str.empty() && remote_source_str == mod.remote_source ||
|
||||
mod.name == mod_name;
|
||||
return !mod_info.remote_source.empty() && mod_info.remote_source == mod.remote_source ||
|
||||
mod.name == mod_name ||
|
||||
mod.remote_mod_id != -1 && mod_info.remote_mod_id == mod.remote_mod_id;
|
||||
};
|
||||
int group_index = -1;
|
||||
int max_match_quality = -1;
|
||||
for(const auto& [i, mod_info] :
|
||||
mod_infos | stv::enumerate | stv::filter(remote_source_or_name_matches))
|
||||
|
||||
if(info.target_group_id == -1)
|
||||
{
|
||||
int match_quality = 0;
|
||||
if(remote_source_str == mod_info.mod.remote_source)
|
||||
match_quality += 4;
|
||||
if(mod_name == mod_info.mod.name)
|
||||
match_quality += 2;
|
||||
if(mod_info.is_active_group_member)
|
||||
match_quality += 1;
|
||||
if(match_quality > max_match_quality)
|
||||
int group_index = -1;
|
||||
int max_match_quality = -1;
|
||||
for(const auto& [i, mod_info] :
|
||||
mod_infos | stv::enumerate | stv::filter(remote_source_or_name_matches))
|
||||
{
|
||||
max_match_quality = match_quality;
|
||||
group_index = i;
|
||||
int match_quality = 0;
|
||||
if(info.remote_type == mod_info.mod.remote_type)
|
||||
{
|
||||
if(mod_info.mod.remote_mod_id != -1 && mod_info.mod.remote_mod_id == info.remote_mod_id)
|
||||
match_quality += 16;
|
||||
if(mod_info.mod.remote_file_id != -1 && mod_info.mod.remote_file_id == info.remote_file_id)
|
||||
match_quality += 8;
|
||||
}
|
||||
if(info.remote_source == mod_info.mod.remote_source)
|
||||
match_quality += 4;
|
||||
if(mod_name == mod_info.mod.name)
|
||||
match_quality += 2;
|
||||
if(mod_info.is_active_group_member)
|
||||
match_quality += 1;
|
||||
if(match_quality > max_match_quality)
|
||||
{
|
||||
max_match_quality = match_quality;
|
||||
group_index = i;
|
||||
}
|
||||
}
|
||||
if(group_index != -1)
|
||||
ui->group_field->setText(groups_[group_index]);
|
||||
}
|
||||
if(group_index != -1)
|
||||
ui->group_field->setText(groups_[group_index]);
|
||||
|
||||
path_prefix_ = prefix.c_str();
|
||||
int target_idx = 0;
|
||||
@@ -304,7 +312,7 @@ bool AddModDialog::setupDialog(const QString& name,
|
||||
ui->deployer_list->clear();
|
||||
std::set<int> selected_deployers;
|
||||
auto settings = QSettings(QCoreApplication::applicationName());
|
||||
settings.beginGroup(QString::number(app_id));
|
||||
settings.beginGroup(QString::number(info.app_id));
|
||||
int size = settings.beginReadArray("selected_deployers");
|
||||
for(int i = 0; i < size; i++)
|
||||
{
|
||||
@@ -315,7 +323,7 @@ bool AddModDialog::setupDialog(const QString& name,
|
||||
|
||||
try
|
||||
{
|
||||
auto mod_file_paths = Installer::getArchiveFileNames(path.toStdString());
|
||||
auto mod_file_paths = Installer::getArchiveFileNames(info.current_path);
|
||||
int max_depth = 0;
|
||||
for(const auto& path : mod_file_paths)
|
||||
max_depth = std::max(addTreeNode(ui->content_tree, path), max_depth);
|
||||
@@ -325,7 +333,7 @@ bool AddModDialog::setupDialog(const QString& name,
|
||||
catch(std::runtime_error& error)
|
||||
{
|
||||
showError(error);
|
||||
emit addModAborted(mod_path_);
|
||||
emit addModAborted(info.current_path.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -365,7 +373,7 @@ void AddModDialog::closeEvent(QCloseEvent* event)
|
||||
if(dialog_completed_)
|
||||
return;
|
||||
dialog_completed_ = true;
|
||||
emit addModAborted(mod_path_);
|
||||
emit addModAborted(import_mod_info_.current_path.c_str());
|
||||
QDialog::reject();
|
||||
}
|
||||
|
||||
@@ -374,7 +382,7 @@ void AddModDialog::reject()
|
||||
if(dialog_completed_)
|
||||
return;
|
||||
dialog_completed_ = true;
|
||||
emit addModAborted(mod_path_);
|
||||
emit addModAborted(import_mod_info_.current_path.c_str());
|
||||
QDialog::reject();
|
||||
}
|
||||
|
||||
@@ -440,7 +448,7 @@ void AddModDialog::on_buttonBox_accepted()
|
||||
group = mod_list_model_->getModInfo()[groups_.indexOf(group_name)].mod.id;
|
||||
std::vector<int> deployers;
|
||||
auto settings = QSettings(QCoreApplication::applicationName());
|
||||
settings.beginGroup(QString::number(app_id_));
|
||||
settings.beginGroup(QString::number(import_mod_info_.app_id));
|
||||
settings.beginWriteArray("selected_deployers");
|
||||
int settings_index = 0;
|
||||
for(int i = 0; i < ui->deployer_list->count(); i++)
|
||||
@@ -455,19 +463,16 @@ void AddModDialog::on_buttonBox_accepted()
|
||||
settings.endArray();
|
||||
settings.setValue("fomod_target_deployer", ui->fomod_deployer_box->currentIndex());
|
||||
settings.endGroup();
|
||||
std::vector<std::pair<sfs::path, sfs::path>> fomod_files{};
|
||||
AddModInfo info{ ui->name_text->text().toStdString(),
|
||||
ui->version_text->text().toStdString(),
|
||||
Installer::INSTALLER_TYPES[ui->installer_box->currentIndex()],
|
||||
mod_path_.toStdString(),
|
||||
deployers,
|
||||
group,
|
||||
options,
|
||||
ui->root_level_box->value(),
|
||||
fomod_files,
|
||||
replace_mod,
|
||||
local_source_.toStdString(),
|
||||
remote_source_.toStdString() };
|
||||
import_mod_info_.action_type = ImportModInfo::ActionType::install;
|
||||
import_mod_info_.name = ui->name_text->text().toStdString();
|
||||
import_mod_info_.version = ui->version_text->text().toStdString();
|
||||
import_mod_info_.installer = Installer::INSTALLER_TYPES[ui->installer_box->currentIndex()];
|
||||
import_mod_info_.deployers = deployers;
|
||||
import_mod_info_.target_group_id = group;
|
||||
import_mod_info_.installer_flags = options;
|
||||
import_mod_info_.root_level = ui->root_level_box->value();
|
||||
import_mod_info_.files = {};
|
||||
import_mod_info_.replace_mod = replace_mod;
|
||||
if(Installer::INSTALLER_TYPES[ui->installer_box->currentIndex()] == Installer::FOMODINSTALLER)
|
||||
{
|
||||
bool case_invariant = true;
|
||||
@@ -480,22 +485,22 @@ void AddModDialog::on_buttonBox_accepted()
|
||||
}
|
||||
}
|
||||
fomod_dialog_->setupDialog(
|
||||
sfs::path(mod_path_.toStdString()) / path_prefix_.toStdString(),
|
||||
import_mod_info_.current_path / path_prefix_.toStdString(),
|
||||
deployer_paths_[ui->fomod_deployer_box->currentIndex()].toStdString(),
|
||||
app_version_,
|
||||
info,
|
||||
app_id_,
|
||||
import_mod_info_,
|
||||
import_mod_info_.app_id,
|
||||
case_invariant);
|
||||
if(!fomod_dialog_->hasSteps())
|
||||
{
|
||||
info.files = fomod_dialog_->getResult();
|
||||
emit addModAccepted(app_id_, info);
|
||||
import_mod_info_.files = fomod_dialog_->getResult();
|
||||
emit addModAccepted(import_mod_info_.app_id, import_mod_info_);
|
||||
}
|
||||
else
|
||||
fomod_dialog_->show();
|
||||
}
|
||||
else
|
||||
emit addModAccepted(app_id_, info);
|
||||
emit addModAccepted(import_mod_info_.app_id, import_mod_info_);
|
||||
}
|
||||
|
||||
void AddModDialog::on_group_check_stateChanged(int state)
|
||||
@@ -512,7 +517,7 @@ void AddModDialog::on_buttonBox_rejected()
|
||||
if(dialog_completed_)
|
||||
return;
|
||||
dialog_completed_ = true;
|
||||
emit addModAborted(mod_path_);
|
||||
emit addModAborted(import_mod_info_.current_path.c_str());
|
||||
}
|
||||
|
||||
void AddModDialog::on_name_text_textChanged(const QString& text)
|
||||
@@ -559,12 +564,12 @@ void AddModDialog::on_group_combo_box_currentIndexChanged(int index)
|
||||
ui->group_check->checkState() == Qt::Unchecked);
|
||||
}
|
||||
|
||||
void AddModDialog::onFomodDialogComplete(int app_id, AddModInfo info)
|
||||
void AddModDialog::onFomodDialogComplete(int app_id, ImportModInfo info)
|
||||
{
|
||||
emit addModAccepted(app_id, info);
|
||||
}
|
||||
|
||||
void AddModDialog::onFomodDialogAborted()
|
||||
{
|
||||
emit addModAborted(mod_path_);
|
||||
emit addModAborted(import_mod_info_.current_path.c_str());
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../core/importmodinfo.h"
|
||||
#include "deployerlistmodel.h"
|
||||
#include "modlistmodel.h"
|
||||
#include "ui/fomoddialog.h"
|
||||
@@ -44,37 +45,23 @@ public:
|
||||
|
||||
/*!
|
||||
* \brief Initializes this dialog with data needed for mod installation.
|
||||
* \param name Default mod name.
|
||||
* \param deployers Contains all available \ref Deployer "deployers".
|
||||
* \param cur_deployer The currently active Deployer.
|
||||
* \param path Source path for the new mod.
|
||||
* \param deployer_paths Contains target paths for all non autonomous deployers.
|
||||
* \param app_id Id of currently active application.
|
||||
* \param autonomous_deployers Vector of bools indicating for each deployer
|
||||
* if that deployer is autonomous.
|
||||
* \param case_invariant_deployers Vector of bools indicating for each deployer
|
||||
* if that deployer is case invariant.
|
||||
* \param local_source Source archive for the mod.
|
||||
* \param remote_source URL from where the mod was downloaded.
|
||||
* \param mod_id If =! -1: Id of the mod to the group of which the new mod should be added by default.
|
||||
* \param version_overwrite If not empty: Use this to overwrite the default version.
|
||||
* \param name_overwrite If not empty: Use this to overwrite the default name.
|
||||
* \param info Contains data relating to the current status of the mod import.
|
||||
* \return True if dialog creation was successful.
|
||||
*/
|
||||
bool setupDialog(const QString& name,
|
||||
const QStringList& deployers,
|
||||
bool setupDialog(const QStringList& deployers,
|
||||
int cur_deployer,
|
||||
const QString& path,
|
||||
const QStringList& deployer_paths,
|
||||
int app_id,
|
||||
const std::vector<bool>& autonomous_deployers,
|
||||
const std::vector<bool>& case_invariant_deployers,
|
||||
const QString& app_version,
|
||||
const QString& local_source,
|
||||
const QString& remote_source,
|
||||
int mod_id,
|
||||
const QString& version_overwrite,
|
||||
const QString& name_overwrite);
|
||||
const ImportModInfo& info);
|
||||
/*!
|
||||
* \brief Closes the dialog and emits a signal indicating installation has been canceled.
|
||||
* \param event The close even sent upon closing the dialog.
|
||||
@@ -86,12 +73,6 @@ public:
|
||||
private:
|
||||
/*! \brief Contains auto-generated UI elements. */
|
||||
Ui::AddModDialog* ui;
|
||||
/*! \brief Contains mod ids corresponding to entries in the field. */
|
||||
// std::vector<int> mod_ids_;
|
||||
/*! \brief Source path for the new mod data. */
|
||||
QString mod_path_;
|
||||
/*! \brief Stores the id of the currently active \ref ModdedApplication "application". */
|
||||
int app_id_;
|
||||
/*! \brief Holds radio button groups used to select installation options. */
|
||||
QList<QButtonGroup*> option_groups_;
|
||||
/*! \brief Used to color tree nodes which will not be removed. */
|
||||
@@ -114,10 +95,6 @@ private:
|
||||
static constexpr int REPLACE_MOD_INDEX = 1;
|
||||
/*! \brief App version used for fomod conditions. */
|
||||
QString app_version_;
|
||||
/*! \brief Path to the source archive for the mod. */
|
||||
QString local_source_;
|
||||
/*! \brief URL from where the mod was downloaded. */
|
||||
QString remote_source_;
|
||||
/*! \brief Indicates whether the dialog has been completed. */
|
||||
bool dialog_completed_ = false;
|
||||
/*! \brief Model containing all mod related data. */
|
||||
@@ -126,6 +103,8 @@ private:
|
||||
DeployerListModel* deployer_list_model_;
|
||||
/*! \brief For every deployer: A bool indicating whether that deployer is case invariant. */
|
||||
std::vector<bool> case_invariant_deployers_;
|
||||
/*! \brief Contains all data related to the current state of the mod installation. */
|
||||
ImportModInfo import_mod_info_;
|
||||
|
||||
/*!
|
||||
* \brief Updates the enabled state of this dialog's OK button to only be enabled when
|
||||
@@ -212,7 +191,7 @@ private slots:
|
||||
* \param app_id Application for which the new mod is to be installed.
|
||||
* \param info Contains all data needed to install the mod.
|
||||
*/
|
||||
void onFomodDialogComplete(int app_id, AddModInfo info);
|
||||
void onFomodDialogComplete(int app_id, ImportModInfo info);
|
||||
/*! \brief Called when fomod dialog has been canceled. Emits addModAborted */
|
||||
void onFomodDialogAborted();
|
||||
|
||||
@@ -222,7 +201,7 @@ signals:
|
||||
* \param app_id Application for which the new mod is to be installed.
|
||||
* \param info Contains all data needed to install the mod.
|
||||
*/
|
||||
void addModAccepted(int app_id, AddModInfo info);
|
||||
void addModAccepted(int app_id, ImportModInfo info);
|
||||
/*!
|
||||
* \brief Signals mod installation has been aborted.
|
||||
* \param temp_dir Directory used for mod extraction.
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "applicationmanager.h"
|
||||
#include "../core/deployerfactory.h"
|
||||
#include "../core/installer.h"
|
||||
#include "../core/pathutils.h"
|
||||
#include <QCoreApplication>
|
||||
#include <QDebug>
|
||||
#include <QMessageBox>
|
||||
@@ -9,7 +10,107 @@
|
||||
#include <regex>
|
||||
|
||||
namespace sfs = std::filesystem;
|
||||
namespace pu = path_utils;
|
||||
|
||||
bool performDownload(ImportModInfo& info, ApplicationManager* app_mgr)
|
||||
{
|
||||
app_mgr->sendLogMessage(Log::LOG_DEBUG,
|
||||
std::format("Downloading from : '{}'", info.remote_download_url));
|
||||
std::regex url_regex(R"(.*/(.*)\?.*)");
|
||||
std::smatch match;
|
||||
if(!std::regex_match(info.remote_download_url, match, url_regex))
|
||||
throw std::runtime_error(std::format("Invalid download URL \"{}\"", info.remote_download_url));
|
||||
sfs::path download_path = info.target_path;
|
||||
if(!sfs::exists(download_path))
|
||||
sfs::create_directories(download_path);
|
||||
sfs::path file_name = match[1].str();
|
||||
const std::string file_name_prefix = file_name.stem();
|
||||
const std::string extension = file_name.extension();
|
||||
int suffix = 1;
|
||||
while(pu::exists(download_path / file_name))
|
||||
{
|
||||
file_name = file_name_prefix + "(" + std::to_string(suffix) + ")" + extension;
|
||||
suffix++;
|
||||
}
|
||||
std::string file_name_str = file_name.string();
|
||||
auto pos = file_name_str.find("%20");
|
||||
while(pos != std::string::npos)
|
||||
{
|
||||
file_name_str.replace(pos, 3, " ");
|
||||
pos = file_name_str.find("%20");
|
||||
}
|
||||
file_name = file_name_str;
|
||||
|
||||
auto progress_callback = [app_mgr](float progress) { app_mgr->sendUpdateProgress(progress); };
|
||||
std::ofstream fstream(download_path / file_name, std::ios::binary);
|
||||
if(!fstream.is_open())
|
||||
throw std::runtime_error("Failed to write to disk.");
|
||||
bool message_sent = false;
|
||||
cpr::Response response = cpr::Download(
|
||||
fstream,
|
||||
cpr::Url(info.remote_download_url),
|
||||
cpr::ProgressCallback(
|
||||
[app_mgr, &message_sent, &file_name, progress_callback](auto download_total,
|
||||
auto download_now,
|
||||
auto upload_total,
|
||||
auto upload_now,
|
||||
intptr_t user_data)
|
||||
{
|
||||
if(!message_sent && download_total > 0)
|
||||
{
|
||||
std::string size_string;
|
||||
long last_size = 0;
|
||||
long size = download_total;
|
||||
int exp = 0;
|
||||
const std::vector<std::string> units{ "B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB" };
|
||||
while(size > 1024 && exp < units.size())
|
||||
{
|
||||
last_size = size;
|
||||
size /= 1024;
|
||||
exp++;
|
||||
}
|
||||
last_size /= 1.024;
|
||||
size_string = std::to_string(size);
|
||||
const int first_digit = (last_size / 100) % 10;
|
||||
const int second_digit = (last_size / 10) % 10;
|
||||
if(first_digit != 0 || second_digit != 0)
|
||||
size_string += "." + std::to_string(first_digit);
|
||||
if(second_digit != 0)
|
||||
size_string += std::to_string(second_digit);
|
||||
size_string += units[exp];
|
||||
|
||||
app_mgr->sendLogMessage(
|
||||
Log::LOG_INFO,
|
||||
("Downloading \"" + file_name.string() + "\" with size: ").c_str() + size_string +
|
||||
"...");
|
||||
message_sent = true;
|
||||
}
|
||||
if(download_total != 0)
|
||||
progress_callback((float)download_now / (float)download_total);
|
||||
return true;
|
||||
}));
|
||||
if(response.status_code != 200)
|
||||
{
|
||||
sfs::remove(download_path / file_name);
|
||||
throw std::runtime_error("Download failed with response: \"" + response.status_line +
|
||||
"\" (code " + std::to_string(response.status_code) + ").");
|
||||
}
|
||||
fstream.close();
|
||||
info.local_source = download_path / file_name;
|
||||
info.current_path = info.local_source;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool performExtraction(ImportModInfo& info, ApplicationManager* app_mgr)
|
||||
{
|
||||
info.last_action_was_successful = false;
|
||||
auto progress_callback = [app_mgr](float progress) { app_mgr->sendUpdateProgress(progress); };
|
||||
ProgressNode node(progress_callback);
|
||||
Installer::extract(info.local_source, info.target_path, &node);
|
||||
info.current_path = info.target_path;
|
||||
info.last_action_was_successful = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
ApplicationManager::ApplicationManager(QObject* parent) : QObject{ parent }
|
||||
{
|
||||
@@ -193,11 +294,11 @@ void ApplicationManager::handleAddDeployerError(int code,
|
||||
emit sendError("Error",
|
||||
"Could not write to staging dir " + QString(staging_dir.string().c_str()) + "!");
|
||||
else if(code == 2)
|
||||
emit sendError("Error",
|
||||
"Could not create hard link from\n\"" + QString(staging_dir.string().c_str()) +
|
||||
"\"\nto\n\"" + QString(dest_dir.string().c_str()) + "\".\n" +
|
||||
"Ensure that both directories are on the same partition!\n"
|
||||
"Alternatively: Switch to sym link deployment.");
|
||||
emit sendError(
|
||||
"Error",
|
||||
"Could not create hard link from\n\"" + QString(staging_dir.string().c_str()) + "\"\nto\n\"" +
|
||||
QString(dest_dir.string().c_str()) + "\".\n" +
|
||||
"Ensure that both directories are on the same partition!\n" "Alternatively: " "Switch to " "sym " "link deployment.");
|
||||
else if(code == 3)
|
||||
emit sendError("Error",
|
||||
"Could no copy files\n\"" + QString(staging_dir.string().c_str()) +
|
||||
@@ -350,7 +451,7 @@ void ApplicationManager::unDeployModsFor(int app_id, std::vector<int> deployer_i
|
||||
emit completedOperations("Mods undeployed");
|
||||
}
|
||||
|
||||
void ApplicationManager::installMod(int app_id, AddModInfo info)
|
||||
void ApplicationManager::installMod(int app_id, ImportModInfo info)
|
||||
{
|
||||
bool has_thrown = false;
|
||||
if(appIndexIsValid(app_id))
|
||||
@@ -625,19 +726,10 @@ void ApplicationManager::sortModsByConflicts(int app_id, int deployer)
|
||||
emit completedOperations("Mods sorted");
|
||||
}
|
||||
|
||||
void ApplicationManager::extractArchive(int app_id,
|
||||
int mod_id,
|
||||
QString source,
|
||||
QString target,
|
||||
QString remote_source,
|
||||
QString version,
|
||||
QString name)
|
||||
void ApplicationManager::extractArchive(ImportModInfo info)
|
||||
{
|
||||
bool has_thrown = true;
|
||||
if(appIndexIsValid(0))
|
||||
has_thrown = handleExceptions<&ModdedApplication::extractArchive>(
|
||||
0, source.toStdString(), target.toStdString());
|
||||
emit extractionComplete(app_id, mod_id, !has_thrown, target, source, remote_source, version, name);
|
||||
handleExceptionsForFunction(performExtraction, info, this);
|
||||
emit extractionComplete(info);
|
||||
}
|
||||
|
||||
void ApplicationManager::addBackupTarget(int app_id,
|
||||
@@ -834,71 +926,59 @@ void ApplicationManager::getNexusPage(int app_id, int mod_id)
|
||||
emit completedOperations();
|
||||
}
|
||||
|
||||
void ApplicationManager::downloadMod(int app_id, QString nxm_url)
|
||||
void ApplicationManager::downloadMod(ImportModInfo info)
|
||||
{
|
||||
if(!appIndexIsValid(app_id))
|
||||
info.last_action_was_successful = false;
|
||||
if(!appIndexIsValid(info.app_id))
|
||||
{
|
||||
emit downloadFailed();
|
||||
return;
|
||||
}
|
||||
|
||||
auto download_url =
|
||||
handleExceptions(&ModdedApplication::getDownloadUrl, apps_[app_id], nxm_url.toStdString());
|
||||
if(!download_url)
|
||||
if(info.remote_request_url.empty())
|
||||
{
|
||||
auto download_url = handleExceptionsForFunction(
|
||||
static_cast<std::string (*)(const std::string&, long)>(nexus::Api::getDownloadUrl),
|
||||
info.remote_source,
|
||||
info.remote_file_id);
|
||||
if(!download_url)
|
||||
{
|
||||
emit downloadFailed();
|
||||
return;
|
||||
}
|
||||
info.remote_download_url = *download_url;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto download_url = handleExceptionsForFunction(
|
||||
static_cast<std::string (*)(const std::string&)>(nexus::Api::getDownloadUrl),
|
||||
info.remote_request_url);
|
||||
if(!download_url)
|
||||
{
|
||||
emit downloadFailed();
|
||||
return;
|
||||
}
|
||||
info.remote_download_url = *download_url;
|
||||
}
|
||||
info.remote_download_url = QUrl(info.remote_download_url.c_str()).toEncoded().toStdString();
|
||||
|
||||
auto init_successful = handleExceptionsForFunction(nexus::Api::initModInfo, info);
|
||||
if(!init_successful || !(*init_successful))
|
||||
{
|
||||
emit downloadFailed();
|
||||
return;
|
||||
}
|
||||
|
||||
auto mod_url =
|
||||
handleExceptions(&ModdedApplication::getNexusPageUrl, apps_[app_id], nxm_url.toStdString());
|
||||
if(!mod_url)
|
||||
{
|
||||
emit downloadFailed();
|
||||
return;
|
||||
}
|
||||
auto file_path =
|
||||
handleExceptions(&ModdedApplication::downloadMod,
|
||||
apps_[app_id],
|
||||
QUrl(download_url->c_str()).toEncoded().toStdString(),
|
||||
[app_mgr = this](float progress) { app_mgr->sendUpdateProgress(progress); });
|
||||
if(!file_path)
|
||||
info.target_path = apps_[info.app_id].getDownloadDir();
|
||||
auto download_successful = handleExceptionsForFunction(performDownload, info, this);
|
||||
if(!download_successful)
|
||||
{
|
||||
emit downloadFailed();
|
||||
return;
|
||||
}
|
||||
|
||||
emit downloadComplete(app_id, -1, file_path->c_str(), mod_url->c_str());
|
||||
}
|
||||
|
||||
void ApplicationManager::downloadModFile(int app_id, int mod_id, int nexus_file_id, QString mod_url)
|
||||
{
|
||||
if(!appIndexIsValid(app_id))
|
||||
{
|
||||
emit downloadFailed();
|
||||
return;
|
||||
}
|
||||
|
||||
auto download_url = handleExceptions(
|
||||
&ModdedApplication::getDownloadUrlForFile, apps_[app_id], nexus_file_id, mod_url.toStdString());
|
||||
if(!download_url)
|
||||
{
|
||||
emit downloadFailed();
|
||||
return;
|
||||
}
|
||||
|
||||
auto file_path =
|
||||
handleExceptions(&ModdedApplication::downloadMod,
|
||||
apps_[app_id],
|
||||
QUrl(download_url->c_str()).toEncoded().toStdString(),
|
||||
[app_mgr = this](float progress) { app_mgr->sendUpdateProgress(progress); });
|
||||
if(!file_path)
|
||||
{
|
||||
emit downloadFailed();
|
||||
return;
|
||||
}
|
||||
|
||||
emit downloadComplete(app_id, mod_id, file_path->c_str(), mod_url);
|
||||
info.last_action_was_successful = true;
|
||||
emit downloadComplete(info);
|
||||
}
|
||||
|
||||
void ApplicationManager::checkForModUpdates(int app_id)
|
||||
|
||||
@@ -72,6 +72,11 @@ public:
|
||||
* \param enabled New status.
|
||||
*/
|
||||
void enableExceptions(bool enabled);
|
||||
/*!
|
||||
* \brief Informs about the progress in the current task by emitting \ref updateProgress.
|
||||
* \param progress The progress.
|
||||
*/
|
||||
void sendUpdateProgress(float progress);
|
||||
|
||||
private:
|
||||
/*!
|
||||
@@ -240,6 +245,91 @@ private:
|
||||
return ret_value;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Wrapper for generic functions. Catches specific exception types and sends an error
|
||||
* message to the gui if an exception was thrown.
|
||||
* \param f Function to run.
|
||||
* \param args Arguments for the function.
|
||||
* \return If no exception was thrown: The return value of the function,
|
||||
* else: An empty optional.
|
||||
*/
|
||||
template<typename Func, typename... Args>
|
||||
auto handleExceptionsForFunction(Func&& f, Args&&... args)
|
||||
-> std::optional<decltype((f)(std::forward<Args>(args)...))>
|
||||
{
|
||||
decltype((f)(std::forward<Args>(args)...)) ret_value;
|
||||
std::string message;
|
||||
bool has_thrown = false;
|
||||
try
|
||||
{
|
||||
ret_value = (f)(std::forward<Args>(args)...);
|
||||
}
|
||||
catch(Json::RuntimeError& error)
|
||||
{
|
||||
has_thrown = true;
|
||||
message = error.what();
|
||||
if(throw_exceptions_)
|
||||
throw error;
|
||||
}
|
||||
catch(Json::LogicError& error)
|
||||
{
|
||||
has_thrown = true;
|
||||
|
||||
message = error.what();
|
||||
if(throw_exceptions_)
|
||||
throw error;
|
||||
}
|
||||
catch(ParseError& error)
|
||||
{
|
||||
has_thrown = true;
|
||||
message = error.what();
|
||||
if(throw_exceptions_)
|
||||
throw error;
|
||||
}
|
||||
catch(std::ios_base::failure& error)
|
||||
{
|
||||
has_thrown = true;
|
||||
message = error.what();
|
||||
if(throw_exceptions_)
|
||||
throw error;
|
||||
}
|
||||
catch(CompressionError& error)
|
||||
{
|
||||
has_thrown = true;
|
||||
message = error.what();
|
||||
if(throw_exceptions_)
|
||||
throw error;
|
||||
}
|
||||
catch(std::runtime_error& error)
|
||||
{
|
||||
has_thrown = true;
|
||||
message = error.what();
|
||||
if(throw_exceptions_)
|
||||
throw error;
|
||||
}
|
||||
catch(std::invalid_argument& error)
|
||||
{
|
||||
has_thrown = true;
|
||||
message = error.what();
|
||||
if(throw_exceptions_)
|
||||
throw error;
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
has_thrown = true;
|
||||
message = "An unexpected error occured!";
|
||||
if(throw_exceptions_)
|
||||
throw std::runtime_error("An unexpected error occured!");
|
||||
}
|
||||
|
||||
if(has_thrown)
|
||||
{
|
||||
emit sendError("Error", message.c_str());
|
||||
return {};
|
||||
}
|
||||
return ret_value;
|
||||
}
|
||||
|
||||
/*! \brief Contains every ModdedApplication handled by this object. */
|
||||
std::vector<ModdedApplication> apps_;
|
||||
/*! \brief If true: Do not catch exceptions. */
|
||||
@@ -296,11 +386,6 @@ private:
|
||||
* \param message Message of the exception thrown during parsing.
|
||||
*/
|
||||
void handleParseError(std::string path, std::string message);
|
||||
/*!
|
||||
* \brief Informs about the progress in the current task by emitting \ref updateProgress.
|
||||
* \param progress The progress.
|
||||
*/
|
||||
void sendUpdateProgress(float progress);
|
||||
|
||||
/*! \brief Counter for the number of instances of this class. */
|
||||
inline static int number_of_instances_ = 0;
|
||||
@@ -379,23 +464,9 @@ signals:
|
||||
void sendError(QString title, QString message);
|
||||
/*!
|
||||
* \brief Emitted after archive extraction is complete.
|
||||
* \param app_id \ref ModdedApplication "application" for which the mod has been extracted.
|
||||
* \param mod_id Id of the mod for which the file was to be extracted or -1 if this is a new mod.
|
||||
* \param success False if exception has been thrown.
|
||||
* \param extracted_path Path to which the mod was extracted.
|
||||
* \param local_source Source archive for the mod.
|
||||
* \param remote_source URL from where the mod was downloaded.
|
||||
* \param version If not empty: Use this to overwrite the default version.
|
||||
* \param name If not empty: Use this to overwrite the default name.
|
||||
* \param info Contains extraction directory as ImportModInfo::current_path.
|
||||
*/
|
||||
void extractionComplete(int app_id,
|
||||
int mod_id,
|
||||
bool success,
|
||||
QString extracted_path,
|
||||
QString local_source,
|
||||
QString remote_source,
|
||||
QString version,
|
||||
QString name);
|
||||
void extractionComplete(ImportModInfo info);
|
||||
/*!
|
||||
* \brief Sends a log message to the logging window.
|
||||
* \param log_level Type of message.
|
||||
@@ -424,13 +495,9 @@ signals:
|
||||
void sendNexusPage(int app_id, int mod_id, nexus::Page page);
|
||||
/*!
|
||||
* \brief Signals successful completion of a mod download.
|
||||
* \param app_id App for which the mod has been downloaded.
|
||||
* \param mod_id Id of the mod for which the file is to be downloaded.
|
||||
* This is the limo internal mod id, NOT the NexusMods id.
|
||||
* \param file_path Path to the downloaded file.
|
||||
* \param mod_url Url from which the mod was downloaded.
|
||||
* \param info ImportModInfo::local_source contains the download directory.
|
||||
*/
|
||||
void downloadComplete(int app_id, int mod_id, QString file_path, QString mod_url);
|
||||
void downloadComplete(ImportModInfo info);
|
||||
/*! \brief Signals a failed download. */
|
||||
void downloadFailed();
|
||||
/*!
|
||||
@@ -500,7 +567,7 @@ public slots:
|
||||
* \param app_id The target \ref ModdedApplication "application".
|
||||
* \param info Contains all data needed to install the mod.
|
||||
*/
|
||||
void installMod(int app_id, AddModInfo info);
|
||||
void installMod(int app_id, ImportModInfo info);
|
||||
/*!
|
||||
* \brief Uninstalls the given mods for one \ref ModdedApplication "application", this includes
|
||||
* deleting all installed files.
|
||||
@@ -728,21 +795,10 @@ public slots:
|
||||
void sortModsByConflicts(int app_id, int deployer);
|
||||
/*!
|
||||
* \brief Extracts the given archive to the given location.
|
||||
* \param app_id \ref ModdedApplication "application" for which the mod is to be extracted.
|
||||
* \param mod_id Id of the mod for which the file is to be extracted or -1 if this is a new mod.
|
||||
* \param source Source path.
|
||||
* \param target Extraction target path.
|
||||
* \param remote_source URL from where the mod was downloaded.
|
||||
* \param version If not empty: Use this to overwrite the default version.
|
||||
* \param name If not empty: Use this to overwrite the default name.
|
||||
* \param info Contains archive source path in ImportModInfo::local_source
|
||||
* and extraction target path in ImportModInfo::target_path.
|
||||
*/
|
||||
void extractArchive(int app_id,
|
||||
int mod_id,
|
||||
QString source,
|
||||
QString target,
|
||||
QString remote_source,
|
||||
QString version,
|
||||
QString name);
|
||||
void extractArchive(ImportModInfo info);
|
||||
/*!
|
||||
* \brief Adds a new target file or directory to be managed by the BackupManager of given
|
||||
* ModdedApplication.
|
||||
@@ -912,22 +968,14 @@ public slots:
|
||||
*/
|
||||
void getNexusPage(int app_id, int mod_id);
|
||||
/*!
|
||||
* \brief Downloads a mod from nexusmods using the given nxm_url.
|
||||
* \param app_id App for which the mod is to be downloaded. The mod is downloaded to the apps
|
||||
* staging directory.
|
||||
* \param nxm_url Url containing all information needed for the download.
|
||||
* \brief Downloads a mod from nexusmods
|
||||
*
|
||||
* The mod is downloaded to the target apps staging directory. Uses either the given nxm URL
|
||||
* or the mod and file id for the download.
|
||||
*
|
||||
* \param info Contains either a nxm URL or nexus mod and file ids.
|
||||
*/
|
||||
void downloadMod(int app_id, QString nxm_url);
|
||||
/*!
|
||||
* \brief Downloads the file with the given id for the given mod url from nexusmods.
|
||||
* \param app_id App for which the mod is to be downloaded. The mod is downloaded to the apps
|
||||
* staging directory.
|
||||
* \param mod_id Id of the mod for which the file is to be downloaded.
|
||||
* This is the Limo internal mod id, NOT the NexusMods id.
|
||||
* \param nexus_file_id File id of the mod.
|
||||
* \param mod_url Url to the mod page on NexusMods.
|
||||
*/
|
||||
void downloadModFile(int app_id, int mod_id, int nexus_file_id, QString mod_url);
|
||||
void downloadMod(ImportModInfo info);
|
||||
/*!
|
||||
* \brief Checks for available mod updates on NexusMods.
|
||||
* \param app_id App for which mod updates are to be checked.
|
||||
|
||||
@@ -34,12 +34,12 @@ FomodDialog::~FomodDialog()
|
||||
void FomodDialog::setupDialog(const sfs::path& config_file,
|
||||
const sfs::path& target_path,
|
||||
const QString& app_version,
|
||||
const AddModInfo& info,
|
||||
const ImportModInfo& info,
|
||||
int app_id,
|
||||
bool paths_are_case_invariant)
|
||||
{
|
||||
dialog_completed_ = false;
|
||||
add_mod_info_ = info;
|
||||
import_mod_info_ = info;
|
||||
app_id_ = app_id;
|
||||
paths_are_case_invariant_ = paths_are_case_invariant;
|
||||
installer_->init(config_file, paths_are_case_invariant, target_path, app_version.toStdString());
|
||||
@@ -284,8 +284,8 @@ void FomodDialog::onNextButtonPressed()
|
||||
}
|
||||
else
|
||||
{
|
||||
add_mod_info_.files = result_;
|
||||
emit addModAccepted(app_id_, add_mod_info_);
|
||||
import_mod_info_.files = result_;
|
||||
emit addModAccepted(app_id_, import_mod_info_);
|
||||
}
|
||||
accept();
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../core/addmodinfo.h"
|
||||
#include "../core/importmodinfo.h"
|
||||
#include "../core/fomod/fomodinstaller.h"
|
||||
#include <QButtonGroup>
|
||||
#include <QDialog>
|
||||
@@ -48,7 +48,7 @@ public:
|
||||
void setupDialog(const std::filesystem::path& config_file,
|
||||
const std::filesystem::path& target_path,
|
||||
const QString& app_version,
|
||||
const AddModInfo& info,
|
||||
const ImportModInfo& info,
|
||||
int app_id,
|
||||
bool paths_are_case_invariant);
|
||||
/*!
|
||||
@@ -86,7 +86,7 @@ private:
|
||||
/*! \brief If true: This dialog is non interactive. */
|
||||
bool has_no_steps_;
|
||||
/*! \brief Contains necessary data to install the mod upon dialog completion. */
|
||||
AddModInfo add_mod_info_;
|
||||
ImportModInfo import_mod_info_;
|
||||
/*! \brief Application for which the new mod is to be installed. */
|
||||
int app_id_;
|
||||
/*! \brief Indicates whether the dialog has been completed. */
|
||||
@@ -153,7 +153,7 @@ signals:
|
||||
* \param app_id Application for which the new mod is to be installed.
|
||||
* \param info Contains all data needed to install the mod.
|
||||
*/
|
||||
void addModAccepted(int app_id, AddModInfo info);
|
||||
void addModAccepted(int app_id, ImportModInfo info);
|
||||
/*! \brief Signals mod installation has been aborted. */
|
||||
void addModAborted();
|
||||
};
|
||||
|
||||
@@ -50,7 +50,6 @@ Q_DECLARE_METATYPE(std::unordered_set<int>);
|
||||
Q_DECLARE_METATYPE(QList<QList<QString>>);
|
||||
Q_DECLARE_METATYPE(EditApplicationInfo);
|
||||
Q_DECLARE_METATYPE(EditDeployerInfo);
|
||||
Q_DECLARE_METATYPE(AddModInfo);
|
||||
Q_DECLARE_METATYPE(std::vector<bool>);
|
||||
Q_DECLARE_METATYPE(Log::LogLevel);
|
||||
Q_DECLARE_METATYPE(std::vector<int>);
|
||||
@@ -62,6 +61,7 @@ Q_DECLARE_METATYPE(nexus::Page);
|
||||
Q_DECLARE_METATYPE(ExternalChangesInfo);
|
||||
Q_DECLARE_METATYPE(FileChangeChoices);
|
||||
Q_DECLARE_METATYPE(Tool);
|
||||
Q_DECLARE_METATYPE(ImportModInfo);
|
||||
|
||||
|
||||
MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWindow)
|
||||
@@ -130,15 +130,14 @@ void MainWindow::setCmdArgument(std::string argument)
|
||||
argument.erase(0, 1);
|
||||
if(argument.ends_with('\"'))
|
||||
argument.erase(argument.size() - 1, 1);
|
||||
std::regex nxm_regex(R"(nxm:\/\/.*\mods\/\d+\/files\/\d+\?.*)");
|
||||
std::smatch match;
|
||||
if(std::regex_match(argument, match, nxm_regex))
|
||||
|
||||
if(nexus::Api::nxmUrlIsValid(argument))
|
||||
{
|
||||
Log::debug("Received download request for \"" + argument + "\".");
|
||||
ImportModInfo info;
|
||||
info.app_id = currentApp();
|
||||
info.type = ImportModInfo::download;
|
||||
info.remote_source = argument;
|
||||
info.action_type = ImportModInfo::download;
|
||||
info.remote_request_url = argument;
|
||||
mod_import_queue_.push(info);
|
||||
}
|
||||
}
|
||||
@@ -175,7 +174,6 @@ void MainWindow::setupConnections()
|
||||
qRegisterMetaType<QList<QList<QString>>>();
|
||||
qRegisterMetaType<EditApplicationInfo>();
|
||||
qRegisterMetaType<EditDeployerInfo>();
|
||||
qRegisterMetaType<AddModInfo>();
|
||||
qRegisterMetaType<std::vector<bool>>();
|
||||
qRegisterMetaType<Log::LogLevel>();
|
||||
qRegisterMetaType<std::vector<int>>();
|
||||
@@ -187,6 +185,7 @@ void MainWindow::setupConnections()
|
||||
qRegisterMetaType<ExternalChangesInfo>();
|
||||
qRegisterMetaType<FileChangeChoices>();
|
||||
qRegisterMetaType<Tool>();
|
||||
qRegisterMetaType<ImportModInfo>();
|
||||
|
||||
connect(this, &MainWindow::getModInfo,
|
||||
app_manager_, &ApplicationManager::getModInfo);
|
||||
@@ -380,8 +379,6 @@ void MainWindow::setupConnections()
|
||||
this, &MainWindow::onDownloadComplete);
|
||||
connect(app_manager_, &ApplicationManager::downloadFailed,
|
||||
this, &MainWindow::onDownloadFailed);
|
||||
connect(this, &MainWindow::downloadModFile,
|
||||
app_manager_, &ApplicationManager::downloadModFile);
|
||||
connect(this, &MainWindow::checkForModUpdates,
|
||||
app_manager_, &ApplicationManager::checkForModUpdates);
|
||||
connect(this, &MainWindow::checkModsForUpdates,
|
||||
@@ -853,51 +850,41 @@ void MainWindow::showEditDeployerDialog(int deployer)
|
||||
|
||||
void MainWindow::importMod()
|
||||
{
|
||||
auto info = mod_import_queue_.top();
|
||||
ImportModInfo info = mod_import_queue_.top();
|
||||
setBusyStatus(true);
|
||||
if(info.type == ImportModInfo::download)
|
||||
if(info.action_type == ImportModInfo::download)
|
||||
{
|
||||
if(!initNexusApiKey())
|
||||
{
|
||||
mod_import_queue_.pop();
|
||||
setBusyStatus(false);
|
||||
if(!mod_import_queue_.empty())
|
||||
importMod();
|
||||
return;
|
||||
}
|
||||
setStatusMessage("Downloading mod");
|
||||
if(info.mod_id != -1)
|
||||
emit downloadModFile(
|
||||
info.app_id, info.mod_id, info.nexus_file_id, info.remote_source.c_str());
|
||||
else
|
||||
emit downloadMod(info.app_id, info.remote_source.c_str());
|
||||
return;
|
||||
emit downloadMod(info);
|
||||
}
|
||||
|
||||
if(std::filesystem::exists(info.target_path))
|
||||
else if(info.action_type == ImportModInfo::extract)
|
||||
{
|
||||
try
|
||||
if(std::filesystem::exists(info.target_path))
|
||||
{
|
||||
std::filesystem::remove_all(info.target_path);
|
||||
}
|
||||
catch(std::filesystem::filesystem_error& error)
|
||||
{
|
||||
onReceiveError(
|
||||
"File system error",
|
||||
std::format("Error while trying to delete '{}'", info.target_path.string()).c_str());
|
||||
setBusyStatus(false);
|
||||
return;
|
||||
try
|
||||
{
|
||||
std::filesystem::remove_all(info.target_path);
|
||||
}
|
||||
catch(std::filesystem::filesystem_error& error)
|
||||
{
|
||||
onReceiveError(
|
||||
"File system error",
|
||||
std::format("Error while trying to delete '{}'", info.target_path.string()).c_str());
|
||||
setBusyStatus(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
Log::info("Importing mod '" + info.local_source.string() + "'");
|
||||
setStatusMessage("Importing mod");
|
||||
emit extractArchive(info);
|
||||
}
|
||||
Log::info("Importing mod '" + info.local_source.string() + "'");
|
||||
setStatusMessage("Importing mod");
|
||||
emit extractArchive(info.app_id,
|
||||
info.mod_id,
|
||||
info.local_source.c_str(),
|
||||
info.target_path.c_str(),
|
||||
info.remote_source.c_str(),
|
||||
info.version_overwrite.c_str(),
|
||||
info.name_overwrite.c_str());
|
||||
}
|
||||
|
||||
void MainWindow::setBusyStatus(bool busy, bool show_progress_bar, bool disable_app_launch)
|
||||
@@ -1371,7 +1358,7 @@ void MainWindow::onModAdded(QList<QUrl> paths)
|
||||
{
|
||||
ImportModInfo info;
|
||||
info.app_id = currentApp();
|
||||
info.type = ImportModInfo::extract;
|
||||
info.action_type = ImportModInfo::extract;
|
||||
info.local_source = url.path().toStdString();
|
||||
info.target_path = ui->info_sdir_label->text().toStdString();
|
||||
info.target_path /= temp_dir_.toStdString();
|
||||
@@ -1381,7 +1368,7 @@ void MainWindow::onModAdded(QList<QUrl> paths)
|
||||
importMod();
|
||||
}
|
||||
|
||||
void MainWindow::onAddModDialogAccept(int app_id, AddModInfo info)
|
||||
void MainWindow::onAddModDialogAccept(int app_id, ImportModInfo info)
|
||||
{
|
||||
setBusyStatus(true);
|
||||
setStatusMessage(QString("Installing \"") + info.name.c_str() + "\"");
|
||||
@@ -1402,7 +1389,7 @@ void MainWindow::onGetModInfo(std::vector<ModInfo> mod_info)
|
||||
mod_list_proxy_->updateRowCountLabel();
|
||||
if(!is_initialized_ && !mod_import_queue_.empty())
|
||||
{
|
||||
auto info = mod_import_queue_.top();
|
||||
ImportModInfo info = mod_import_queue_.top();
|
||||
mod_import_queue_.pop();
|
||||
info.app_id = currentApp();
|
||||
info.queue_time = std::chrono::high_resolution_clock::now();
|
||||
@@ -1943,8 +1930,6 @@ void MainWindow::onModAddedToGroup(int mod_id, int target_id)
|
||||
|
||||
void MainWindow::onAddModAborted(QString temp_dir)
|
||||
{
|
||||
if(!mod_import_queue_.empty())
|
||||
mod_import_queue_.pop();
|
||||
Log::info("Mod installation aborted");
|
||||
bool abort = true;
|
||||
if(!mod_import_queue_.empty())
|
||||
@@ -1996,24 +1981,16 @@ void MainWindow::onModMovedTo(int from, int to)
|
||||
emit getDeployerInfo(currentApp(), currentDeployer());
|
||||
}
|
||||
|
||||
void MainWindow::onExtractionComplete(int app_id,
|
||||
int mod_id,
|
||||
bool success,
|
||||
QString extracted_path,
|
||||
QString local_source,
|
||||
QString remote_source,
|
||||
QString version,
|
||||
QString name)
|
||||
void MainWindow::onExtractionComplete(ImportModInfo info)
|
||||
{
|
||||
setBusyStatus(false);
|
||||
if(!success)
|
||||
mod_import_queue_.pop();
|
||||
if(!info.last_action_was_successful)
|
||||
{
|
||||
if(!mod_import_queue_.empty())
|
||||
mod_import_queue_.pop();
|
||||
if(!mod_import_queue_.empty())
|
||||
importMod();
|
||||
setStatusMessage("Import failed", 3000);
|
||||
Log::error("Failed to import mod \"" + local_source.toStdString() + "\"");
|
||||
Log::error("Failed to import mod \"" + info.local_source.string() + "\"");
|
||||
return;
|
||||
}
|
||||
setStatusMessage("Mod imported", 3000);
|
||||
@@ -2031,28 +2008,22 @@ void MainWindow::onExtractionComplete(int app_id,
|
||||
deployer_paths.append(
|
||||
ui->info_deployer_list->item(i, getColumnIndex(ui->info_deployer_list, "Target"))->text());
|
||||
}
|
||||
std::filesystem::path mod_path = local_source.toStdString();
|
||||
bool was_successful = add_mod_dialog_->setupDialog(mod_path.filename().c_str(),
|
||||
deployers,
|
||||
info.action_type = ImportModInfo::ActionType::install_dialog;
|
||||
bool was_successful = add_mod_dialog_->setupDialog(deployers,
|
||||
deployer,
|
||||
extracted_path,
|
||||
deployer_paths,
|
||||
app_id,
|
||||
auto_deployers,
|
||||
app_info_.deployer_is_case_invariant,
|
||||
ui->info_version_label->text(),
|
||||
local_source,
|
||||
remote_source,
|
||||
mod_id,
|
||||
version,
|
||||
name);
|
||||
info);
|
||||
if(was_successful)
|
||||
{
|
||||
setBusyStatus(true, false);
|
||||
add_mod_dialog_->show();
|
||||
}
|
||||
else
|
||||
onReceiveError("Error", ("Failed to import mod from \"" + mod_path.string() + "\"").c_str());
|
||||
onReceiveError("Error",
|
||||
("Failed to import mod from \"" + info.local_source.string() + "\"").c_str());
|
||||
}
|
||||
|
||||
void MainWindow::onSettingsDialogComplete()
|
||||
@@ -3167,16 +3138,15 @@ void MainWindow::onReceiveIpcMessage(QString message)
|
||||
activateWindow();
|
||||
if(message == "Started")
|
||||
return;
|
||||
std::regex nxm_regex(R"(nxm:\/\/.*\mods\/\d+\/files\/\d+\?.*)");
|
||||
std::smatch match;
|
||||
|
||||
std::string message_str = message.toStdString();
|
||||
if(std::regex_match(message_str, match, nxm_regex))
|
||||
if(nexus::Api::nxmUrlIsValid(message_str))
|
||||
{
|
||||
Log::debug("Received download request for \"" + message.toStdString() + "\".");
|
||||
ImportModInfo info;
|
||||
info.app_id = currentApp();
|
||||
info.type = ImportModInfo::download;
|
||||
info.remote_source = message.toStdString();
|
||||
info.action_type = ImportModInfo::download;
|
||||
info.remote_request_url = message.toStdString();
|
||||
mod_import_queue_.push(info);
|
||||
if(mod_import_queue_.size() == 1)
|
||||
importMod();
|
||||
@@ -3185,16 +3155,11 @@ void MainWindow::onReceiveIpcMessage(QString message)
|
||||
Log::debug("Unknown IPC message: \"" + message.toStdString() + "\"");
|
||||
}
|
||||
|
||||
void MainWindow::onDownloadComplete(int app_id, int mod_id, QString file_path, QString mod_url)
|
||||
void MainWindow::onDownloadComplete(ImportModInfo info)
|
||||
{
|
||||
onCompletedOperations("Download complete");
|
||||
mod_import_queue_.pop();
|
||||
ImportModInfo info;
|
||||
info.type = ImportModInfo::extract;
|
||||
info.app_id = app_id;
|
||||
info.mod_id = mod_id;
|
||||
info.local_source = file_path.toStdString();
|
||||
info.remote_source = mod_url.toStdString();
|
||||
info.action_type = ImportModInfo::extract;
|
||||
info.target_path = ui->info_sdir_label->text().toStdString();
|
||||
info.target_path /= temp_dir_.toStdString();
|
||||
mod_import_queue_.push(info);
|
||||
@@ -3209,10 +3174,10 @@ void MainWindow::onModDownloadRequested(int app_id,
|
||||
{
|
||||
ImportModInfo info;
|
||||
info.app_id = app_id;
|
||||
info.type = ImportModInfo::download;
|
||||
info.nexus_file_id = file_id;
|
||||
info.action_type = ImportModInfo::download;
|
||||
info.remote_file_id = file_id;
|
||||
info.remote_source = mod_url.toStdString();
|
||||
info.mod_id = mod_id;
|
||||
info.target_group_id = mod_id;
|
||||
info.version_overwrite = version.toStdString();
|
||||
mod_import_queue_.push(info);
|
||||
if(mod_import_queue_.size() == 1)
|
||||
@@ -3248,10 +3213,10 @@ void MainWindow::on_actionReinstall_From_Local_triggered()
|
||||
|
||||
ImportModInfo info;
|
||||
info.app_id = currentApp();
|
||||
info.type = ImportModInfo::extract;
|
||||
info.action_type = ImportModInfo::extract;
|
||||
info.local_source = local_source;
|
||||
info.remote_source = remote_source;
|
||||
info.mod_id = mod_id;
|
||||
info.target_group_id = mod_id;
|
||||
info.target_path = ui->info_sdir_label->text().toStdString();
|
||||
info.target_path /= temp_dir_.toStdString();
|
||||
info.name_overwrite = name;
|
||||
@@ -3318,8 +3283,6 @@ void MainWindow::on_actionSuppress_Update_triggered()
|
||||
void MainWindow::onModInstallationComplete(bool success)
|
||||
{
|
||||
onCompletedOperations(success ? "Installation complete" : "Installation failed");
|
||||
if(!mod_import_queue_.empty())
|
||||
mod_import_queue_.pop();
|
||||
if(!mod_import_queue_.empty())
|
||||
importMod();
|
||||
}
|
||||
|
||||
@@ -485,7 +485,7 @@ public slots:
|
||||
* \param app_id Application for which the new mod is to be installed.
|
||||
* \param info Contains all data needed to install the mod.
|
||||
*/
|
||||
void onAddModDialogAccept(int app_id, AddModInfo info);
|
||||
void onAddModDialogAccept(int app_id, ImportModInfo info);
|
||||
/*!
|
||||
* \brief Called when a mod gets disabled/ enabled in ui->deployer_list.
|
||||
* Updates the mods state.
|
||||
@@ -631,23 +631,9 @@ public slots:
|
||||
void onModMovedTo(int from, int to);
|
||||
/*!
|
||||
* \brief Called after archive has been extracted. Installs the newly extracted mod.
|
||||
* \param app_id Target app for the mod.
|
||||
* \param mod_id Id of the mod for which the file is to be extracted or -1 if this is a new mod.
|
||||
* \param success False when exception was thrown.
|
||||
* \param extracted_path Path to which the mod was extracted.
|
||||
* \param local_source Source archive for the mod.
|
||||
* \param remote_source URL from where the mod was downloaded.
|
||||
* \param version If not empty: Use this to overwrite the default version.
|
||||
* \param name If not empty: Use this to overwrite the default name.
|
||||
* \param info Contains data for the extracted mod.
|
||||
*/
|
||||
void onExtractionComplete(int app_id,
|
||||
int mod_id,
|
||||
bool success,
|
||||
QString extracted_path,
|
||||
QString local_source,
|
||||
QString remote_source,
|
||||
QString version,
|
||||
QString name);
|
||||
void onExtractionComplete(ImportModInfo info);
|
||||
/*! \brief Called when the settings dialog has completed. Updates state with new settings. */
|
||||
void onSettingsDialogComplete();
|
||||
/*!
|
||||
@@ -980,13 +966,9 @@ private slots:
|
||||
void onReceiveIpcMessage(QString message);
|
||||
/*!
|
||||
* \brief Begins extraction of downloaded mod.
|
||||
* \param app_id App for which the mod has been downloaded.
|
||||
* \param mod_id If !=-1: The downloaded mod should be added to this mods group after
|
||||
* installation.
|
||||
* \param file_path Path to the downloaded file.
|
||||
* \param mod_url Url from which the mod was downloaded.
|
||||
* \param info Contains all relevant data for the extraction process.
|
||||
*/
|
||||
void onDownloadComplete(int app_id, int mod_id, QString file_path, QString mod_url);
|
||||
void onDownloadComplete(ImportModInfo info);
|
||||
/*!
|
||||
* \brief Downloads a mod from nexusmods using the given mod_url. Only works if the given
|
||||
* api key belongs to a premium user.
|
||||
@@ -1153,7 +1135,7 @@ signals:
|
||||
* \param app_id The target \ref ModdedApplication "application".
|
||||
* \param info Contains all data needed to install the mod.
|
||||
*/
|
||||
void installMod(int app_id, AddModInfo info);
|
||||
void installMod(int app_id, ImportModInfo info);
|
||||
/*!
|
||||
* \brief Uninstalls the given mods for one \ref ModdedApplication "application", this includes
|
||||
* deleting all installed files.
|
||||
@@ -1389,21 +1371,9 @@ signals:
|
||||
void sortModsByConflicts(int app_id, int deployer);
|
||||
/*!
|
||||
* \brief Extracts an archive to target directory.
|
||||
* \param app_id \ref ModdedApplication "application" for which the mod has been extracted.
|
||||
* \param mod_id Id of the mod for which the file is to be extracted or -1 if this is a new mod.
|
||||
* \param source Source path.
|
||||
* \param target Target path
|
||||
* \param remote_source URL from where the mod was downloaded.
|
||||
* \param version If not empty: Use this to overwrite the default version.
|
||||
* \param name If not empty: Use this to overwrite the default name.
|
||||
* \param info Contains all data needed to extract the mod archive.
|
||||
*/
|
||||
void extractArchive(int app_id,
|
||||
int mod_id,
|
||||
QString source,
|
||||
QString target,
|
||||
QString remote_source,
|
||||
QString version,
|
||||
QString name);
|
||||
void extractArchive(ImportModInfo info);
|
||||
/*!
|
||||
* \brief Requests info about backups for one ModdedApplication.
|
||||
* \param app_id Target app.
|
||||
@@ -1551,23 +1521,11 @@ signals:
|
||||
*/
|
||||
void getNexusPage(int app_id, int mod_id);
|
||||
/*!
|
||||
* \brief Downloads a mod from nexusmods using the given nxm_url.
|
||||
* \param app_id App for which the mod is to be downloaded. The mod is downloaded to the apps
|
||||
* staging directory.
|
||||
* \param nxm_url Url containing all information needed for the download.
|
||||
* \brief Downloads a mod from remote using the given import info.
|
||||
* \param info Contains either the ImportModInfo::remote_request_url or
|
||||
* ImportModInfo::remote_mod_id and ImportModInfo::remote_file_id.
|
||||
*/
|
||||
void downloadMod(int app_id, QString nxm_url);
|
||||
/*!
|
||||
* \brief Downloads a mod from nexusmods using the given mod_url. Only works if the given
|
||||
* api key belongs to a premium user.
|
||||
* \param app_id App for which the mod is to be downloaded. The mod is downloaded to the apps
|
||||
* staging directory.
|
||||
* \param mod_id Id of the mod for which the file is to be downloaded. This is the limo internal
|
||||
* mod id, NOT the NexusMods id.
|
||||
* \param file_id NexusMods file id of the target file.
|
||||
* \param mod_url Url to the NexusMods page of the mod.
|
||||
*/
|
||||
void downloadModFile(int app_id, int mod_id, int file_id, QString mod_url);
|
||||
void downloadMod(ImportModInfo info);
|
||||
/*!
|
||||
* \brief Checks for available mod updates on NexusMods.
|
||||
* \param app_id App for which mod updates are to be checked.
|
||||
|
||||
@@ -14,24 +14,26 @@ TEST_CASE("Mods are installed", "[app]")
|
||||
{
|
||||
resetStagingDir();
|
||||
ModdedApplication app(DATA_DIR / "staging", "test");
|
||||
AddModInfo info{
|
||||
"mod 0", "1.0", Installer::SIMPLEINSTALLER, DATA_DIR / "source" / "mod0.tar.gz", {}, -1,
|
||||
INSTALLER_FLAGS, 0
|
||||
};
|
||||
ImportModInfo info;
|
||||
info.name = "mod 0";
|
||||
info.version = "1.0";
|
||||
info.installer = Installer::SIMPLEINSTALLER;
|
||||
info.current_path = DATA_DIR / "source" / "mod0.tar.gz";
|
||||
info.installer_flags = INSTALLER_FLAGS;
|
||||
app.installMod(info);
|
||||
verifyDirsAreEqual(DATA_DIR / "staging" / "0", DATA_DIR / "source" / "0");
|
||||
info.name = "mod 2";
|
||||
info.source_path = DATA_DIR / "source" / "mod2.tar.gz";
|
||||
info.current_path = DATA_DIR / "source" / "mod2.tar.gz";
|
||||
app.installMod(info);
|
||||
verifyDirsAreEqual(DATA_DIR / "staging" / "1", DATA_DIR / "source" / "2");
|
||||
info.name = "mod 1";
|
||||
info.source_path = DATA_DIR / "source" / "mod1.zip";
|
||||
info.current_path = DATA_DIR / "source" / "mod1.zip";
|
||||
app.installMod(info);
|
||||
verifyDirsAreEqual(DATA_DIR / "staging" / "2", DATA_DIR / "source" / "1");
|
||||
|
||||
info.name = "mod 0->2";
|
||||
info.source_path = DATA_DIR / "source" / "mod2.tar.gz";
|
||||
info.group = 0;
|
||||
info.current_path = DATA_DIR / "source" / "mod2.tar.gz";
|
||||
info.target_group_id = 0;
|
||||
info.replace_mod = true;
|
||||
app.installMod(info);
|
||||
verifyDirsAreEqual(DATA_DIR / "staging" / "0", DATA_DIR / "source" / "2");
|
||||
@@ -46,20 +48,20 @@ TEST_CASE("Deployers are added", "[app]")
|
||||
resetAppDir();
|
||||
ModdedApplication app(DATA_DIR / "staging", "test");
|
||||
app.addDeployer({ DeployerFactory::SIMPLEDEPLOYER, "depl0", DATA_DIR / "app", Deployer::hard_link });
|
||||
AddModInfo info{ "mod 0",
|
||||
"1.0",
|
||||
Installer::SIMPLEINSTALLER,
|
||||
DATA_DIR / "source" / "mod0.tar.gz",
|
||||
{ 0 },
|
||||
-1,
|
||||
INSTALLER_FLAGS,
|
||||
0 };
|
||||
ImportModInfo info;
|
||||
info.name = "mod 0";
|
||||
info.version = "1.0";
|
||||
info.installer = Installer::SIMPLEINSTALLER;
|
||||
info.current_path = DATA_DIR / "source" / "mod0.tar.gz";
|
||||
info.deployers = {0};
|
||||
info.installer_flags = INSTALLER_FLAGS;
|
||||
info.root_level = 0;
|
||||
app.installMod(info);
|
||||
info.name = "mod 1";
|
||||
info.source_path = DATA_DIR / "source" / "mod1.zip";
|
||||
info.current_path = DATA_DIR / "source" / "mod1.zip";
|
||||
app.installMod(info);
|
||||
info.name = "mod 2";
|
||||
info.source_path = DATA_DIR / "source" / "mod2.tar.gz";
|
||||
info.current_path = DATA_DIR / "source" / "mod2.tar.gz";
|
||||
app.installMod(info);
|
||||
app.deployMods();
|
||||
verifyDirsAreEqual(DATA_DIR / "app", DATA_DIR / "target" / "mod012", true);
|
||||
@@ -75,20 +77,20 @@ TEST_CASE("State is saved", "[app]")
|
||||
app.addProfile(EditProfileInfo{ "test profile", "", -1 });
|
||||
app.addTool({"t1", "", "command string"});
|
||||
app.addTool({"t4", "", "/bin/prog.exe", true, 220, "/tmp", {{"VAR_1", "VAL_1"}}, "-arg", "-parg"});
|
||||
AddModInfo info{ "mod 0",
|
||||
"1.0",
|
||||
Installer::SIMPLEINSTALLER,
|
||||
DATA_DIR / "source" / "mod0.tar.gz",
|
||||
{ 0 },
|
||||
-1,
|
||||
INSTALLER_FLAGS,
|
||||
0 };
|
||||
ImportModInfo info;
|
||||
info.name = "mod 0";
|
||||
info.version = "1.0";
|
||||
info.installer = Installer::SIMPLEINSTALLER;
|
||||
info.current_path = DATA_DIR / "source" / "mod0.tar.gz";
|
||||
info.deployers = {0};
|
||||
info.installer_flags = INSTALLER_FLAGS;
|
||||
info.root_level = 0;
|
||||
app.installMod(info);
|
||||
info.name = "mod 1";
|
||||
info.source_path = DATA_DIR / "source" / "mod1.zip";
|
||||
info.current_path = DATA_DIR / "source" / "mod1.zip";
|
||||
app.installMod(info);
|
||||
info.name = "mod 2";
|
||||
info.source_path = DATA_DIR / "source" / "mod2.tar.gz";
|
||||
info.current_path = DATA_DIR / "source" / "mod2.tar.gz";
|
||||
info.deployers = { 0, 1 };
|
||||
app.installMod(info);
|
||||
|
||||
@@ -117,18 +119,18 @@ TEST_CASE("Groups update loadorders", "[app]")
|
||||
ModdedApplication app(DATA_DIR / "staging", "test");
|
||||
app.addDeployer({ DeployerFactory::SIMPLEDEPLOYER, "depl0", DATA_DIR / "app", Deployer::hard_link });
|
||||
app.addDeployer({ DeployerFactory::SIMPLEDEPLOYER, "depl1", DATA_DIR / "app_2", Deployer::hard_link });
|
||||
AddModInfo info{ "mod 0",
|
||||
"1.0",
|
||||
Installer::SIMPLEINSTALLER,
|
||||
DATA_DIR / "source" / "mod0.tar.gz",
|
||||
{ 0 },
|
||||
-1,
|
||||
INSTALLER_FLAGS,
|
||||
0 };
|
||||
ImportModInfo info;
|
||||
info.name = "mod 0";
|
||||
info.version = "1.0";
|
||||
info.installer = Installer::SIMPLEINSTALLER;
|
||||
info.current_path = DATA_DIR / "source" / "mod0.tar.gz";
|
||||
info.deployers = {0};
|
||||
info.installer_flags = INSTALLER_FLAGS;
|
||||
info.root_level = 0;
|
||||
app.installMod(info);
|
||||
info.name = "mod 1";
|
||||
info.deployers = { 0, 1 };
|
||||
info.source_path = DATA_DIR / "source" / "mod1.zip";
|
||||
info.current_path = DATA_DIR / "source" / "mod1.zip";
|
||||
app.installMod(info);
|
||||
app.createGroup(1, 0);
|
||||
REQUIRE_THAT(app.getLoadorder(0), Catch::Matchers::Equals(app.getLoadorder(1)));
|
||||
@@ -138,7 +140,7 @@ TEST_CASE("Groups update loadorders", "[app]")
|
||||
REQUIRE_THAT(app.getLoadorder(0),
|
||||
Catch::Matchers::Equals(std::vector<std::tuple<int, bool>>{ { 0, true } }));
|
||||
info.name = "mod 2";
|
||||
info.source_path = DATA_DIR / "source" / "mod2.tar.gz";
|
||||
info.current_path = DATA_DIR / "source" / "mod2.tar.gz";
|
||||
app.installMod(info);
|
||||
REQUIRE_THAT(
|
||||
app.getLoadorder(0),
|
||||
@@ -174,14 +176,14 @@ TEST_CASE("Mods are split", "[app]")
|
||||
"depl2",
|
||||
DATA_DIR / "source" / "split" / "targets" / "d",
|
||||
Deployer::hard_link });
|
||||
AddModInfo info{ "mod 0",
|
||||
"1.0",
|
||||
Installer::SIMPLEINSTALLER,
|
||||
DATA_DIR / "source" / "split" / "mod",
|
||||
{ 0 },
|
||||
-1,
|
||||
INSTALLER_FLAGS,
|
||||
0 };
|
||||
ImportModInfo info;
|
||||
info.name = "mod 0";
|
||||
info.version = "1.0";
|
||||
info.installer = Installer::SIMPLEINSTALLER;
|
||||
info.current_path = DATA_DIR / "source" / "split" / "mod";
|
||||
info.deployers = {0};
|
||||
info.installer_flags = INSTALLER_FLAGS;
|
||||
info.root_level = 0;
|
||||
app.installMod(info);
|
||||
sfs::remove(DATA_DIR / "staging" / "lmm_mods.json");
|
||||
sfs::remove(DATA_DIR / "staging" / ".lmm_mods.json.bak");
|
||||
@@ -194,22 +196,22 @@ TEST_CASE("Mods are uninstalled", "[app]")
|
||||
ModdedApplication app(DATA_DIR / "staging", "test");
|
||||
app.addDeployer({ DeployerFactory::SIMPLEDEPLOYER, "depl0", DATA_DIR / "app", Deployer::hard_link });
|
||||
app.addDeployer({ DeployerFactory::SIMPLEDEPLOYER, "depl1", DATA_DIR / "app_2", Deployer::hard_link });
|
||||
AddModInfo info{ "mod 0",
|
||||
"1.0",
|
||||
Installer::SIMPLEINSTALLER,
|
||||
DATA_DIR / "source" / "mod0.tar.gz",
|
||||
{ 0 },
|
||||
-1,
|
||||
INSTALLER_FLAGS,
|
||||
0 };
|
||||
ImportModInfo info;
|
||||
info.name = "mod 0";
|
||||
info.version = "1.0";
|
||||
info.installer = Installer::SIMPLEINSTALLER;
|
||||
info.current_path = DATA_DIR / "source" / "mod0.tar.gz";
|
||||
info.deployers = {0};
|
||||
info.installer_flags = INSTALLER_FLAGS;
|
||||
info.root_level = 0;
|
||||
app.installMod(info);
|
||||
info.name = "mod 1";
|
||||
info.source_path = DATA_DIR / "source" / "mod1.zip";
|
||||
info.current_path = DATA_DIR / "source" / "mod1.zip";
|
||||
app.installMod(info);
|
||||
info.name = "mod 2";
|
||||
info.source_path = DATA_DIR / "source" / "mod2.tar.gz";
|
||||
info.current_path = DATA_DIR / "source" / "mod2.tar.gz";
|
||||
info.deployers = { 0, 1 };
|
||||
info.group = 1;
|
||||
info.target_group_id = 1;
|
||||
app.installMod(info);
|
||||
app.uninstallMods({ 0, 2 });
|
||||
auto mod_info = app.getModInfo();
|
||||
@@ -224,13 +226,13 @@ TEST_CASE("Mods are uninstalled", "[app]")
|
||||
verifyDirsAreEqual(DATA_DIR / "staging", DATA_DIR / "target" / "remove" / "simple");
|
||||
|
||||
info.deployers = { 0 };
|
||||
info.group = -1;
|
||||
info.target_group_id = -1;
|
||||
info.name = "mod 0";
|
||||
info.source_path = DATA_DIR / "source" / "mod0.tar.gz";
|
||||
info.current_path = DATA_DIR / "source" / "mod0.tar.gz";
|
||||
app.installMod(info);
|
||||
info.name = "mod 2";
|
||||
info.source_path = DATA_DIR / "source" / "mod2.tar.gz";
|
||||
info.group = 1;
|
||||
info.current_path = DATA_DIR / "source" / "mod2.tar.gz";
|
||||
info.target_group_id = 1;
|
||||
app.installMod(info);
|
||||
app.uninstallGroupMembers({ 1 });
|
||||
mod_info = app.getModInfo();
|
||||
|
||||
Reference in New Issue
Block a user