🧱 Chunk is a download manager for slow and unstable servers
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Replaces: `staticcheck` -> `golangci-lint`

+107 -29
+3 -1
.github/workflows/golint.yaml
··· 8 8 - uses: WillAbides/setup-go-faster@v1.14.0 9 9 with: 10 10 go-version: "1.25.x" 11 - - uses: dominikh/staticcheck-action@v1.3.1 11 + - uses: golangci/golangci-lint-action@v6 12 + with: 13 + version: v1.60
+2 -2
CONTRIBUTING.md
··· 1 1 # Contributing to Chunk 2 2 3 - You might need to [install Staticcheck](https://staticcheck.io/docs/getting-started/#installation) if you don't have it in your system yet. 3 + You might need to [install golangci-lint](https://golangci-lint.run) if you don't have it in your system yet. 4 4 5 5 Then, write and run tests, and use the auto-format as well as the linter tools: 6 6 7 7 ```console 8 8 $ gofmt -w ./ 9 - $ staticcheck ./... 9 + $ golangci-lint run ./... 10 10 $ go test -race ./... 11 11 ```
+20 -5
downloader.go
··· 4 4 "bytes" 5 5 "context" 6 6 "fmt" 7 + "log/slog" 7 8 "net/http" 8 9 "net/url" 9 10 "os" ··· 126 127 if err != nil { 127 128 return nil, fmt.Errorf("error sending a get http request to %s: %w", u, err) 128 129 } 129 - defer resp.Body.Close() 130 + defer func() { 131 + if err := resp.Body.Close(); err != nil { 132 + slog.Warn("error closing HTTP response body", "url", u, "chunk", c.rangeHeader(), "error", err) 133 + } 134 + }() 130 135 if resp.StatusCode != http.StatusPartialContent && resp.StatusCode != http.StatusOK { 131 136 return nil, fmt.Errorf("got http response %s from %s", resp.Status, u) 132 137 } ··· 193 198 return 0, fmt.Errorf("error sending get http request to %s: %w", u, err) 194 199 } 195 200 resp := <-ch 196 - defer resp.Body.Close() 201 + defer func() { 202 + if err := resp.Body.Close(); err != nil { 203 + slog.Warn("error closing HTTP response body", "url", u, "error", err) 204 + } 205 + }() 197 206 if resp.ContentLength <= 0 { 198 207 var s int64 199 208 r := strings.TrimSpace(resp.Header.Get("Content-Range")) ··· 201 210 return 0, fmt.Errorf("could not get content length for %s", u) 202 211 } 203 212 p := strings.Split(r, "/") 204 - fmt.Sscan(p[len(p)-1], &s) 213 + if _, err := fmt.Sscan(p[len(p)-1], &s); err != nil { 214 + return 0, fmt.Errorf("error parsing content range for %s: %w", u, err) 215 + } 205 216 return s, nil 206 217 } 207 218 return resp.ContentLength, nil ··· 278 289 var urlDownload sync.WaitGroup 279 290 defer func() { 280 291 urlDownload.Wait() 281 - p.close() 282 - f.Close() 292 + if err := p.close(); err != nil { 293 + slog.Warn("error closing progress file", "url", s.URL, "path", s.DownloadedFilePath, "error", err) 294 + } 295 + if err := f.Close(); err != nil { 296 + slog.Error("error closing downloaded file", "url", s.URL, "path", s.DownloadedFilePath, "error", err) 297 + } 283 298 }() 284 299 if err := f.Truncate(int64(t)); err != nil { 285 300 s.Error = fmt.Errorf("error truncating %s to %d: %w", path, t, err)
+53 -13
downloader_test.go
··· 68 68 w.Header().Add("Content-Length", "2") 69 69 return 70 70 } 71 - fmt.Fprint(w, "42") 71 + if _, err := fmt.Fprint(w, "42"); err != nil { 72 + t.Errorf("failed to write response: %v", err) 73 + } 72 74 }, 73 75 )) 74 76 defer s.Close() ··· 78 80 ch := d.Download(s.URL + "/My%20File.txt") 79 81 <-ch // discard the first status (just the file size) 80 82 got := <-ch 81 - defer os.Remove(got.DownloadedFilePath) 83 + defer func() { 84 + if err := os.Remove(got.DownloadedFilePath); err != nil { 85 + t.Errorf("failed to remove test file: %v", err) 86 + } 87 + }() 82 88 83 89 if got.Error != nil { 84 90 t.Errorf("invalid error. want:nil got:%q", got.Error) ··· 121 127 if err != nil { 122 128 t.Errorf("expected no error creating zip archive, got %s", err) 123 129 } 124 - defer z.Close() 130 + defer func() { 131 + if err := z.Close(); err != nil { 132 + t.Errorf("failed to close zip file: %v", err) 133 + } 134 + }() 125 135 w := zip.NewWriter(z) 126 136 f, err := w.Create("file.txt") 127 137 if err != nil { 128 138 t.Errorf("expected no error creating archived file, got %s", err) 129 139 } 130 - defer w.Close() 140 + defer func() { 141 + if err := w.Close(); err != nil { 142 + t.Errorf("failed to close zip writer: %v", err) 143 + } 144 + }() 131 145 if _, err := f.Write(expected); err != nil { 132 146 t.Errorf("expected no error writing to archived file, got %s", err) 133 147 } ··· 143 157 144 158 // download 145 159 var got string 146 - defer os.Remove(got) 160 + defer func() { 161 + if got != "" { 162 + if err := os.Remove(got); err != nil { 163 + t.Errorf("failed to remove test file: %v", err) 164 + } 165 + } 166 + }() 147 167 d := DefaultDownloader() 148 168 d.OutputDir = t.TempDir() 149 169 for g := range d.Download(s.URL + "/archive.zip") { ··· 158 178 if err != nil { 159 179 t.Errorf("expected no error opening downloaded zip archive %s, got %s", got, err) 160 180 } 161 - defer a.Close() 181 + defer func() { 182 + if err := a.Close(); err != nil { 183 + t.Errorf("failed to close zip reader: %v", err) 184 + } 185 + }() 162 186 r, err := a.Open("file.txt") 163 187 if err != nil { 164 188 t.Errorf("expected no error reading downloaded zip archive, got %s", err) 165 189 } 166 - defer r.Close() 190 + defer func() { 191 + if err := r.Close(); err != nil { 192 + t.Errorf("failed to close zip file reader: %v", err) 193 + } 194 + }() 167 195 var b bytes.Buffer 168 196 if _, err := io.Copy(&b, r); err != nil { 169 197 t.Errorf("expected no error reading archived file, got %s", err) ··· 193 221 if atomic.CompareAndSwapInt32(&attempts, 0, 1) { 194 222 tc.proc(w) 195 223 } 196 - fmt.Fprint(w, "42") 224 + if _, err := fmt.Fprint(w, "42"); err != nil { 225 + t.Errorf("failed to write response: %v", err) 226 + } 197 227 }, 198 228 )) 199 229 defer s.Close() ··· 256 286 url, first := func() (string, DownloadStatus) { 257 287 s := httptest.NewServer(http.HandlerFunc( 258 288 func(w http.ResponseWriter, r *http.Request) { 259 - fmt.Fprint(w, "42") 289 + if _, err := fmt.Fprint(w, "42"); err != nil { 290 + t.Errorf("failed to write response: %v", err) 291 + } 260 292 }, 261 293 )) 262 294 defer s.Close() ··· 353 385 func TestGetDownloadSize_ContentLength(t *testing.T) { 354 386 s := httptest.NewServer(http.HandlerFunc( 355 387 func(w http.ResponseWriter, r *http.Request) { 356 - fmt.Fprint(w, "Test") 388 + if _, err := fmt.Fprint(w, "Test"); err != nil { 389 + t.Errorf("failed to write response: %v", err) 390 + } 357 391 }, 358 392 )) 359 393 defer s.Close() ··· 377 411 w.WriteHeader(http.StatusTooManyRequests) 378 412 return 379 413 } 380 - fmt.Fprint(w, "Test") 414 + if _, err := fmt.Fprint(w, "Test"); err != nil { 415 + t.Errorf("failed to write response: %v", err) 416 + } 381 417 }, 382 418 )) 383 419 defer s.Close() ··· 397 433 s := httptest.NewServer(http.HandlerFunc( 398 434 func(w http.ResponseWriter, r *http.Request) { 399 435 w.Header().Set("Content-Range", "bytes 1-10/123") 400 - fmt.Fprint(w, "") 436 + if _, err := fmt.Fprint(w, ""); err != nil { 437 + t.Errorf("failed to write response: %v", err) 438 + } 401 439 }, 402 440 )) 403 441 defer s.Close() ··· 428 466 func TestGetDownloadSize_NoContent(t *testing.T) { 429 467 s := httptest.NewServer(http.HandlerFunc( 430 468 func(w http.ResponseWriter, r *http.Request) { 431 - fmt.Fprint(w, "") 469 + if _, err := fmt.Fprint(w, ""); err != nil { 470 + t.Errorf("failed to write response: %v", err) 471 + } 432 472 }, 433 473 )) 434 474 defer s.Close()
+11 -2
progress.go
··· 4 4 "crypto/md5" 5 5 "encoding/gob" 6 6 "fmt" 7 + "log/slog" 7 8 "os" 8 9 "os/user" 9 10 "path/filepath" ··· 64 65 if err != nil { 65 66 return fmt.Errorf("error opening %s: %w", p.path, err) 66 67 } 67 - defer f.Close() 68 + defer func() { 69 + if err := f.Close(); err != nil { 70 + slog.Warn("error closing progress file", "path", p.path, "error", err) 71 + } 72 + }() 68 73 d := gob.NewDecoder(f) 69 74 var got progress 70 75 if err := d.Decode(&got); err != nil { ··· 109 114 if err != nil { 110 115 return fmt.Errorf("error opening progress file %s: %w", p.path, err) 111 116 } 112 - defer f.Close() 117 + defer func() { 118 + if err := f.Close(); err != nil { 119 + slog.Warn("error closing progress file", "path", p.path, "error", err) 120 + } 121 + }() 113 122 e := gob.NewEncoder(f) 114 123 if err := e.Encode(p); err != nil { 115 124 return fmt.Errorf("error encoding progress file %s: %w", p.path, err)
+18 -6
progress_test.go
··· 78 78 if err != nil { 79 79 t.Errorf("expected no error creating the old progress, got %s", err) 80 80 } 81 - old.done(1, 3) 82 - old.close() 81 + if err := old.done(1, 3); err != nil { 82 + t.Errorf("failed to mark chunk done: %v", err) 83 + } 84 + if err := old.close(); err != nil { 85 + t.Errorf("failed to close progress: %v", err) 86 + } 83 87 84 88 p, err := newProgress(name, tmp, "https://test.etc/chunk.zip", 5, 3, false) 85 89 if err != nil { ··· 115 119 if err != nil { 116 120 t.Errorf("expected no error creating the old progress, got %s", err) 117 121 } 118 - old.done(1, 3) 119 - old.close() 122 + if err := old.done(1, 3); err != nil { 123 + t.Errorf("failed to mark chunk done: %v", err) 124 + } 125 + if err := old.close(); err != nil { 126 + t.Errorf("failed to close progress: %v", err) 127 + } 120 128 121 129 if _, err := newProgress(name, tmp, "https://test.etc/chunk.zip", 10, 3, false); err == nil { 122 130 t.Error("expected error creating the progress with different chunk size") ··· 130 138 if err != nil { 131 139 t.Errorf("expected no error creating the old progress, got %s", err) 132 140 } 133 - old.done(1, 3) 134 - old.close() 141 + if err := old.done(1, 3); err != nil { 142 + t.Errorf("failed to mark chunk done: %v", err) 143 + } 144 + if err := old.close(); err != nil { 145 + t.Errorf("failed to close progress: %v", err) 146 + } 135 147 136 148 p, err := newProgress(name, tmp, "https://test.etc/chunk.zip", 10, 3, true) 137 149 if err != nil {