github.com/anacrolix/torrent@v1.61.0/storage/file-torrent.go (about)

     1  package storage
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"io"
     7  	"io/fs"
     8  	"log/slog"
     9  	"os"
    10  
    11  	"github.com/anacrolix/missinggo/v2"
    12  	"github.com/anacrolix/missinggo/v2/panicif"
    13  
    14  	"github.com/anacrolix/torrent/metainfo"
    15  	"github.com/anacrolix/torrent/segments"
    16  )
    17  
    18  type fileTorrentImpl struct {
    19  	info              *metainfo.Info
    20  	files             []fileExtra
    21  	metainfoFileInfos []metainfo.FileInfo
    22  	segmentLocater    segments.Index
    23  	infoHash          metainfo.Hash
    24  	io                fileIo
    25  	// Save memory by pointing to the other data.
    26  	client *fileClientImpl
    27  }
    28  
    29  func (fts *fileTorrentImpl) logger() *slog.Logger {
    30  	return fts.client.opts.Logger
    31  }
    32  
    33  func (fts *fileTorrentImpl) pieceCompletion() PieceCompletion {
    34  	return fts.client.opts.PieceCompletion
    35  }
    36  
    37  func (fts *fileTorrentImpl) pieceCompletionKey(p int) metainfo.PieceKey {
    38  	return metainfo.PieceKey{
    39  		InfoHash: fts.infoHash,
    40  		Index:    p,
    41  	}
    42  }
    43  
    44  func (fts *fileTorrentImpl) setPieceCompletion(p int, complete bool) error {
    45  	return fts.pieceCompletion().Set(fts.pieceCompletionKey(p), complete)
    46  }
    47  
    48  // Set piece completions based on whether all files in each piece are not .part files.
    49  func (fts *fileTorrentImpl) setCompletionFromPartFiles() error {
    50  	notComplete := make([]bool, fts.info.NumPieces())
    51  	for fileIndex := range fts.files {
    52  		f := fts.file(fileIndex)
    53  		fi, err := os.Stat(f.safeOsPath)
    54  		if err == nil {
    55  			if fi.Size() == f.length() {
    56  				continue
    57  			}
    58  			fts.logger().Warn("file has unexpected size", "file", f.safeOsPath, "size", fi.Size(), "expected", f.length())
    59  		} else if !errors.Is(err, fs.ErrNotExist) {
    60  			fts.logger().Warn("error checking file size", "err", err)
    61  		}
    62  		// Ensure all pieces associated with a file are not marked as complete (at most unknown).
    63  		for pieceIndex := f.beginPieceIndex(); pieceIndex < f.endPieceIndex(); pieceIndex++ {
    64  			notComplete[pieceIndex] = true
    65  		}
    66  	}
    67  	for i, nc := range notComplete {
    68  		if nc {
    69  			c := fts.getCompletion(i)
    70  			if c.Complete {
    71  				// TODO: We need to set unknown so that verification of the data we do have could
    72  				// occur naturally but that'll be a big change.
    73  				panicif.Err(fts.setPieceCompletion(i, false))
    74  			}
    75  		} else {
    76  			err := fts.setPieceCompletion(i, true)
    77  			if err != nil {
    78  				return fmt.Errorf("setting piece %v completion: %w", i, err)
    79  			}
    80  		}
    81  	}
    82  	return nil
    83  }
    84  
    85  func (fts *fileTorrentImpl) partFiles() bool {
    86  	return fts.client.opts.partFiles()
    87  }
    88  
    89  func (fts *fileTorrentImpl) pathForWrite(f *file) string {
    90  	if fts.partFiles() {
    91  		return f.partFilePath()
    92  	}
    93  	return f.safeOsPath
    94  }
    95  
    96  func (fts *fileTorrentImpl) getCompletion(piece int) Completion {
    97  	cmpl, err := fts.pieceCompletion().Get(metainfo.PieceKey{fts.infoHash, piece})
    98  	cmpl.Err = errors.Join(cmpl.Err, err)
    99  	return cmpl
   100  }
   101  
   102  func (fts *fileTorrentImpl) Piece(p metainfo.Piece) PieceImpl {
   103  	// Create a view onto the file-based torrent storage.
   104  	_io := fileTorrentImplIO{fts}
   105  	// Return the appropriate segments of this.
   106  	return &filePieceImpl{
   107  		fts,
   108  		p,
   109  		missinggo.NewSectionWriter(_io, p.Offset(), p.Length()),
   110  		io.NewSectionReader(_io, p.Offset(), p.Length()),
   111  	}
   112  }
   113  
   114  func (fs *fileTorrentImpl) Close() error {
   115  	return nil
   116  }
   117  
   118  func (fts *fileTorrentImpl) file(index int) file {
   119  	return file{
   120  		Info:      fts.info,
   121  		FileInfo:  &fts.metainfoFileInfos[index],
   122  		fileExtra: &fts.files[index],
   123  	}
   124  }
   125  
   126  // Open file for reading.
   127  func (me *fileTorrentImpl) openSharedFile(file file) (f sharedFileIf, err error) {
   128  	file.mu.RLock()
   129  	// Fine to open once under each name on a unix system. We could make the shared file keys more
   130  	// constrained, but it shouldn't matter. TODO: Ensure at most one of the names exist.
   131  	if me.partFiles() {
   132  		f, err = me.io.openForSharedRead(file.partFilePath())
   133  	}
   134  	if err == nil && f == nil || errors.Is(err, fs.ErrNotExist) {
   135  		f, err = me.io.openForSharedRead(file.safeOsPath)
   136  	}
   137  	file.mu.RUnlock()
   138  	return
   139  }
   140  
   141  // Open file for reading. Not a shared handle if that matters.
   142  func (me *fileTorrentImpl) openFile(file file) (f fileReader, err error) {
   143  	file.mu.RLock()
   144  	// Fine to open once under each name on a unix system. We could make the shared file keys more
   145  	// constrained, but it shouldn't matter. TODO: Ensure at most one of the names exist.
   146  	if me.partFiles() {
   147  		f, err = me.io.openForRead(file.partFilePath())
   148  	}
   149  	if err == nil && f == nil || errors.Is(err, fs.ErrNotExist) {
   150  		f, err = me.io.openForRead(file.safeOsPath)
   151  	}
   152  	file.mu.RUnlock()
   153  	return
   154  }
   155  
   156  func (fst *fileTorrentImpl) openForWrite(file file) (_ fileWriter, err error) {
   157  	// It might be possible to have a writable handle shared files cache if we need it.
   158  	fst.logger().Debug("openForWrite", "file.safeOsPath", file.safeOsPath)
   159  	return fst.io.openForWrite(fst.pathForWrite(&file), file.FileInfo.Length)
   160  }