diff options
author | Sam Truzjan <pxqr.sta@gmail.com> | 2014-02-08 07:19:31 +0400 |
---|---|---|
committer | Sam Truzjan <pxqr.sta@gmail.com> | 2014-02-08 07:19:31 +0400 |
commit | 7edacaaedd432c71169bbd59c9c0948e9a83da26 (patch) | |
tree | 1ca88d119c3575c502e184bfcc15572ee6a406f9 /src/Network/BitTorrent/Tracker/Session.hs | |
parent | f1f28f1a128caa3df5cdab2eb4c22ec07633af06 (diff) |
Add multitracker session
Diffstat (limited to 'src/Network/BitTorrent/Tracker/Session.hs')
-rw-r--r-- | src/Network/BitTorrent/Tracker/Session.hs | 206 |
1 files changed, 206 insertions, 0 deletions
diff --git a/src/Network/BitTorrent/Tracker/Session.hs b/src/Network/BitTorrent/Tracker/Session.hs new file mode 100644 index 00000000..7be16fd6 --- /dev/null +++ b/src/Network/BitTorrent/Tracker/Session.hs | |||
@@ -0,0 +1,206 @@ | |||
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 sessions. | ||
9 | -- | ||
10 | module Network.BitTorrent.Tracker.Session | ||
11 | ( -- * Session | ||
12 | Session | ||
13 | , newSession | ||
14 | , closeSession | ||
15 | |||
16 | -- * Events | ||
17 | , Event (..) | ||
18 | , notify | ||
19 | |||
20 | -- * Query | ||
21 | , askPeers | ||
22 | ) where | ||
23 | |||
24 | import Control.Applicative | ||
25 | import Control.Concurrent | ||
26 | import Control.Concurrent.STM | ||
27 | import Control.Exception | ||
28 | import Control.Monad | ||
29 | import Data.Default | ||
30 | import Data.Fixed | ||
31 | import Data.Foldable | ||
32 | import Data.List as L | ||
33 | import Data.Maybe | ||
34 | import Data.IORef | ||
35 | import Data.Text as T | ||
36 | import Data.Time | ||
37 | import Data.Traversable | ||
38 | import Network | ||
39 | import Network.URI | ||
40 | |||
41 | import Data.Torrent | ||
42 | import Data.Torrent.InfoHash | ||
43 | import Network.BitTorrent.Core | ||
44 | import Network.BitTorrent.Tracker.Cache | ||
45 | import Network.BitTorrent.Tracker.List | ||
46 | import Network.BitTorrent.Tracker.Message | ||
47 | import Network.BitTorrent.Tracker.RPC as RPC | ||
48 | |||
49 | {----------------------------------------------------------------------- | ||
50 | -- Tracker entry | ||
51 | -----------------------------------------------------------------------} | ||
52 | |||
53 | data Scrape = Scrape | ||
54 | { leechersCount :: Maybe Int | ||
55 | , seedersCount :: Maybe Int | ||
56 | } deriving (Show, Eq) | ||
57 | |||
58 | instance Default Scrape where | ||
59 | def = Scrape Nothing Nothing | ||
60 | |||
61 | |||
62 | data Status | ||
63 | = Running | ||
64 | | Paused | ||
65 | deriving (Show, Eq) | ||
66 | |||
67 | instance Default Status where | ||
68 | def = Paused | ||
69 | |||
70 | nextStatus :: Maybe Event -> Status | ||
71 | nextStatus Nothing = Running | ||
72 | nextStatus (Just Started ) = Running | ||
73 | nextStatus (Just Stopped ) = Paused | ||
74 | nextStatus (Just Completed) = Running | ||
75 | |||
76 | needNotify :: Maybe Event -> Maybe Status -> Bool | ||
77 | -- we always send _regular_ announce requests (for e.g. to get more peers); | ||
78 | needNotify Nothing _ = True | ||
79 | needNotify (Just Started) Nothing = True | ||
80 | needNotify (Just Stopped) Nothing = False | ||
81 | needNotify (Just Completed) Nothing = False | ||
82 | needNotify Nothing (Just Running) = True | ||
83 | needNotify Nothing (Just Paused ) = True | ||
84 | |||
85 | -- | Do we need to sent this event to a first working tracker or to | ||
86 | -- the all known good trackers? | ||
87 | allNotify :: Maybe Event -> Bool | ||
88 | allNotify Nothing = False | ||
89 | allNotify (Just Started) = False | ||
90 | allNotify (Just Stopped) = True | ||
91 | allNotify (Just Completed) = True | ||
92 | |||
93 | -- | Single tracker session. | ||
94 | data TrackerEntry = TrackerEntry | ||
95 | { -- | Tracker announce URI. | ||
96 | trackerURI :: !URI | ||
97 | |||
98 | -- | Used to notify 'Stopped' and 'Completed' events. | ||
99 | , statusSent :: !(Maybe Status) | ||
100 | |||
101 | -- | | ||
102 | , peersCache :: Cached [PeerAddr IP] | ||
103 | |||
104 | -- | May be used to show brief swarm stats in client GUI. | ||
105 | , scrapeCache :: Cached Scrape | ||
106 | } | ||
107 | |||
108 | nullEntry :: URI -> TrackerEntry | ||
109 | nullEntry uri = TrackerEntry uri Nothing def def | ||
110 | |||
111 | {----------------------------------------------------------------------- | ||
112 | -- Multitracker Session | ||
113 | -----------------------------------------------------------------------} | ||
114 | |||
115 | -- | Multitracker session. | ||
116 | data Session = Session | ||
117 | { infohash :: !InfoHash | ||
118 | , currentStatus :: !(MVar Status) | ||
119 | , trackers :: !(MVar (TrackerList TrackerEntry)) | ||
120 | } | ||
121 | |||
122 | -- Just Started | ||
123 | newSession :: InfoHash -> TrackerList URI -> IO Session | ||
124 | newSession ih origUris = do | ||
125 | uris <- shuffleTiers origUris | ||
126 | status <- newMVar def | ||
127 | entries <- newMVar (fmap nullEntry uris) | ||
128 | return (Session ih status entries) | ||
129 | |||
130 | -- Just Stopped | ||
131 | closeSession :: Session -> IO () | ||
132 | closeSession _ = return () | ||
133 | |||
134 | seconds :: Int -> NominalDiffTime | ||
135 | seconds n = realToFrac (toEnum n :: Uni) | ||
136 | |||
137 | cachePeers :: AnnounceInfo -> IO (Cached [PeerAddr IP]) | ||
138 | cachePeers AnnounceInfo {..} = | ||
139 | newCached (seconds respInterval) | ||
140 | (seconds (fromMaybe respInterval respMinInterval)) | ||
141 | (getPeerList respPeers) | ||
142 | |||
143 | cacheScrape :: AnnounceInfo -> IO (Cached Scrape) | ||
144 | cacheScrape AnnounceInfo {..} = | ||
145 | newCached (seconds respInterval) | ||
146 | (seconds (fromMaybe respInterval respMinInterval)) | ||
147 | Scrape | ||
148 | { seedersCount = respComplete | ||
149 | , leechersCount = respIncomplete | ||
150 | } | ||
151 | |||
152 | announceAll :: Manager -> Session -> Maybe Event -> IO () | ||
153 | announceAll mgr Session {..} mevent = do | ||
154 | modifyMVar_ trackers (traversal announceTo) | ||
155 | where | ||
156 | traversal | ||
157 | | allNotify mevent = traverseAll | ||
158 | | otherwise = traverseTiers | ||
159 | |||
160 | announceTo entry @ TrackerEntry {..} | ||
161 | | mevent `needNotify` statusSent = do | ||
162 | let q = SAnnounceQuery infohash def Nothing mevent | ||
163 | res <- RPC.announce mgr trackerURI q | ||
164 | TrackerEntry trackerURI (Just (nextStatus mevent)) | ||
165 | <$> cachePeers res <*> cacheScrape res | ||
166 | | otherwise = return entry | ||
167 | |||
168 | -- TODO send notifications to tracker periodically. | ||
169 | -- | | ||
170 | -- | ||
171 | -- This function /may/ block until tracker query proceed. | ||
172 | notify :: Manager -> Session -> Event -> IO () | ||
173 | notify mgr ses event = announceAll mgr ses (Just event) | ||
174 | |||
175 | -- TODO fork thread for reannounces | ||
176 | -- | | ||
177 | announce :: Manager -> Session -> IO () | ||
178 | announce mgr ses = announceAll mgr ses Nothing | ||
179 | |||
180 | -- TODO run announce if sesion have no peers | ||
181 | -- | This function /may/ block. Use async if needed. | ||
182 | askPeers :: Manager -> Session -> IO [PeerAddr IP] | ||
183 | askPeers mgr ses = do | ||
184 | list <- readMVar (trackers ses) | ||
185 | L.concat <$> collect (tryTakeData . peersCache) list | ||
186 | |||
187 | collect :: (a -> IO (Maybe b)) -> TrackerList a -> IO [b] | ||
188 | collect f lst =(catMaybes . toList) <$> traverse f lst | ||
189 | |||
190 | --sourcePeers :: Session -> Source (PeerAddr IP) | ||
191 | --sourcePeers | ||
192 | |||
193 | {----------------------------------------------------------------------- | ||
194 | -- State query | ||
195 | -----------------------------------------------------------------------} | ||
196 | |||
197 | data TrackerInfo = TrackerInfo | ||
198 | { | ||
199 | } | ||
200 | |||
201 | --instance ToJSON TrackerInfo where | ||
202 | -- toJSON = undefined | ||
203 | |||
204 | -- | | ||
205 | --getSessionState :: Session -> IO (TrackerList TrackerInfo) | ||
206 | --getSessionState = undefined | ||