diff --git a/go.mod b/go.mod index f332873..346263c 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.25.0 require ( github.com/CalebQ42/squashfs v1.4.1 github.com/ProtonMail/go-crypto v1.4.1 + github.com/adrg/xdg v0.5.3 github.com/alyraffauf/goxdgdesktop v0.1.0 github.com/spf13/cobra v1.10.2 ) diff --git a/go.sum b/go.sum index 1b13c3c..35fb776 100644 --- a/go.sum +++ b/go.sum @@ -2,11 +2,15 @@ github.com/CalebQ42/squashfs v1.4.1 h1:tBcFMQSRQvWcY50e9r9cv2uVzNf06fcUhly0LeZg8 github.com/CalebQ42/squashfs v1.4.1/go.mod h1:/As5wg6ScFFaab9SaNFNHyCOsd73Q5IFPOFJCVnwWzQ= github.com/ProtonMail/go-crypto v1.4.1 h1:9RfcZHqEQUvP8RzecWEUafnZVtEvrBVL9BiF67IQOfM= github.com/ProtonMail/go-crypto v1.4.1/go.mod h1:e1OaTyu5SYVrO9gKOEhTc+5UcXtTUa+P3uLudwcgPqo= +github.com/adrg/xdg v0.5.3 h1:xRnxJXne7+oWDatRhR1JLnvuccuIeCoBu2rtuLqQB78= +github.com/adrg/xdg v0.5.3/go.mod h1:nlTsY+NNiCBGCK2tpm09vRqfVzrc2fLmXGpBLF0zlTQ= github.com/alyraffauf/goxdgdesktop v0.1.0 h1:n5+3AjF2ntvON6W0nN4O5bHDmcjYuFtKVCwBr6ekqj0= github.com/alyraffauf/goxdgdesktop v0.1.0/go.mod h1:K91gqx5usBl0hjhNUxHcBSRaOaU8gaggPxfKpCnTXzQ= github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8= github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= @@ -17,6 +21,8 @@ github.com/mikelolasagasti/xz v1.0.1 h1:Q2F2jX0RYJUG3+WsM+FJknv+6eVjsjXNDV0KJXZz github.com/mikelolasagasti/xz v1.0.1/go.mod h1:muAirjiOUxPRXwm9HdDtB3uoRPrGnL85XHtokL9Hcgc= github.com/pierrec/lz4/v4 v4.1.27 h1:+PhzhWDrjRj89TH2sw43nE3+4+W8lSxIuQadEHZyjUk= github.com/pierrec/lz4/v4 v4.1.27/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rasky/go-lzo v0.0.0-20200203143853-96a758eda86e h1:dCWirM5F3wMY+cmRda/B1BiPsFtmzXqV9b0hLWtVBMs= github.com/rasky/go-lzo v0.0.0-20200203143853-96a758eda86e/go.mod h1:9leZcVcItj6m9/CfHY5Em/iBrCz7js8LcRQGTKEEv2M= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= @@ -25,6 +31,8 @@ github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiT github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/ulikunitz/xz v0.5.15 h1:9DNdB5s+SgV3bQ2ApL10xRc35ck0DuIX/isZvIk+ubY= github.com/ulikunitz/xz v0.5.15/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= @@ -33,3 +41,5 @@ golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsi golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw= golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/appherder/app.go b/internal/appherder/app.go index 3cfa54b..2b8a66c 100644 --- a/internal/appherder/app.go +++ b/internal/appherder/app.go @@ -4,6 +4,8 @@ import ( "fmt" "os" "path/filepath" + + "github.com/adrg/xdg" ) // App is the core engine for managing AppImages. It holds no CLI or I/O @@ -12,9 +14,12 @@ type App struct { appimagesDir string applicationsDir string iconsDir string + binDir string } -// NewApp returns an App wired to the current user's home directory. +// NewApp returns an App wired to the current user's home directory. The +// applications directory honors XDG_DATA_HOME; the user bin directory +// (~/.local/bin) is not covered by the XDG spec and is derived from home. func NewApp() (App, error) { home, err := os.UserHomeDir() if err != nil { @@ -22,17 +27,19 @@ func NewApp() (App, error) { } return NewAppWithDirs( filepath.Join(home, "AppImages"), - filepath.Join(home, ".local", "share", "applications"), + filepath.Join(xdg.DataHome, "applications"), filepath.Join(home, "AppImages", ".icons"), + filepath.Join(home, ".local", "bin"), ), nil } // NewAppWithDirs returns an App that uses the given directories directly, // for tests or non-standard layouts. -func NewAppWithDirs(appimagesDir, applicationsDir, iconsDir string) App { +func NewAppWithDirs(appimagesDir, applicationsDir, iconsDir, binDir string) App { return App{ appimagesDir: appimagesDir, applicationsDir: applicationsDir, iconsDir: iconsDir, + binDir: binDir, } } diff --git a/internal/appherder/helpers_test.go b/internal/appherder/helpers_test.go index 17831c3..63fa1fa 100644 --- a/internal/appherder/helpers_test.go +++ b/internal/appherder/helpers_test.go @@ -14,6 +14,7 @@ func newTestApp(t *testing.T) (App, string) { filepath.Join(home, "AppImages"), filepath.Join(home, ".local", "share", "applications"), filepath.Join(home, "AppImages", ".icons"), + filepath.Join(home, ".local", "bin"), ), home } diff --git a/internal/appherder/http.go b/internal/appherder/http.go index bc3670a..3282920 100644 --- a/internal/appherder/http.go +++ b/internal/appherder/http.go @@ -50,9 +50,9 @@ func newIdleTimeoutReader(reader io.Reader, timeout time.Duration, cancel contex func (t *idleTimeoutReader) Read(buf []byte) (int, error) { t.timer.Reset(t.timeout) - n, err := t.reader.Read(buf) + bytesRead, err := t.reader.Read(buf) t.timer.Stop() - return n, err + return bytesRead, err } // httpGetOK sends a GET request and returns the response when the server diff --git a/internal/appherder/link.go b/internal/appherder/link.go index 04c34b5..03d0195 100644 --- a/internal/appherder/link.go +++ b/internal/appherder/link.go @@ -6,12 +6,8 @@ import ( "path/filepath" ) -func (a App) localBinDir() string { - return filepath.Join(filepath.Dir(filepath.Dir(a.applicationsDir)), "bin") -} - func (a App) linkPath(appName string) string { - return filepath.Join(a.localBinDir(), appName) + return filepath.Join(a.binDir, appName) } func (a App) appImagePath(appName string) string { @@ -24,9 +20,8 @@ func (a App) Link(appName string) error { if _, err := os.Stat(src); err != nil { return fmt.Errorf("%s is not installed", appName) } - binDir := a.localBinDir() - if err := os.MkdirAll(binDir, 0o755); err != nil { - return fmt.Errorf("create %s: %w", binDir, err) + if err := os.MkdirAll(a.binDir, 0o755); err != nil { + return fmt.Errorf("create %s: %w", a.binDir, err) } dst := a.linkPath(appName) if err := os.Remove(dst); err != nil && !os.IsNotExist(err) { diff --git a/package.nix b/package.nix index 3036270..7410370 100644 --- a/package.nix +++ b/package.nix @@ -10,7 +10,7 @@ in pname = "appherder"; inherit version; src = ./.; - vendorHash = "sha256-DW+OYl2Lr7j4ZGOD/Cml2/2yuauX4EudLRaYH15YtAA="; + vendorHash = "sha256-YoNtqb5dflJNCZBstAQxP458ktpUighC8uYuqFWjTyo="; subPackages = ["cmd/appherder"]; ldflags = ["-X main.version=${version}"]; nativeBuildInputs = [makeWrapper];