diff options
Diffstat (limited to 'src/Data/Torrent.hs')
-rw-r--r-- | src/Data/Torrent.hs | 93 |
1 files changed, 86 insertions, 7 deletions
diff --git a/src/Data/Torrent.hs b/src/Data/Torrent.hs index 873d90dd..3d5f669e 100644 --- a/src/Data/Torrent.hs +++ b/src/Data/Torrent.hs | |||
@@ -6,31 +6,57 @@ | |||
6 | -- Portability : portable | 6 | -- Portability : portable |
7 | -- | 7 | -- |
8 | -- This module provides torrent metainfo serialization. | 8 | -- This module provides torrent metainfo serialization. |
9 | -- | ||
9 | {-# OPTIONS -fno-warn-orphans #-} | 10 | {-# OPTIONS -fno-warn-orphans #-} |
11 | {-# LANGUAGE FlexibleInstances #-} | ||
10 | {-# LANGUAGE OverloadedStrings #-} | 12 | {-# LANGUAGE OverloadedStrings #-} |
11 | {-# LANGUAGE RecordWildCards #-} | 13 | {-# LANGUAGE RecordWildCards #-} |
14 | -- TODO refine interface | ||
12 | module Data.Torrent | 15 | module Data.Torrent |
13 | ( module Data.Torrent.InfoHash | 16 | ( -- * Torrent |
14 | , Torrent(..), ContentInfo(..), FileInfo(..) | 17 | Torrent(..), ContentInfo(..), FileInfo(..) |
15 | , contentLength, pieceCount, blockCount | 18 | , contentLength, pieceCount, blockCount |
19 | , fromFile | ||
20 | |||
21 | -- * Files layout | ||
16 | , Layout, contentLayout | 22 | , Layout, contentLayout |
17 | , isSingleFile, isMultiFile | 23 | , isSingleFile, isMultiFile |
18 | , fromFile | ||
19 | 24 | ||
25 | -- * Info hash | ||
26 | , InfoHash, ppInfoHash | ||
27 | , hash, hashlazy | ||
28 | , addHashToURI | ||
29 | |||
30 | -- * Extra | ||
20 | , sizeInBase | 31 | , sizeInBase |
32 | |||
33 | -- * Internal | ||
34 | , InfoHash(..) | ||
21 | ) where | 35 | ) where |
22 | 36 | ||
37 | import Prelude hiding (sum) | ||
38 | |||
23 | import Control.Applicative | 39 | import Control.Applicative |
24 | import Control.Arrow | 40 | import Control.Arrow |
41 | import Data.BEncode as BE | ||
42 | import Data.Char | ||
43 | import Data.Foldable | ||
44 | import Data.Map (Map) | ||
25 | import qualified Data.Map as M | 45 | import qualified Data.Map as M |
26 | import Data.ByteString (ByteString) | 46 | import Data.ByteString (ByteString) |
27 | import qualified Data.ByteString as B | 47 | import qualified Data.ByteString as B |
28 | import qualified Data.ByteString.Char8 as BC (pack, unpack) | 48 | import qualified Data.ByteString.Char8 as BC (pack, unpack) |
49 | import qualified Data.ByteString.Builder as B | ||
50 | import qualified Data.ByteString.Builder.Prim as B | ||
51 | import qualified Data.ByteString.Lazy as Lazy | ||
52 | import qualified Data.List as L | ||
29 | import Data.Text (Text) | 53 | import Data.Text (Text) |
30 | import Data.BEncode | 54 | import Data.Serialize as S hiding (Result) |
31 | import Data.Torrent.InfoHash | 55 | import qualified Crypto.Hash.SHA1 as C |
32 | import Network.URI | 56 | import Network.URI |
33 | import System.FilePath | 57 | import System.FilePath |
58 | import Numeric | ||
59 | |||
34 | 60 | ||
35 | type Time = Text | 61 | type Time = Text |
36 | 62 | ||
@@ -157,7 +183,7 @@ instance BEncodable Torrent where | |||
157 | ] | 183 | ] |
158 | 184 | ||
159 | fromBEncode (BDict d) | Just info <- M.lookup "info" d = | 185 | fromBEncode (BDict d) | Just info <- M.lookup "info" d = |
160 | Torrent <$> pure (hashlazy (encode info)) -- WARN | 186 | Torrent <$> pure (hashlazy (BE.encode info)) -- WARN |
161 | <*> d >-- "announce" | 187 | <*> d >-- "announce" |
162 | <*> d >--? "announce-list" | 188 | <*> d >--? "announce-list" |
163 | <*> d >--? "comment" | 189 | <*> d >--? "comment" |
@@ -267,7 +293,60 @@ isMultiFile :: ContentInfo -> Bool | |||
267 | isMultiFile MultiFile {} = True | 293 | isMultiFile MultiFile {} = True |
268 | isMultiFile _ = False | 294 | isMultiFile _ = False |
269 | 295 | ||
270 | |||
271 | -- | Read and decode a .torrent file. | 296 | -- | Read and decode a .torrent file. |
272 | fromFile :: FilePath -> IO (Result Torrent) | 297 | fromFile :: FilePath -> IO (Result Torrent) |
273 | fromFile filepath = decoded <$> B.readFile filepath | 298 | fromFile filepath = decoded <$> B.readFile filepath |
299 | |||
300 | {----------------------------------------------------------------------- | ||
301 | Serialization | ||
302 | -----------------------------------------------------------------------} | ||
303 | |||
304 | -- | Exactly 20 bytes long SHA1 hash. | ||
305 | newtype InfoHash = InfoHash { getInfoHash :: ByteString } | ||
306 | deriving (Eq, Ord) | ||
307 | |||
308 | instance BEncodable InfoHash where | ||
309 | toBEncode = toBEncode . getInfoHash | ||
310 | |||
311 | instance Show InfoHash where | ||
312 | show = BC.unpack . ppInfoHash | ||
313 | |||
314 | instance Serialize InfoHash where | ||
315 | put = putByteString . getInfoHash | ||
316 | get = InfoHash <$> getBytes 20 | ||
317 | |||
318 | instance BEncodable a => BEncodable (Map InfoHash a) where | ||
319 | {-# SPECIALIZE instance BEncodable a => BEncodable (Map InfoHash a) #-} | ||
320 | fromBEncode b = M.mapKeys InfoHash <$> fromBEncode b | ||
321 | {-# INLINE fromBEncode #-} | ||
322 | |||
323 | toBEncode = toBEncode . M.mapKeys getInfoHash | ||
324 | {-# INLINE toBEncode #-} | ||
325 | |||
326 | hash :: ByteString -> InfoHash | ||
327 | hash = InfoHash . C.hash | ||
328 | |||
329 | hashlazy :: Lazy.ByteString -> InfoHash | ||
330 | hashlazy = InfoHash . C.hashlazy | ||
331 | |||
332 | ppInfoHash :: InfoHash -> ByteString | ||
333 | ppInfoHash = Lazy.toStrict . B.toLazyByteString . | ||
334 | foldMap (B.primFixed B.word8HexFixed) . B.unpack . getInfoHash | ||
335 | |||
336 | addHashToURI :: URI -> InfoHash -> URI | ||
337 | addHashToURI uri s = uri { | ||
338 | uriQuery = uriQuery uri ++ mkPref (uriQuery uri) ++ | ||
339 | "info_hash=" ++ rfc1738Encode (BC.unpack (getInfoHash s)) | ||
340 | } | ||
341 | where | ||
342 | mkPref [] = "?" | ||
343 | mkPref ('?' : _) = "&" | ||
344 | mkPref _ = error "addHashToURI" | ||
345 | |||
346 | rfc1738Encode = L.concatMap (\c -> if unreservedS c then [c] else encodeHex c) | ||
347 | where | ||
348 | unreservedS = (`L.elem` chars) | ||
349 | chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_./" | ||
350 | encodeHex c = '%' : pHex c | ||
351 | pHex c = let p = (showHex . ord $ c) "" | ||
352 | in if L.length p == 1 then '0' : p else p | ||