From: Matt Joiner Date: Fri, 23 May 2025 04:40:18 +0000 (+1000) Subject: Derive initial completion from part files by default X-Git-Tag: v1.59.0~123 X-Git-Url: http://www.git.stargrave.org/?a=commitdiff_plain;h=d5114e4a6eaaa463bd64c91c308ab81ff8201b0e;p=btrtrc.git Derive initial completion from part files by default --- diff --git a/storage/file-piece.go b/storage/file-piece.go index c8d2dce5..24e28f7f 100644 --- a/storage/file-piece.go +++ b/storage/file-piece.go @@ -49,7 +49,7 @@ func (me *filePieceImpl) pieceFiles() iter.Seq2[int, file] { } func (me *filePieceImpl) pieceCompletion() PieceCompletion { - return me.t.client.opts.PieceCompletion + return me.t.pieceCompletion() } func (me *filePieceImpl) Completion() Completion { diff --git a/storage/file.go b/storage/file.go index c905be12..f823e831 100644 --- a/storage/file.go +++ b/storage/file.go @@ -39,6 +39,11 @@ type NewFileClientOpts struct { Logger *slog.Logger } +// The specific part-files option or the default. +func (me NewFileClientOpts) partFiles() bool { + return me.UsePartFiles.UnwrapOr(true) +} + // NewFileOpts creates a new ClientImplCloser that stores files using the OS native filesystem. func NewFileOpts(opts NewFileClientOpts) ClientImplCloser { if opts.TorrentDirMaker == nil { @@ -54,7 +59,11 @@ func NewFileOpts(opts NewFileClientOpts) ClientImplCloser { } } if opts.PieceCompletion == nil { - opts.PieceCompletion = pieceCompletionForDir(opts.ClientBaseDir) + if opts.partFiles() { + opts.PieceCompletion = NewMapPieceCompletion() + } else { + opts.PieceCompletion = pieceCompletionForDir(opts.ClientBaseDir) + } } if opts.Logger == nil { opts.Logger = log.Default.Slogger() @@ -118,6 +127,13 @@ func (fs *fileClientImpl) OpenTorrent( infoHash, fs, } + if t.partFiles() { + err = t.setCompletionFromPartFiles() + if err != nil { + err = fmt.Errorf("setting completion from part files: %w", err) + return + } + } return TorrentImpl{ Piece: t.Piece, Close: t.Close, @@ -146,8 +162,57 @@ type fileTorrentImpl struct { client *fileClientImpl } +func (fts *fileTorrentImpl) logger() *slog.Logger { + return fts.client.opts.Logger +} + +func (fts *fileTorrentImpl) pieceCompletion() PieceCompletion { + return fts.client.opts.PieceCompletion +} + +func (fts *fileTorrentImpl) pieceCompletionKey(p int) metainfo.PieceKey { + return metainfo.PieceKey{ + InfoHash: fts.infoHash, + Index: p, + } +} + +func (fts *fileTorrentImpl) setPieceCompletion(p int, complete bool) error { + return fts.pieceCompletion().Set(fts.pieceCompletionKey(p), complete) +} + +// Set piece completions based on whether all files in each piece are not .part files. +func (fts *fileTorrentImpl) setCompletionFromPartFiles() error { + notComplete := make([]bool, fts.info.NumPieces()) + for _, f := range fts.files { + fi, err := os.Stat(f.safeOsPath) + if err == nil { + if fi.Size() == f.length { + continue + } + fts.logger().Warn("file has unexpected size", "file", f.safeOsPath, "size", fi.Size(), "expected", f.length) + } else if !errors.Is(err, fs.ErrNotExist) { + fts.logger().Warn("error checking file size", "err", err) + } + for i := f.beginPieceIndex; i < f.endPieceIndex; i++ { + notComplete[i] = true + } + } + for i, nc := range notComplete { + if nc { + // Use whatever the piece completion has, or trigger a hash. + continue + } + err := fts.setPieceCompletion(i, true) + if err != nil { + return fmt.Errorf("setting piece %v completion: %w", i, err) + } + } + return nil +} + func (fts *fileTorrentImpl) partFiles() bool { - return fts.client.opts.UsePartFiles.UnwrapOr(true) + return fts.client.opts.partFiles() } func (fts *fileTorrentImpl) pathForWrite(f file) string { @@ -158,9 +223,7 @@ func (fts *fileTorrentImpl) pathForWrite(f file) string { } func (fts *fileTorrentImpl) getCompletion(piece int) Completion { - cmpl, err := fts.client.opts.PieceCompletion.Get(metainfo.PieceKey{ - fts.infoHash, piece, - }) + cmpl, err := fts.pieceCompletion().Get(metainfo.PieceKey{fts.infoHash, piece}) cmpl.Err = errors.Join(cmpl.Err, err) return cmpl }