From: Matt Joiner Date: Mon, 11 Aug 2025 06:09:47 +0000 (+1000) Subject: Break out file IO abstraction X-Git-Tag: v1.59.0~2^2~30 X-Git-Url: http://www.git.stargrave.org/?a=commitdiff_plain;h=ecb02b60e921915463d14da26870ce720d2ecce0;p=btrtrc.git Break out file IO abstraction --- diff --git a/storage/file-client.go b/storage/file-client.go index f4ffb037..ee3b5efc 100644 --- a/storage/file-client.go +++ b/storage/file-client.go @@ -104,6 +104,7 @@ func (fs *fileClientImpl) OpenTorrent( metainfoFileInfos, info.FileSegmentsIndex(), infoHash, + classicFileIo{}, fs, } if t.partFiles() { diff --git a/storage/file-io.go b/storage/file-io.go new file mode 100644 index 00000000..5dd6e3c7 --- /dev/null +++ b/storage/file-io.go @@ -0,0 +1,69 @@ +package storage + +import ( + "errors" + "io" + "io/fs" + "os" + "path/filepath" +) + +type fileWriter interface { + io.WriterAt + io.Closer +} + +type fileReader interface { + // Seeks to the next data in the file. If hole-seeking/sparse-files are not supported, should + // seek to the offset. + seekData(offset int64) (ret int64, err error) + io.WriterTo + io.ReadCloser +} + +type fileIo interface { + openForSharedRead(name string) (sharedFileIf, error) + openForRead(name string) (fileReader, error) + openForWrite(name string) (fileWriter, error) +} + +type classicFileIo struct{} + +func (i classicFileIo) openForSharedRead(name string) (sharedFileIf, error) { + return sharedFiles.Open(name) +} + +type classicFileReader struct { + *os.File +} + +func (c classicFileReader) seekData(offset int64) (ret int64, err error) { + return seekData(c.File, offset) +} + +func (i classicFileIo) openForRead(name string) (fileReader, error) { + f, err := os.Open(name) + return classicFileReader{f}, err +} + +func (classicFileIo) openForWrite(p string) (f fileWriter, err error) { + f, err = os.OpenFile(p, os.O_WRONLY|os.O_CREATE, filePerm) + if err == nil { + return + } + if errors.Is(err, fs.ErrNotExist) { + err = os.MkdirAll(filepath.Dir(p), dirPerm) + if err != nil { + return + } + } else if errors.Is(err, fs.ErrPermission) { + err = os.Chmod(p, filePerm) + if err != nil { + return + } + } else { + return + } + f, err = os.OpenFile(p, os.O_WRONLY|os.O_CREATE, filePerm) + return +} diff --git a/storage/file-piece.go b/storage/file-piece.go index 54f1556b..ee2da24e 100644 --- a/storage/file-piece.go +++ b/storage/file-piece.go @@ -304,7 +304,8 @@ func (me *filePieceImpl) writeFileTo(w io.Writer, fileIndex int, extent segments return } file := me.t.file(fileIndex) - var f *os.File + // Do we want io.WriterTo here, or are we happy to let that be type asserted in io.CopyN? + var f fileReader f, err = me.t.openFile(file) if err != nil { if errors.Is(err, fs.ErrNotExist) { @@ -316,7 +317,7 @@ func (me *filePieceImpl) writeFileTo(w io.Writer, fileIndex int, extent segments panicif.GreaterThan(extent.End(), file.FileInfo.Length) extentRemaining := extent.Length var dataOffset int64 - dataOffset, err = seekData(f, extent.Start) + dataOffset, err = f.seekData(extent.Start) if err == io.EOF { return } diff --git a/storage/file-torrent-io.go b/storage/file-torrent-io.go index 095d0b0f..1c7120e4 100644 --- a/storage/file-torrent-io.go +++ b/storage/file-torrent-io.go @@ -4,8 +4,6 @@ import ( "errors" "io" "io/fs" - "os" - "path/filepath" "github.com/anacrolix/missinggo/v2/panicif" "github.com/anacrolix/torrent/segments" @@ -76,37 +74,12 @@ func (fst fileTorrentImplIO) ReadAt(b []byte, off int64) (n int, err error) { return } -func (fst fileTorrentImplIO) openForWrite(file file) (f *os.File, err error) { - // It might be possible to have a writable handle shared files cache if we need it. - fst.fts.logger().Debug("openForWrite", "file.safeOsPath", file.safeOsPath) - p := fst.fts.pathForWrite(&file) - f, err = os.OpenFile(p, os.O_WRONLY|os.O_CREATE, filePerm) - if err == nil { - return - } - if errors.Is(err, fs.ErrNotExist) { - err = os.MkdirAll(filepath.Dir(p), dirPerm) - if err != nil { - return - } - } else if errors.Is(err, fs.ErrPermission) { - err = os.Chmod(p, filePerm) - if err != nil { - return - } - } else { - return - } - f, err = os.OpenFile(p, os.O_WRONLY|os.O_CREATE, filePerm) - return -} - func (fst fileTorrentImplIO) WriteAt(p []byte, off int64) (n int, err error) { for i, e := range fst.fts.segmentLocater.LocateIter( segments.Extent{off, int64(len(p))}, ) { - var f *os.File - f, err = fst.openForWrite(fst.fts.file(i)) + var f fileWriter + f, err = fst.fts.openForWrite(fst.fts.file(i)) if err != nil { return } diff --git a/storage/file-torrent.go b/storage/file-torrent.go index ad9e98f8..0c753ed2 100644 --- a/storage/file-torrent.go +++ b/storage/file-torrent.go @@ -21,6 +21,7 @@ type fileTorrentImpl struct { metainfoFileInfos []metainfo.FileInfo segmentLocater segments.Index infoHash metainfo.Hash + io fileIo // Save memory by pointing to the other data. client *fileClientImpl } @@ -139,26 +140,32 @@ func (me *fileTorrentImpl) openSharedFile(file file) (f sharedFileIf, err error) // Fine to open once under each name on a unix system. We could make the shared file keys more // constrained, but it shouldn't matter. TODO: Ensure at most one of the names exist. if me.partFiles() { - f, err = sharedFiles.Open(file.partFilePath()) + f, err = me.io.openForSharedRead(file.partFilePath()) } if err == nil && f == nil || errors.Is(err, fs.ErrNotExist) { - f, err = sharedFiles.Open(file.safeOsPath) + f, err = me.io.openForSharedRead(file.safeOsPath) } file.mu.RUnlock() return } -// Open file for reading. -func (me *fileTorrentImpl) openFile(file file) (f *os.File, err error) { +// Open file for reading. Not a shared handle if that matters. +func (me *fileTorrentImpl) openFile(file file) (f fileReader, err error) { file.mu.RLock() // Fine to open once under each name on a unix system. We could make the shared file keys more // constrained, but it shouldn't matter. TODO: Ensure at most one of the names exist. if me.partFiles() { - f, err = os.Open(file.partFilePath()) + f, err = me.io.openForRead(file.partFilePath()) } if err == nil && f == nil || errors.Is(err, fs.ErrNotExist) { - f, err = os.Open(file.safeOsPath) + f, err = me.io.openForRead(file.safeOsPath) } file.mu.RUnlock() return } + +func (fst fileTorrentImpl) openForWrite(file file) (_ fileWriter, err error) { + // It might be possible to have a writable handle shared files cache if we need it. + fst.logger().Debug("openForWrite", "file.safeOsPath", file.safeOsPath) + return fst.io.openForWrite(fst.pathForWrite(&file)) +}