From 5355a0170a942ab30f4285fa5365016f9d7448a0 Mon Sep 17 00:00:00 2001 From: bbassie <17990055+bbassie@users.noreply.github.com> Date: Thu, 22 Oct 2026 11:19:16 +0000 Subject: [PATCH] Use the exclusive move for save rename and widen its copy fallback Fall back to copying on any link error except EEXIST, since filesystems without hard links report it with several codes (EISDIR on exFAT under Windows, ENOSYS on some FUSE mounts). Rename now goes through the same move so it also gets the fallback and the rollback. Only report a missing source save on transfer when the source is actually missing. Co-Authored-By: Claude Opus 5.5 --- packages/host/src/Host.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/host/src/Host.ts b/packages/host/src/Host.ts index 06261c43..ca3a872e 100644 --- a/packages/host/src/Host.ts +++ b/packages/host/src/Host.ts @@ -42,8 +42,9 @@ async function moveSaveExclusive(sourcePath: string, targetPath: string) { try { await fs.link(sourcePath, targetPath); } catch (err: any) { - // Hard links are not possible across filesystems and on some filesystems. - if (!["EXDEV", "EPERM", "ENOTSUP"].includes(err.code)) { + // Hard links fail across filesystems and on some filesystems with + // varying codes, the copy is still exclusive. + if (err.code === "EEXIST") { throw err; } await copySaveExclusive(sourcePath, targetPath); @@ -1054,13 +1055,10 @@ export default class Host extends lib.Link { checkRequestSaveName(newName); let instance = this.getRequestInstance(instanceId); try { - // Link save in the new location. - await fs.link( + await moveSaveExclusive( path.join(instance.path, "saves", oldName), path.join(instance.path, "saves", newName), ); - // Unlink save in old location. - await fs.unlink(path.join(instance.path, "saves", oldName)); } catch (err: any) { if (err.code === "EEXIST") { throw new lib.RequestError(`${newName} already exists`); @@ -1125,14 +1123,16 @@ export default class Host extends lib.Link { // For consistency with remote transfer initiated through pullSave the // target is renamed if it already exists. + const sourcePath = path.join(sourceInstance.path, "saves", sourceName); try { targetName = await this._moveOrCopySave( - path.join(sourceInstance.path, "saves", sourceName), + sourcePath, path.join(targetInstance.path, "saves", targetName), copy, ); } catch (err: any) { - if (err.code === "ENOENT") { + // ENOENT may also come from a missing target saves folder. + if (err.code === "ENOENT" && !await fs.access(sourcePath).then(() => true, () => false)) { throw new lib.RequestError(`${sourceName} does not exist`); } throw err;