mirror of
https://github.com/Dingo-Shenanigans/ReSkate.git
synced 2026-10-17 11:57:01 +02:00
Mods: an update deletes the copy it replaces instead of recycling it
Every update, and every install over an installed mod, sent the old folder to the Recycle Bin, which filled up with old versions. - commit() moves the installed copy aside into the staging folder, moves the new one in, then deletes the old for good. A new copy that cannot go in puts the old one back; a copy with a file in use is not replaced and stays whole. - Uninstalling still goes to the Recycle Bin. The Replace dialog says the installed copy is deleted.
This commit is contained in:
1 parent
a14a6f44b5
commit
181e02454b
5 files changed
+29
-7
No files matched your search
@@ -121,8 +121,19 @@ int main() {
|
||||
}
|
||||
check(conflict, "Installing over an installed mod asks first");
|
||||
make_zip(sources / L"v2.zip", {{"Cool Park/layout.toc", "toc v2"}});
|
||||
// A file of the installed copy held open: the replacement is refused and the copy stays whole.
|
||||
{
|
||||
const auto held = CreateFileW((mods / L"Cool Park" / L"layout.toc").c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr,
|
||||
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
|
||||
bool refused = false;
|
||||
try { install(mods, sources / L"v2.zip", true); } catch (const std::exception&) { refused = true; }
|
||||
CloseHandle(held);
|
||||
check(refused && read(mods / L"Cool Park" / L"layout.toc") == "toc" && !fs::exists(mods / L".reskate-install"),
|
||||
"A copy in use is not replaced, and stays as it was");
|
||||
}
|
||||
check(install(mods, sources / L"v2.zip", true) == "Cool Park" && read(mods / L"Cool Park" / L"layout.toc") == "toc v2",
|
||||
"Replacing installs the new copy");
|
||||
check(!fs::exists(mods / L".reskate-install"), "The replaced copy is deleted, not left beside the mods");
|
||||
{
|
||||
std::atomic<bool> cancel{};
|
||||
unsigned callbacks{};
|
||||
|
||||
@@ -1219,7 +1219,7 @@ void mods_window(Launcher& launcher, const Fonts& fonts, ImVec2 size, Ui& ui, Mo
|
||||
ImGui::SetNextWindowSize(ImVec2(S(440), 0));
|
||||
if (ImGui::BeginPopupModal("Replace mod", nullptr, ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove)) {
|
||||
ImGui::PushTextWrapPos(0);
|
||||
ImGui::Text("\"%s\" is already installed. Replace it? The installed copy goes to the Recycle Bin.",
|
||||
ImGui::Text("\"%s\" is already installed. Replace it? The installed copy is deleted.",
|
||||
panel.conflict_name.c_str());
|
||||
ImGui::PopTextWrapPos();
|
||||
ImGui::Spacing();
|
||||
|
||||
@@ -147,7 +147,7 @@ void set_activity(ModsPanel& panel, std::string text) {
|
||||
}
|
||||
|
||||
// Downloads one package into Mods/.reskate-download, then installs it over any
|
||||
// installed copy (which goes to the Recycle Bin, keeping its place in mods.json).
|
||||
// installed copy (which is deleted, its place in mods.json kept).
|
||||
std::string install_package(ModsPanel& panel, const fs::path& root, const ts::Package& package) {
|
||||
const auto& version = package.latest();
|
||||
const auto folder = root / L".reskate-download";
|
||||
|
||||
@@ -225,16 +225,27 @@ private:
|
||||
fs::path path_;
|
||||
};
|
||||
|
||||
// Moves the finished staging folder into place, replacing only when asked.
|
||||
// Moves the finished staging folder into place, replacing only when asked. The copy it
|
||||
// replaces (an update, mostly) is deleted for good rather than recycled: it is moved aside
|
||||
// into the staging folder first, so a new copy that cannot go in leaves the old one where it was.
|
||||
void commit(const fs::path& mods, const Staging& staging, const std::string& name, bool replace) {
|
||||
const auto target = mods / from_utf8(name);
|
||||
std::error_code error;
|
||||
fs::path aside;
|
||||
if (fs::exists(target, error)) {
|
||||
if (!replace) throw AlreadyInstalled(name);
|
||||
recycle(target);
|
||||
aside = staging.path().parent_path() / (from_utf8(name).wstring() + L".replaced");
|
||||
fs::remove_all(aside, error);
|
||||
if (!MoveFileExW(target.c_str(), aside.c_str(), MOVEFILE_WRITE_THROUGH))
|
||||
fail("Could not replace " + name + ": close anything using its files (the game, an editor) and try again.");
|
||||
}
|
||||
if (!MoveFileExW(staging.path().c_str(), target.c_str(), MOVEFILE_WRITE_THROUGH))
|
||||
fail("Could not move the mod into the Mods folder (Windows error " + std::to_string(GetLastError()) + ").");
|
||||
if (!MoveFileExW(staging.path().c_str(), target.c_str(), MOVEFILE_WRITE_THROUGH)) {
|
||||
const auto code = GetLastError();
|
||||
if (!aside.empty()) MoveFileExW(aside.c_str(), target.c_str(), MOVEFILE_WRITE_THROUGH);
|
||||
fail("Could not move the mod into the Mods folder (Windows error " + std::to_string(code) + ").");
|
||||
}
|
||||
// (Whatever of it cannot be deleted now goes with the staging folder.)
|
||||
if (!aside.empty()) fs::remove_all(aside, error);
|
||||
}
|
||||
|
||||
// miniz is built without stdio, so the archive is read through a handle.
|
||||
|
||||
@@ -45,7 +45,7 @@ struct InstallOptions {
|
||||
// CHANGELOG.md at the top of a package are copied in when the mod sits one
|
||||
// folder deeper. A top-level mod takes the archive's name without a trailing
|
||||
// version ("Team-Map-1.0.0.zip" installs as Team-Map). With `replace`, an
|
||||
// installed mod of the same name goes to the Recycle Bin first. Throws a
|
||||
// installed mod of the same name is deleted for good once the new copy is in. Throws a
|
||||
// user-facing message; a failed or cancelled install leaves the Mods folder as it was.
|
||||
std::string install(const std::filesystem::path& mods_root, const std::filesystem::path& source, bool replace,
|
||||
const Progress& progress, const std::atomic<bool>& cancel, const InstallOptions& options = {});
|
||||
|
||||
Reference in new issue
Block a user