"io"
"os"
"path/filepath"
+ "sync"
+ "time"
"github.com/anacrolix/missinggo/v2"
"github.com/anacrolix/torrent/segments"
)
+var (
+ fdRCache = map[string]*os.File{}
+ fdRCacheM sync.Mutex
+ fdWCache = map[string]*os.File{}
+ fdWCacheM sync.Mutex
+ fdMkdirAllCache = map[string]struct{}{}
+ fdCacheCleanerM sync.Once
+)
+
+func fdCacheCleaner() {
+ for range time.Tick(10 * time.Second) {
+ fdRCacheM.Lock()
+ for _, v := range fdRCache {
+ v.Close()
+ }
+ fdRCache = make(map[string]*os.File)
+ fdRCacheM.Unlock()
+
+ fdWCacheM.Lock()
+ for _, v := range fdWCache {
+ v.Close()
+ }
+ fdWCache = make(map[string]*os.File)
+ fdWCacheM.Unlock()
+ }
+}
+
// File-based storage for torrents, that isn't yet bound to a particular torrent.
type fileClientImpl struct {
opts NewFileClientOpts
if opts.PieceCompletion == nil {
opts.PieceCompletion = pieceCompletionForDir(opts.ClientBaseDir)
}
+ fdCacheCleanerM.Do(func() { go fdCacheCleaner() })
return fileClientImpl{opts}
}
// Returns EOF on short or missing file.
func (fst *fileTorrentImplIO) readFileAt(file file, b []byte, off int64) (n int, err error) {
- f, err := os.Open(file.path)
- if os.IsNotExist(err) {
- // File missing is treated the same as a short file.
- err = io.EOF
- return
- }
- if err != nil {
- return
+ fdRCacheM.Lock()
+ defer fdRCacheM.Unlock()
+ f := fdRCache[file.path]
+ if f == nil {
+ f, err = os.Open(file.path)
+ if os.IsNotExist(err) {
+ // File missing is treated the same as a short file.
+ err = io.EOF
+ return
+ }
+ if err != nil {
+ return
+ }
+ fdRCache[file.path] = f
}
- defer f.Close()
// Limit the read to within the expected bounds of this file.
if int64(len(b)) > file.length-off {
b = b[:file.length-off]
}
- for off < file.length && len(b) != 0 {
- n1, err1 := f.ReadAt(b, off)
- b = b[n1:]
- n += n1
- off += int64(n1)
- if n1 == 0 {
- err = err1
- break
- }
- }
- return
+ return f.ReadAt(b, off)
}
// Only returns EOF at the end of the torrent. Premature EOF is ErrUnexpectedEOF.
}
func (fst fileTorrentImplIO) WriteAt(p []byte, off int64) (n int, err error) {
- // log.Printf("write at %v: %v bytes", off, len(p))
fst.fts.segmentLocater.Locate(segments.Extent{off, int64(len(p))}, func(i int, e segments.Extent) bool {
name := fst.fts.files[i].path
- os.MkdirAll(filepath.Dir(name), 0o777)
- var f *os.File
- f, err = os.OpenFile(name, os.O_WRONLY|os.O_CREATE, 0o666)
- if err != nil {
- return false
+ _, ok := fdMkdirAllCache[filepath.Dir(name)]
+ if !ok {
+ os.MkdirAll(filepath.Dir(name), 0o777)
+ fdMkdirAllCache[filepath.Dir(name)] = struct{}{}
+ }
+ fdWCacheM.Lock()
+ defer fdWCacheM.Unlock()
+ f := fdWCache[name]
+ if f == nil {
+ f, err = os.OpenFile(name, os.O_WRONLY|os.O_CREATE, 0o666)
+ if err != nil {
+ return false
+ }
+ fdWCache[name] = f
}
var n1 int
n1, err = f.WriteAt(p[:e.Length], e.Start)
// log.Printf("%v %v wrote %v: %v", i, e, n1, err)
- closeErr := f.Close()
n += n1
p = p[n1:]
- if err == nil {
- err = closeErr
- }
if err == nil && int64(n1) != e.Length {
err = io.ErrShortWrite
}