summaryrefslogtreecommitdiff
path: root/src/Network/BitTorrent/Tracker/HTTP.hs
diff options
context:
space:
mode:
authorSam T <pxqr.sta@gmail.com>2013-07-21 02:13:59 +0400
committerSam T <pxqr.sta@gmail.com>2013-07-21 02:13:59 +0400
commit5d5a7dab5ab0d5d7e35617f8476382a99b38d6db (patch)
treedccb1f72a7f3ccdb4d1896272dcf6bbedebf2254 /src/Network/BitTorrent/Tracker/HTTP.hs
parentda55acae9bba103ddda4385cb4d8918afcad7be1 (diff)
~ Move HTTP tracker stuff to its own module.
Diffstat (limited to 'src/Network/BitTorrent/Tracker/HTTP.hs')
-rw-r--r--src/Network/BitTorrent/Tracker/HTTP.hs115
1 files changed, 115 insertions, 0 deletions
diff --git a/src/Network/BitTorrent/Tracker/HTTP.hs b/src/Network/BitTorrent/Tracker/HTTP.hs
new file mode 100644
index 00000000..38388316
--- /dev/null
+++ b/src/Network/BitTorrent/Tracker/HTTP.hs
@@ -0,0 +1,115 @@
1{-# LANGUAGE OverloadedStrings #-}
2module Network.BitTorrent.Tracker.HTTP
3 ( askTracker, leaveTracker
4 , scrapeURL
5 ) where
6
7import Control.Applicative
8import Control.Monad
9import Data.BEncode
10import Data.ByteString as B
11import Data.ByteString.Char8 as BC
12import Data.List as L
13import Data.Map as M
14import Data.Monoid
15import Data.URLEncoded as URL
16import Network.URI
17import Network.HTTP
18
19import Data.Torrent
20import Network.BitTorrent.Tracker.Protocol
21
22{-----------------------------------------------------------------------
23 Announce
24-----------------------------------------------------------------------}
25
26encodeRequest :: URI -> AnnounceQuery -> URI
27encodeRequest announce req = URL.urlEncode req
28 `addToURI` announce
29 `addHashToURI` reqInfoHash req
30
31mkHTTPRequest :: URI -> Request ByteString
32mkHTTPRequest uri = Request uri GET [] ""
33
34-- TODO rename to something like "announceBlahBlah"
35
36-- | Send request and receive response from the tracker specified in
37-- announce list. This function throws 'IOException' if it couldn't
38-- send request or receive response or decode response.
39--
40askTracker :: URI -> AnnounceQuery -> IO AnnounceInfo
41askTracker announce req = do
42 let r = mkHTTPRequest (encodeRequest announce req)
43
44 rawResp <- simpleHTTP r
45 respBody <- getResponseBody rawResp
46 checkResult $ decoded respBody
47 where
48 checkResult (Left err)
49 = ioError $ userError $ err ++ " in tracker response"
50 checkResult (Right (Failure err))
51 = ioError $ userError $ show err ++ " in tracker response"
52 checkResult (Right resp) = return resp
53
54-- | The same as the 'askTracker' but ignore response. Used in
55-- conjunction with 'Stopped'.
56leaveTracker :: URI -> AnnounceQuery -> IO ()
57leaveTracker announce req = do
58 let r = mkHTTPRequest (encodeRequest announce req)
59 void $ simpleHTTP r >>= getResponseBody
60
61{-----------------------------------------------------------------------
62 Scrape
63-----------------------------------------------------------------------}
64
65-- | Scrape info about a set of torrents.
66type Scrape = Map InfoHash ScrapeInfo
67
68-- | Trying to convert /announce/ URL to /scrape/ URL. If 'scrapeURL'
69-- gives 'Nothing' then tracker do not support scraping. The info hash
70-- list is used to restrict the tracker's report to that particular
71-- torrents. Note that scrapping of multiple torrents may not be
72-- supported. (Even if scrapping convention is supported)
73--
74scrapeURL :: URI -> [InfoHash] -> Maybe URI
75scrapeURL uri ihs = do
76 newPath <- replace (BC.pack (uriPath uri))
77 let newURI = uri { uriPath = BC.unpack newPath }
78 return (L.foldl addHashToURI newURI ihs)
79 where
80 replace :: ByteString -> Maybe ByteString
81 replace p
82 | ps <- BC.splitWith (== '/') p
83 , "announce" `B.isPrefixOf` L.last ps
84 = let newSuff = "scrape" <> B.drop (B.length "announce") (L.last ps)
85 in Just (B.intercalate "/" (L.init ps ++ [newSuff]))
86 | otherwise = Nothing
87
88
89-- | For each 'InfoHash' of torrents request scrape info from the tracker.
90-- However if the info hash list is 'null', the tracker should list
91-- all available torrents.
92-- Note that the 'URI' should be /announce/ URI, not /scrape/ URI.
93--
94scrape :: URI -- ^ Announce 'URI'.
95 -> [InfoHash] -- ^ Torrents to be scrapped.
96 -> IO (Result Scrape) -- ^ 'ScrapeInfo' for each torrent.
97scrape announce ihs
98 | Just uri<- scrapeURL announce ihs = do
99 rawResp <- simpleHTTP (Request uri GET [] "")
100 respBody <- getResponseBody rawResp
101 return (decoded (BC.pack respBody))
102
103 | otherwise = return (Left "Tracker do not support scraping")
104
105-- | More particular version of 'scrape', just for one torrent.
106--
107scrapeOne :: URI -- ^ Announce 'URI'
108 -> InfoHash -- ^ Hash of the torrent info.
109 -> IO (Result ScrapeInfo) -- ^ 'ScrapeInfo' for the torrent.
110scrapeOne uri ih = extract <$> scrape uri [ih]
111 where
112 extract (Right m)
113 | Just s <- M.lookup ih m = Right s
114 | otherwise = Left "unable to find info hash in response dict"
115 extract (Left e) = Left e