mirror of
https://github.com/GopeedLab/gopeed.git
synced 2026-11-12 19:33:41 +00:00
fix(http): re-probe range support after sequential retry (#1464)
This commit is contained in:
5 files changed
+1245
-41
No files matched your search
@@ -14,8 +14,15 @@ import (
|
||||
// ============================================================================
|
||||
|
||||
type fetcherData struct {
|
||||
Connections []*connection
|
||||
RedirectURL string // Saved redirect URL for resume
|
||||
Connections []*connection
|
||||
RedirectURL string // Saved redirect URL for resume
|
||||
IfRange string // Strong ETag or Last-Modified validator for safe resume
|
||||
RangeReprobeEligible bool // Origin advertised Range before falling back to sequential mode
|
||||
RangeValidatorPinned bool // Recovered Range mode must keep the same If-Range validator
|
||||
SequentialSizeUnknown bool // Interrupted chunked restart must retry from byte zero
|
||||
Range *bool // Authoritative Range mode; nil for records saved by older versions
|
||||
ResourceSize *int64 // Authoritative resource size from the same snapshot as Connections
|
||||
FileSize *int64 // Authoritative single-file size; nil for older records
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
@@ -75,12 +82,69 @@ func (fm *FetcherManager) Store(f fetcher.Fetcher) (data any, err error) {
|
||||
_f.redirectLock.Lock()
|
||||
redirectURL := _f.redirectURL
|
||||
_f.redirectLock.Unlock()
|
||||
|
||||
// Build an immutable snapshot while holding the same lock used by download
|
||||
// workers to update Range mode and connection progress. The storage layer
|
||||
// marshals this value after Store returns, so returning live pointers would
|
||||
// otherwise race with the active download and could persist a mixed state.
|
||||
_f.connMu.Lock()
|
||||
connections := snapshotConnectionsLocked(_f.connections)
|
||||
ifRange := _f.ifRange
|
||||
rangeReprobeEligible := _f.rangeReprobeEligible
|
||||
rangeValidatorPinned := _f.rangeValidatorPinned
|
||||
sequentialSizeUnknown := _f.sequentialSizeUnknown
|
||||
var rangeMode *bool
|
||||
var resourceSize *int64
|
||||
var fileSize *int64
|
||||
if _f.meta != nil && _f.meta.Res != nil {
|
||||
value := _f.meta.Res.Range
|
||||
rangeMode = &value
|
||||
size := _f.meta.Res.Size
|
||||
resourceSize = &size
|
||||
if len(_f.meta.Res.Files) > 0 && _f.meta.Res.Files[0] != nil {
|
||||
size := _f.meta.Res.Files[0].Size
|
||||
fileSize = &size
|
||||
}
|
||||
}
|
||||
_f.connMu.Unlock()
|
||||
|
||||
return &fetcherData{
|
||||
Connections: _f.connections,
|
||||
RedirectURL: redirectURL,
|
||||
Connections: connections,
|
||||
RedirectURL: redirectURL,
|
||||
IfRange: ifRange,
|
||||
RangeReprobeEligible: rangeReprobeEligible,
|
||||
RangeValidatorPinned: rangeValidatorPinned,
|
||||
SequentialSizeUnknown: sequentialSizeUnknown,
|
||||
Range: rangeMode,
|
||||
ResourceSize: resourceSize,
|
||||
FileSize: fileSize,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// snapshotConnectionsLocked copies only the serialized connection state and
|
||||
// deep-copies chunks. The caller must hold the fetcher's connMu.
|
||||
func snapshotConnectionsLocked(connections []*connection) []*connection {
|
||||
snapshot := make([]*connection, len(connections))
|
||||
for i, conn := range connections {
|
||||
if conn == nil {
|
||||
continue
|
||||
}
|
||||
copyConn := &connection{
|
||||
ID: conn.ID,
|
||||
Role: conn.Role,
|
||||
State: conn.State,
|
||||
Downloaded: conn.Downloaded,
|
||||
Completed: conn.Completed,
|
||||
}
|
||||
if conn.Chunk != nil {
|
||||
copyChunk := *conn.Chunk
|
||||
copyConn.Chunk = ©Chunk
|
||||
}
|
||||
snapshot[i] = copyConn
|
||||
}
|
||||
return snapshot
|
||||
}
|
||||
|
||||
func (fm *FetcherManager) Restore() (v any, f func(meta *fetcher.FetcherMeta, v any) fetcher.Fetcher) {
|
||||
return &fetcherData{}, func(meta *fetcher.FetcherMeta, v any) fetcher.Fetcher {
|
||||
fd := v.(*fetcherData)
|
||||
@@ -96,6 +160,22 @@ func (fm *FetcherManager) Restore() (v any, f func(meta *fetcher.FetcherMeta, v
|
||||
if fd.RedirectURL != "" {
|
||||
fetcher.redirectURL = fd.RedirectURL
|
||||
}
|
||||
fetcher.ifRange = fd.IfRange
|
||||
fetcher.rangeReprobeEligible = fd.RangeReprobeEligible
|
||||
fetcher.rangeValidatorPinned = fd.RangeValidatorPinned
|
||||
fetcher.sequentialSizeUnknown = fd.SequentialSizeUnknown
|
||||
if fd.Range != nil && fetcher.meta.Res != nil {
|
||||
// Recovery-critical resource fields live in the same persisted snapshot
|
||||
// as Connections, making them authoritative over task metadata that may
|
||||
// have been saved just before or after this record.
|
||||
fetcher.meta.Res.Range = *fd.Range
|
||||
}
|
||||
if fd.ResourceSize != nil && fetcher.meta.Res != nil {
|
||||
fetcher.meta.Res.Size = *fd.ResourceSize
|
||||
}
|
||||
if fd.FileSize != nil && fetcher.meta.Res != nil && len(fetcher.meta.Res.Files) > 0 && fetcher.meta.Res.Files[0] != nil {
|
||||
fetcher.meta.Res.Files[0].Size = *fd.FileSize
|
||||
}
|
||||
return fetcher
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user