]> git.rkrishnan.org Git - functorrent.git/blob - src/FuncTorrent/Metainfo.hs
multi-file torrent metainfo tests
[functorrent.git] / src / FuncTorrent / Metainfo.hs
1 {-
2  - Copyright (C) 2015-2016 Ramakrishnan Muthukrishnan <ram@rkrishnan.org>
3  -
4  - This file is part of FuncTorrent.
5  -
6  - FuncTorrent is free software; you can redistribute it and/or modify
7  - it under the terms of the GNU General Public License as published by
8  - the Free Software Foundation; either version 3 of the License, or
9  - (at your option) any later version.
10  -
11  - FuncTorrent is distributed in the hope that it will be useful,
12  - but WITHOUT ANY WARRANTY; without even the implied warranty of
13  - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  - GNU General Public License for more details.
15  -
16  - You should have received a copy of the GNU General Public License
17  - along with FuncTorrent; if not,  see <http://www.gnu.org/licenses/>
18  -}
19
20 {-# LANGUAGE DuplicateRecordFields #-}
21
22 module FuncTorrent.Metainfo
23     (Info(..)
24     , Metainfo(..)
25     , DynamicInfo(..)
26     , FileInfo(..)
27     , torrentToMetainfo
28     ) where
29
30 import Prelude hiding (lookup)
31 import Data.ByteString.Char8 (ByteString, unpack)
32 import Data.Map as M ((!), lookup)
33 import Data.List (intercalate)
34 import Crypto.Hash.SHA1 (hash)
35 import Data.Maybe (maybeToList)
36
37 import FuncTorrent.Bencode (BVal(..), encode, decode, bstrToString, bValToInteger)
38
39 -- only single file mode supported for the time being.
40 data Info = Info { pieceLength :: !Integer
41                  , pieces :: !ByteString
42                  , private :: !(Maybe Integer)
43                  , dyninfo :: !DynamicInfo
44                  } deriving (Eq, Show)
45
46 data DynamicInfo = SingleFileInfo { file :: FileInfo }
47                  | MultiFileInfo { dname :: String
48                                  , files :: [FileInfo]
49                                  }
50                  deriving (Eq, Show)
51
52 data FileInfo = FileInfo { lengthInBytes :: Integer
53                          , md5sum :: Maybe String
54                          , path :: String
55                          } deriving (Eq, Show)
56
57 data Metainfo = Metainfo { info :: !Info
58                          , announceList :: ![String]
59                          , creationDate :: !(Maybe Integer)
60                          , comment :: !(Maybe String)
61                          , createdBy :: !(Maybe String)
62                          , encoding :: !(Maybe String)
63                          , infoHash :: !ByteString
64                          } deriving (Eq, Show)
65
66 mkPath :: [BVal] -> String
67 mkPath xs = intercalate "/" $ map (\b -> let (Just s) = bstrToString b in s) xs
68
69 mkInfo :: BVal -> Maybe Info
70 mkInfo (Bdict m) = let (Bint pieceLength') = m ! "piece length"
71                        (Bstr pieces') = m ! "pieces"
72                        private' = Nothing
73                        bdictfiles = lookup "files" m
74                    in
75                      case bdictfiles of
76                        Just fs -> let (Blist files') = fs
77                                       (Bstr name') = m ! "name"
78                                   in
79                                     Just Info { pieceLength = pieceLength'
80                                               , pieces = pieces'
81                                               , private = private'
82                                               , dyninfo = MultiFileInfo {
83                                                   dname = unpack name',
84                                                   files =
85                                                       map (\(Bdict f) ->
86                                                             let (Just len') = bValToInteger (f ! "length")
87                                                                 (Blist ds) = f ! "path"
88                                                                 path' = mkPath ds
89                                                             in
90                                                               FileInfo { lengthInBytes = len',
91                                                                          md5sum = Nothing,
92                                                                          path = path'
93                                                                      })
94                                                       files' }
95                                               }
96                        Nothing -> let (Bstr name') = m ! "name"
97                                       (Bint length') = m ! "length"
98                                       md5sum' = Nothing
99                                   in
100                                     Just Info { pieceLength = pieceLength'
101                                               , pieces = pieces'
102                                               , private = private'
103                                               , dyninfo = SingleFileInfo {
104                                                   file = FileInfo {
105                                                       path = unpack name',
106                                                       lengthInBytes = length',
107                                                       md5sum = md5sum'
108                                                       }
109                                                   }
110                                               }
111
112 mkInfo _ = Nothing
113
114 mkMetaInfo :: BVal   -> Either String Metainfo
115 mkMetaInfo (Bdict m)  =
116     let (Just info')  = mkInfo $ m ! "info"
117         announce'     = lookup "announce" m
118         announceList' = lookup "announce-list" m
119         creationDate' = lookup "creation date" m
120         comment'      = lookup "comment" m
121         createdBy'    = lookup "created by" m
122         encoding'     = lookup "encoding" m
123     in Right Metainfo {
124                info         = info'
125              , announceList = maybeToList (announce' >>= bstrToString)
126                               ++ getAnnounceList announceList'
127              , creationDate = bValToInteger =<< creationDate'
128              , comment      = bstrToString  =<< comment'
129              , createdBy    = bstrToString  =<< createdBy'
130              , encoding     = bstrToString  =<< encoding'
131              , infoHash     = hash . encode $ (m ! "info")
132              }
133 mkMetaInfo _ = Left "mkMetaInfo: expect an input dict"
134
135 getAnnounceList :: Maybe BVal -> [String]
136 getAnnounceList Nothing          = []
137 getAnnounceList (Just (Bint _))  = []
138 getAnnounceList (Just (Bstr _))  = []
139 getAnnounceList (Just (Blist l)) = map (\s -> case s of
140                                                (Bstr s') ->  unpack s'
141                                                (Blist s') -> case s' of
142                                                               [Bstr s''] -> unpack s''
143                                                               _ -> ""
144                                                _ -> "") l
145 getAnnounceList (Just (Bdict _)) = []
146
147 torrentToMetainfo :: ByteString -> Either String Metainfo
148 torrentToMetainfo s =
149   case decode s of
150    Right d -> mkMetaInfo d
151    Left e -> Left $ show e