···4646 chunk.MaxRetries = maxRetriesChunk
4747 chunk.WaitRetry = waitBetweenRetries
4848 chunk.ChunkSize = chunkSize
4949+ chunk.RestartDownloads = restartDownloads
4950 prog := newProgress()
5050- for status := range chunk.Download(os.Args[1:len(os.Args)]...) {
5151+ for status := range chunk.Download(args...) {
5152 if status.Error != nil {
5253 log.Fatal(status.Error)
5354 }
5455 prog.update(status)
5556 }
5656- fmt.Printf("\r%s\nDownloaded to: %s", prog.String(), os.TempDir())
5757 return nil
5858 },
5959}
···6666 maxRetriesChunk uint
6767 chunkSize int64
6868 waitBetweenRetries time.Duration
6969- continueDownload bool
6969+ restartDownloads bool
7070)
71717272func init() {
···7676 rootCmd.Flags().DurationVarP(&waitBetweenRetries, "wait-retry", "w", chunk.DefaultWaitRetry, "pause before retrying an HTTP request that has failed.")
7777 rootCmd.Flags().Int64VarP(&chunkSize, "chunk-size", "s", chunk.DefaultChunkSize, "maximum size of each HTTP request done using the content range header.")
7878 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")
7979+ rootCmd.Flags().BoolVarP(&restartDownloads, "force-restart", "f", chunk.DefaultRestartDownload, "restart previous downloads, ignoring where they were stopped")
8080}
81818282func main() {
+9-7
downloader.go
···99 "path/filepath"
1010 "strings"
1111 "sync"
1212+ "sync/atomic"
1213 "time"
13141415 "github.com/avast/retry-go"
···2021 DefaultMaxRetries = 5
2122 DefaultChunkSize = 8192
2223 DefaultWaitRetry = 1 * time.Second
2323- DefaultContinueDownloads = false
2424+ DefaultRestartDownload = false
2425)
25262627// DownloadStatus is the data propagated via the channel sent back to the user
···8990 // that has failed.
9091 WaitRetry time.Duration
91929292- // ContinueDownloads controls whether or not to continue the download of
9393+ // RestartDownloads controls whether or not to continue the download of
9394 // previous download attempts, skipping chunks alreadt downloaded.
9494- ContinueDownloads bool
9595+ RestartDownloads bool
9596}
96979798type chunk struct{ start, end int64 }
···245246 return
246247 }
247248 chunks := d.chunks(t)
248248- p, err := newProgress(s.DownloadedFilePath, s.URL, d.ChunkSize, len(chunks), d.ContinueDownloads)
249249+ p, err := newProgress(s.DownloadedFilePath, s.URL, d.ChunkSize, len(chunks), d.RestartDownloads)
249250 if err != nil {
250251 s.Error = fmt.Errorf("could not creat a progress file: %w", err)
251252 ch <- s
···262263 ch <- s
263264 return
264265 }
266266+ downloadedBytes := p.downloadedBytes()
265267 for idx, c := range chunks {
266268 pending, err := p.shouldDownload(idx)
267269 if err != nil {
···281283 ch <- s
282284 return
283285 }
284284- n, err := f.WriteAt(b, c.start)
285285- if err != nil {
286286+ if _, err := f.WriteAt(b, c.start); err != nil {
286287 s.Error = fmt.Errorf("error writing to %s: %w", path, err)
287288 ch <- s
288289 return
···292293 ch <- s
293294 return
294295 }
295295- s.DownloadedFileBytes += int64(n)
296296+ atomic.AddInt64(&downloadedBytes, c.size())
297297+ s.DownloadedFileBytes = downloadedBytes
296298 ch <- s
297299 }(c, idx, s)
298300 }
+11
progress.go
···138138 return nil // Either not empty or error, suits both cases
139139}
140140141141+// calculates the number of bytes downloaded
142142+func (p *progress) downloadedBytes() int64 {
143143+ var downloaded int64
144144+ for idx := range p.Chunks {
145145+ if p.Chunks[idx] == 1 {
146146+ downloaded += p.ChunkSize
147147+ }
148148+ }
149149+ return downloaded
150150+}
151151+141152func newProgress(path, url string, chunkSize int64, chunks int, restart bool) (*progress, error) {
142153 dir, err := getChunkDirectory()
143154 if err != nil {