@@ -10,7 +10,7 @@ import (
1010 "os"
1111 "path"
1212 "strconv"
13- "sync "
13+ "strings "
1414 "time"
1515
1616 "github.com/linuxsuren/http-downloader/pkg/common"
@@ -50,6 +50,7 @@ type HTTPDownloader struct {
5050 Debug bool
5151 RoundTripper http.RoundTripper
5252 progressIndicator * ProgressIndicator
53+ suggestedFilename string
5354}
5455
5556// SetProxy set the proxy for a http
@@ -150,6 +151,14 @@ func (h *HTTPDownloader) DownloadFile() error {
150151 }
151152 }
152153
154+ if disposition , ok := resp .Header ["Content-Disposition" ]; ok && len (disposition ) >= 1 {
155+ h .suggestedFilename = strings .TrimPrefix (disposition [0 ], `filename="` )
156+ h .suggestedFilename = strings .TrimSuffix (h .suggestedFilename , `"` )
157+ if h .suggestedFilename == filepath {
158+ h .suggestedFilename = ""
159+ }
160+ }
161+
153162 // pre-hook before get started to download file
154163 if h .PreStart != nil && ! h .PreStart (resp ) {
155164 return nil
@@ -192,127 +201,15 @@ func (h *HTTPDownloader) DownloadFile() error {
192201 return err
193202}
194203
195- // DownloadFileWithMultipleThread downloads the files with multiple threads
196- func DownloadFileWithMultipleThread (targetURL , targetFilePath string , thread int , showProgress bool ) (err error ) {
197- return DownloadFileWithMultipleThreadKeepParts (targetURL , targetFilePath , thread , false , showProgress )
204+ // GetSuggestedFilename returns the suggested filename which comes from the HTTP response header.
205+ // Returns empty string if the filename is same with the given name.
206+ func (h * HTTPDownloader ) GetSuggestedFilename () string {
207+ return h .suggestedFilename
198208}
199209
200- // MultiThreadDownloader is a download with multi-thread
201- type MultiThreadDownloader struct {
202- noProxy bool
203- keepParts , showProgress bool
204-
205- roundTripper http.RoundTripper
206- }
207-
208- // WithoutProxy indicates not use HTTP proxy
209- func (d * MultiThreadDownloader ) WithoutProxy (noProxy bool ) * MultiThreadDownloader {
210- d .noProxy = noProxy
211- return d
212- }
213-
214- // WithShowProgress indicate if show the download progress
215- func (d * MultiThreadDownloader ) WithShowProgress (showProgress bool ) * MultiThreadDownloader {
216- d .showProgress = showProgress
217- return d
218- }
219-
220- // WithKeepParts indicates if keeping the part files
221- func (d * MultiThreadDownloader ) WithKeepParts (keepParts bool ) * MultiThreadDownloader {
222- d .keepParts = keepParts
223- return d
224- }
225-
226- // WithRoundTripper sets RoundTripper
227- func (d * MultiThreadDownloader ) WithRoundTripper (roundTripper http.RoundTripper ) * MultiThreadDownloader {
228- d .roundTripper = roundTripper
229- return d
230- }
231-
232- // Download starts to download the target URL
233- func (d * MultiThreadDownloader ) Download (targetURL , targetFilePath string , thread int ) (err error ) {
234- // get the total size of the target file
235- var total int64
236- var rangeSupport bool
237- if total , rangeSupport , err = DetectSizeWithRoundTripper (targetURL , targetFilePath , true , d .noProxy , d .roundTripper ); err != nil {
238- return
239- }
240-
241- if rangeSupport {
242- unit := total / int64 (thread )
243- offset := total - unit * int64 (thread )
244- var wg sync.WaitGroup
245- var partItems []string
246- var m sync.Mutex
247-
248- defer func () {
249- // remove all partial files
250- for _ , part := range partItems {
251- _ = os .RemoveAll (part )
252- }
253- }()
254-
255- fmt .Printf ("start to download with %d threads, size: %d, unit: %d\n " , thread , total , unit )
256- for i := 0 ; i < thread ; i ++ {
257- wg .Add (1 )
258- go func (index int , wg * sync.WaitGroup ) {
259- defer wg .Done ()
260- output := fmt .Sprintf ("%s-%d" , targetFilePath , index )
261-
262- m .Lock ()
263- partItems = append (partItems , output )
264- m .Unlock ()
265-
266- end := unit * int64 (index + 1 ) - 1
267- if index == thread - 1 {
268- // this is the last part
269- end += offset
270- }
271- start := unit * int64 (index )
272-
273- downloader := & ContinueDownloader {}
274- downloader .WithoutProxy (d .noProxy ).
275- WithRoundTripper (d .roundTripper )
276- if downloadErr := downloader .DownloadWithContinue (targetURL , output ,
277- int64 (index ), start , end , d .showProgress ); downloadErr != nil {
278- fmt .Println (downloadErr )
279- }
280- }(i , & wg )
281- }
282-
283- wg .Wait ()
284- ProgressIndicator {}.Close ()
285-
286- // concat all these partial files
287- var f * os.File
288- if f , err = os .OpenFile (targetFilePath , os .O_CREATE | os .O_WRONLY , 0600 ); err == nil {
289- defer func () {
290- _ = f .Close ()
291- }()
292-
293- for i := 0 ; i < thread ; i ++ {
294- partFile := fmt .Sprintf ("%s-%d" , targetFilePath , i )
295- if data , ferr := os .ReadFile (partFile ); ferr == nil {
296- if _ , err = f .Write (data ); err != nil {
297- err = fmt .Errorf ("failed to write file: '%s'" , partFile )
298- break
299- } else if ! d .keepParts {
300- _ = os .RemoveAll (partFile )
301- }
302- } else {
303- err = fmt .Errorf ("failed to read file: '%s'" , partFile )
304- break
305- }
306- }
307- }
308- } else {
309- fmt .Println ("cannot download it using multiple threads, failed to one" )
310- downloader := & ContinueDownloader {}
311- downloader .WithoutProxy (d .noProxy )
312- downloader .WithRoundTripper (d .roundTripper )
313- err = downloader .DownloadWithContinue (targetURL , targetFilePath , - 1 , 0 , 0 , true )
314- }
315- return
210+ // SuggestedFilenameAware is the interface for getting suggested filename
211+ type SuggestedFilenameAware interface {
212+ GetSuggestedFilename () string
316213}
317214
318215// DownloadFileWithMultipleThreadKeepParts downloads the files with multiple threads
@@ -326,8 +223,14 @@ func DownloadFileWithMultipleThreadKeepParts(targetURL, targetFilePath string, t
326223type ContinueDownloader struct {
327224 downloader * HTTPDownloader
328225
329- roundTripper http.RoundTripper
330- noProxy bool
226+ roundTripper http.RoundTripper
227+ noProxy bool
228+ insecureSkipVerify bool
229+ }
230+
231+ // GetSuggestedFilename returns the suggested filename
232+ func (c * ContinueDownloader ) GetSuggestedFilename () string {
233+ return c .downloader .GetSuggestedFilename ()
331234}
332235
333236// WithRoundTripper set WithRoundTripper
@@ -342,14 +245,21 @@ func (c *ContinueDownloader) WithoutProxy(noProxy bool) *ContinueDownloader {
342245 return c
343246}
344247
248+ // WithInsecureSkipVerify set if skip the insecure verify
249+ func (c * ContinueDownloader ) WithInsecureSkipVerify (insecureSkipVerify bool ) * ContinueDownloader {
250+ c .insecureSkipVerify = insecureSkipVerify
251+ return c
252+ }
253+
345254// DownloadWithContinue downloads the files continuously
346255func (c * ContinueDownloader ) DownloadWithContinue (targetURL , output string , index , continueAt , end int64 , showProgress bool ) (err error ) {
347256 c .downloader = & HTTPDownloader {
348- TargetFilePath : output ,
349- URL : targetURL ,
350- ShowProgress : showProgress ,
351- NoProxy : c .noProxy ,
352- RoundTripper : c .roundTripper ,
257+ TargetFilePath : output ,
258+ URL : targetURL ,
259+ ShowProgress : showProgress ,
260+ NoProxy : c .noProxy ,
261+ RoundTripper : c .roundTripper ,
262+ InsecureSkipVerify : c .insecureSkipVerify ,
353263 }
354264 if index >= 0 {
355265 c .downloader .Title = fmt .Sprintf ("Downloading part %d" , index )
@@ -371,21 +281,16 @@ func (c *ContinueDownloader) DownloadWithContinue(targetURL, output string, inde
371281 return
372282}
373283
374- // DetectSize returns the size of target resource
375- //
376- // Deprecated, use DetectSizeWithRoundTripper instead
377- func DetectSize (targetURL , output string , showProgress bool ) (int64 , bool , error ) {
378- return DetectSizeWithRoundTripper (targetURL , output , showProgress , false , nil )
379- }
380-
381284// DetectSizeWithRoundTripper returns the size of target resource
382- func DetectSizeWithRoundTripper (targetURL , output string , showProgress bool , noProxy bool , roundTripper http.RoundTripper ) (total int64 , rangeSupport bool , err error ) {
285+ func DetectSizeWithRoundTripper (targetURL , output string , showProgress , noProxy , insecureSkipVerify bool ,
286+ roundTripper http.RoundTripper ) (total int64 , rangeSupport bool , err error ) {
383287 downloader := HTTPDownloader {
384- TargetFilePath : output ,
385- URL : targetURL ,
386- ShowProgress : showProgress ,
387- RoundTripper : roundTripper ,
388- NoProxy : false , // below HTTP request does not need proxy
288+ TargetFilePath : output ,
289+ URL : targetURL ,
290+ ShowProgress : showProgress ,
291+ RoundTripper : roundTripper ,
292+ NoProxy : false , // below HTTP request does not need proxy
293+ InsecureSkipVerify : insecureSkipVerify ,
389294 }
390295
391296 var detectOffset int64
@@ -400,6 +305,8 @@ func DetectSizeWithRoundTripper(targetURL, output string, showProgress bool, noP
400305 contentLen := resp .Header .Get ("Content-Length" )
401306 if total , lenErr = strconv .ParseInt (contentLen , 10 , 0 ); lenErr == nil {
402307 total += detectOffset
308+ } else {
309+ rangeSupport = false
403310 }
404311 // always return false because we just want to get the header from response
405312 return false
0 commit comments