// Require pieces to be checked as soon as info is available. This is because we have no way to
// schedule an initial check only, and don't want to race against use of Torrent.Complete.
IgnoreUnverifiedPieceCompletion bool
+ // Whether to initially allow data download or upload
+ DisallowDataUpload bool
+ DisallowDataDownload bool
}
// Add or merge a torrent spec. Returns new if the torrent wasn't already in the client. See also
}
// The trackers will be merged with the existing ones. If the Info isn't yet known, it will be set.
-// spec.DisallowDataDownload/Upload will be read and applied
// The display name is replaced if the new spec provides one. Note that any `Storage` is ignored.
+// Many fields in the AddTorrentOpts field in TorrentSpec are ignored because the Torrent is already
+// added.
func (t *Torrent) MergeSpec(spec *TorrentSpec) error {
if spec.DisplayName != "" {
t.SetDisplayName(spec.DisplayName)
}
t.addTrackers(spec.Trackers)
t.maybeNewConns()
- t.dataDownloadDisallowed.SetBool(spec.DisallowDataDownload)
- t.dataUploadDisallowed = spec.DisallowDataUpload
return errors.Join(t.addPieceLayersLocked(spec.PieceLayers)...)
}
--- /dev/null
+package storage
+
+import (
+ "errors"
+ "io/fs"
+ "os"
+ "path/filepath"
+
+ "github.com/anacrolix/missinggo/v2/panicif"
+)
+
+// Opens file for write, creating dirs and fixing permissions as necessary.
+func openFileExtra(p string, osRdwr int) (f *os.File, err error) {
+ panicif.NotZero(osRdwr & ^(os.O_RDONLY | os.O_RDWR | os.O_WRONLY))
+ flag := osRdwr | os.O_CREATE
+ f, err = os.OpenFile(p, flag, 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, flag, filePerm)
+ return
+}
--- /dev/null
+package storage
+
+import (
+ "fmt"
+ "io"
+ "io/fs"
+ "os"
+ "sync"
+ "sync/atomic"
+
+ g "github.com/anacrolix/generics"
+ "github.com/anacrolix/missinggo/v2/panicif"
+ "github.com/edsrzf/mmap-go"
+)
+
+type mmapFileIo struct {
+ mu sync.Mutex
+ paths map[string]*fileMmap
+}
+
+type fileMmap struct {
+ m mmap.MMap
+ writable bool
+ refs atomic.Int32
+}
+
+func (me *fileMmap) dec() error {
+ if me.refs.Add(-1) == 0 {
+ return me.m.Unmap()
+ }
+ return nil
+}
+
+func (me *fileMmap) inc() {
+ panicif.LessThanOrEqual(me.refs.Add(1), 0)
+}
+
+func (m *mmapFileIo) openForSharedRead(name string) (_ sharedFileIf, err error) {
+ return m.openReadOnly(name)
+}
+
+func (m *mmapFileIo) openForRead(name string) (_ fileReader, err error) {
+ sh, err := m.openReadOnly(name)
+ if err != nil {
+ return
+ }
+ return &mmapFileHandle{
+ shared: sh,
+ }, nil
+}
+
+func (m *mmapFileIo) openReadOnly(name string) (_ *mmapSharedFileHandle, err error) {
+ m.mu.Lock()
+ defer m.mu.Unlock()
+ v, ok := m.paths[name]
+ if ok {
+ return newMmapFile(v), nil
+ }
+ f, err := os.Open(name)
+ if err != nil {
+ return
+ }
+ defer f.Close()
+ mm, err := mmap.Map(f, mmap.RDONLY, 0)
+ if err != nil {
+ err = fmt.Errorf("mapping file: %w", err)
+ return
+ }
+ v = m.addNewMmap(name, mm, false)
+ return newMmapFile(v), nil
+}
+
+func (m *mmapFileIo) openForWrite(name string, size int64) (_ fileWriter, err error) {
+ m.mu.Lock()
+ defer m.mu.Unlock()
+ v, ok := m.paths[name]
+ if ok {
+ if int64(len(v.m)) == size && v.writable {
+ v.inc()
+ return newMmapFile(v), nil
+ } else {
+ v.dec()
+ g.MustDelete(m.paths, name)
+ }
+ }
+ f, err := openFileExtra(name, os.O_RDWR)
+ if err != nil {
+ return
+ }
+ defer f.Close()
+ err = f.Truncate(size)
+ if err != nil {
+ err = fmt.Errorf("error truncating file: %w", err)
+ return
+ }
+ mm, err := mmap.Map(f, mmap.RDWR, 0)
+ if err != nil {
+ return
+ }
+ // This can happen due to filesystem changes outside our control. Don't be naive.
+ if int64(len(mm)) != size {
+ err = fmt.Errorf("new mmap has wrong size %v, expected %v", len(mm), size)
+ mm.Unmap()
+ return
+ }
+ return newMmapFile(m.addNewMmap(name, mm, true)), nil
+}
+
+func newMmapFile(f *fileMmap) *mmapSharedFileHandle {
+ ret := &mmapSharedFileHandle{
+ f: f,
+ }
+ ret.f.inc()
+ return ret
+}
+
+func (me *mmapFileIo) addNewMmap(name string, mm mmap.MMap, writable bool) *fileMmap {
+ v := &fileMmap{
+ m: mm,
+ writable: writable,
+ }
+ // One for the store, one for the caller.
+ v.refs.Store(1)
+ g.MakeMapIfNil(&me.paths)
+ g.MapMustAssignNew(me.paths, name, v)
+ return v
+}
+
+var _ fileIo = (*mmapFileIo)(nil)
+
+type mmapSharedFileHandle struct {
+ f *fileMmap
+ close sync.Once
+}
+
+func (m *mmapSharedFileHandle) WriteAt(p []byte, off int64) (n int, err error) {
+ //fmt.Println("mmapSharedFileHandle.WriteAt", off, len(p), len(m.f.m))
+ n = copy(m.f.m[off:], p)
+ return
+}
+
+func (m *mmapSharedFileHandle) WriteTo(w io.Writer) (n int64, err error) {
+ //TODO implement me
+ panic("implement me")
+}
+
+func (m *mmapSharedFileHandle) ReadAt(p []byte, off int64) (n int, err error) {
+ n = copy(p, m.f.m[off:])
+ if n < len(p) {
+ if off < 0 {
+ err = fs.ErrInvalid
+ return
+ }
+ }
+ if off+int64(n) == int64(len(m.f.m)) {
+ err = io.EOF
+ }
+ return
+}
+
+func (m *mmapSharedFileHandle) Close() (err error) {
+ m.close.Do(func() {
+ err = m.f.dec()
+ })
+ return
+}
+
+type mmapFileHandle struct {
+ shared *mmapSharedFileHandle
+ pos int64
+}
+
+func (me *mmapFileHandle) WriteTo(w io.Writer) (n int64, err error) {
+ n1, err := w.Write(me.shared.f.m[me.pos:])
+ n = int64(n1)
+ me.pos += n
+ return
+}
+
+func (me *mmapFileHandle) Close() error {
+ return me.shared.Close()
+}
+
+func (me *mmapFileHandle) Read(p []byte) (n int, err error) {
+ if me.pos > int64(len(me.shared.f.m)) {
+ err = io.EOF
+ return
+ }
+ n = copy(p, me.shared.f.m[me.pos:])
+ me.pos += int64(n)
+ if me.pos >= int64(len(me.shared.f.m)) {
+ err = io.EOF
+ }
+ return
+}
+
+func (me *mmapFileHandle) seekData(offset int64) (ret int64, err error) {
+ me.pos = offset
+ ret = offset
+ return
+}