github.com/anacrolix/torrent@v1.61.0/storage/wrappers.go (about) 1 package storage 2 3 import ( 4 "context" 5 "fmt" 6 "io" 7 "os" 8 9 g "github.com/anacrolix/generics" 10 11 "github.com/anacrolix/torrent/metainfo" 12 ) 13 14 type Client struct { 15 ci ClientImpl 16 } 17 18 func NewClient(cl ClientImpl) *Client { 19 return &Client{cl} 20 } 21 22 func (cl Client) OpenTorrent( 23 ctx context.Context, 24 info *metainfo.Info, 25 infoHash metainfo.Hash, 26 ) (*Torrent, error) { 27 t, err := cl.ci.OpenTorrent(ctx, info, infoHash) 28 if err != nil { 29 return nil, err 30 } 31 return &Torrent{t}, nil 32 } 33 34 type Torrent struct { 35 TorrentImpl 36 } 37 38 // Deprecated. Use PieceWithHash, as this doesn't work with pure v2 torrents. 39 func (t *Torrent) Piece(p metainfo.Piece) Piece { 40 return t.PieceWithHash(p, g.Some(p.V1Hash().Unwrap().Bytes())) 41 } 42 43 func (t *Torrent) PieceWithHash(p metainfo.Piece, pieceHash g.Option[[]byte]) Piece { 44 var pieceImpl PieceImpl 45 if t.TorrentImpl.PieceWithHash != nil { 46 pieceImpl = t.TorrentImpl.PieceWithHash(p, pieceHash) 47 } else { 48 pieceImpl = t.TorrentImpl.Piece(p) 49 } 50 return Piece{pieceImpl, p} 51 } 52 53 type Piece struct { 54 PieceImpl 55 mip metainfo.Piece 56 } 57 58 var _ io.WriterTo = Piece{} 59 60 // Why do we have this wrapper? Well PieceImpl doesn't implement io.Reader, so we can't let io.Copy 61 // and friends check for io.WriterTo and fallback for us since they expect an io.Reader. 62 func (p Piece) WriteTo(w io.Writer) (_ int64, err error) { 63 if i, ok := p.PieceImpl.(io.WriterTo); ok { 64 return i.WriteTo(w) 65 } 66 n := p.mip.Length() 67 // NewReader will do the next smartest thing which may allow more efficient resource use between 68 // ReadAt calls. Worst case it gives us a nopCloser+p. 69 rc, err := p.NewReader() 70 if err != nil { 71 err = fmt.Errorf("opening reader: %w", err) 72 return 73 } 74 defer rc.Close() 75 r := io.NewSectionReader(rc, 0, n) 76 return io.CopyN(w, r, n) 77 } 78 79 func (p Piece) WriteAt(b []byte, off int64) (n int, err error) { 80 // Callers should not be writing to completed pieces, but it's too 81 // expensive to be checking this on every single write using uncached 82 // completions. 83 84 // c := p.Completion() 85 // if c.Ok && c.Complete { 86 // err = errors.New("piece already completed") 87 // return 88 // } 89 if off+int64(len(b)) > p.mip.Length() { 90 panic("write overflows piece") 91 } 92 return p.PieceImpl.WriteAt(b, off) 93 } 94 95 // If you're calling this you're probably doing something very inefficient. Consider WriteTo which 96 // handles data spread across multiple objects in storage. 97 func (p Piece) ReadAt(b []byte, off int64) (n int, err error) { 98 if off < 0 { 99 err = os.ErrInvalid 100 return 101 } 102 if off >= p.mip.Length() { 103 err = io.EOF 104 return 105 } 106 b = b[:min(int64(len(b)), p.mip.Length()-off)] 107 if len(b) == 0 { 108 return 109 } 110 n, err = p.PieceImpl.ReadAt(b, off) 111 if n > len(b) { 112 panic(n) 113 } 114 if n == 0 && err == nil { 115 panic("io.Copy will get stuck") 116 } 117 off += int64(n) 118 119 // Doing this here may be inaccurate. There's legitimate reasons we may fail to read while the 120 // data is still there, such as too many open files. There should probably be a specific error 121 // to return if the data has been lost. 122 if off < p.mip.Length() { 123 if err == io.EOF { 124 // TODO: Hey, this guy over here isn't checking errors. 125 p.MarkNotComplete() 126 } 127 } 128 129 return 130 } 131 132 func (p Piece) NewReader() (PieceReader, error) { 133 pr, ok := p.PieceImpl.(PieceReaderer) 134 if ok { 135 return pr.NewReader() 136 } 137 // TODO: Make generic reflect wrapper for nop Closer. 138 return struct { 139 io.ReaderAt 140 io.Closer 141 }{ 142 p, 143 nopCloser{}, 144 }, nil 145 } 146 147 type nopCloser struct{} 148 149 func (nopCloser) Close() error { 150 return nil 151 }