3232
3333 Concurrency uint
3434
35- URL , Dir , Name , Dest string
35+ URL , Dir , Dest string
3636
3737 Interval , ChunkSize , MinChunkSize , MaxChunkSize uint64
3838
4242
4343 size , lastSize uint64
4444
45+ name string
46+
4547 info * Info
4648
4749 chunks []Chunk
@@ -91,7 +93,7 @@ func (d *Download) Init() (err error) {
9193
9294 // Set default client.
9395 if d .Client == nil {
94- d .Client = GetDefaultClient ()
96+ d .Client = DefaultClient
9597 }
9698
9799 // Set default context.
@@ -104,73 +106,19 @@ func (d *Download) Init() (err error) {
104106 return err
105107 }
106108
107- // Set default dest path.
108- if d .Dest == "" {
109-
110- fname := d .info .Name
111-
112- // if info name invalid get name from url.
113- if fname == "" {
114- fname = GetFilename (d .URL )
115- }
116-
117- d .Dest = filepath .Join (d .Dir , fname )
118- }
119-
120109 // Partial content not supported 😢!
121110 if d .info .Rangeable == false || d .info .Size == 0 {
122111 return nil
123112 }
124113
125114 // Set concurrency default.
126115 if d .Concurrency == 0 {
127-
128- d .Concurrency = uint (runtime .NumCPU () * 3 )
129-
130- // Set default max concurrency to 20.
131- if d .Concurrency > 20 {
132- d .Concurrency = 20
133- }
134-
135- // Set default min concurrency to 4.
136- if d .Concurrency <= 2 {
137- d .Concurrency = 4
138- }
116+ d .Concurrency = getDefaultConcurrency ()
139117 }
140118
141119 // Set default chunk size
142120 if d .ChunkSize == 0 {
143-
144- d .ChunkSize = d .info .Size / uint64 (d .Concurrency )
145-
146- // if chunk size >= 102400000 bytes set default to (ChunkSize / 2)
147- if d .ChunkSize >= 102400000 {
148- d .ChunkSize = d .ChunkSize / 2
149- }
150-
151- // Set default min chunk size to 2m, or file size / 2
152- if d .MinChunkSize == 0 {
153-
154- d .MinChunkSize = 2000000
155-
156- if d .MinChunkSize >= d .info .Size {
157- d .MinChunkSize = d .info .Size / 2
158- }
159- }
160-
161- // if Chunk size < Min size set chunk size to min.
162- if d .ChunkSize < d .MinChunkSize {
163- d .ChunkSize = d .MinChunkSize
164- }
165-
166- // Change ChunkSize if MaxChunkSize are set and ChunkSize > Max size
167- if d .MaxChunkSize > 0 && d .ChunkSize > d .MaxChunkSize {
168- d .ChunkSize = d .MaxChunkSize
169- }
170-
171- } else if d .ChunkSize >= d .info .Size {
172-
173- d .ChunkSize = d .info .Size / 2
121+ d .ChunkSize = getDefaultChunkSize (d .info .Size , d .MinChunkSize , d .MaxChunkSize , uint64 (d .Concurrency ))
174122 }
175123
176124 var i , startRange , endRange , chunksLen uint64
@@ -211,74 +159,51 @@ func (d *Download) Init() (err error) {
211159}
212160
213161// Start downloads the file chunks, and merges them.
214- func (d * Download ) Start () error {
215-
216- var (
217- err error
218- temp string
219- )
220-
221- // Create a new temp dir for this download.
222- if temp , err = ioutil .TempDir ("" , "GotChunks" ); err != nil {
223- return err
224- }
225-
226- done := make (chan struct {}, 1 )
227- errs := make (chan error , 1 )
162+ func (d * Download ) Start () (err error ) {
228163
229- defer func () {
230-
231- // Close channels.
232- close (done )
233- close (errs )
234- // Remove temp dir.
235- os .RemoveAll (temp )
236- }()
237-
238- // Partial content not supported, just download the file in one chunk.
164+ // Partial content not supported,
165+ // just download the file in one chunk.
239166 if len (d .chunks ) == 0 {
240167
241- file , err := os .Create (d .Dest )
168+ file , err := os .Create (d .Name () )
242169
243170 if err != nil {
244171 return err
245172 }
246173
247174 defer file .Close ()
248175
249- return d .DownloadChunk (d . ctx , Chunk {}, file )
176+ return d .DownloadChunk (Chunk {}, file )
250177 }
251178
252- go func () {
253- select {
254- case <- d . ctx . Done ():
255- // System or user interrupted the program
256- errs <- ErrDownloadAborted
257- return
258- case <- done :
259- // Everything went ok, no interruptions
260- return
261- }
262- }( )
179+ var (
180+ temp string
181+ done = make ( chan struct {}, 1 )
182+ errs = make ( chan error , 1 )
183+ )
184+
185+ // Create a new temp dir for this download.
186+ if temp , err = ioutil . TempDir ( "" , "GotChunks" ); err != nil {
187+ return err
188+ }
189+ defer os . RemoveAll ( temp )
263190
264191 // Download chunks.
265- go d .dl (d . ctx , temp , errs )
192+ go d .dl (temp , errs )
266193
267194 // Merge chunks.
268195 go func () {
269- errs <- d .merge (d . ctx )
196+ errs <- d .merge ()
270197 }()
271198
272- // Wait for chunks...
273- if err := <- errs ; err != nil {
274-
275- // Remove dest file on error.
276- os .Remove (d .Dest )
277-
278- return err
199+ select {
200+ case <- done :
201+ case err = <- errs :
202+ case <- d .ctx .Done ():
203+ err = d .ctx .Err ()
279204 }
280205
281- return nil
206+ return
282207}
283208
284209// RunProgress runs ProgressFunc based on Interval and updates lastSize.
@@ -363,7 +288,7 @@ func (d Download) IsRangeable() bool {
363288}
364289
365290// Download chunks
366- func (d * Download ) dl (ctx context. Context , temp string , errc chan error ) {
291+ func (d * Download ) dl (temp string , errc chan error ) {
367292
368293 var (
369294 // Wait group.
@@ -394,7 +319,7 @@ func (d *Download) dl(ctx context.Context, temp string, errc chan error) {
394319 defer chunk .Close ()
395320
396321 // Download chunk.
397- if err = d .DownloadChunk (ctx , d .chunks [i ], chunk ); err != nil {
322+ if err = d .DownloadChunk (d .chunks [i ], chunk ); err != nil {
398323 errc <- err
399324 return
400325 }
@@ -413,9 +338,9 @@ func (d *Download) dl(ctx context.Context, temp string, errc chan error) {
413338}
414339
415340// Merge downloaded chunks.
416- func (d * Download ) merge (ctx context. Context ) error {
341+ func (d * Download ) merge () error {
417342
418- file , err := os .Create (d .Dest )
343+ file , err := os .Create (d .Name () )
419344 if err != nil {
420345 return err
421346 }
@@ -425,9 +350,9 @@ func (d *Download) merge(ctx context.Context) error {
425350 for i := range d .chunks {
426351
427352 select {
353+ case <- d .ctx .Done ():
354+ return d .ctx .Err ()
428355 case <- d .chunks [i ].Done :
429- case <- ctx .Done ():
430- return ctx .Err ()
431356 }
432357
433358 chunk , err := os .Open (d .chunks [i ].Path )
@@ -452,16 +377,42 @@ func (d *Download) merge(ctx context.Context) error {
452377 return nil
453378}
454379
380+ // Name returns the downloaded file path.
381+ func (d * Download ) Name () string {
382+
383+ // Avoid returning different path at runtime.
384+ if d .name == "" {
385+
386+ fileName := d .Dest
387+
388+ // Set default file name.
389+ if fileName == "" {
390+
391+ // Content disposition name.
392+ fileName = d .info .Name
393+
394+ // if name invalid get name from url.
395+ if fileName == "" {
396+ fileName = GetFilename (d .URL )
397+ }
398+ }
399+
400+ d .name = filepath .Join (d .Dir , fileName )
401+ }
402+
403+ return d .name
404+ }
405+
455406// DownloadChunk downloads a file chunk.
456- func (d * Download ) DownloadChunk (ctx context. Context , c Chunk , dest * os.File ) error {
407+ func (d * Download ) DownloadChunk (c Chunk , dest * os.File ) error {
457408
458409 var (
459410 err error
460411 req * http.Request
461412 res * http.Response
462413 )
463414
464- if req , err = NewRequest (ctx , "GET" , d .URL ); err != nil {
415+ if req , err = NewRequest (d . ctx , "GET" , d .URL ); err != nil {
465416 return err
466417 }
467418
@@ -487,8 +438,63 @@ func (d *Download) DownloadChunk(ctx context.Context, c Chunk, dest *os.File) er
487438// NewDownload returns new *Download with context.
488439func NewDownload (ctx context.Context , URL , dest string ) * Download {
489440 return & Download {
490- ctx : ctx ,
491- URL : URL ,
492- Dest : dest ,
441+ ctx : ctx ,
442+ URL : URL ,
443+ Dest : dest ,
444+ Client : DefaultClient ,
445+ }
446+ }
447+
448+ func getDefaultConcurrency () uint {
449+
450+ c := uint (runtime .NumCPU () * 3 )
451+
452+ // Set default max concurrency to 20.
453+ if c > 20 {
454+ c = 20
455+ }
456+
457+ // Set default min concurrency to 4.
458+ if c <= 2 {
459+ c = 4
460+ }
461+
462+ return c
463+ }
464+
465+ func getDefaultChunkSize (totalSize , min , max , concurrency uint64 ) uint64 {
466+
467+ cs := totalSize / concurrency
468+
469+ // if chunk size >= 102400000 bytes set default to (ChunkSize / 2)
470+ if cs >= 102400000 {
471+ cs = cs / 2
493472 }
473+
474+ // Set default min chunk size to 2m, or file size / 2
475+ if min == 0 {
476+
477+ min = 2097152
478+
479+ if min >= totalSize {
480+ min = totalSize / 2
481+ }
482+ }
483+
484+ // if Chunk size < Min size set chunk size to min.
485+ if cs < min {
486+ cs = min
487+ }
488+
489+ // Change ChunkSize if MaxChunkSize are set and ChunkSize > Max size
490+ if max > 0 && cs > max {
491+ cs = max
492+ }
493+
494+ // When chunk size > total file size, divide chunk / 2
495+ if cs >= totalSize {
496+ cs = totalSize / 2
497+ }
498+
499+ return cs
494500}
0 commit comments