Dead, vestigial, and over-built code with no behavior change (except the
bool-vocabulary trim, a deliberate behavior tightening):
- httpdl: drop the vestigial sort+copy in validSegs (work-stealing was
removed in 270812d, so segments always tile [0,total) ascending now);
validate order in place, which also rejects a scrambled sidecar. Inline
the endOff() accessor to s.end.
- cli: shrink the boolean vocabulary to true/false, which is all that's
accepted. The invented yes/no/1/0/on/off spellings are gone, so e.g.
--enable-dht=yes (or yes in got.conf) is now a usage error.
- download: remove the dead, never-called Stat.Done() method.
- progress: drop speed(), a pure alias of humanSize; call humanSize directly.
- main: replace the jobsHolder mutex box with atomic.Pointer[[]job] for the
forced-exit session save; the capability stays, the lock and type go.
- bt: collapse isAddrInUse's X||X (missinggo.IsAddrInUse is that exact
string match); drop the now-unused missinggo import (tidy -> indirect).
- option: note that prealloc and falloc are identical here.
109 lines
3.4 KiB
Go
109 lines
3.4 KiB
Go
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. The segment slice is
|
|
// fixed once the file is divided and each segment's frontier is owned by one
|
|
// worker, so reading written atomically gives a consistent record with no lock.
|
|
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
|
|
}
|
|
// Reject a control file whose segments do not exactly tile [0,total): a
|
|
// truncated/corrupted/hand-edited sidecar that still parses as JSON could
|
|
// otherwise mark a segment done() without its bytes on disk (inflated Written)
|
|
// or leave an un-downloaded hole, both of which would be reported as a complete
|
|
// file. We restart cleanly instead of trusting it.
|
|
if !validSegs(c.Segs, c.Total) {
|
|
return nil
|
|
}
|
|
return &c
|
|
}
|
|
|
|
// validSegs reports whether segs cover [0,total) with no gap or overlap and a
|
|
// sane written count for each. got always writes segments in ascending start
|
|
// order, so coverage is checked in place; an out-of-order sidecar (only possible
|
|
// from a hand-edited or corrupted file) is rejected, which restarts cleanly.
|
|
func validSegs(segs []segState, total int64) bool {
|
|
if total <= 0 || len(segs) == 0 {
|
|
return false
|
|
}
|
|
var next int64
|
|
for _, s := range segs {
|
|
length := s.End - s.Start + 1
|
|
if s.Start != next || s.End < s.Start || s.Written < 0 || s.Written > length {
|
|
return false
|
|
}
|
|
next = s.End + 1
|
|
}
|
|
return next == total
|
|
}
|
|
|
|
func removeControl(out string) { os.Remove(controlPath(out)) }
|