"context"
"fmt"
"log/slog"
+ "os"
"path/filepath"
g "github.com/anacrolix/generics"
return me.opts.PieceCompletion.Close()
}
+var defaultFileIo func() fileIo = func() fileIo {
+ return classicFileIo{}
+}
+
+func init() {
+ s, ok := os.LookupEnv("TORRENT_STORAGE_DEFAULT_FILE_IO")
+ if !ok {
+ return
+ }
+ switch s {
+ case "mmap":
+ defaultFileIo = func() fileIo {
+ return &mmapFileIo{}
+ }
+ case "classic":
+ defaultFileIo = func() fileIo {
+ return classicFileIo{}
+ }
+ default:
+ panic(s)
+ }
+}
+
func (fs *fileClientImpl) OpenTorrent(
ctx context.Context,
info *metainfo.Info,
metainfoFileInfos,
info.FileSegmentsIndex(),
infoHash,
- classicFileIo{},
+ defaultFileIo(),
fs,
}
if t.partFiles() {
package storage
import (
- "errors"
"io"
- "io/fs"
- "os"
- "path/filepath"
)
type fileWriter interface {
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
+ openForWrite(name string, size int64) (fileWriter, error)
}
packageExpvarMap = expvar.NewMap("torrentStorage")
)
+type limitWriter struct {
+ rem int64
+ w io.Writer
+}
+
+func (me *limitWriter) Write(p []byte) (n int, err error) {
+ n, err = me.w.Write(p[:min(int64(len(p)), me.rem)])
+ me.rem -= int64(n)
+ if err != nil {
+ return
+ }
+ p = p[n:]
+ if len(p) > 0 {
+ err = io.ErrShortWrite
+ }
+ return
+}
+
func (me *filePieceImpl) writeFileTo(w io.Writer, fileIndex int, extent segments.Extent) (written int64, err error) {
if extent.Length == 0 {
return
extentRemaining -= n1
}
var n1 int64
- n1, err = io.CopyN(w, f, extentRemaining)
+ if true {
+ n1, err = f.WriteTo(&limitWriter{
+ rem: extentRemaining,
+ w: w,
+ })
+ // limitWriter will block f from writing too much.
+ if n1 == extentRemaining {
+ err = nil
+ }
+ } else {
+ n1, err = io.CopyN(w, f, extentRemaining)
+ }
packageExpvarMap.Add("bytesReadNotSkipped", n1)
written += n1
return
return
}
-func (fst fileTorrentImpl) openForWrite(file file) (_ fileWriter, err error) {
+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))
+ return fst.io.openForWrite(fst.pathForWrite(&file), file.FileInfo.Length)
}
package torrent
+// TODO: This should implement extra methods to make io.CopyN more efficient.
var zeroReader zeroReaderType
type zeroReaderType struct{}