From e82e69136d468dac0b133e4f8c99d5f91cbdc598 Mon Sep 17 00:00:00 2001
From: Ramakrishnan Muthukrishnan <ram@rkrishnan.org>
Date: Tue, 25 Jul 2017 19:34:13 +0530
Subject: [PATCH] metainfo: support multifile .torrent files

---
 src/FuncTorrent/Metainfo.hs | 46 +++++++++++++++++++++++++++++--------
 1 file changed, 37 insertions(+), 9 deletions(-)

diff --git a/src/FuncTorrent/Metainfo.hs b/src/FuncTorrent/Metainfo.hs
index 4cc6ed1..801b53d 100644
--- a/src/FuncTorrent/Metainfo.hs
+++ b/src/FuncTorrent/Metainfo.hs
@@ -27,18 +27,22 @@ module FuncTorrent.Metainfo
 import Prelude hiding (lookup)
 import Data.ByteString.Char8 (ByteString, unpack)
 import Data.Map as M ((!), lookup)
+import Data.List (intersperse)
 import Crypto.Hash.SHA1 (hash)
 import Data.Maybe (maybeToList)
 
 import FuncTorrent.Bencode (BVal(..), encode, decode, bstrToString, bValToInteger)
 
--- only single file mode supported for the time being.
+data FileMeta = FileMeta { lengthInBytes :: !Integer
+                         , md5sum :: !(Maybe String)
+                         , path :: String
+                         } deriving (Eq, Show)
+
 data Info = Info { pieceLength :: !Integer
                  , pieces :: !ByteString
                  , private :: !(Maybe Integer)
                  , name :: !String
-                 , lengthInBytes :: !Integer
-                 , md5sum :: !(Maybe String)
+                 , filemeta :: [FileMeta]
                  } deriving (Eq, Show)
 
 data Metainfo = Metainfo { info :: !(Maybe Info)
@@ -55,16 +59,40 @@ bvalToInfo (Bdict m) = let (Bint pieceLength') = m ! "piece length"
                            (Bstr pieces') = m ! "pieces"
                            private' = Nothing
                            (Bstr name') = m ! "name"
+                           -- is the key "files" present? If so, it is a multi-file torrent
+                           -- if not, it is a single file torrent.
+                           filesIfMulti = lookup "files" m
                            (Bint length') = m ! "length"
                            md5sum' = Nothing
-                       in Just Info { pieceLength = pieceLength'
-                                    , pieces = pieces'
-                                    , private = private'
-                                    , name = unpack name'
-                                    , lengthInBytes = length'
-                                    , md5sum = md5sum'}
+                           partialInfo = Info { pieceLength = pieceLength'
+                                               , pieces = pieces'
+                                               , private = private'
+                                               , name = unpack name'
+                                               }
+                       in
+                         case filesIfMulti of
+                           Nothing -> let (Bint length') = m ! "length"
+                                          filemeta' = FileMeta { lengthInBytes = length'
+                                                               , md5sum = Nothing
+                                                               , path = unpack name' }
+                                      in Just (partialInfo { filemeta = [filemeta'] })
+                           Just (Blist files) -> mapM toFileMeta files >>=
+                                                 \filemeta' ->
+                                                   Just partialInfo { filemeta = filemeta' }
 bvalToInfo _ = Nothing
 
+toFileMeta :: BVal -> Maybe FileMeta
+toFileMeta (Bdict fm) = let (Bint length) = fm ! "length"
+                            (Blist pathElems) = fm ! "path"
+                            pathStrings = fmap bstrToString pathElems
+                        in
+                          sequence pathStrings >>=
+                          \pathList -> let path' = concat $ intersperse "/" pathList
+                                       in Just (FileMeta { lengthInBytes = length
+                                                         , md5sum = Nothing
+                                                         , path = path' })
+toFileMeta _ = Nothing
+
 mkMetaInfo :: BVal   -> Either String Metainfo
 mkMetaInfo (Bdict m)  =
     let info'         = bvalToInfo $ m ! "info"
-- 
2.45.2