From 84ff146acf31ee2e36655b0c36ec8f3714688800 Mon Sep 17 00:00:00 2001 From: Luspuz Date: Sat, 10 Oct 2026 22:00:58 +0200 Subject: [PATCH] Dedupe installs resolving to the same directory (os.SameFile) Steam finder emits both steamapps and SteamApps casings. On case-insensitive filesystems (default macOS APFS) both paths resolve to the same folder, but OsPathEqual only folded case on Windows, so the same install showed up twice. Compare file identity first, which is correct on every platform. --- backend/installfinders/common/helpers.go | 10 ++++++ backend/installfinders/common/helpers_test.go | 31 +++++++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 backend/installfinders/common/helpers_test.go diff --git a/backend/installfinders/common/helpers.go b/backend/installfinders/common/helpers.go index 021a21b2..6d8464b1 100644 --- a/backend/installfinders/common/helpers.go +++ b/backend/installfinders/common/helpers.go @@ -2,12 +2,22 @@ package common import ( "log/slog" + "os" "path/filepath" "runtime" "strings" ) func OsPathEqual(path1, path2 string) bool { + // Same file on disk (inode) means same install, regardless of path + // spelling. This catches duplicates from case-only differences on + // case-insensitive filesystems (e.g. macOS default APFS), where string + // comparison alone sees two different installs. + if info1, err1 := os.Stat(path1); err1 == nil { + if info2, err2 := os.Stat(path2); err2 == nil && os.SameFile(info1, info2) { + return true + } + } path1 = realPath(path1) path2 = realPath(path2) if runtime.GOOS == "windows" { diff --git a/backend/installfinders/common/helpers_test.go b/backend/installfinders/common/helpers_test.go new file mode 100644 index 00000000..69771027 --- /dev/null +++ b/backend/installfinders/common/helpers_test.go @@ -0,0 +1,31 @@ +package common + +import ( + "os" + "path/filepath" + "testing" +) + +func TestOsPathEqualSameFile(t *testing.T) { + dir := t.TempDir() + + // Same path is trivially equal. + if !OsPathEqual(dir, dir) { + t.Error("identical paths should be equal") + } + + // Same directory reached through a symlink must be equal. + link := filepath.Join(t.TempDir(), "link") + if err := os.Symlink(dir, link); err != nil { + t.Skipf("cannot create symlink: %v", err) + } + if !OsPathEqual(dir, link) { + t.Error("symlinked paths to the same directory should be equal") + } + + // Different directories must not be equal. + other := t.TempDir() + if OsPathEqual(dir, other) { + t.Error("different directories should not be equal") + } +}