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 }