GetControlFile reads a zsync control file using the provided HTTPRequester. If keepZsync is non-empty, the control file is also cached locally. The returned string is the referer URL that should be used when resolving any relative URLs contained in the control file. This is a helper function that is
(client HTTPRequester, source, keepZsync, referer string)
| 27 | // with a different client. This is essentially just a HTTP downloader with |
| 28 | // mtime/If-Modified-Since caching.. |
| 29 | func GetControlFile(client HTTPRequester, source, keepZsync, referer string) (io.ReadCloser, string, error) { |
| 30 | u, err := url.Parse(source) |
| 31 | if err != nil || u.Scheme == "" { |
| 32 | return nil, "", fmt.Errorf("%s is not a valid URL", source) |
| 33 | } |
| 34 | |
| 35 | var fileInfo os.FileInfo |
| 36 | gotMtime := false |
| 37 | if keepZsync != "" { |
| 38 | fileInfo, err = os.Stat(keepZsync) |
| 39 | if err != nil && !os.IsNotExist(err) { |
| 40 | fmt.Fprintf(os.Stderr, "failed to get mtime for existing .zsync control file: %v", err) |
| 41 | // Fall through with no mtime - we can just download the control file. |
| 42 | } else if err == nil { |
| 43 | gotMtime = true |
| 44 | } |
| 45 | } |
| 46 | |
| 47 | req, err := http.NewRequest("GET", source, nil) |
| 48 | if err != nil { |
| 49 | return nil, "", fmt.Errorf("failed to form HTTP request: %w", err) |
| 50 | } |
| 51 | if referer != "" { |
| 52 | req.Header.Set("Referer", referer) |
| 53 | } |
| 54 | if gotMtime { |
| 55 | req.Header.Set("If-Modified-Since", fileInfo.ModTime().UTC().Format(time.RFC1123)) |
| 56 | } |
| 57 | |
| 58 | resp, err := client.Do(req) |
| 59 | if err != nil { |
| 60 | return nil, "", fmt.Errorf("HTTP request failed: %w", err) |
| 61 | } |
| 62 | if keepZsync != "" && resp.StatusCode == http.StatusNotModified { |
| 63 | // Not modified, so we can drop the response and read the local copy. |
| 64 | fmt.Fprintf(os.Stderr, "control file not modified - using local copy\n") |
| 65 | _ = resp.Body.Close() |
| 66 | f, err := os.Open(keepZsync) |
| 67 | return f, source, err |
| 68 | } |
| 69 | // Otherwise we need the response. |
| 70 | if resp.StatusCode != http.StatusOK { |
| 71 | _ = resp.Body.Close() |
| 72 | return nil, "", fmt.Errorf("failed to download .zsync: %s", resp.Status) |
| 73 | } |
| 74 | |
| 75 | // If we are not saving a local copy of the .zsync file, we can just |
| 76 | // pass the response body reader to the caller. |
| 77 | if keepZsync == "" { |
| 78 | return resp.Body, source, nil |
| 79 | } |
| 80 | |
| 81 | controlFile, err := os.Create(keepZsync) |
| 82 | if err != nil { |
| 83 | return nil, "", fmt.Errorf("zsync local file creation failed: %w", err) |
| 84 | } |
| 85 | // Copy zsync file from response to temporary file, then seek back to the |
| 86 | // start for reading. |
no test coverage detected