diff options
author | Sam T <pxqr.sta@gmail.com> | 2013-07-21 02:13:59 +0400 |
---|---|---|
committer | Sam T <pxqr.sta@gmail.com> | 2013-07-21 02:13:59 +0400 |
commit | 5d5a7dab5ab0d5d7e35617f8476382a99b38d6db (patch) | |
tree | dccb1f72a7f3ccdb4d1896272dcf6bbedebf2254 /src/Network/BitTorrent/Tracker/HTTP.hs | |
parent | da55acae9bba103ddda4385cb4d8918afcad7be1 (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.hs | 115 |
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 #-} | ||
2 | module Network.BitTorrent.Tracker.HTTP | ||
3 | ( askTracker, leaveTracker | ||
4 | , scrapeURL | ||
5 | ) where | ||
6 | |||
7 | import Control.Applicative | ||
8 | import Control.Monad | ||
9 | import Data.BEncode | ||
10 | import Data.ByteString as B | ||
11 | import Data.ByteString.Char8 as BC | ||
12 | import Data.List as L | ||
13 | import Data.Map as M | ||
14 | import Data.Monoid | ||
15 | import Data.URLEncoded as URL | ||
16 | import Network.URI | ||
17 | import Network.HTTP | ||
18 | |||
19 | import Data.Torrent | ||
20 | import Network.BitTorrent.Tracker.Protocol | ||
21 | |||
22 | {----------------------------------------------------------------------- | ||
23 | Announce | ||
24 | -----------------------------------------------------------------------} | ||
25 | |||
26 | encodeRequest :: URI -> AnnounceQuery -> URI | ||
27 | encodeRequest announce req = URL.urlEncode req | ||
28 | `addToURI` announce | ||
29 | `addHashToURI` reqInfoHash req | ||
30 | |||
31 | mkHTTPRequest :: URI -> Request ByteString | ||
32 | mkHTTPRequest 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 | -- | ||
40 | askTracker :: URI -> AnnounceQuery -> IO AnnounceInfo | ||
41 | askTracker 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'. | ||
56 | leaveTracker :: URI -> AnnounceQuery -> IO () | ||
57 | leaveTracker 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. | ||
66 | type 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 | -- | ||
74 | scrapeURL :: URI -> [InfoHash] -> Maybe URI | ||
75 | scrapeURL 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 | -- | ||
94 | scrape :: URI -- ^ Announce 'URI'. | ||
95 | -> [InfoHash] -- ^ Torrents to be scrapped. | ||
96 | -> IO (Result Scrape) -- ^ 'ScrapeInfo' for each torrent. | ||
97 | scrape 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 | -- | ||
107 | scrapeOne :: URI -- ^ Announce 'URI' | ||
108 | -> InfoHash -- ^ Hash of the torrent info. | ||
109 | -> IO (Result ScrapeInfo) -- ^ 'ScrapeInfo' for the torrent. | ||
110 | scrapeOne 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 | ||