diff options
author | Sam Truzjan <pxqr.sta@gmail.com> | 2014-02-06 21:43:56 +0400 |
---|---|---|
committer | Sam Truzjan <pxqr.sta@gmail.com> | 2014-02-06 21:43:56 +0400 |
commit | 25c46cddb6498155e2b8b07d85f900c4a950267e (patch) | |
tree | fcbf9c32ca74eae000883b127c49a46fb25c629f /src/Network | |
parent | b0d2f2883c9d134b04944d6ec4a4ac15fa516cab (diff) |
Add BEP12: Multitracker Protocol Extension
Diffstat (limited to 'src/Network')
-rw-r--r-- | src/Network/BitTorrent/Tracker/List.hs | 153 |
1 files changed, 153 insertions, 0 deletions
diff --git a/src/Network/BitTorrent/Tracker/List.hs b/src/Network/BitTorrent/Tracker/List.hs new file mode 100644 index 00000000..b6371f1d --- /dev/null +++ b/src/Network/BitTorrent/Tracker/List.hs | |||
@@ -0,0 +1,153 @@ | |||
1 | -- | | ||
2 | -- Copyright : (c) Sam Truzjan 2014 | ||
3 | -- License : BSD | ||
4 | -- Maintainer : pxqr.sta@gmail.com | ||
5 | -- Stability : experimental | ||
6 | -- Portability : portable | ||
7 | -- | ||
8 | -- Multitracker Metadata Extension support. | ||
9 | -- | ||
10 | -- For more info see: <http://www.bittorrent.org/beps/bep_0012.html> | ||
11 | -- | ||
12 | module Network.BitTorrent.Tracker.List | ||
13 | ( -- * Tracker list | ||
14 | TrackerList | ||
15 | |||
16 | -- * Construction | ||
17 | , trackerList | ||
18 | , shuffleTiers | ||
19 | |||
20 | -- * Traversals | ||
21 | , traverseAll | ||
22 | , traverseTiers | ||
23 | ) where | ||
24 | |||
25 | import Prelude hiding (mapM, foldr) | ||
26 | import Control.Applicative | ||
27 | import Control.Exception | ||
28 | import Data.List as L (elem, any, filter, null) | ||
29 | import Data.Foldable | ||
30 | import Data.Traversable | ||
31 | import Network.URI | ||
32 | import System.Random.Shuffle | ||
33 | |||
34 | import Data.Torrent | ||
35 | import Network.BitTorrent.Tracker.RPC as RPC | ||
36 | |||
37 | {----------------------------------------------------------------------- | ||
38 | -- Tracker list datatype | ||
39 | -----------------------------------------------------------------------} | ||
40 | |||
41 | type Tier a = [a] | ||
42 | |||
43 | -- | Tracker list is either a single tracker or list of tiers. All | ||
44 | -- trackers in each tier must be checked before the client goes on to | ||
45 | -- the next tier. | ||
46 | data TrackerList a | ||
47 | = Announce a -- ^ torrent file 'announce' field only | ||
48 | | TierList [Tier a] -- ^ torrent file 'announce-list' field only | ||
49 | deriving (Show, Eq) | ||
50 | |||
51 | instance Functor TrackerList where | ||
52 | fmap f (Announce a) = Announce (f a) | ||
53 | fmap f (TierList a) = TierList (fmap (fmap f) a) | ||
54 | |||
55 | instance Foldable TrackerList where | ||
56 | foldr f z (Announce a ) = f a z | ||
57 | foldr f z (TierList xs) = foldr (flip (foldr f)) z xs | ||
58 | |||
59 | instance Traversable TrackerList where | ||
60 | traverse f (Announce a ) = Announce <$> f a | ||
61 | traverse f (TierList xs) = TierList <$> traverse (traverse f) xs | ||
62 | |||
63 | {----------------------------------------------------------------------- | ||
64 | -- List extraction | ||
65 | -----------------------------------------------------------------------} | ||
66 | -- BEP12 do not expose any restrictions for the content of | ||
67 | -- 'announce-list' key - there are some /bad/ cases can happen with | ||
68 | -- poorly designed or even malicious torrent creation software. | ||
69 | -- | ||
70 | -- Bad case #1: announce-list is present, but empty. | ||
71 | -- | ||
72 | -- { tAnnounce = Just "http://a.com" | ||
73 | -- , tAnnounceList = Just [[]] | ||
74 | -- } | ||
75 | -- | ||
76 | -- Bad case #2: announce uri do not present in announce list. | ||
77 | -- | ||
78 | -- { tAnnounce = Just "http://a.com" | ||
79 | -- , tAnnounceList = Just [["udp://a.com"]] | ||
80 | -- } | ||
81 | -- | ||
82 | -- The addBackup function solves both problems by adding announce uri | ||
83 | -- as backup tier. | ||
84 | -- | ||
85 | addBackup :: [[URI]] -> URI -> [[URI]] | ||
86 | addBackup tiers bkp | ||
87 | | L.any (L.elem bkp) tiers = tiers | ||
88 | | otherwise = tiers ++ [[bkp]] | ||
89 | |||
90 | fixList :: Maybe [[URI]] -> Maybe URI -> Maybe [[URI]] | ||
91 | fixList mxss mx = do | ||
92 | xss <- mxss | ||
93 | let xss' = L.filter (not . L.null) xss | ||
94 | return $ maybe xss' (addBackup xss') mx | ||
95 | |||
96 | -- | Extract set of trackers from torrent file. The 'tAnnounce' key is | ||
97 | -- only ignored if the 'tAnnounceList' key is present. | ||
98 | trackerList :: Torrent -> Maybe (TrackerList URI) | ||
99 | trackerList Torrent {..} = TierList <$> (tAnnounceList `fixList` tAnnounce) | ||
100 | <|> Announce <$> tAnnounce | ||
101 | |||
102 | -- | Shuffle /order of trackers/ in each tier, preserving original | ||
103 | -- /order of tiers/. This can help to balance the load between the | ||
104 | -- trackers. | ||
105 | shuffleTiers :: TrackerList a -> IO (TrackerList a) | ||
106 | shuffleTiers (Announce a ) = return (Announce a) | ||
107 | shuffleTiers (TierList xs) = TierList <$> mapM shuffleM xs | ||
108 | |||
109 | {----------------------------------------------------------------------- | ||
110 | -- Special traversals (suppressed RPC exceptions) | ||
111 | -----------------------------------------------------------------------} | ||
112 | |||
113 | catchRPC :: IO a -> IO a -> IO a | ||
114 | catchRPC a b = catch a (f b) | ||
115 | where | ||
116 | f :: a -> RpcException -> a | ||
117 | f = const | ||
118 | |||
119 | throwRPC :: String -> IO a | ||
120 | throwRPC = throwIO . GenericException | ||
121 | |||
122 | -- | Like 'traverse' but ignore 'RpcExceptions'. | ||
123 | traverseAll :: (a -> IO a) -> TrackerList a -> IO (TrackerList a) | ||
124 | traverseAll action = traverse (action $?) | ||
125 | where | ||
126 | f $? x = catchRPC (f x) (return x) | ||
127 | |||
128 | -- | Like 'traverse' but put working trackers to the head of tiers. | ||
129 | -- This can help to avoid exceessive requests to not available | ||
130 | -- trackers at each reannounce. | ||
131 | traverseTiers :: (a -> IO a) -> TrackerList a -> IO (TrackerList a) | ||
132 | traverseTiers action (Announce a) = Announce <$> action a | ||
133 | traverseTiers action (TierList tiers) = TierList <$> goTiers (goTier []) tiers | ||
134 | where | ||
135 | goTiers _ [] = throwRPC "traverseTiers: no tiers" | ||
136 | goTiers f (x : xs) = catchRPC shortcut failback | ||
137 | where | ||
138 | shortcut = do | ||
139 | x' <- f x | ||
140 | return (x' : xs) | ||
141 | |||
142 | failback = do | ||
143 | xs' <- goTiers f xs | ||
144 | return (x : xs') | ||
145 | |||
146 | goTier _ [] = throwRPC "traverseTiers: no trackers in tier" | ||
147 | goTier failed (a : as) = catchRPC shortcut failback | ||
148 | where | ||
149 | shortcut = do | ||
150 | a' <- action a | ||
151 | return (a' : as ++ failed) -- failed trackers at the end | ||
152 | |||
153 | failback = goTier (a : failed) as | ||