106 lines
2.1 KiB
Go
106 lines
2.1 KiB
Go
package cache
|
|
|
|
import (
|
|
"io"
|
|
"net/http"
|
|
"os"
|
|
"strconv"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// DownloadError conveys information about a download request that failed.
|
|
type DownloadError struct {
|
|
Status string
|
|
}
|
|
|
|
// Error returns a description of the error.
|
|
func (d *DownloadError) Error() string {
|
|
return d.Status
|
|
}
|
|
|
|
// downloader attempts to download a file from a remote URL.
|
|
type downloader struct {
|
|
doneMutex sync.Mutex
|
|
err error
|
|
entry *Entry
|
|
entryMutex sync.Mutex
|
|
}
|
|
|
|
// newDownloader creates a new downloader.
|
|
func newDownloader(rawurl, jsonFilename, dataFilename string) *downloader {
|
|
d := &downloader{}
|
|
d.doneMutex.Lock()
|
|
d.entryMutex.Lock()
|
|
go func() {
|
|
defer func() {
|
|
d.doneMutex.Unlock()
|
|
}()
|
|
once := &sync.Once{}
|
|
trigger := func() {
|
|
once.Do(func() {
|
|
d.entryMutex.Unlock()
|
|
})
|
|
}
|
|
defer trigger()
|
|
resp, err := http.Get(rawurl)
|
|
if err != nil {
|
|
d.err = err
|
|
return
|
|
}
|
|
defer resp.Body.Close()
|
|
if resp.StatusCode != 200 {
|
|
d.err = &DownloadError{
|
|
Status: resp.Status,
|
|
}
|
|
return
|
|
}
|
|
f, err := os.Create(dataFilename)
|
|
if err != nil {
|
|
d.err = err
|
|
return
|
|
}
|
|
defer f.Close()
|
|
d.entry = &Entry{
|
|
URL: rawurl,
|
|
ContentLength: strconv.FormatInt(resp.ContentLength, 10),
|
|
ContentType: resp.Header.Get("Content-Type"),
|
|
LastModified: resp.Header.Get("Last-Modified"),
|
|
}
|
|
if d.entry.ContentType == "" {
|
|
d.entry.ContentType = "application/octet-stream"
|
|
}
|
|
if d.entry.LastModified == "" {
|
|
d.entry.LastModified = time.Now().Format(http.TimeFormat)
|
|
}
|
|
if err = d.entry.Save(jsonFilename); err != nil {
|
|
d.err = err
|
|
return
|
|
}
|
|
trigger()
|
|
n, err := io.Copy(f, resp.Body)
|
|
if err != nil {
|
|
d.err = err
|
|
return
|
|
}
|
|
d.entry.ContentLength = strconv.FormatInt(n, 10)
|
|
d.entry.Complete = true
|
|
d.entry.Save(jsonFilename)
|
|
}()
|
|
return d
|
|
}
|
|
|
|
// GetEntry retrieves the entry associated with the download.
|
|
func (d *downloader) GetEntry() (*Entry, error) {
|
|
d.entryMutex.Lock()
|
|
defer d.entryMutex.Unlock()
|
|
return d.entry, d.err
|
|
}
|
|
|
|
// WaitForDone will block until the download completes.
|
|
func (d *downloader) WaitForDone() error {
|
|
d.doneMutex.Lock()
|
|
defer d.doneMutex.Unlock()
|
|
return d.err
|
|
}
|