first commit
This commit is contained in:
79
httpdl/control.go
Normal file
79
httpdl/control.go
Normal file
@@ -0,0 +1,79 @@
|
||||
package httpdl
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
// control is the on-disk resume state, a small JSON sidecar next to the output
|
||||
// file (<out>.got). It is a Go-simple binary-free control file: enough to skip
|
||||
// finished segments and resume partial ones, plus validators (Total +
|
||||
// ETag/LastModified) so we never trust a stale file.
|
||||
type control struct {
|
||||
URL string `json:"url"`
|
||||
Total int64 `json:"total"`
|
||||
ETag string `json:"etag,omitempty"`
|
||||
LastModified string `json:"last_modified,omitempty"`
|
||||
Segs []segState `json:"segs"`
|
||||
}
|
||||
|
||||
type segState struct {
|
||||
Start int64 `json:"start"`
|
||||
End int64 `json:"end"`
|
||||
Written int64 `json:"written"`
|
||||
}
|
||||
|
||||
func controlPath(out string) string { return out + ".got" }
|
||||
|
||||
// snapshot builds a control record from the live segments.
|
||||
func snapshot(url string, total int64, etag, lastmod string, segs []seg) control {
|
||||
c := control{URL: url, Total: total, ETag: etag, LastModified: lastmod, Segs: make([]segState, len(segs))}
|
||||
for i := range segs {
|
||||
c.Segs[i] = segState{segs[i].start, segs[i].end, atomic.LoadInt64(&segs[i].written)}
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// save writes the control file atomically (temp + rename).
|
||||
func (c control) save(out string) error {
|
||||
data, err := json.Marshal(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.MkdirAll(filepath.Dir(out), 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
tmp := controlPath(out) + ".tmp"
|
||||
if err := os.WriteFile(tmp, data, 0o644); err != nil {
|
||||
return err
|
||||
}
|
||||
return os.Rename(tmp, controlPath(out))
|
||||
}
|
||||
|
||||
// loadControl reads a control file if it is present and still matches the
|
||||
// download (same URL, length and validator). It returns nil when there is
|
||||
// nothing trustworthy to resume from.
|
||||
func loadControl(out, url string, total int64, etag, lastmod string) *control {
|
||||
data, err := os.ReadFile(controlPath(out))
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
var c control
|
||||
if json.Unmarshal(data, &c) != nil {
|
||||
return nil
|
||||
}
|
||||
if c.URL != url || c.Total != total {
|
||||
return nil
|
||||
}
|
||||
if (etag != "" || c.ETag != "") && c.ETag != etag {
|
||||
return nil
|
||||
}
|
||||
if (lastmod != "" || c.LastModified != "") && c.LastModified != lastmod {
|
||||
return nil
|
||||
}
|
||||
return &c
|
||||
}
|
||||
|
||||
func removeControl(out string) { os.Remove(controlPath(out)) }
|
||||
Reference in New Issue
Block a user