diff options
Diffstat (limited to 'src/Data/Torrent.hs')
-rw-r--r-- | src/Data/Torrent.hs | 54 |
1 files changed, 45 insertions, 9 deletions
diff --git a/src/Data/Torrent.hs b/src/Data/Torrent.hs index 464bc5bb..16b94828 100644 --- a/src/Data/Torrent.hs +++ b/src/Data/Torrent.hs | |||
@@ -5,7 +5,16 @@ | |||
5 | -- Stability : experimental | 5 | -- Stability : experimental |
6 | -- Portability : portable | 6 | -- Portability : portable |
7 | -- | 7 | -- |
8 | -- This module provides torrent metainfo serialization. | 8 | -- Torrent file contains metadata about files and folders but not |
9 | -- content itself. The files are bencoded dictionaries. There is | ||
10 | -- also other info which is used to help join the swarm. | ||
11 | -- | ||
12 | -- This module provides torrent metainfo serialization and info hash | ||
13 | -- extraction. | ||
14 | -- | ||
15 | -- For more info see: | ||
16 | -- <http://www.bittorrent.org/beps/bep_0003.html#metainfo-files>, | ||
17 | -- <https://wiki.theory.org/BitTorrentSpecification#Metainfo_File_Structure> | ||
9 | -- | 18 | -- |
10 | {-# OPTIONS -fno-warn-orphans #-} | 19 | {-# OPTIONS -fno-warn-orphans #-} |
11 | {-# LANGUAGE FlexibleInstances #-} | 20 | {-# LANGUAGE FlexibleInstances #-} |
@@ -15,11 +24,11 @@ | |||
15 | module Data.Torrent | 24 | module Data.Torrent |
16 | ( -- * Torrent | 25 | ( -- * Torrent |
17 | Torrent(..), ContentInfo(..), FileInfo(..) | 26 | Torrent(..), ContentInfo(..), FileInfo(..) |
18 | , contentLength, pieceCount, blockCount | ||
19 | , fromFile | 27 | , fromFile |
20 | 28 | ||
21 | -- * Files layout | 29 | -- * Files layout |
22 | , Layout, contentLayout | 30 | , Layout, contentLayout |
31 | , contentLength, pieceCount, blockCount | ||
23 | , isSingleFile, isMultiFile | 32 | , isSingleFile, isMultiFile |
24 | 33 | ||
25 | -- * Info hash | 34 | -- * Info hash |
@@ -156,8 +165,14 @@ data FileInfo = FileInfo { | |||
156 | -- ^ One or more string elements that together represent the path and | 165 | -- ^ One or more string elements that together represent the path and |
157 | -- filename. Each element in the list corresponds to either a directory | 166 | -- filename. Each element in the list corresponds to either a directory |
158 | -- name or (in the case of the last element) the filename. | 167 | -- name or (in the case of the last element) the filename. |
159 | -- For example, the file "dir1/dir2/file.ext" would consist of three | 168 | -- For example, the file |
160 | -- string elements ["dir1", "dir2", "file.ext"] | 169 | -- |
170 | -- > "dir1/dir2/file.ext" | ||
171 | -- | ||
172 | -- would consist of three string elements: | ||
173 | -- | ||
174 | -- > ["dir1", "dir2", "file.ext"] | ||
175 | -- | ||
161 | } deriving (Show, Read, Eq) | 176 | } deriving (Show, Read, Eq) |
162 | 177 | ||
163 | 178 | ||
@@ -250,6 +265,8 @@ instance BEncodable FileInfo where | |||
250 | 265 | ||
251 | fromBEncode _ = decodingError "FileInfo" | 266 | fromBEncode _ = decodingError "FileInfo" |
252 | 267 | ||
268 | |||
269 | -- | Divide and round up. | ||
253 | sizeInBase :: Integral a => a -> Int -> Int | 270 | sizeInBase :: Integral a => a -> Int -> Int |
254 | sizeInBase n b = fromIntegral (n `div` fromIntegral b) + align | 271 | sizeInBase n b = fromIntegral (n `div` fromIntegral b) + align |
255 | where | 272 | where |
@@ -257,14 +274,22 @@ sizeInBase n b = fromIntegral (n `div` fromIntegral b) + align | |||
257 | {-# SPECIALIZE sizeInBase :: Int -> Int -> Int #-} | 274 | {-# SPECIALIZE sizeInBase :: Int -> Int -> Int #-} |
258 | {-# SPECIALIZE sizeInBase :: Integer -> Int -> Int #-} | 275 | {-# SPECIALIZE sizeInBase :: Integer -> Int -> Int #-} |
259 | 276 | ||
277 | |||
278 | -- | Find sum of sizes of the all torrent files. | ||
260 | contentLength :: ContentInfo -> Integer | 279 | contentLength :: ContentInfo -> Integer |
261 | contentLength SingleFile { ciLength = len } = len | 280 | contentLength SingleFile { ciLength = len } = len |
262 | contentLength MultiFile { ciFiles = tfs } = sum (map fiLength tfs) | 281 | contentLength MultiFile { ciFiles = tfs } = sum (map fiLength tfs) |
263 | 282 | ||
283 | -- | Find count of pieces in the torrent. If torrent size is not a | ||
284 | -- multiple of piece size then the count is rounded up. | ||
264 | pieceCount :: ContentInfo -> Int | 285 | pieceCount :: ContentInfo -> Int |
265 | pieceCount ci = contentLength ci `sizeInBase` ciPieceLength ci | 286 | pieceCount ci = contentLength ci `sizeInBase` ciPieceLength ci |
266 | 287 | ||
267 | blockCount :: Int -> ContentInfo -> Int | 288 | -- | Find number of blocks of the specified size. If torrent size is |
289 | -- not a multiple of block size then the count is rounded up. | ||
290 | blockCount :: Int -- ^ Block size. | ||
291 | -> ContentInfo -- ^ Torrent content info. | ||
292 | -> Int -- ^ Number of blocks. | ||
268 | blockCount blkSize ci = contentLength ci `sizeInBase` blkSize | 293 | blockCount blkSize ci = contentLength ci `sizeInBase` blkSize |
269 | 294 | ||
270 | -- | File layout specifies the order and the size of each file in the storage. | 295 | -- | File layout specifies the order and the size of each file in the storage. |
@@ -273,7 +298,10 @@ blockCount blkSize ci = contentLength ci `sizeInBase` blkSize | |||
273 | -- | 298 | -- |
274 | type Layout = [(FilePath, Int)] | 299 | type Layout = [(FilePath, Int)] |
275 | 300 | ||
276 | contentLayout :: FilePath -> ContentInfo -> Layout | 301 | -- | Extract files layout from torrent info with the given root path. |
302 | contentLayout :: FilePath -- ^ Root path for the all torrent files. | ||
303 | -> ContentInfo -- ^ Torrent content information. | ||
304 | -> Layout -- ^ The all file paths prefixed with the given root. | ||
277 | contentLayout rootPath = filesLayout | 305 | contentLayout rootPath = filesLayout |
278 | where | 306 | where |
279 | filesLayout (SingleFile { ciName = name, ciLength = len }) | 307 | filesLayout (SingleFile { ciName = name, ciLength = len }) |
@@ -285,11 +313,12 @@ contentLayout rootPath = filesLayout | |||
285 | 313 | ||
286 | fl (FileInfo { fiPath = p, fiLength = len }) = (p, fromIntegral len) | 314 | fl (FileInfo { fiPath = p, fiLength = len }) = (p, fromIntegral len) |
287 | 315 | ||
288 | 316 | -- | Test if this is single file torrent. | |
289 | isSingleFile :: ContentInfo -> Bool | 317 | isSingleFile :: ContentInfo -> Bool |
290 | isSingleFile SingleFile {} = True | 318 | isSingleFile SingleFile {} = True |
291 | isSingleFile _ = False | 319 | isSingleFile _ = False |
292 | 320 | ||
321 | -- | Test if this is multifile torrent. | ||
293 | isMultiFile :: ContentInfo -> Bool | 322 | isMultiFile :: ContentInfo -> Bool |
294 | isMultiFile MultiFile {} = True | 323 | isMultiFile MultiFile {} = True |
295 | isMultiFile _ = False | 324 | isMultiFile _ = False |
@@ -299,10 +328,10 @@ fromFile :: FilePath -> IO (Result Torrent) | |||
299 | fromFile filepath = decoded <$> B.readFile filepath | 328 | fromFile filepath = decoded <$> B.readFile filepath |
300 | 329 | ||
301 | {----------------------------------------------------------------------- | 330 | {----------------------------------------------------------------------- |
302 | Serialization | 331 | Info hash |
303 | -----------------------------------------------------------------------} | 332 | -----------------------------------------------------------------------} |
304 | 333 | ||
305 | -- | Exactly 20 bytes long SHA1 hash. | 334 | -- | Exactly 20 bytes long SHA1 hash of the info part of torrent file. |
306 | newtype InfoHash = InfoHash { getInfoHash :: ByteString } | 335 | newtype InfoHash = InfoHash { getInfoHash :: ByteString } |
307 | deriving (Eq, Ord) | 336 | deriving (Eq, Ord) |
308 | 337 | ||
@@ -325,18 +354,25 @@ instance BEncodable a => BEncodable (Map InfoHash a) where | |||
325 | toBEncode = toBEncode . M.mapKeys getInfoHash | 354 | toBEncode = toBEncode . M.mapKeys getInfoHash |
326 | {-# INLINE toBEncode #-} | 355 | {-# INLINE toBEncode #-} |
327 | 356 | ||
357 | -- | Hash strict bytestring using SHA1 algorithm. | ||
328 | hash :: ByteString -> InfoHash | 358 | hash :: ByteString -> InfoHash |
329 | hash = InfoHash . C.hash | 359 | hash = InfoHash . C.hash |
330 | 360 | ||
361 | -- | Hash lazy bytestring using SHA1 algorithm. | ||
331 | hashlazy :: Lazy.ByteString -> InfoHash | 362 | hashlazy :: Lazy.ByteString -> InfoHash |
332 | hashlazy = InfoHash . C.hashlazy | 363 | hashlazy = InfoHash . C.hashlazy |
333 | 364 | ||
365 | -- | Pretty print info hash in hexadecimal format. | ||
334 | ppInfoHash :: InfoHash -> Doc | 366 | ppInfoHash :: InfoHash -> Doc |
335 | ppInfoHash = text . BC.unpack . Lazy.toStrict . ppHex . getInfoHash | 367 | ppInfoHash = text . BC.unpack . Lazy.toStrict . ppHex . getInfoHash |
336 | 368 | ||
337 | ppHex :: ByteString -> Lazy.ByteString | 369 | ppHex :: ByteString -> Lazy.ByteString |
338 | ppHex = B.toLazyByteString . foldMap (B.primFixed B.word8HexFixed) . B.unpack | 370 | ppHex = B.toLazyByteString . foldMap (B.primFixed B.word8HexFixed) . B.unpack |
339 | 371 | ||
372 | -- | Add query info hash parameter to uri. | ||
373 | -- | ||
374 | -- > info_hash=<url_encoded_info_hash> | ||
375 | -- | ||
340 | addHashToURI :: URI -> InfoHash -> URI | 376 | addHashToURI :: URI -> InfoHash -> URI |
341 | addHashToURI uri s = uri { | 377 | addHashToURI uri s = uri { |
342 | uriQuery = uriQuery uri ++ mkPref (uriQuery uri) ++ | 378 | uriQuery = uriQuery uri ++ mkPref (uriQuery uri) ++ |