diff options
author | Sam Truzjan <pxqr.sta@gmail.com> | 2013-12-13 05:57:21 +0400 |
---|---|---|
committer | Sam Truzjan <pxqr.sta@gmail.com> | 2013-12-13 05:57:21 +0400 |
commit | d61e6f0b46bb2d92e40c256aab2f47fc54c6d161 (patch) | |
tree | e8fd823c78be96aafd52e9dd18ce2e6a08044dc8 | |
parent | 172f5f8efdc3f2c9c01e56f628521e3aa22ef883 (diff) |
Add block bucket
-rw-r--r-- | src/Network/BitTorrent/Exchange/Block.hs | 172 |
1 files changed, 167 insertions, 5 deletions
diff --git a/src/Network/BitTorrent/Exchange/Block.hs b/src/Network/BitTorrent/Exchange/Block.hs index ca126acb..d713bcf1 100644 --- a/src/Network/BitTorrent/Exchange/Block.hs +++ b/src/Network/BitTorrent/Exchange/Block.hs | |||
@@ -7,6 +7,7 @@ | |||
7 | -- | 7 | -- |
8 | -- Blocks are used to transfer pieces. | 8 | -- Blocks are used to transfer pieces. |
9 | -- | 9 | -- |
10 | {-# LANGUAGE BangPatterns #-} | ||
10 | {-# LANGUAGE FlexibleInstances #-} | 11 | {-# LANGUAGE FlexibleInstances #-} |
11 | {-# LANGUAGE TemplateHaskell #-} | 12 | {-# LANGUAGE TemplateHaskell #-} |
12 | {-# LANGUAGE DeriveFunctor #-} | 13 | {-# LANGUAGE DeriveFunctor #-} |
@@ -29,15 +30,40 @@ module Network.BitTorrent.Exchange.Block | |||
29 | , blockSize | 30 | , blockSize |
30 | , blockRange | 31 | , blockRange |
31 | , isPiece | 32 | , isPiece |
33 | |||
34 | -- * Block bucket | ||
35 | , Bucket | ||
36 | |||
37 | -- ** Query | ||
38 | , Network.BitTorrent.Exchange.Block.null | ||
39 | , Network.BitTorrent.Exchange.Block.full | ||
40 | , Network.BitTorrent.Exchange.Block.size | ||
41 | , Network.BitTorrent.Exchange.Block.spans | ||
42 | |||
43 | -- ** Construction | ||
44 | , Network.BitTorrent.Exchange.Block.empty | ||
45 | , Network.BitTorrent.Exchange.Block.insert | ||
46 | , Network.BitTorrent.Exchange.Block.merge | ||
47 | |||
48 | -- ** Rendering | ||
49 | , Network.BitTorrent.Exchange.Block.toPiece | ||
50 | |||
51 | -- ** Debug | ||
52 | , Network.BitTorrent.Exchange.Block.valid | ||
32 | ) where | 53 | ) where |
33 | 54 | ||
55 | import Prelude hiding (span) | ||
34 | import Control.Applicative | 56 | import Control.Applicative |
35 | import Data.Aeson.TH | 57 | import Data.Aeson.TH |
36 | import Data.ByteString.Lazy as BL | 58 | import Data.ByteString as BS hiding (span) |
59 | import Data.ByteString.Lazy as BL hiding (span) | ||
60 | import Data.ByteString.Lazy.Builder as BS | ||
37 | import Data.Default | 61 | import Data.Default |
62 | import Data.Monoid | ||
63 | import Data.List hiding (span) | ||
38 | import Data.Serialize as S | 64 | import Data.Serialize as S |
39 | import Data.Typeable | 65 | import Data.Typeable |
40 | import Text.PrettyPrint | 66 | import Text.PrettyPrint as PP hiding ((<>)) |
41 | import Text.PrettyPrint.Class | 67 | import Text.PrettyPrint.Class |
42 | 68 | ||
43 | import Data.Torrent.JSON | 69 | import Data.Torrent.JSON |
@@ -113,9 +139,9 @@ instance Serialize BlockIx where | |||
113 | 139 | ||
114 | instance Pretty BlockIx where | 140 | instance Pretty BlockIx where |
115 | pretty BlockIx {..} = | 141 | pretty BlockIx {..} = |
116 | "piece = " <> int ixPiece <> "," <+> | 142 | ("piece = " <> int ixPiece <> ",") <+> |
117 | "offset = " <> int ixOffset <> "," <+> | 143 | ("offset = " <> int ixOffset <> ",") <+> |
118 | "length = " <> int ixLength | 144 | ("length = " <> int ixLength) |
119 | 145 | ||
120 | -- | Get location of payload bytes in the torrent content. | 146 | -- | Get location of payload bytes in the torrent content. |
121 | blockIxRange :: (Num a, Integral a) => PieceSize -> BlockIx -> (a, a) | 147 | blockIxRange :: (Num a, Integral a) => PieceSize -> BlockIx -> (a, a) |
@@ -166,3 +192,139 @@ isPiece :: PieceSize -> Block BL.ByteString -> Bool | |||
166 | isPiece pieceLen blk @ (Block i offset _) = | 192 | isPiece pieceLen blk @ (Block i offset _) = |
167 | offset == 0 && blockSize blk == pieceLen && i >= 0 | 193 | offset == 0 && blockSize blk == pieceLen && i >= 0 |
168 | {-# INLINE isPiece #-} | 194 | {-# INLINE isPiece #-} |
195 | |||
196 | {----------------------------------------------------------------------- | ||
197 | -- Bucket | ||
198 | -----------------------------------------------------------------------} | ||
199 | |||
200 | type Pos = Int | ||
201 | type ChunkSize = Int | ||
202 | |||
203 | -- | A sparse set of blocks used to represent an /in progress/ piece. | ||
204 | data Bucket | ||
205 | = Nil | ||
206 | | Span {-# UNPACK #-} !ChunkSize !Bucket | ||
207 | | Fill {-# UNPACK #-} !ChunkSize !Builder !Bucket | ||
208 | |||
209 | -- | INVARIANT: 'Nil' should appear only after 'Span' of 'Fill'. | ||
210 | nilInvFailed :: a | ||
211 | nilInvFailed = error "Nil: bucket invariant failed" | ||
212 | |||
213 | valid :: Bucket -> Bool | ||
214 | valid = check Nothing | ||
215 | where | ||
216 | check Nothing Nil = False -- see 'nilInvFailed' | ||
217 | check (Just _) _ = True | ||
218 | check prevIsSpan (Span sz xs) = | ||
219 | prevIsSpan /= Just True && -- Span n (NotSpan .. ) invariant | ||
220 | sz > 0 && -- Span is always non-empty | ||
221 | check (Just True) xs | ||
222 | check prevIsSpan (Fill sz b xs) = | ||
223 | prevIsSpan /= Just True && -- Fill n (NotFill .. ) invariant | ||
224 | sz > 0 && -- Fill is always non-empty | ||
225 | check (Just False) xs | ||
226 | |||
227 | instance Pretty Bucket where | ||
228 | pretty Nil = nilInvFailed | ||
229 | pretty bkt = go bkt | ||
230 | where | ||
231 | go Nil = PP.empty | ||
232 | go (Span sz xs) = "Span" <+> PP.int sz <+> go xs | ||
233 | go (Fill sz b xs) = "Fill" <+> PP.int sz <+> go xs | ||
234 | |||
235 | -- | Smart constructor: use it when some block is /deleted/ from | ||
236 | -- bucket. | ||
237 | span :: ChunkSize -> Bucket -> Bucket | ||
238 | span sz (Span sz' xs) = Span (sz + sz') xs | ||
239 | span sz xxs = Span sz xxs | ||
240 | {-# INLINE span #-} | ||
241 | |||
242 | -- | Smart constructor: use it when some block is /inserted/ to | ||
243 | -- bucket. | ||
244 | fill :: ChunkSize -> Builder -> Bucket -> Bucket | ||
245 | fill sz b (Fill sz' b' xs) = Fill (sz + sz') (b <> b') xs | ||
246 | fill sz b xxs = Fill sz b xxs | ||
247 | {-# INLINE fill #-} | ||
248 | |||
249 | {----------------------------------------------------------------------- | ||
250 | -- Bucket queries | ||
251 | -----------------------------------------------------------------------} | ||
252 | |||
253 | -- | /O(1)/. Test if this bucket is empty. | ||
254 | null :: Bucket -> Bool | ||
255 | null Nil = nilInvFailed | ||
256 | null (Span _ Nil) = True | ||
257 | null _ = False | ||
258 | {-# INLINE null #-} | ||
259 | |||
260 | -- | /O(1)/. Test if this bucket is complete. | ||
261 | full :: Bucket -> Bool | ||
262 | full Nil = nilInvFailed | ||
263 | full (Fill _ _ Nil) = True | ||
264 | full _ = False | ||
265 | {-# INLINE full #-} | ||
266 | |||
267 | -- | /O(n)/. Total size of the incompleted piece. | ||
268 | size :: Bucket -> PieceSize | ||
269 | size Nil = nilInvFailed | ||
270 | size bkt = go bkt | ||
271 | where | ||
272 | go Nil = 0 | ||
273 | go (Span sz xs) = sz + go xs | ||
274 | go (Fill sz _ xs) = sz + go xs | ||
275 | |||
276 | -- | /O(n)/. List incomplete blocks to download. | ||
277 | spans :: BlockSize -> Bucket -> [(BlockOffset, BlockSize)] | ||
278 | spans = undefined | ||
279 | |||
280 | {----------------------------------------------------------------------- | ||
281 | -- Bucket contstruction | ||
282 | -----------------------------------------------------------------------} | ||
283 | |||
284 | -- | /O(1)/. A new empty bucket capable to alloof specified size. | ||
285 | empty :: PieceSize -> Bucket | ||
286 | empty 0 = error "empty: Bucket size is zero" | ||
287 | empty sz = Span sz Nil | ||
288 | {-# INLINE empty #-} | ||
289 | |||
290 | insertSpan :: Pos -> BS.ByteString -> ChunkSize -> Bucket -> Bucket | ||
291 | insertSpan !pos !bs !span_sz !xs = | ||
292 | let pref_len = pos | ||
293 | fill_len = span_sz - pos `min` BS.length bs | ||
294 | suff_len = (span_sz - pos) - fill_len | ||
295 | in mkSpan pref_len $ | ||
296 | fill fill_len (byteString (BS.take fill_len bs)) $ | ||
297 | mkSpan suff_len $ | ||
298 | xs | ||
299 | where | ||
300 | mkSpan 0 xs = xs | ||
301 | mkSpan sz xs = Span sz xs | ||
302 | |||
303 | -- | /O(n)/. Insert a strict bytestring at specified position. | ||
304 | -- | ||
305 | -- Best case: if blocks are inserted in sequential order, then this | ||
306 | -- operation should take /O(1)/. | ||
307 | -- | ||
308 | insert :: Pos -> BS.ByteString -> Bucket -> Bucket | ||
309 | insert _ _ Nil = nilInvFailed | ||
310 | insert dstPos bs bucket = go 0 bucket | ||
311 | where | ||
312 | intersects curPos sz = dstPos >= curPos && dstPos <= curPos + sz | ||
313 | |||
314 | go _ Nil = Nil | ||
315 | go curPos (Span sz xs) | ||
316 | | intersects curPos sz = insertSpan (dstPos - curPos) bs sz xs | ||
317 | | otherwise = span sz (go (curPos + sz) xs) | ||
318 | go curPos bkt @ (Fill sz br xs) | ||
319 | | intersects curPos sz = bkt | ||
320 | | otherwise = fill sz br (go (curPos + sz) xs) | ||
321 | |||
322 | -- | /O(n)/. | ||
323 | merge :: Bucket -> Bucket -> Bucket | ||
324 | merge = undefined | ||
325 | |||
326 | -- | /O(1)/. | ||
327 | toPiece :: Bucket -> Maybe BL.ByteString | ||
328 | toPiece Nil = nilInvFailed | ||
329 | toPiece (Fill _ b Nil) = Just (toLazyByteString b) | ||
330 | toPiece _ = Nothing | ||