From feab262e44372c937903bcc48aab360976fa24a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Raes?= Date: Fri, 6 Dec 2024 09:05:05 +0100 Subject: [PATCH] Rework MAD `cleanup` handles (#817) Mods are no longer marked as successfully installed when the process failed. When mod downloading/extraction fails, the mod directory is removed again. --- primedev/mods/autodownload/moddownloader.cpp | 57 +++++++++++++------- primedev/mods/autodownload/moddownloader.h | 14 ++--- 2 files changed, 46 insertions(+), 25 deletions(-) diff --git a/primedev/mods/autodownload/moddownloader.cpp b/primedev/mods/autodownload/moddownloader.cpp index 21b98942a..de95a0c2c 100644 --- a/primedev/mods/autodownload/moddownloader.cpp +++ b/primedev/mods/autodownload/moddownloader.cpp @@ -176,7 +176,7 @@ int ModDownloader::ModFetchingProgressCallback( return 0; } -std::optional ModDownloader::FetchModFromDistantStore(std::string_view modName, VerifiedModVersion version) +std::tuple ModDownloader::FetchModFromDistantStore(std::string_view modName, VerifiedModVersion version) { std::string url = version.downloadLink; std::string archiveName = fs::path(url).filename().generic_string(); @@ -190,7 +190,7 @@ std::optional ModDownloader::FetchModFromDistantStore(std::string_view modState.state = DOWNLOADING; // Download the actual archive - bool failed = false; + bool success = false; FILE* fp = fopen(downloadPath.generic_string().c_str(), "wb"); CURLcode result; CURL* easyhandle; @@ -219,13 +219,14 @@ std::optional ModDownloader::FetchModFromDistantStore(std::string_view if (result == CURLcode::CURLE_OK) { spdlog::info("Mod archive successfully fetched."); - return std::optional(downloadPath); + success = true; } else { spdlog::error("Fetching mod archive failed: {}", curl_easy_strerror(result)); - return std::optional(); } + + return {downloadPath, success}; } bool ModDownloader::IsModLegit(fs::path modPath, std::string_view expectedChecksum) @@ -399,11 +400,9 @@ int GetModArchiveSize(unzFile file, unz_global_info64 info) return totalSize; } -void ModDownloader::ExtractMod(fs::path modPath, VerifiedModPlatform platform) +void ModDownloader::ExtractMod(fs::path modPath, fs::path destinationPath, VerifiedModPlatform platform) { unzFile file; - std::string name; - fs::path modDirectory; file = unzOpen(modPath.generic_string().c_str()); ScopeGuard cleanup( @@ -445,11 +444,6 @@ void ModDownloader::ExtractMod(fs::path modPath, VerifiedModPlatform platform) return; } - // Mod directory name (removing the ".zip" fom the archive name) - name = modPath.filename().string(); - name = name.substr(0, name.length() - 4); - modDirectory = GetRemoteModFolderPath() / name; - for (int i = 0; i < gi.number_entry; i++) { char zipFilename[256]; @@ -459,7 +453,7 @@ void ModDownloader::ExtractMod(fs::path modPath, VerifiedModPlatform platform) // Extract file { std::error_code ec; - fs::path fileDestination = modDirectory / zipFilename; + fs::path fileDestination = destinationPath / zipFilename; spdlog::info("=> {}", fileDestination.generic_string()); // Create parent directory if needed @@ -589,6 +583,9 @@ void ModDownloader::ExtractMod(fs::path modPath, VerifiedModPlatform platform) } } } + + // Mod extraction went fine + modState.state = DONE; } void ModDownloader::DownloadMod(std::string modName, std::string modVersion) @@ -603,11 +600,14 @@ void ModDownloader::DownloadMod(std::string modName, std::string modVersion) std::thread requestThread( [this, modName, modVersion]() { + std::string name; fs::path archiveLocation; + fs::path modDirectory; ScopeGuard cleanup( [&] { + // Remove downloaded archive try { remove(archiveLocation); @@ -617,14 +617,31 @@ void ModDownloader::DownloadMod(std::string modName, std::string modVersion) spdlog::error("Error while removing downloaded archive: {}", a.what()); } + // Remove mod if auto-download process failed + if (modState.state != DONE) + { + try + { + remove_all(modDirectory); + } + catch (const std::exception& e) + { + spdlog::error("Error while removing downloaded mod: {}", e.what()); + } + } + spdlog::info("Done cleaning after downloading {}.", modName); }); // Download mod archive VerifiedModVersion fullVersion = verifiedMods[modName].versions[modVersion]; std::string expectedHash = fullVersion.checksum; - std::optional fetchingResult = FetchModFromDistantStore(std::string_view(modName), fullVersion); - if (!fetchingResult.has_value()) + + const std::tuple downloadResult = FetchModFromDistantStore(std::string_view(modName), fullVersion); + archiveLocation = get<0>(downloadResult); + bool downloadSuccessful = get<1>(downloadResult); + + if (!downloadSuccessful) { spdlog::error("Something went wrong while fetching archive, aborting."); if (modState.state != ABORTED) @@ -633,7 +650,7 @@ void ModDownloader::DownloadMod(std::string modName, std::string modVersion) } return; } - archiveLocation = fetchingResult.value(); + if (!IsModLegit(archiveLocation, std::string_view(expectedHash))) { spdlog::warn("Archive hash does not match expected checksum, aborting."); @@ -641,9 +658,13 @@ void ModDownloader::DownloadMod(std::string modName, std::string modVersion) return; } + // Mod directory name (removing the ".zip" fom the archive name) + name = archiveLocation.filename().string(); + name = name.substr(0, name.length() - 4); + modDirectory = GetRemoteModFolderPath() / name; + // Extract downloaded mod archive - ExtractMod(archiveLocation, fullVersion.platform); - modState.state = DONE; + ExtractMod(archiveLocation, modDirectory, fullVersion.platform); }); requestThread.detach(); diff --git a/primedev/mods/autodownload/moddownloader.h b/primedev/mods/autodownload/moddownloader.h index 2ac72a48c..f8c0c6646 100644 --- a/primedev/mods/autodownload/moddownloader.h +++ b/primedev/mods/autodownload/moddownloader.h @@ -43,14 +43,12 @@ class ModDownloader * input mod name as mod dependency string if bypass flag is set up; fetched * archive is then stored in a temporary location. * - * If something went wrong during archive download, this will return an empty - * optional object. * * @param modName name of the mod to be downloaded * @param modVersion version of the mod to be downloaded - * @returns location of the downloaded archive + * @returns tuple containing location of the downloaded archive and whether download completed successfully */ - std::optional FetchModFromDistantStore(std::string_view modName, VerifiedModVersion modVersion); + std::tuple FetchModFromDistantStore(std::string_view modName, VerifiedModVersion modVersion); /** * Tells if a mod archive has not been corrupted. @@ -69,14 +67,16 @@ class ModDownloader /** * Extracts a mod archive to the game folder. * - * This extracts a downloaded mod archive from its original location to the - * current game profile; the install folder is defined by the platform parameter. + * This extracts a downloaded mod archive from its original location, `modPath`, + * to the specified `destinationPath`; the way to decompress the archive is + * defined by the `platform` parameter. * * @param modPath location of the downloaded archive + * @param destinationPath destination of the extraction * @param platform origin of the downloaded archive * @returns nothing */ - void ExtractMod(fs::path modPath, VerifiedModPlatform platform); + void ExtractMod(fs::path modPath, fs::path destinationPath, VerifiedModPlatform platform); public: ModDownloader();