summaryrefslogtreecommitdiff
path: root/src/Data/Torrent.hs
diff options
context:
space:
mode:
authorSam Truzjan <pxqr.sta@gmail.com>2013-10-31 11:25:59 +0400
committerSam Truzjan <pxqr.sta@gmail.com>2013-10-31 11:25:59 +0400
commit01cef3fafc27d39d88c94cacdcd8e204c5f66b86 (patch)
tree01040aca19e49f4e7937383fef53b8c82bcec12b /src/Data/Torrent.hs
parentc1fec260f47084300ac30de2e43d52966316a2c7 (diff)
Merge bittorrent package with torrent-content
Diffstat (limited to 'src/Data/Torrent.hs')
-rw-r--r--src/Data/Torrent.hs273
1 files changed, 273 insertions, 0 deletions
diff --git a/src/Data/Torrent.hs b/src/Data/Torrent.hs
new file mode 100644
index 00000000..15ada35f
--- /dev/null
+++ b/src/Data/Torrent.hs
@@ -0,0 +1,273 @@
1-- |
2-- Copyright : (c) Sam Truzjan 2013
3-- License : BSD3
4-- Maintainer : pxqr.sta@gmail.com
5-- Stability : experimental
6-- Portability : portable
7--
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>
18--
19{-# LANGUAGE CPP #-}
20{-# LANGUAGE FlexibleInstances #-}
21{-# LANGUAGE BangPatterns #-}
22{-# LANGUAGE GeneralizedNewtypeDeriving #-}
23{-# LANGUAGE DeriveDataTypeable #-}
24{-# LANGUAGE TemplateHaskell #-}
25{-# OPTIONS -fno-warn-orphans #-}
26-- TODO refine interface
27module Data.Torrent
28 ( -- * Info dictionary
29 InfoDict (..)
30 , infohash
31 , layoutInfo
32 , pieceInfo
33 , isPrivate
34
35 -- * Torrent file
36 , Torrent(..)
37 , announce
38 , announceList
39 , comment
40 , createdBy
41 , creationDate
42 , encoding
43 , infoDict
44 , publisher
45 , publisherURL
46 , signature
47
48 , nullTorrent
49
50 -- * IO
51 , torrentExt
52 , isTorrentPath
53 , fromFile
54 , toFile
55
56{-
57 , nullTorrent
58 , mktorrent
59
60
61-}
62 ) where
63
64import Prelude hiding (sum)
65
66import Control.Applicative
67import Control.DeepSeq
68import Control.Exception
69import Control.Lens
70
71import Data.Aeson.TH
72import Data.BEncode as BE
73import Data.BEncode.Types as BE
74import Data.ByteString as BS
75import qualified Data.ByteString.Char8 as BC (pack, unpack)
76import qualified Data.ByteString.Lazy as BL
77import Data.Char
78import Data.Hashable as Hashable
79import qualified Data.List as L
80import Data.Text (Text)
81import Data.Time.Clock.POSIX
82import Data.Typeable
83import Network.URI
84import System.FilePath
85
86import Data.Torrent.InfoHash as IH
87import Data.Torrent.Layout
88import Data.Torrent.Piece
89
90
91{-----------------------------------------------------------------------
92-- Info dictionary
93-----------------------------------------------------------------------}
94
95{- note that info hash is actually reduntant field
96 but it's better to keep it here to avoid heavy recomputations
97-}
98
99-- | Info part of the .torrent file contain info about each content file.
100data InfoDict = InfoDict
101 { idInfoHash :: !InfoHash
102 -- ^ SHA1 hash of the (other) 'DictInfo' fields.
103 , idLayoutInfo :: !LayoutInfo
104 , idPieceInfo :: !PieceInfo
105 , idPrivate :: !Bool
106 -- ^ If set the client MUST publish its presence to get other
107 -- peers ONLY via the trackers explicity described in the
108 -- metainfo file.
109 --
110 -- BEP 27: <http://www.bittorrent.org/beps/bep_0027.html>
111 } deriving (Show, Read, Eq, Typeable)
112
113$(deriveJSON (L.map toLower . L.dropWhile isLower) ''InfoDict)
114
115makeLensesFor
116 [ ("idInfoHash" , "infohash" )
117 , ("idLayoutInfo", "layoutInfo")
118 , ("idPieceInfo" , "pieceInfo" )
119 , ("idPrivate" , "isPrivate" )
120 ]
121 ''InfoDict
122
123instance NFData InfoDict where
124 rnf InfoDict {..} = rnf idLayoutInfo
125
126instance Hashable InfoDict where
127 hash = Hashable.hash . idInfoHash
128 {-# INLINE hash #-}
129
130getPrivate :: Get Bool
131getPrivate = (Just True ==) <$>? "private"
132
133putPrivate :: Bool -> BDict -> BDict
134putPrivate False = id
135putPrivate True = \ cont -> "private" .=! True .: cont
136
137instance BEncode InfoDict where
138 toBEncode InfoDict {..} = toDict $
139 putLayoutInfo idLayoutInfo $
140 putPieceInfo idPieceInfo $
141 putPrivate idPrivate $
142 endDict
143
144 fromBEncode dict = (`fromDict` dict) $ do
145 InfoDict ih <$> getLayoutInfo
146 <*> getPieceInfo
147 <*> getPrivate
148 where
149 ih = IH.hashlazy (encode dict)
150
151{-----------------------------------------------------------------------
152-- Torrent info
153-----------------------------------------------------------------------}
154
155-- | Metainfo about particular torrent.
156data Torrent = Torrent
157 { tAnnounce :: !URI
158 -- ^ The URL of the tracker.
159
160 , tAnnounceList :: !(Maybe [[URI]])
161 -- ^ Announce list add multiple tracker support.
162 --
163 -- BEP 12: <http://www.bittorrent.org/beps/bep_0012.html>
164
165 , tComment :: !(Maybe Text)
166 -- ^ Free-form comments of the author.
167
168 , tCreatedBy :: !(Maybe Text)
169 -- ^ Name and version of the program used to create the .torrent.
170
171 , tCreationDate :: !(Maybe POSIXTime)
172 -- ^ Creation time of the torrent, in standard UNIX epoch.
173
174 , tEncoding :: !(Maybe Text)
175 -- ^ String encoding format used to generate the pieces part of
176 -- the info dictionary in the .torrent metafile.
177
178 , tInfoDict :: !InfoDict
179 -- ^ Info about each content file.
180
181 , tPublisher :: !(Maybe URI)
182 -- ^ Containing the RSA public key of the publisher of the
183 -- torrent. Private counterpart of this key that has the
184 -- authority to allow new peers onto the swarm.
185
186 , tPublisherURL :: !(Maybe URI)
187 , tSignature :: !(Maybe ByteString)
188 -- ^ The RSA signature of the info dictionary (specifically, the
189 -- encrypted SHA-1 hash of the info dictionary).
190 } deriving (Show, Eq, Typeable)
191
192makeLensesFor
193 [ ("tAnnounce" , "announce" )
194 , ("tAnnounceList", "announceList")
195 , ("tComment" , "comment" )
196 , ("tCreatedBy" , "createdBy" )
197 , ("tCreationDate", "creationDate")
198 , ("tEncoding" , "encoding" )
199 , ("tInfoDict" , "infoDict" )
200 , ("tPublisher" , "publisher" )
201 , ("tPublisherURL", "publisherURL")
202 , ("tSignature" , "signature" )
203 ]
204 ''Torrent
205
206instance NFData Torrent where
207 rnf Torrent {..} = rnf tInfoDict
208
209-- TODO move to bencoding
210instance BEncode URI where
211 toBEncode uri = toBEncode (BC.pack (uriToString id uri ""))
212 {-# INLINE toBEncode #-}
213
214 fromBEncode (BString s) | Just url <- parseURI (BC.unpack s) = return url
215 fromBEncode b = decodingError $ "url <" ++ show b ++ ">"
216 {-# INLINE fromBEncode #-}
217
218-- TODO move to bencoding
219instance BEncode POSIXTime where
220 toBEncode pt = toBEncode (floor pt :: Integer)
221 fromBEncode (BInteger i) = return $ fromIntegral i
222 fromBEncode _ = decodingError $ "POSIXTime"
223
224instance BEncode Torrent where
225 toBEncode Torrent {..} = toDict $
226 "announce" .=! tAnnounce
227 .: "announce-list" .=? tAnnounceList
228 .: "comment" .=? tComment
229 .: "created by" .=? tCreatedBy
230 .: "creation date" .=? tCreationDate
231 .: "encoding" .=? tEncoding
232 .: "info" .=! tInfoDict
233 .: "publisher" .=? tPublisher
234 .: "publisher-url" .=? tPublisherURL
235 .: "signature" .=? tSignature
236 .: endDict
237
238 fromBEncode = fromDict $ do
239 Torrent <$>! "announce"
240 <*>? "announce-list"
241 <*>? "comment"
242 <*>? "created by"
243 <*>? "creation date"
244 <*>? "encoding"
245 <*>! "info"
246 <*>? "publisher"
247 <*>? "publisher-url"
248 <*>? "signature"
249
250-- | A simple torrent contains only required fields.
251nullTorrent :: URI -> InfoDict -> Torrent
252nullTorrent ann info = Torrent
253 ann Nothing Nothing Nothing Nothing Nothing
254 info Nothing Nothing Nothing
255
256-- | Extension usually used for torrent metafiles.
257torrentExt :: String
258torrentExt = "torrent"
259
260-- | Test if this path has proper extension.
261isTorrentPath :: FilePath -> Bool
262isTorrentPath filepath = takeExtension filepath == extSeparator : torrentExt
263
264-- | Read and decode a .torrent file.
265fromFile :: FilePath -> IO Torrent
266fromFile filepath = do
267 contents <- BS.readFile filepath
268 case decode contents of
269 Right !t -> return t
270 Left msg -> throwIO $ userError $ msg ++ " while reading torrent file"
271
272toFile :: FilePath -> Torrent -> IO ()
273toFile filepath = BL.writeFile filepath . encode