]> Sergey Matveev's repositories - btrtrc.git/blob - reader.go
Move dataReadAt to torrent.readAt, and do the waitNoPendingWrites check there
[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                 ip := r.t.Info().Piece(pi)
129                 po := pos % ip.Length()
130                 if int64(len(b1)) > ip.Length()-po {
131                         b1 = b1[:ip.Length()-po]
132                 }
133                 n, err = r.t.torrent.readAt(b1, pos)
134                 if n != 0 {
135                         return
136                 }
137                 log.Printf("%s: error reading from torrent storage pos=%d: %s", r.t, pos, err)
138                 r.t.torrent.updatePieceCompletion(pi)
139                 r.t.torrent.updatePiecePriority(pi)
140         }
141 }
142
143 // Must only return EOF at the end of the torrent. Fills b until error or
144 // valid EOF. Note that the Reader pos is not updated until the read
145 // completes, this may reduce piece priority recalculation, but also the
146 // effectiveness of readahead.
147 func (r *Reader) readAt(b []byte, pos int64) (n int, err error) {
148         for len(b) != 0 {
149                 var n1 int
150                 n1, err = r.readOnceAt(b, pos)
151                 if n1 == 0 {
152                         if err == nil {
153                                 panic("expected error")
154                         }
155                         break
156                 }
157                 b = b[n1:]
158                 n += n1
159                 pos += int64(n1)
160         }
161         if pos >= r.t.torrent.length {
162                 err = io.EOF
163         } else if err == io.EOF {
164                 err = io.ErrUnexpectedEOF
165         }
166         return
167 }
168
169 func (r *Reader) Close() error {
170         r.t.deleteReader(r)
171         r.t = nil
172         return nil
173 }
174
175 func (r *Reader) posChanged() {
176         r.t.cl.mu.Lock()
177         defer r.t.cl.mu.Unlock()
178         r.t.torrent.readersChanged()
179 }
180
181 func (r *Reader) Seek(off int64, whence int) (ret int64, err error) {
182         r.mu.Lock()
183         switch whence {
184         case os.SEEK_SET:
185                 r.pos = off
186         case os.SEEK_CUR:
187                 r.pos += off
188         case os.SEEK_END:
189                 r.pos = r.t.torrent.Info.TotalLength() + off
190         default:
191                 err = errors.New("bad whence")
192         }
193         ret = r.pos
194         r.mu.Unlock()
195         r.posChanged()
196         return
197 }