diff --git a/Sources/removemacai/SelfTest.swift b/Sources/removemacai/SelfTest.swift index 79e71e0..8d3d28e 100644 --- a/Sources/removemacai/SelfTest.swift +++ b/Sources/removemacai/SelfTest.swift @@ -321,6 +321,8 @@ func selfTest() -> Bool { } check(Storage.uniqueName("a.app", in: "/nonexistent-folder") == "a.app", "trash names stay as they are when free") + check(Storage.uniqueName("a.db", in: "/nonexistent-folder", avoiding: ["a.db"]) == "a 2.db", "a name already given to another item in the batch is numbered") + check(Storage.uniqueName("a.db", in: "/nonexistent-folder", avoiding: ["a.db", "a 2.db"]) == "a 3.db", "numbering skips names already given in the batch") let script = BackgroundItem(label: "com.example.sync", plist: "", program: "/bin/bash", arguments: ["/bin/bash", "/Users/x/bin/sync.sh"], system: false) let helper = BackgroundItem(label: "dev.orbstack.OrbStack.privhelper", plist: "", diff --git a/Sources/removemacai/Storage.swift b/Sources/removemacai/Storage.swift index f95171c..45985a8 100644 --- a/Sources/removemacai/Storage.swift +++ b/Sources/removemacai/Storage.swift @@ -210,6 +210,7 @@ enum Storage { static func clean(_ items: [StorageItem]) -> CleanResult { var result = CleanResult() var adminCommands: [String] = [] + var reservedNames = Set() let trash = home + "/.Trash" for item in items { switch item.kind { @@ -229,7 +230,11 @@ enum Storage { continue } let name = (path as NSString).lastPathComponent - let target = trash + "/" + uniqueName(name, in: trash) + // These moves run later, in one batch, so a name picked for one item + // isn't in the Trash yet. Reserve it so a same-named item gets another. + let unique = uniqueName(name, in: trash, avoiding: reservedNames) + reservedNames.insert(unique) + let target = trash + "/" + unique adminCommands.append("/bin/mv -f \(Shell.quote(path)) \(Shell.quote(target))") } } @@ -247,10 +252,10 @@ enum Storage { return result } - static func uniqueName(_ name: String, in folder: String) -> String { + static func uniqueName(_ name: String, in folder: String, avoiding taken: Set = []) -> String { var candidate = name var n = 2 - while FileManager.default.fileExists(atPath: folder + "/" + candidate) { + while FileManager.default.fileExists(atPath: folder + "/" + candidate) || taken.contains(candidate) { let ext = (name as NSString).pathExtension let base = (name as NSString).deletingPathExtension candidate = ext.isEmpty ? "\(base) \(n)" : "\(base) \(n).\(ext)"