]> Sergey Matveev's repositories - btrtrc.git/blob - reader.go
Rework Reader reading, and TestCompletedPieceWrongSize
[btrtrc.git] / reader.go
1 package torrent
2
3 import (
4         "errors"
5         "io"
6         "log"
7         "os"
8         "sync"
9 )
10
11 // Accesses torrent data via a client.
12 type Reader struct {
13         t *Torrent
14
15         mu         sync.Mutex
16         pos        int64
17         responsive bool
18         readahead  int64
19 }
20
21 var _ io.ReadCloser = &Reader{}
22
23 // Don't wait for pieces to complete and be verified. Read calls return as
24 // soon as they can when the underlying chunks become available.
25 func (r *Reader) SetResponsive() {
26         r.responsive = true
27 }
28
29 // Configure the number of bytes ahead of a read that should also be
30 // prioritized in preparation for further reads.
31 func (r *Reader) SetReadahead(readahead int64) {
32         r.mu.Lock()
33         defer r.mu.Unlock()
34         r.readahead = readahead
35 }
36
37 func (r *Reader) readable(off int64) (ret bool) {
38         if r.torrentClosed() {
39                 return true
40         }
41         req, ok := r.t.torrent.offsetRequest(off)
42         if !ok {
43                 panic(off)
44         }
45         if r.responsive {
46                 return r.t.torrent.haveChunk(req)
47         }
48         return r.t.torrent.pieceComplete(int(req.Index))
49 }
50
51 // How many bytes are available to read. Max is the most we could require.
52 func (r *Reader) available(off, max int64) (ret int64) {
53         for max > 0 {
54                 req, ok := r.t.torrent.offsetRequest(off)
55                 if !ok {
56                         break
57                 }
58                 if !r.t.torrent.haveChunk(req) {
59                         break
60                 }
61                 len1 := int64(req.Length) - (off - r.t.torrent.requestOffset(req))
62                 max -= len1
63                 ret += len1
64                 off += len1
65         }
66         // Ensure that ret hasn't exceeded our original max.
67         if max < 0 {
68                 ret += max
69         }
70         return
71 }
72
73 func (r *Reader) tickleClient() {
74         r.t.torrent.readersChanged()
75 }
76
77 func (r *Reader) waitReadable(off int64) {
78         // We may have been sent back here because we were told we could read but
79         // it failed.
80         r.tickleClient()
81         r.t.cl.event.Wait()
82 }
83
84 func (r *Reader) Read(b []byte) (n int, err error) {
85         r.mu.Lock()
86         pos := r.pos
87         r.mu.Unlock()
88         n, err = r.readAt(b, pos)
89         r.mu.Lock()
90         r.pos += int64(n)
91         r.mu.Unlock()
92         r.posChanged()
93         return
94 }
95
96 // Safe to call with or without client lock.
97 func (r *Reader) torrentClosed() bool {
98         return r.t.torrent.isClosed()
99 }
100
101 // Wait until some data should be available to read. Tickles the client if it
102 // isn't. Returns how much should be readable without blocking.
103 func (r *Reader) waitAvailable(pos, wanted int64) (avail int64) {
104         r.t.cl.mu.Lock()
105         defer r.t.cl.mu.Unlock()
106         for !r.readable(pos) {
107                 r.waitReadable(pos)
108         }
109         return r.available(pos, wanted)
110 }
111
112 // Performs at most one successful read to torrent storage.
113 func (r *Reader) readOnceAt(b []byte, pos int64) (n int, err error) {
114         if pos >= r.t.torrent.length {
115                 err = io.EOF
116                 return
117         }
118         for {
119                 avail := r.waitAvailable(pos, int64(len(b)))
120                 if avail == 0 {
121                         if r.torrentClosed() {
122                                 err = errors.New("torrent closed")
123                                 return
124                         }
125                 }
126                 b1 := b[:avail]
127                 pi := int(pos / r.t.Info().PieceLength)
128                 tp := &r.t.torrent.Pieces[pi]
129                 ip := r.t.Info().Piece(pi)
130                 po := pos % ip.Length()
131                 if int64(len(b1)) > ip.Length()-po {
132                         b1 = b1[:ip.Length()-po]
133                 }
134                 tp.waitNoPendingWrites()
135                 n, err = dataReadAt(r.t.torrent.data, b1, pos)
136                 if n != 0 {
137                         return
138                 }
139                 log.Printf("error reading from torrent storage: %s", err)
140                 r.t.torrent.updatePieceCompletion(pi)
141                 r.t.torrent.updatePiecePriority(pi)
142         }
143 }
144
145 // Must only return EOF at the end of the torrent. Fills b until error or
146 // valid EOF. Note that the Reader pos is not updated until the read
147 // completes, this may reduce piece priority recalculation, but also the
148 // effectiveness of readahead.
149 func (r *Reader) readAt(b []byte, pos int64) (n int, err error) {
150         for len(b) != 0 {
151                 var n1 int
152                 n1, err = r.readOnceAt(b, pos)
153                 if n1 == 0 {
154                         if err == nil {
155                                 panic("expected error")
156                         }
157                         break
158                 }
159                 b = b[n1:]
160                 n += n1
161                 pos += int64(n1)
162         }
163         if pos >= r.t.torrent.length {
164                 err = io.EOF
165         } else if err == io.EOF {
166                 err = io.ErrUnexpectedEOF
167         }
168         return
169 }
170
171 func (r *Reader) Close() error {
172         r.t.deleteReader(r)
173         r.t = nil
174         return nil
175 }
176
177 func (r *Reader) posChanged() {
178         r.t.cl.mu.Lock()
179         defer r.t.cl.mu.Unlock()
180         r.t.torrent.readersChanged()
181 }
182
183 func (r *Reader) Seek(off int64, whence int) (ret int64, err error) {
184         r.mu.Lock()
185         switch whence {
186         case os.SEEK_SET:
187                 r.pos = off
188         case os.SEEK_CUR:
189                 r.pos += off
190         case os.SEEK_END:
191                 r.pos = r.t.torrent.Info.TotalLength() + off
192         default:
193                 err = errors.New("bad whence")
194         }
195         ret = r.pos
196         r.mu.Unlock()
197         r.posChanged()
198         return
199 }