···1010 "github.com/spf13/cobra"
1111)
12121313+func checkOutputDir() error {
1414+ var err error
1515+ if outputDir == "" {
1616+ outputDir, err = os.Getwd()
1717+ if err != nil {
1818+ return fmt.Errorf("could not load current directory: %w", err)
1919+ }
2020+ }
2121+ i, err := os.Stat(outputDir)
2222+ if os.IsNotExist(err) {
2323+ return fmt.Errorf("directory %s does not exist", outputDir)
2424+ }
2525+ if err != nil {
2626+ return fmt.Errorf("could not get info for %s: %w", outputDir, err)
2727+ }
2828+ if !i.Mode().IsDir() {
2929+ return fmt.Errorf("%s is not a directory", outputDir)
3030+ }
3131+ return nil
3232+}
3333+1334var rootCmd = &cobra.Command{
1435 Use: "chunk",
1536 Short: "Download tool for slow and unstable servers",
1637 Long: "Download tool for slow and unstable servers using HTTP range requests, retries per HTTP request (not by file), prevents re-downloading the same content range and supports wait time to give servers time to recover.",
1717- Run: func(cmd *cobra.Command, args []string) {
3838+ RunE: func(cmd *cobra.Command, args []string) error {
3939+ if err := checkOutputDir(); err != nil {
4040+ return fmt.Errorf("error checking %s directory: %w", outputDir, err)
4141+ }
1842 chunk := chunk.DefaultDownloader()
4343+ chunk.OutputDir = outputDir
1944 chunk.Timeout = timeoutChunk
2045 chunk.ConcurrencyPerServer = concurrencyPerServer
2146 chunk.MaxRetries = maxRetriesChunk
···2954 prog.update(status)
3055 }
3156 fmt.Printf("\r%s\nDownloaded to: %s", prog.String(), os.TempDir())
5757+ return nil
3258 },
3359}
34603561// Flags
3662var (
6363+ outputDir string
3764 timeoutChunk time.Duration
3865 concurrencyPerServer int
3966 maxRetriesChunk uint
···4269)
43704471func init() {
7272+ rootCmd.Flags().StringVarP(&outputDir, "output-directory", "d", "", "directory where to save the downloaded files.")
4573 rootCmd.Flags().DurationVarP(&timeoutChunk, "timeout", "t", chunk.DefaultTimeout, "timeout for the download of each chunk from each URL.")
4674 rootCmd.Flags().UintVarP(&maxRetriesChunk, "max-retries", "r", chunk.DefaultMaxRetries, "maximum number of retries for each chunk.")
4775 rootCmd.Flags().DurationVarP(&waitBetweenRetries, "wait-retry", "w", chunk.DefaultWaitRetry, "pause before retrying an HTTP request that has failed.")
+11-2
downloader.go
···5454// handled, including retries, amoutn of requets, and size of each request, for
5555// example.
5656type Downloader struct {
5757- // Client is the HTTP client used for every request needed to download all
5757+ // OutputDir is where the downloaded files will be saved. If not set,
5858+ // defaults to the current working directory.
5959+ OutputDir string
6060+6161+ // client is the HTTP client used for every request needed to download all
5862 // the files.
5963 client *http.Client
6064···222226}
223227224228func (d *Downloader) prepareAndStartDownload(ctx context.Context, url string, ch chan<- DownloadStatus) {
225225- path := filepath.Join(os.TempDir(), filepath.Base(url))
229229+ path := filepath.Join(d.OutputDir, filepath.Base(url))
226230 s := DownloadStatus{URL: url, DownloadedFilePath: path}
227231 t, err := d.getDownloadSize(ctx, url)
228232 if err != nil {
···302306// NewDownloader creates a downloader with the defalt configuration. Check
303307// the constants in this package for their values.
304308func DefaultDownloader() *Downloader {
309309+ dir, err := os.Getwd()
310310+ if err != nil {
311311+ dir = ""
312312+ }
305313 return &Downloader{
314314+ OutputDir: dir,
306315 Timeout: DefaultTimeout,
307316 ConcurrencyPerServer: DefaultConcurrencyPerServer,
308317 MaxRetries: DefaultMaxRetries,
+9-2
downloader_test.go
···3838 ))
3939 defer s.Close()
4040 d := Downloader{
4141+ OutputDir: t.TempDir(),
4142 Timeout: timeout,
4243 MaxRetries: 4,
4344 ConcurrencyPerServer: 1,
···7273 ))
7374 defer s.Close()
74757575- ch := DefaultDownloader().Download(s.URL)
7676+ d := DefaultDownloader()
7777+ d.OutputDir = t.TempDir()
7878+ ch := d.Download(s.URL)
7679 <-ch // discard the first status (just the file size)
7780 got := <-ch
7881 defer os.Remove(got.DownloadedFilePath)
···138141 // download
139142 var got string
140143 defer os.Remove(got)
141141- for g := range DefaultDownloader().Download(s.URL + "/archive.zip") {
144144+ d := DefaultDownloader()
145145+ d.OutputDir = t.TempDir()
146146+ for g := range d.Download(s.URL + "/archive.zip") {
142147 got = g.DownloadedFilePath
143148 if g.Error != nil {
144149 t.Errorf("expected no error during the download of the zip archive, got %s", g.Error)
···191196 defer s.Close()
192197193198 d := Downloader{
199199+ OutputDir: t.TempDir(),
194200 Timeout: timeout,
195201 MaxRetries: 4,
196202 ConcurrencyPerServer: 1,
···243249 ))
244250 defer s.Close()
245251 d := Downloader{
252252+ OutputDir: t.TempDir(),
246253 Timeout: timeout,
247254 MaxRetries: 4,
248255 ConcurrencyPerServer: 1,