···6666 maxRetriesChunk uint
6767 chunkSize int64
6868 waitBetweenRetries time.Duration
6969+ continueDownload bool
6970)
70717172func init() {
···7576 rootCmd.Flags().DurationVarP(&waitBetweenRetries, "wait-retry", "w", chunk.DefaultWaitRetry, "pause before retrying an HTTP request that has failed.")
7677 rootCmd.Flags().Int64VarP(&chunkSize, "chunk-size", "s", chunk.DefaultChunkSize, "maximum size of each HTTP request done using the content range header.")
7778 rootCmd.Flags().IntVarP(&concurrencyPerServer, "concurrency-per-server", "c", chunk.DefaultConcurrencyPerServer, "controls the max number of concurrent connections opened to the same server.")
7979+ rootCmd.Flags().BoolVarP(&continueDownload, "continue-download", "n", chunk.DefaultContinueDownloads, "continues previous downloads from where they were stopped")
7880}
79818082func main() {
+32-8
downloader.go
···2020 DefaultMaxRetries = 5
2121 DefaultChunkSize = 8192
2222 DefaultWaitRetry = 1 * time.Second
2323+ DefaultContinueDownloads = false
2324)
24252526// DownloadStatus is the data propagated via the channel sent back to the user
···8788 // WaitBetweenRetries is an optional pause before retrying an HTTP request
8889 // that has failed.
8990 WaitRetry time.Duration
9191+9292+ // ContinueDownloads controls whether or not to continue the download of
9393+ // previous download attempts, skipping chunks alreadt downloaded.
9494+ ContinueDownloads bool
9095}
91969292-type chunk struct {
9393- start int64
9494- end int64
9595-}
9797+type chunk struct{ start, end int64 }
96989799func (c chunk) size() int64 { return (c.end + 1) - c.start }
98100func (c chunk) rangeHeader() string { return fmt.Sprintf("bytes=%d-%d", c.start, c.end) }
···242244 ch <- s
243245 return
244246 }
247247+ chunks := d.chunks(t)
248248+ p, err := newProgress(s.DownloadedFilePath, s.URL, d.ChunkSize, len(chunks), d.ContinueDownloads)
249249+ if err != nil {
250250+ s.Error = fmt.Errorf("could not creat a progress file: %w", err)
251251+ ch <- s
252252+ return
253253+ }
245254 var urlDownload sync.WaitGroup
246255 defer func() {
247256 urlDownload.Wait()
257257+ p.close()
248258 f.Close()
249259 }()
250260 if err := f.Truncate(int64(t)); err != nil {
···252262 ch <- s
253263 return
254264 }
255255- for _, c := range d.chunks(t) {
265265+ for idx, c := range chunks {
266266+ pending, err := p.shouldDownload(idx)
267267+ if err != nil {
268268+ s.Error = fmt.Errorf("could not determine whether chunk #%d is pending: %w", idx+1, err)
269269+ ch <- s
270270+ return
271271+ }
272272+ if !pending {
273273+ continue
274274+ }
256275 urlDownload.Add(1)
257257- go func(c chunk, s DownloadStatus) {
276276+ go func(c chunk, idx int, s DownloadStatus) {
258277 defer urlDownload.Done()
259278 b, err := d.downloadChunk(ctx, url, c)
260279 if err != nil {
261261- s.Error = err
280280+ s.Error = fmt.Errorf("error downloadinf chunk #%d: %w", idx+1, err)
262281 ch <- s
263282 return
264283 }
···268287 ch <- s
269288 return
270289 }
290290+ if err := p.done(idx); err != nil {
291291+ s.Error = fmt.Errorf("error checking chunk #%d as done: %w", idx+1, err)
292292+ ch <- s
293293+ return
294294+ }
271295 s.DownloadedFileBytes += int64(n)
272296 ch <- s
273273- }(c, s)
297297+ }(c, idx, s)
274298 }
275299}
276300