From 5dc3a50ea009693290f543b4647a2be71ec5a68b Mon Sep 17 00:00:00 2001 From: Aly Raffauf Date: Thu, 18 Jun 2026 00:37:07 -0400 Subject: [PATCH] centralize dir resolution --- cmd/appherder/main.go | 7 ++++- internal/appherder/app.go | 30 ++++++++++++++++++--- internal/appherder/appimage.go | 14 +++------- internal/appherder/appimage_test.go | 15 ++++------- internal/appherder/desktop.go | 21 ++++----------- internal/appherder/desktop_test.go | 22 ++++++++-------- internal/appherder/helpers_test.go | 30 +++++++++++++++++++++ internal/appherder/icons.go | 12 +++------ internal/appherder/install_url_test.go | 6 ++--- internal/appherder/list.go | 13 ++-------- internal/appherder/list_test.go | 9 +++---- internal/appherder/sync.go | 19 +++++--------- internal/appherder/sync_test.go | 36 ++++++-------------------- internal/appherder/uninstall.go | 14 ++++------ internal/appherder/uninstall_test.go | 14 +++++----- internal/appherder/upgrade.go | 6 +---- 16 files changed, 123 insertions(+), 145 deletions(-) create mode 100644 internal/appherder/helpers_test.go diff --git a/cmd/appherder/main.go b/cmd/appherder/main.go index 42e9239..0ee02b0 100644 --- a/cmd/appherder/main.go +++ b/cmd/appherder/main.go @@ -10,7 +10,12 @@ import ( var version = "dev" func main() { - cmd := newRootCommand(appherder.NewApp(), os.Stdout, os.Stderr) + app, err := appherder.NewApp() + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + cmd := newRootCommand(app, os.Stdout, os.Stderr) cmd.SetArgs(os.Args[1:]) if err := cmd.Execute(); err != nil { fmt.Fprintln(os.Stderr, err) diff --git a/internal/appherder/app.go b/internal/appherder/app.go index 1afbfa0..3cfa54b 100644 --- a/internal/appherder/app.go +++ b/internal/appherder/app.go @@ -1,16 +1,38 @@ package appherder -import "os" +import ( + "fmt" + "os" + "path/filepath" +) // App is the core engine for managing AppImages. It holds no CLI or I/O // state; all output formatting is the caller's responsibility. type App struct { - homeDir func() (string, error) + appimagesDir string + applicationsDir string + iconsDir string } // NewApp returns an App wired to the current user's home directory. -func NewApp() App { +func NewApp() (App, error) { + home, err := os.UserHomeDir() + if err != nil { + return App{}, fmt.Errorf("resolve home directory: %w", err) + } + return NewAppWithDirs( + filepath.Join(home, "AppImages"), + filepath.Join(home, ".local", "share", "applications"), + filepath.Join(home, "AppImages", ".icons"), + ), nil +} + +// NewAppWithDirs returns an App that uses the given directories directly, +// for tests or non-standard layouts. +func NewAppWithDirs(appimagesDir, applicationsDir, iconsDir string) App { return App{ - homeDir: os.UserHomeDir, + appimagesDir: appimagesDir, + applicationsDir: applicationsDir, + iconsDir: iconsDir, } } diff --git a/internal/appherder/appimage.go b/internal/appherder/appimage.go index 039ef47..8e4bb20 100644 --- a/internal/appherder/appimage.go +++ b/internal/appherder/appimage.go @@ -97,18 +97,12 @@ func appImageSquashfsOffset(reader io.ReaderAt) (int64, error) { } func (a App) installAppImage(file string, appName string) (string, error) { - home, err := a.homeDir() - if err != nil { - return "", fmt.Errorf("resolve home directory: %w", err) - } - - appimagesDir := filepath.Join(home, "AppImages") - if err := os.MkdirAll(appimagesDir, 0o755); err != nil { - return "", fmt.Errorf("create AppImages directory %s: %w", appimagesDir, err) + if err := os.MkdirAll(a.appimagesDir, 0o755); err != nil { + return "", fmt.Errorf("create AppImages directory %s: %w", a.appimagesDir, err) } - dest := filepath.Join(appimagesDir, appName+".appimage") - inFolder := samePath(filepath.Dir(file), appimagesDir) + dest := filepath.Join(a.appimagesDir, appName+".appimage") + inFolder := samePath(filepath.Dir(file), a.appimagesDir) if !samePath(file, dest) { same, err := sameContent(file, dest) diff --git a/internal/appherder/appimage_test.go b/internal/appherder/appimage_test.go index ae5f4cd..def6e1b 100644 --- a/internal/appherder/appimage_test.go +++ b/internal/appherder/appimage_test.go @@ -58,12 +58,11 @@ func assertExecutableFile(t *testing.T, path, want string) { } func TestInstallAppImageCopiesFromElsewhere(t *testing.T) { - home := t.TempDir() + a, home := newTestApp(t) src := filepath.Join(t.TempDir(), "Foo-1.0-x86_64.AppImage") if err := os.WriteFile(src, []byte("payload"), 0o644); err != nil { t.Fatal(err) } - a := App{homeDir: func() (string, error) { return home, nil }} dest, err := a.installAppImage(src, "foo") if err != nil { @@ -79,7 +78,7 @@ func TestInstallAppImageCopiesFromElsewhere(t *testing.T) { } func TestInstallAppImageMovesVersionedFileInPlace(t *testing.T) { - home := t.TempDir() + a, home := newTestApp(t) appimages := filepath.Join(home, "AppImages") if err := os.MkdirAll(appimages, 0o755); err != nil { t.Fatal(err) @@ -88,7 +87,6 @@ func TestInstallAppImageMovesVersionedFileInPlace(t *testing.T) { if err := os.WriteFile(src, []byte("payload"), 0o644); err != nil { t.Fatal(err) } - a := App{homeDir: func() (string, error) { return home, nil }} dest, err := a.installAppImage(src, "foo") if err != nil { @@ -101,7 +99,7 @@ func TestInstallAppImageMovesVersionedFileInPlace(t *testing.T) { } func TestInstallAppImageSkipsCopyWhenIdentical(t *testing.T) { - home := t.TempDir() + a, home := newTestApp(t) appimages := filepath.Join(home, "AppImages") if err := os.MkdirAll(appimages, 0o755); err != nil { t.Fatal(err) @@ -119,7 +117,6 @@ func TestInstallAppImageSkipsCopyWhenIdentical(t *testing.T) { if err != nil { t.Fatal(err) } - a := App{homeDir: func() (string, error) { return home, nil }} if _, err := a.installAppImage(src, "foo"); err != nil { t.Fatal(err) } @@ -137,7 +134,7 @@ func TestInstallAppImageSkipsCopyWhenIdentical(t *testing.T) { } func TestInstallAppImageRemovesIdenticalDuplicateInFolder(t *testing.T) { - home := t.TempDir() + a, home := newTestApp(t) appimages := filepath.Join(home, "AppImages") if err := os.MkdirAll(appimages, 0o755); err != nil { t.Fatal(err) @@ -155,7 +152,6 @@ func TestInstallAppImageRemovesIdenticalDuplicateInFolder(t *testing.T) { if err != nil { t.Fatal(err) } - a := App{homeDir: func() (string, error) { return home, nil }} if _, err := a.installAppImage(dup, "foo"); err != nil { t.Fatal(err) } @@ -173,7 +169,7 @@ func TestInstallAppImageRemovesIdenticalDuplicateInFolder(t *testing.T) { } func TestInstallAppImageNoOpWhenAlreadyCanonical(t *testing.T) { - home := t.TempDir() + a, home := newTestApp(t) appimages := filepath.Join(home, "AppImages") if err := os.MkdirAll(appimages, 0o755); err != nil { t.Fatal(err) @@ -182,7 +178,6 @@ func TestInstallAppImageNoOpWhenAlreadyCanonical(t *testing.T) { if err := os.WriteFile(dest, []byte("payload"), 0o644); err != nil { t.Fatal(err) } - a := App{homeDir: func() (string, error) { return home, nil }} got, err := a.installAppImage(dest, "foo") if err != nil { diff --git a/internal/appherder/desktop.go b/internal/appherder/desktop.go index d8f4f3f..8c89f39 100644 --- a/internal/appherder/desktop.go +++ b/internal/appherder/desktop.go @@ -137,8 +137,8 @@ func isManagedDesktop(path string) (bool, error) { // managedApps returns the ids of desktop entries appherder installed, found by // scanning the user applications directory for the ownership marker. -func managedApps(home string) ([]string, error) { - matches, err := filepath.Glob(filepath.Join(home, ".local", "share", "applications", "*.desktop")) +func managedApps(applicationsDir string) ([]string, error) { + matches, err := filepath.Glob(filepath.Join(applicationsDir, "*.desktop")) if err != nil { return nil, err } @@ -240,17 +240,11 @@ func (d *desktopFile) write(path string) error { } func (a App) patchDesktopFile(desktop *desktopFile, appName string, hasIcon bool) error { - home, err := a.homeDir() - if err != nil { - return fmt.Errorf("resolve home directory: %w", err) - } - - appimages := filepath.Join(home, "AppImages") - appimage := filepath.Join(appimages, appName+".appimage") + appimage := filepath.Join(a.appimagesDir, appName+".appimage") desktop.set(desktopOwnerKey, "true", desktopEntrySection) if hasIcon { - desktop.set("Icon", filepath.Join(appimages, ".icons", appName), desktopEntrySection) + desktop.set("Icon", filepath.Join(a.iconsDir, appName), desktopEntrySection) } desktop.set("TryExec", appimage, desktopEntrySection) @@ -273,12 +267,7 @@ func (a App) patchDesktopFile(desktop *desktopFile, appName string, hasIcon bool } func (a App) installDesktopFile(desktop *desktopFile, appName string) (string, error) { - home, err := a.homeDir() - if err != nil { - return "", fmt.Errorf("resolve home directory: %w", err) - } - - dest := filepath.Join(home, ".local", "share", "applications", appName+".desktop") + dest := filepath.Join(a.applicationsDir, appName+".desktop") if err := os.MkdirAll(filepath.Dir(dest), 0o755); err != nil { return "", fmt.Errorf("create desktop file directory %s: %w", filepath.Dir(dest), err) } diff --git a/internal/appherder/desktop_test.go b/internal/appherder/desktop_test.go index 05c2cf7..7e083a7 100644 --- a/internal/appherder/desktop_test.go +++ b/internal/appherder/desktop_test.go @@ -49,15 +49,14 @@ func TestDesktopFileRoundTripMatchesInput(t *testing.T) { } func TestPatchDesktopFilePreservesDesktopActions(t *testing.T) { + a, home := newTestApp(t) + dir := t.TempDir() source := filepath.Join(dir, "source.desktop") - output := filepath.Join(dir, "output.desktop") if err := os.WriteFile(source, []byte(sampleDesktopFile), 0o644); err != nil { t.Fatal(err) } - a := App{homeDir: func() (string, error) { return "/home/test", nil }} - desktop, err := readDesktopFile(source) if err != nil { t.Fatal(err) @@ -65,6 +64,7 @@ func TestPatchDesktopFilePreservesDesktopActions(t *testing.T) { if err := a.patchDesktopFile(desktop, "example", true); err != nil { t.Fatal(err) } + output := filepath.Join(dir, "output.desktop") if err := desktop.write(output); err != nil { t.Fatal(err) } @@ -75,11 +75,11 @@ func TestPatchDesktopFilePreservesDesktopActions(t *testing.T) { } assertDesktopValue(t, patched, desktopEntrySection, desktopOwnerKey, "true") - assertDesktopValue(t, patched, desktopEntrySection, "Icon", "/home/test/AppImages/.icons/example") - assertDesktopValue(t, patched, desktopEntrySection, "TryExec", "/home/test/AppImages/example.appimage") - assertDesktopExec(t, patched, desktopEntrySection, []string{"env", "FOO=bar", "DESKTOPINTEGRATION=1", "/home/test/AppImages/example.appimage", "%U"}) - assertDesktopExec(t, patched, "Desktop Action new-window", []string{"env", "DESKTOPINTEGRATION=1", "/home/test/AppImages/example.appimage", "--new-window", "%U"}) - assertDesktopExec(t, patched, "Desktop Action new-private-window", []string{"env", "DESKTOPINTEGRATION=1", "/home/test/AppImages/example.appimage", "--private-window", "%U"}) + assertDesktopValue(t, patched, desktopEntrySection, "Icon", filepath.Join(home, "AppImages", ".icons", "example")) + assertDesktopValue(t, patched, desktopEntrySection, "TryExec", filepath.Join(home, "AppImages", "example.appimage")) + assertDesktopExec(t, patched, desktopEntrySection, []string{"env", "FOO=bar", "DESKTOPINTEGRATION=1", filepath.Join(home, "AppImages", "example.appimage"), "%U"}) + assertDesktopExec(t, patched, "Desktop Action new-window", []string{"env", "DESKTOPINTEGRATION=1", filepath.Join(home, "AppImages", "example.appimage"), "--new-window", "%U"}) + assertDesktopExec(t, patched, "Desktop Action new-private-window", []string{"env", "DESKTOPINTEGRATION=1", filepath.Join(home, "AppImages", "example.appimage"), "--private-window", "%U"}) assertDesktopValue(t, patched, "Desktop Action new-private-window", "Name", "New Private Window") } @@ -138,7 +138,7 @@ func TestDeriveAppName(t *testing.T) { } func TestPatchDesktopFileSetsExecWhenMissing(t *testing.T) { - a := App{homeDir: func() (string, error) { return "/home/test", nil }} + a, home := newTestApp(t) desktop := parseDesktopFile([]byte( "[Desktop Entry]\nType=Application\nName=Foo\nTerminal=true\n", )) @@ -146,8 +146,8 @@ func TestPatchDesktopFileSetsExecWhenMissing(t *testing.T) { t.Fatal(err) } assertDesktopValue(t, desktop, desktopEntrySection, "Terminal", "true") - assertDesktopValue(t, desktop, desktopEntrySection, "TryExec", "/home/test/AppImages/foo.appimage") - assertDesktopValue(t, desktop, desktopEntrySection, "Exec", "/home/test/AppImages/foo.appimage") + assertDesktopValue(t, desktop, desktopEntrySection, "TryExec", filepath.Join(home, "AppImages", "foo.appimage")) + assertDesktopValue(t, desktop, desktopEntrySection, "Exec", filepath.Join(home, "AppImages", "foo.appimage")) assertDesktopValue(t, desktop, desktopEntrySection, desktopOwnerKey, "true") } diff --git a/internal/appherder/helpers_test.go b/internal/appherder/helpers_test.go new file mode 100644 index 0000000..17831c3 --- /dev/null +++ b/internal/appherder/helpers_test.go @@ -0,0 +1,30 @@ +package appherder + +import ( + "os" + "path/filepath" + "testing" +) + +// newTestApp returns an App whose directories live under a temp dir. +func newTestApp(t *testing.T) (App, string) { + t.Helper() + home := t.TempDir() + return NewAppWithDirs( + filepath.Join(home, "AppImages"), + filepath.Join(home, ".local", "share", "applications"), + filepath.Join(home, "AppImages", ".icons"), + ), home +} + +// writeManagedDesktop stamps a launcher with appherder's ownership marker. +func writeManagedDesktop(t *testing.T, home, appid string) { + t.Helper() + dir := filepath.Join(home, ".local", "share", "applications") + if err := os.MkdirAll(dir, 0o755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(dir, appid+".desktop"), []byte(managedDesktop), 0o644); err != nil { + t.Fatal(err) + } +} diff --git a/internal/appherder/icons.go b/internal/appherder/icons.go index 75942fd..b5fca7a 100644 --- a/internal/appherder/icons.go +++ b/internal/appherder/icons.go @@ -86,17 +86,11 @@ func iconRank(name string) int { } func (a App) installIcon(fsys fs.FS, icon string, appName string) (string, error) { - home, err := a.homeDir() - if err != nil { - return "", fmt.Errorf("resolve home directory: %w", err) - } - - iconsDir := filepath.Join(home, "AppImages", ".icons") - if err := os.MkdirAll(iconsDir, 0o755); err != nil { - return "", fmt.Errorf("create icon directory %s: %w", iconsDir, err) + if err := os.MkdirAll(a.iconsDir, 0o755); err != nil { + return "", fmt.Errorf("create icon directory %s: %w", a.iconsDir, err) } - dest := filepath.Join(iconsDir, appName) + dest := filepath.Join(a.iconsDir, appName) if err := copyFromFS(fsys, icon, dest); err != nil { return "", fmt.Errorf("install icon %s to %s: %w", icon, dest, err) } diff --git a/internal/appherder/install_url_test.go b/internal/appherder/install_url_test.go index 313a538..e7225d2 100644 --- a/internal/appherder/install_url_test.go +++ b/internal/appherder/install_url_test.go @@ -15,8 +15,7 @@ func TestInstallFromURLDownloadsAndCleansUp(t *testing.T) { })) defer srv.Close() - home := t.TempDir() - a := App{homeDir: func() (string, error) { return home, nil }} + a, _ := newTestApp(t) _, err := a.InstallFromURL(context.Background(), srv.URL+"/Foo.AppImage") if err == nil { t.Fatal("expected error for non-AppImage content") @@ -34,8 +33,7 @@ func TestInstallFromURLHandlesDownloadFailure(t *testing.T) { })) defer srv.Close() - home := t.TempDir() - a := App{homeDir: func() (string, error) { return home, nil }} + a, _ := newTestApp(t) if _, err := a.InstallFromURL(context.Background(), srv.URL+"/Foo.AppImage"); err == nil { t.Fatal("expected error for 404 download") } diff --git a/internal/appherder/list.go b/internal/appherder/list.go index 4073070..d8f2cc1 100644 --- a/internal/appherder/list.go +++ b/internal/appherder/list.go @@ -1,7 +1,6 @@ package appherder import ( - "fmt" "os" "path/filepath" "sort" @@ -20,22 +19,14 @@ type AppInfo struct { // List returns display metadata for every app appherder manages. Offline and // instant; use CheckUpgrades to see what's stale. func (a App) List() ([]AppInfo, error) { - home, err := a.homeDir() - if err != nil { - return nil, fmt.Errorf("resolve home directory: %w", err) - } - - appids, err := managedApps(home) + appids, err := managedApps(a.applicationsDir) if err != nil { return nil, err } - appimagesDir := filepath.Join(home, "AppImages") - appsDir := filepath.Join(home, ".local", "share", "applications") - infos := make([]AppInfo, 0, len(appids)) for _, appid := range appids { - infos = append(infos, gatherAppInfo(appsDir, appimagesDir, appid)) + infos = append(infos, gatherAppInfo(a.applicationsDir, a.appimagesDir, appid)) } sort.Slice(infos, func(i, j int) bool { return infos[i].Name < infos[j].Name }) return infos, nil diff --git a/internal/appherder/list_test.go b/internal/appherder/list_test.go index 7e96c78..1e4b988 100644 --- a/internal/appherder/list_test.go +++ b/internal/appherder/list_test.go @@ -7,7 +7,7 @@ import ( ) func TestListShowsInstalledAndOrphaned(t *testing.T) { - home := t.TempDir() + a, home := newTestApp(t) appimages := filepath.Join(home, "AppImages") appsDir := filepath.Join(home, ".local", "share", "applications") if err := os.MkdirAll(appsDir, 0o755); err != nil { @@ -38,7 +38,6 @@ func TestListShowsInstalledAndOrphaned(t *testing.T) { t.Fatal(err) } - a := App{homeDir: func() (string, error) { return home, nil }} infos, err := a.List() if err != nil { t.Fatal(err) @@ -72,8 +71,7 @@ func TestListShowsInstalledAndOrphaned(t *testing.T) { } func TestListEmptyWhenNothingManaged(t *testing.T) { - home := t.TempDir() - a := App{homeDir: func() (string, error) { return home, nil }} + a, _ := newTestApp(t) infos, err := a.List() if err != nil { t.Fatal(err) @@ -84,7 +82,7 @@ func TestListEmptyWhenNothingManaged(t *testing.T) { } func TestListFallsBackToFilenameForName(t *testing.T) { - home := t.TempDir() + a, home := newTestApp(t) appimages := filepath.Join(home, "AppImages") appsDir := filepath.Join(home, ".local", "share", "applications") if err := os.MkdirAll(appimages, 0o755); err != nil { @@ -104,7 +102,6 @@ func TestListFallsBackToFilenameForName(t *testing.T) { t.Fatal(err) } - a := App{homeDir: func() (string, error) { return home, nil }} infos, err := a.List() if err != nil { t.Fatal(err) diff --git a/internal/appherder/sync.go b/internal/appherder/sync.go index c5bc955..e2262ba 100644 --- a/internal/appherder/sync.go +++ b/internal/appherder/sync.go @@ -40,13 +40,7 @@ type SyncResult struct { // (entries left by another tool). Per-file errors are included in the result // rather than aborting the pass. func (a App) Sync(ctx context.Context, force bool) (SyncResult, error) { - home, err := a.homeDir() - if err != nil { - return SyncResult{}, fmt.Errorf("resolve home directory: %w", err) - } - appimagesDir := filepath.Join(home, "AppImages") - - existing, err := managedApps(home) + existing, err := managedApps(a.applicationsDir) if err != nil { return SyncResult{}, err } @@ -55,7 +49,7 @@ func (a App) Sync(ctx context.Context, force bool) (SyncResult, error) { managed[appid] = true } - files, err := listAppImages(appimagesDir) + files, err := listAppImages(a.appimagesDir) if err != nil { return SyncResult{}, err } @@ -76,14 +70,14 @@ func (a App) Sync(ctx context.Context, force bool) (SyncResult, error) { candidates := existing if force { - extra, err := appImageBackedOrphans(home, appimagesDir) + extra, err := appImageBackedOrphans(a.applicationsDir, a.appimagesDir) if err != nil { return result, err } candidates = append(candidates, extra...) } for _, appid := range candidates { - present, err := appImagePresent(appimagesDir, appid) + present, err := appImagePresent(a.appimagesDir, appid) if err != nil { return result, err } @@ -157,9 +151,8 @@ func findAppImagePath(dir, appid string) (string, error) { // appImageBackedOrphans returns appids of unmanaged desktop entries whose // TryExec or Exec points at a missing file inside appimagesDir — launchers left // by another tool after their AppImage was deleted. -func appImageBackedOrphans(home, appimagesDir string) ([]string, error) { - appsDir := filepath.Join(home, ".local", "share", "applications") - matches, err := filepath.Glob(filepath.Join(appsDir, "*.desktop")) +func appImageBackedOrphans(applicationsDir, appimagesDir string) ([]string, error) { + matches, err := filepath.Glob(filepath.Join(applicationsDir, "*.desktop")) if err != nil { return nil, err } diff --git a/internal/appherder/sync_test.go b/internal/appherder/sync_test.go index 12f6e6c..35e6bbb 100644 --- a/internal/appherder/sync_test.go +++ b/internal/appherder/sync_test.go @@ -8,26 +8,13 @@ import ( "testing" ) -// writeManagedDesktop stamps a launcher with appherder's ownership marker. -func writeManagedDesktop(t *testing.T, home, appid string) { - t.Helper() - dir := filepath.Join(home, ".local", "share", "applications") - if err := os.MkdirAll(dir, 0o755); err != nil { - t.Fatal(err) - } - if err := os.WriteFile(filepath.Join(dir, appid+".desktop"), []byte(managedDesktop), 0o644); err != nil { - t.Fatal(err) - } -} - func TestSyncRemovesOrphanedManagedLauncher(t *testing.T) { - home := t.TempDir() + a, home := newTestApp(t) if err := os.MkdirAll(filepath.Join(home, "AppImages"), 0o755); err != nil { t.Fatal(err) } writeManagedDesktop(t, home, "gone") - a := App{homeDir: func() (string, error) { return home, nil }} result, err := a.Sync(context.Background(), false) if err != nil { t.Fatal(err) @@ -42,7 +29,7 @@ func TestSyncRemovesOrphanedManagedLauncher(t *testing.T) { } func TestSyncKeepsManagedLauncherWhenAppImagePresent(t *testing.T) { - home := t.TempDir() + a, home := newTestApp(t) appimages := filepath.Join(home, "AppImages") if err := os.MkdirAll(appimages, 0o755); err != nil { t.Fatal(err) @@ -54,7 +41,6 @@ func TestSyncKeepsManagedLauncherWhenAppImagePresent(t *testing.T) { } writeManagedDesktop(t, home, "present") - a := App{homeDir: func() (string, error) { return home, nil }} result, err := a.Sync(context.Background(), false) if err != nil { t.Fatal(err) @@ -69,7 +55,7 @@ func TestSyncKeepsManagedLauncherWhenAppImagePresent(t *testing.T) { } func TestSyncKeepsUnmanagedLauncherEvenWhenAppImageAbsent(t *testing.T) { - home := t.TempDir() + a, home := newTestApp(t) if err := os.MkdirAll(filepath.Join(home, "AppImages"), 0o755); err != nil { t.Fatal(err) } @@ -83,7 +69,6 @@ func TestSyncKeepsUnmanagedLauncherEvenWhenAppImageAbsent(t *testing.T) { t.Fatal(err) } - a := App{homeDir: func() (string, error) { return home, nil }} for _, force := range []bool{false, true} { if _, err := a.Sync(context.Background(), force); err != nil { t.Fatal(err) @@ -95,7 +80,7 @@ func TestSyncKeepsUnmanagedLauncherEvenWhenAppImageAbsent(t *testing.T) { } func TestSyncForceRemovesAppImageBackedOrphan(t *testing.T) { - home := t.TempDir() + a, home := newTestApp(t) appimages := filepath.Join(home, "AppImages") if err := os.MkdirAll(appimages, 0o755); err != nil { t.Fatal(err) @@ -111,7 +96,6 @@ func TestSyncForceRemovesAppImageBackedOrphan(t *testing.T) { t.Fatal(err) } - a := App{homeDir: func() (string, error) { return home, nil }} result, err := a.Sync(context.Background(), true) if err != nil { t.Fatal(err) @@ -125,7 +109,7 @@ func TestSyncForceRemovesAppImageBackedOrphan(t *testing.T) { } func TestSyncForceKeepsOrphanWhenAppImageStillPresent(t *testing.T) { - home := t.TempDir() + a, home := newTestApp(t) appimages := filepath.Join(home, "AppImages") if err := os.MkdirAll(appimages, 0o755); err != nil { t.Fatal(err) @@ -144,7 +128,6 @@ func TestSyncForceKeepsOrphanWhenAppImageStillPresent(t *testing.T) { t.Fatal(err) } - a := App{homeDir: func() (string, error) { return home, nil }} if _, err := a.Sync(context.Background(), true); err != nil { t.Fatal(err) } @@ -154,7 +137,7 @@ func TestSyncForceKeepsOrphanWhenAppImageStillPresent(t *testing.T) { } func TestSyncIgnoresHiddenAndTempFiles(t *testing.T) { - home := t.TempDir() + a, home := newTestApp(t) appimages := filepath.Join(home, "AppImages") if err := os.MkdirAll(appimages, 0o755); err != nil { t.Fatal(err) @@ -166,7 +149,6 @@ func TestSyncIgnoresHiddenAndTempFiles(t *testing.T) { } } - a := App{homeDir: func() (string, error) { return home, nil }} result, err := a.Sync(context.Background(), false) if err != nil { t.Fatal(err) @@ -177,10 +159,9 @@ func TestSyncIgnoresHiddenAndTempFiles(t *testing.T) { } func TestSyncHandlesMissingAppImagesDir(t *testing.T) { - home := t.TempDir() + a, home := newTestApp(t) writeManagedDesktop(t, home, "orphan") - a := App{homeDir: func() (string, error) { return home, nil }} if _, err := a.Sync(context.Background(), false); err != nil { t.Fatal(err) } @@ -190,7 +171,7 @@ func TestSyncHandlesMissingAppImagesDir(t *testing.T) { } func TestSyncReportsSkipsInInputOrder(t *testing.T) { - home := t.TempDir() + a, home := newTestApp(t) appimages := filepath.Join(home, "AppImages") if err := os.MkdirAll(appimages, 0o755); err != nil { t.Fatal(err) @@ -202,7 +183,6 @@ func TestSyncReportsSkipsInInputOrder(t *testing.T) { } } - a := App{homeDir: func() (string, error) { return home, nil }} result, err := a.Sync(context.Background(), false) if err != nil { t.Fatal(err) diff --git a/internal/appherder/uninstall.go b/internal/appherder/uninstall.go index 7be6428..d91224b 100644 --- a/internal/appherder/uninstall.go +++ b/internal/appherder/uninstall.go @@ -10,12 +10,8 @@ import ( func (a App) Uninstall(name string, force bool) error { appName := NormalizeAppName(name) - home, err := a.homeDir() - if err != nil { - return fmt.Errorf("resolve home directory: %w", err) - } - for _, path := range installedPaths(home, appName) { + for _, path := range a.installedPaths(appName) { if strings.HasSuffix(path, ".desktop") && !force { managed, err := isManagedDesktop(path) if err != nil { @@ -44,10 +40,10 @@ func NormalizeAppName(name string) string { return name } -func installedPaths(home string, appName string) []string { +func (a App) installedPaths(appName string) []string { return []string{ - filepath.Join(home, "AppImages", appName+".appimage"), - filepath.Join(home, "AppImages", ".icons", appName), - filepath.Join(home, ".local", "share", "applications", appName+".desktop"), + filepath.Join(a.appimagesDir, appName+".appimage"), + filepath.Join(a.iconsDir, appName), + filepath.Join(a.applicationsDir, appName+".desktop"), } } diff --git a/internal/appherder/uninstall_test.go b/internal/appherder/uninstall_test.go index 59a3b90..fda189f 100644 --- a/internal/appherder/uninstall_test.go +++ b/internal/appherder/uninstall_test.go @@ -29,8 +29,8 @@ func TestNormalizeAppNameAcceptsNamesAndAppImagePaths(t *testing.T) { } func TestUninstallRemovesInstalledFilesOnly(t *testing.T) { - home := t.TempDir() - for _, path := range installedPaths(home, "example") { + a, home := newTestApp(t) + for _, path := range a.installedPaths("example") { if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { t.Fatal(err) } @@ -43,12 +43,11 @@ func TestUninstallRemovesInstalledFilesOnly(t *testing.T) { } } - a := App{homeDir: func() (string, error) { return home, nil }} if err := a.Uninstall(filepath.Join(home, "AppImages", "example.AppImage"), false); err != nil { t.Fatal(err) } - for _, path := range installedPaths(home, "example") { + for _, path := range a.installedPaths("example") { if _, err := os.Stat(path); !errors.Is(err, os.ErrNotExist) { t.Fatalf("expected %s to be removed, stat err: %v", path, err) } @@ -56,7 +55,7 @@ func TestUninstallRemovesInstalledFilesOnly(t *testing.T) { } func TestUninstallKeepsUnmanagedDesktopFile(t *testing.T) { - home := t.TempDir() + a, home := newTestApp(t) desktop := filepath.Join(home, ".local", "share", "applications", "example.desktop") if err := os.MkdirAll(filepath.Dir(desktop), 0o755); err != nil { t.Fatal(err) @@ -66,7 +65,6 @@ func TestUninstallKeepsUnmanagedDesktopFile(t *testing.T) { t.Fatal(err) } - a := App{homeDir: func() (string, error) { return home, nil }} if err := a.Uninstall("example", false); err != nil { t.Fatal(err) } @@ -84,7 +82,7 @@ func TestUninstallKeepsUnmanagedDesktopFile(t *testing.T) { } func TestManagedApps(t *testing.T) { - home := t.TempDir() + _, home := newTestApp(t) dir := filepath.Join(home, ".local", "share", "applications") if err := os.MkdirAll(dir, 0o755); err != nil { t.Fatal(err) @@ -96,7 +94,7 @@ func TestManagedApps(t *testing.T) { t.Fatal(err) } - got, err := managedApps(home) + got, err := managedApps(dir) if err != nil { t.Fatal(err) } diff --git a/internal/appherder/upgrade.go b/internal/appherder/upgrade.go index ca86e19..05a32df 100644 --- a/internal/appherder/upgrade.go +++ b/internal/appherder/upgrade.go @@ -36,11 +36,7 @@ type UpgradeApplied struct { // already current are included with NoSource/Available=false so the caller // can decide what to show. func (a App) CheckUpgrades(ctx context.Context) ([]UpgradeCheck, error) { - home, err := a.homeDir() - if err != nil { - return nil, fmt.Errorf("resolve home directory: %w", err) - } - files, err := listAppImages(filepath.Join(home, "AppImages")) + files, err := listAppImages(a.appimagesDir) if err != nil { return nil, err } -- 2.51.2