···3636// idleTimeoutReader cancels via cancel() when a single Read stalls longer than
3737// timeout, guarding a download against a connection that goes quiet.
3838type idleTimeoutReader struct {
3939- r io.Reader
3939+ reader io.Reader
4040 timer *time.Timer
4141 timeout time.Duration
4242}
43434444-func newIdleTimeoutReader(r io.Reader, timeout time.Duration, cancel context.CancelFunc) *idleTimeoutReader {
4444+func newIdleTimeoutReader(reader io.Reader, timeout time.Duration, cancel context.CancelFunc) *idleTimeoutReader {
4545 timer := time.AfterFunc(timeout, cancel)
4646 timer.Stop()
4747- return &idleTimeoutReader{r: r, timer: timer, timeout: timeout}
4747+ return &idleTimeoutReader{reader: reader, timer: timer, timeout: timeout}
4848}
49495050-func (t *idleTimeoutReader) Read(p []byte) (int, error) {
5050+func (t *idleTimeoutReader) Read(buf []byte) (int, error) {
5151 t.timer.Reset(t.timeout)
5252- n, err := t.r.Read(p)
5252+ n, err := t.reader.Read(buf)
5353 t.timer.Stop()
5454 return n, err
5555}
+6-6
cmd/appherder/icons.go
···2727 if err != nil {
2828 return ""
2929 }
3030- var p iconPicker
3030+ var picker iconPicker
3131 for _, entry := range entries {
3232 if !entry.IsDir() {
3333- p.consider(entry.Name(), entry)
3333+ picker.consider(entry.Name(), entry)
3434 }
3535 }
3636- return p.best
3636+ return picker.best
3737}
38383939func bestThemedIcon(fsys fs.FS) string {
4040- var p iconPicker
4040+ var picker iconPicker
4141 for _, dir := range []string{"usr/share/icons", "usr/share/pixmaps"} {
4242 _ = fs.WalkDir(fsys, dir, func(name string, entry fs.DirEntry, err error) error {
4343 if err != nil || entry.IsDir() {
4444 return nil
4545 }
4646- p.consider(name, entry)
4646+ picker.consider(name, entry)
4747 return nil
4848 })
4949 }
5050- return p.best
5050+ return picker.best
5151}
52525353// iconPicker keeps the best icon seen, preferring format rank then larger size.
+11-11
cmd/appherder/install.go
···21212222 // The squashfs reader parses untrusted input; turn any panic into an error.
2323 defer func() {
2424- if r := recover(); r != nil {
2525- err = fmt.Errorf("read AppImage %s: %v", appimage, r)
2424+ if recovered := recover(); recovered != nil {
2525+ err = fmt.Errorf("read AppImage %s: %v", appimage, recovered)
2626 }
2727 }()
2828···9696// sanitizeAppName lowercases s, turns spaces into underscores, and drops any
9797// character that isn't alphanumeric, underscore, or dot — GearLever's naming
9898// rule.
9999-func sanitizeAppName(s string) string {
100100- s = strings.ToLower(s)
101101- s = strings.ReplaceAll(s, " ", "_")
102102- var b strings.Builder
103103- b.Grow(len(s))
104104- for _, r := range s {
105105- if (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9') || r == '_' || r == '.' {
106106- b.WriteRune(r)
9999+func sanitizeAppName(name string) string {
100100+ name = strings.ToLower(name)
101101+ name = strings.ReplaceAll(name, " ", "_")
102102+ var builder strings.Builder
103103+ builder.Grow(len(name))
104104+ for _, char := range name {
105105+ if (char >= 'a' && char <= 'z') || (char >= '0' && char <= '9') || char == '_' || char == '.' {
106106+ builder.WriteRune(char)
107107 }
108108 }
109109- return b.String()
109109+ return builder.String()
110110}
111111112112func appNameFromPath(path string) string {
+11-11
cmd/appherder/source.go
···25252626// checksum returns the strongest hash the release carries with a hasher to
2727// match; ok is false when the source provided no cryptographic hash.
2828-func (r release) checksum() (want string, h hash.Hash, ok bool) {
2828+func (r release) checksum() (want string, hasher hash.Hash, ok bool) {
2929 switch {
3030 case r.sha256 != "":
3131 return r.sha256, sha256.New(), true
···3838// localMatches reports whether file already equals this release, by the
3939// strongest available signal: checksum, then mtime, then size.
4040func (r release) localMatches(file string) (bool, error) {
4141- if want, h, ok := r.checksum(); ok {
4242- sum, err := fileSum(file, h)
4141+ if want, hasher, ok := r.checksum(); ok {
4242+ sum, err := fileSum(file, hasher)
4343 if err != nil {
4444 return false, err
4545 }
···6464// verifyDownload checks a freshly downloaded file against the release's
6565// checksum. With no checksum there is nothing to verify and it returns nil.
6666func (r release) verifyDownload(file string) error {
6767- want, h, ok := r.checksum()
6767+ want, hasher, ok := r.checksum()
6868 if !ok {
6969 return nil
7070 }
7171- sum, err := fileSum(file, h)
7171+ sum, err := fileSum(file, hasher)
7272 if err != nil {
7373 return err
7474 }
···85858686// readUpdateInfo returns the AppImage's embedded update-information string from
8787// its .upd_info ELF section, or "" when absent or empty.
8888-func readUpdateInfo(file string) (string, error) {
8989- f, err := elf.Open(file)
8888+func readUpdateInfo(path string) (string, error) {
8989+ file, err := elf.Open(path)
9090 if err != nil {
9191- return "", fmt.Errorf("open AppImage %s: %w", file, err)
9191+ return "", fmt.Errorf("open AppImage %s: %w", path, err)
9292 }
9393- defer f.Close()
9393+ defer file.Close()
94949595- section := f.Section(".upd_info")
9595+ section := file.Section(".upd_info")
9696 if section == nil {
9797 return "", nil
9898 }
9999 data, err := section.Data()
100100 if err != nil {
101101- return "", fmt.Errorf("read .upd_info from %s: %w", file, err)
101101+ return "", fmt.Errorf("read .upd_info from %s: %w", path, err)
102102 }
103103 return strings.TrimRight(string(data), "\x00"), nil
104104}
+5-5
cmd/appherder/source_github.go
···8282// multiple matches it takes the first by name, for determinism.
8383func matchAsset(assets []ghAsset, pattern string) (ghAsset, error) {
8484 var matches []ghAsset
8585- for _, a := range assets {
8686- if ok, _ := path.Match(pattern, a.Name); ok {
8787- matches = append(matches, a)
8585+ for _, asset := range assets {
8686+ if ok, _ := path.Match(pattern, asset.Name); ok {
8787+ matches = append(matches, asset)
8888 }
8989 }
9090 if len(matches) == 0 {
···9898// raise the API rate limit. It is never sent to asset download URLs.
9999func githubToken() string {
100100 for _, key := range []string{"GH_TOKEN", "GITHUB_TOKEN"} {
101101- if v := os.Getenv(key); v != "" {
102102- return v
101101+ if token := os.Getenv(key); token != "" {
102102+ return token
103103 }
104104 }
105105 return ""
+16-16
cmd/appherder/source_gitlab.go
···79798080 // No digest from the API: take the checksum from the sibling .zsync asset
8181 // when present, else comparison falls back to size.
8282- if z, ok := findLink(rel.Assets.Links, asset.Name+".zsync"); ok {
8383- header, err := fetchZsyncHeader(ctx, linkURL(z))
8282+ if zsyncLink, ok := findLink(rel.Assets.Links, asset.Name+".zsync"); ok {
8383+ header, err := fetchZsyncHeader(ctx, linkURL(zsyncLink))
8484 if err != nil {
8585 return release{}, err
8686 }
8787 out.sha1 = header["sha-1"]
8888- if n, err := strconv.ParseInt(header["length"], 10, 64); err == nil {
8989- out.size = n
8888+ if size, err := strconv.ParseInt(header["length"], 10, 64); err == nil {
8989+ out.size = size
9090 }
9191 }
9292 return out, nil
···9696// the first by name when several match, for determinism.
9797func matchLink(links []glLink, pattern string) (glLink, error) {
9898 var matches []glLink
9999- for _, l := range links {
100100- if ok, _ := path.Match(pattern, l.Name); ok {
101101- matches = append(matches, l)
9999+ for _, link := range links {
100100+ if ok, _ := path.Match(pattern, link.Name); ok {
101101+ matches = append(matches, link)
102102 }
103103 }
104104 if len(matches) == 0 {
···109109}
110110111111func findLink(links []glLink, name string) (glLink, bool) {
112112- for _, l := range links {
113113- if l.Name == name {
114114- return l, true
112112+ for _, link := range links {
113113+ if link.Name == name {
114114+ return link, true
115115 }
116116 }
117117 return glLink{}, false
118118}
119119120120// linkURL prefers the permalinked direct_asset_url, falling back to the raw url.
121121-func linkURL(l glLink) string {
122122- if l.DirectAssetURL != "" {
123123- return l.DirectAssetURL
121121+func linkURL(link glLink) string {
122122+ if link.DirectAssetURL != "" {
123123+ return link.DirectAssetURL
124124 }
125125- return l.URL
125125+ return link.URL
126126}
127127128128// gitlabToken returns a token from the environment, used for private projects
129129// and to raise rate limits. It is never sent to asset download URLs.
130130func gitlabToken() string {
131131 for _, key := range []string{"GL_TOKEN", "GITLAB_TOKEN"} {
132132- if v := os.Getenv(key); v != "" {
133133- return v
132132+ if token := os.Getenv(key); token != "" {
133133+ return token
134134 }
135135 }
136136 return ""
···3535 sha1: header["sha-1"],
3636 version: zsyncVersion(header),
3737 }
3838- if n, err := strconv.ParseInt(header["length"], 10, 64); err == nil {
3939- rel.size = n
3838+ if size, err := strconv.ParseInt(header["length"], 10, 64); err == nil {
3939+ rel.size = size
4040 }
4141 return rel, nil
4242}
···7070// parseZsyncHeader reads a .zsync file's text header: "Key: Value" lines ending
7171// at the blank line that separates them from the binary checksum block. Keys
7272// are lowercased. It stops at the blank line so the body isn't consumed.
7373-func parseZsyncHeader(r io.Reader) (map[string]string, error) {
7373+func parseZsyncHeader(reader io.Reader) (map[string]string, error) {
7474 header := make(map[string]string)
7575- scanner := bufio.NewScanner(r)
7575+ scanner := bufio.NewScanner(reader)
7676 for scanner.Scan() {
7777 line := scanner.Text()
7878 if line == "" { // header ends; binary data follows
···110110// zsyncVersion picks a human label for the build. zsync carries no version, so
111111// MTime (which changes per build) is the most useful, then the filename.
112112func zsyncVersion(header map[string]string) string {
113113- if v := header["mtime"]; v != "" {
114114- return v
113113+ if value := header["mtime"]; value != "" {
114114+ return value
115115 }
116116- if v := header["filename"]; v != "" {
117117- return v
116116+ if value := header["filename"]; value != "" {
117117+ return value
118118 }
119119 return "latest"
120120}
+11-11
cmd/appherder/sync.go
···4040 results := parallelMap(ctx, files, installConcurrency, func(_ context.Context, f string) syncResult {
4141 return syncResult{file: f, err: a.install(f)}
4242 })
4343- for _, r := range results {
4444- if r.err != nil {
4545- fmt.Fprintf(out, "skip %s: %v\n", filepath.Base(r.file), r.err)
4343+ for _, result := range results {
4444+ if result.err != nil {
4545+ fmt.Fprintf(out, "skip %s: %v\n", filepath.Base(result.file), result.err)
4646 }
4747 }
4848···8585 return nil, err
8686 }
8787 var files []string
8888- for _, e := range entries {
8989- if e.IsDir() || strings.HasPrefix(e.Name(), ".") {
8888+ for _, entry := range entries {
8989+ if entry.IsDir() || strings.HasPrefix(entry.Name(), ".") {
9090 continue
9191 }
9292- if !strings.EqualFold(filepath.Ext(e.Name()), ".appimage") {
9292+ if !strings.EqualFold(filepath.Ext(entry.Name()), ".appimage") {
9393 continue
9494 }
9595- files = append(files, filepath.Join(dir, e.Name()))
9595+ files = append(files, filepath.Join(dir, entry.Name()))
9696 }
9797 sort.Strings(files)
9898 return files, nil
···108108 }
109109 return false, err
110110 }
111111- for _, e := range entries {
112112- if e.IsDir() {
111111+ for _, entry := range entries {
112112+ if entry.IsDir() {
113113 continue
114114 }
115115- if strings.EqualFold(e.Name(), appid+".appimage") {
115115+ if strings.EqualFold(entry.Name(), appid+".appimage") {
116116 return true, nil
117117 }
118118 }
···135135 if err != nil {
136136 return nil, fmt.Errorf("read desktop file %s: %w", path, err)
137137 }
138138- if v, ok := desktop.get(desktopOwnerKey, desktopEntrySection); ok && v == "true" {
138138+ if value, ok := desktop.get(desktopOwnerKey, desktopEntrySection); ok && value == "true" {
139139 continue
140140 }
141141 target := desktopTarget(desktop)
+5-5
cmd/appherder/sync_test.go
···216216 got := out.String()
217217 want := []string{"alpha.appimage", "bravo.appimage", "charlie.appimage"}
218218 pos := 0
219219- for _, w := range want {
220220- i := strings.Index(got[pos:], w)
221221- if i < 0 {
222222- t.Fatalf("output missing %s in order:\n%s", w, got)
219219+ for _, wantName := range want {
220220+ idx := strings.Index(got[pos:], wantName)
221221+ if idx < 0 {
222222+ t.Fatalf("output missing %s in order:\n%s", wantName, got)
223223 }
224224- pos += i + len(w)
224224+ pos += idx + len(wantName)
225225 }
226226}