]> git.rkrishnan.org Git - functorrent.git/blobdiff - src/FuncTorrent/Metainfo.hs
multi-file torrent metainfo tests
[functorrent.git] / src / FuncTorrent / Metainfo.hs
index 92b7b96a6b98a10c7b0d9a357c8ff0d55d0ec9b4..ec65294d852fca3125d0f1ec5db08e27ddbfcc01 100644 (file)
+{-
+ - Copyright (C) 2015-2016 Ramakrishnan Muthukrishnan <ram@rkrishnan.org>
+ -
+ - This file is part of FuncTorrent.
+ -
+ - FuncTorrent is free software; you can redistribute it and/or modify
+ - it under the terms of the GNU General Public License as published by
+ - the Free Software Foundation; either version 3 of the License, or
+ - (at your option) any later version.
+ -
+ - FuncTorrent is distributed in the hope that it will be useful,
+ - but WITHOUT ANY WARRANTY; without even the implied warranty of
+ - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ - GNU General Public License for more details.
+ -
+ - You should have received a copy of the GNU General Public License
+ - along with FuncTorrent; if not,  see <http://www.gnu.org/licenses/>
+ -}
+
+{-# LANGUAGE DuplicateRecordFields #-}
+
 module FuncTorrent.Metainfo
-    (Info,
-     Metainfo,
-     mkMetaInfo,
-     mkInfo,
-     announce,
-     lengthInBytes,
-     info,
-     name
+    (Info(..)
+    , Metainfo(..)
+    , DynamicInfo(..)
+    , FileInfo(..)
+    , torrentToMetainfo
     ) where
 
 import Prelude hiding (lookup)
-import Data.ByteString.Char8 (ByteString, pack, unpack)
+import Data.ByteString.Char8 (ByteString, unpack)
 import Data.Map as M ((!), lookup)
+import Data.List (intercalate)
+import Crypto.Hash.SHA1 (hash)
+import Data.Maybe (maybeToList)
 
-import FuncTorrent.Bencode (BVal(..))
+import FuncTorrent.Bencode (BVal(..), encode, decode, bstrToString, bValToInteger)
 
 -- only single file mode supported for the time being.
 data Info = Info { pieceLength :: !Integer
                  , pieces :: !ByteString
                  , private :: !(Maybe Integer)
-                 , name :: !String
-                 , lengthInBytes :: !Integer
-                 , md5sum :: !(Maybe String)
+                 , dyninfo :: !DynamicInfo
                  } deriving (Eq, Show)
 
+data DynamicInfo = SingleFileInfo { file :: FileInfo }
+                 | MultiFileInfo { dname :: String
+                                 , files :: [FileInfo]
+                                 }
+                 deriving (Eq, Show)
+
+data FileInfo = FileInfo { lengthInBytes :: Integer
+                         , md5sum :: Maybe String
+                         , path :: String
+                         } deriving (Eq, Show)
+
 data Metainfo = Metainfo { info :: !Info
-                         , announce :: !String
-                         , announceList :: !(Maybe [[String]])
-                         , creationDate :: !(Maybe String)
+                         , announceList :: ![String]
+                         , creationDate :: !(Maybe Integer)
                          , comment :: !(Maybe String)
                          , createdBy :: !(Maybe String)
                          , encoding :: !(Maybe String)
+                         , infoHash :: !ByteString
                          } deriving (Eq, Show)
 
+mkPath :: [BVal] -> String
+mkPath xs = intercalate "/" $ map (\b -> let (Just s) = bstrToString b in s) xs
+
 mkInfo :: BVal -> Maybe Info
-mkInfo (Bdict m) = let (Bint pieceLength') = m ! Bstr (pack "piece length")
-                       (Bstr pieces') = m ! Bstr (pack "pieces")
+mkInfo (Bdict m) = let (Bint pieceLength') = m ! "piece length"
+                       (Bstr pieces') = m ! "pieces"
                        private' = Nothing
-                       (Bstr name') = m ! Bstr (pack "name")
-                       (Bint length') = m ! Bstr (pack "length")
-                       md5sum' = Nothing
-                   in Just Info { pieceLength = pieceLength'
-                                , pieces = pieces'
-                                , private = private'
-                                , name = unpack name'
-                                , lengthInBytes = length'
-                                , md5sum = md5sum'}
+                       bdictfiles = lookup "files" m
+                   in
+                     case bdictfiles of
+                       Just fs -> let (Blist files') = fs
+                                      (Bstr name') = m ! "name"
+                                  in
+                                    Just Info { pieceLength = pieceLength'
+                                              , pieces = pieces'
+                                              , private = private'
+                                              , dyninfo = MultiFileInfo {
+                                                  dname = unpack name',
+                                                  files =
+                                                      map (\(Bdict f) ->
+                                                            let (Just len') = bValToInteger (f ! "length")
+                                                                (Blist ds) = f ! "path"
+                                                                path' = mkPath ds
+                                                            in
+                                                              FileInfo { lengthInBytes = len',
+                                                                         md5sum = Nothing,
+                                                                         path = path'
+                                                                     })
+                                                      files' }
+                                              }
+                       Nothing -> let (Bstr name') = m ! "name"
+                                      (Bint length') = m ! "length"
+                                      md5sum' = Nothing
+                                  in
+                                    Just Info { pieceLength = pieceLength'
+                                              , pieces = pieces'
+                                              , private = private'
+                                              , dyninfo = SingleFileInfo {
+                                                  file = FileInfo {
+                                                      path = unpack name',
+                                                      lengthInBytes = length',
+                                                      md5sum = md5sum'
+                                                      }
+                                                  }
+                                              }
+
 mkInfo _ = Nothing
 
-maybeBstrToString :: Maybe BVal -> Maybe String
-maybeBstrToString Nothing = Nothing
-maybeBstrToString (Just s) = let (Bstr bs) = s
-                             in Just (unpack bs)
+mkMetaInfo :: BVal   -> Either String Metainfo
+mkMetaInfo (Bdict m)  =
+    let (Just info')  = mkInfo $ m ! "info"
+        announce'     = lookup "announce" m
+        announceList' = lookup "announce-list" m
+        creationDate' = lookup "creation date" m
+        comment'      = lookup "comment" m
+        createdBy'    = lookup "created by" m
+        encoding'     = lookup "encoding" m
+    in Right Metainfo {
+               info         = info'
+             , announceList = maybeToList (announce' >>= bstrToString)
+                              ++ getAnnounceList announceList'
+             , creationDate = bValToInteger =<< creationDate'
+             , comment      = bstrToString  =<< comment'
+             , createdBy    = bstrToString  =<< createdBy'
+             , encoding     = bstrToString  =<< encoding'
+             , infoHash     = hash . encode $ (m ! "info")
+             }
+mkMetaInfo _ = Left "mkMetaInfo: expect an input dict"
+
+getAnnounceList :: Maybe BVal -> [String]
+getAnnounceList Nothing          = []
+getAnnounceList (Just (Bint _))  = []
+getAnnounceList (Just (Bstr _))  = []
+getAnnounceList (Just (Blist l)) = map (\s -> case s of
+                                               (Bstr s') ->  unpack s'
+                                               (Blist s') -> case s' of
+                                                              [Bstr s''] -> unpack s''
+                                                              _ -> ""
+                                               _ -> "") l
+getAnnounceList (Just (Bdict _)) = []
 
-mkMetaInfo :: BVal -> Maybe Metainfo
-mkMetaInfo (Bdict m) = let (Just info') = mkInfo (m ! Bstr (pack "info"))
-                           (Bstr announce') = m ! Bstr (pack "announce")
-                           -- announceList = lookup (Bstr (pack "announce list"))
-                           announceList' = Nothing
-                           -- creationDate = lookup (Bstr (pack "creation date")) m
-                           creationDate' = Nothing
-                           comment' = lookup (Bstr (pack "comment")) m
-                           createdBy' = lookup (Bstr (pack "created by")) m
-                           encoding' = lookup (Bstr (pack "encoding")) m
-                       in Just Metainfo { info = info'
-                                        , announce = unpack announce'
-                                        , announceList = announceList'
-                                        , creationDate = creationDate'
-                                        , comment = maybeBstrToString comment'
-                                        , createdBy = maybeBstrToString createdBy'
-                                        , encoding = maybeBstrToString encoding'
-                                        }
-mkMetaInfo _ = Nothing
+torrentToMetainfo :: ByteString -> Either String Metainfo
+torrentToMetainfo s =
+  case decode s of
+   Right d -> mkMetaInfo d
+   Left e -> Left $ show e