-
Notifications
You must be signed in to change notification settings - Fork 0
/
metainfo.hs
144 lines (126 loc) · 4.41 KB
/
metainfo.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
module Metainfo
--( loadMetainfoFile
--, loadMetainfo
--, BTMetainfo
--, totalSize
--, infoHash
--, trackers
--) where
where
import Crypto.Hash.SHA1 (hashlazy)
import Data.BEncode as BE
import Data.ByteString.Char8 as BS8 (readFile, ByteString)
import qualified Data.ByteString as B
import Data.Maybe (fromMaybe)
import Data.Typeable (Typeable)
data BTMetainfo = BTMetainfo
{ announce :: ByteString
, announceList :: Maybe [[ByteString]]
, comment :: Maybe ByteString
, createdBy :: Maybe ByteString
, creationDate :: Maybe ByteString
, encoding :: Maybe ByteString
, info :: BTInfo } deriving (Typeable, Show)
instance BEncode BTMetainfo where
toBEncode BTMetainfo {..} = toDict $
"announce" .=! announce .:
"announce-list" .=? announceList .:
"comment" .=? comment .:
"created by" .=? createdBy .:
"creation date" .=? creationDate .:
"encoding" .=? encoding .:
"info" .=! info .:
endDict
fromBEncode = fromDict $ BTMetainfo
<$>! "announce"
<*>? "announce-list"
<*>? "comment"
<*>? "created by"
<*>? "creation date"
<*>? "encoding"
<*>! "info"
data BTInfo = BTSingleFileInfo
{ sfFileLength :: Int
, sfMd5sum :: Maybe ByteString
, sfName :: ByteString
, sfPieceLength :: Int
, sfPieces :: ByteString
, sfPrivate :: Maybe Bool } |
BTMultiFileInfo
{ mfFiles :: [BTFileinfo]
, mfName :: ByteString
, mfPieceLength :: Int
, mfPieces :: ByteString
, mfPrivate :: Maybe Bool } deriving (Typeable, Show)
instance BEncode BTInfo where
toBEncode BTSingleFileInfo {..} = toDict $
"length" .=! sfFileLength .:
"md5sum" .=? sfMd5sum .:
"name" .=! sfName .:
"piece length" .=! sfPieceLength .:
"pieces" .=! sfPieces .:
"private" .=? sfPrivate .:
endDict
toBEncode BTMultiFileInfo {..} = toDict $
"files" .=! mfFiles .:
"name" .=! mfName .:
"piece length".=! mfPieceLength .:
"pieces" .=! mfPieces .:
"private" .=? mfPrivate .:
endDict
fromBEncode = fromDict $ do
files <- lookAhead $ opt "files"
case files of
Just _ -> BTMultiFileInfo
<$>! "files"
<*>! "name"
<*>! "piece length"
<*>! "pieces"
<*>? "private"
Nothing -> BTSingleFileInfo
<$>! "length"
<*>? "md5sum"
<*>! "name"
<*>! "piece length"
<*>! "pieces"
<*>? "private"
getPieces :: ByteString -> [ByteString]
getPieces bs = chunk [] $ B.splitAt n bs
where
n = 20
chunk acc (piece, "") = acc
chunk acc (piece, remain) = chunk (piece:acc) $ B.splitAt n remain
data BTFileinfo = BTFileinfo
{ filelen :: Int
, fMD5sum :: Maybe ByteString
, path :: [ByteString] } deriving (Typeable, Show)
instance BEncode BTFileinfo where
toBEncode BTFileinfo {..} = toDict $
"length" .=! filelen .:
"md5sum" .=? fMD5sum .:
"path" .=! path .:
endDict
fromBEncode = fromDict $ BTFileinfo
<$>! "length"
<*>? "md5sum"
<*>! "path"
loadMetainfoFile :: String -> IO (Result BTMetainfo)
loadMetainfoFile fn = decode `fmap` BS8.readFile fn
loadMetainfo :: ByteString -> Result BTMetainfo
loadMetainfo = decode
{- HELPER FUNCTIONS -}
infoHash :: BTMetainfo -> ByteString
infoHash = hashlazy . encode . info
totalSize :: BTMetainfo -> Int
totalSize minfo = case info minfo of
BTSingleFileInfo {..} -> sfFileLength
BTMultiFileInfo {..} -> totalSize' 0 mfFiles
where
totalSize' acc [] = acc
totalSize' acc (f:fs) = totalSize' (acc + filelen f) fs
trackers :: BTMetainfo -> [ByteString]
trackers BTMetainfo {..} = announce:concat list
where list = fromMaybe [] announceList