]> Sergey Matveev's repositories - btrtrc.git/commitdiff
Break out file IO abstraction
authorMatt Joiner <anacrolix@gmail.com>
Mon, 11 Aug 2025 06:09:47 +0000 (16:09 +1000)
committerMatt Joiner <anacrolix@gmail.com>
Mon, 11 Aug 2025 06:09:47 +0000 (16:09 +1000)
storage/file-client.go
storage/file-io.go [new file with mode: 0644]
storage/file-piece.go
storage/file-torrent-io.go
storage/file-torrent.go

index f4ffb0378b091ea01ee7f1aaaac459a560b1d2f9..ee3b5efc825976875650a85e4b672f18b54e5bce 100644 (file)
@@ -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 (file)
index 0000000..5dd6e3c
--- /dev/null
@@ -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
+}
index 54f1556b02d8792ea7b8021dafcb19baa058dee7..ee2da24e7dd2850bc08d60ee79da89eca86466f8 100644 (file)
@@ -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
        }
index 095d0b0ff3ed6feb6ae26681e2d079ca658b04eb..1c7120e4218c166a9d699423ba95a06e3d7080bb 100644 (file)
@@ -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
                }
index ad9e98f8863b1970c28351e15a3bf58bd1588381..0c753ed2f70ba5b6b7eb2e812b2f493cd9990127 100644 (file)
@@ -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))
+}