diff options
-rw-r--r-- | bittorrent.cabal | 1 | ||||
-rw-r--r-- | exsamples/Main.hs | 13 | ||||
-rw-r--r-- | src/Network/BitTorrent.hs | 19 | ||||
-rw-r--r-- | src/Network/BitTorrent/Exchange.hs | 31 | ||||
-rw-r--r-- | src/Network/BitTorrent/Internal.hs | 27 | ||||
-rw-r--r-- | src/Network/BitTorrent/Tracker.hs | 1 |
6 files changed, 81 insertions, 11 deletions
diff --git a/bittorrent.cabal b/bittorrent.cabal index a95d6ac2..28188ce3 100644 --- a/bittorrent.cabal +++ b/bittorrent.cabal | |||
@@ -86,6 +86,7 @@ library | |||
86 | , conduit == 1.* | 86 | , conduit == 1.* |
87 | , network-conduit == 1.* | 87 | , network-conduit == 1.* |
88 | , cereal-conduit >= 0.5 | 88 | , cereal-conduit >= 0.5 |
89 | , resourcet | ||
89 | 90 | ||
90 | -- Misc | 91 | -- Misc |
91 | , lens | 92 | , lens |
diff --git a/exsamples/Main.hs b/exsamples/Main.hs index a5b865b0..88796642 100644 --- a/exsamples/Main.hs +++ b/exsamples/Main.hs | |||
@@ -6,7 +6,7 @@ import Data.Bitfield | |||
6 | import Network.BitTorrent | 6 | import Network.BitTorrent |
7 | import System.Environment | 7 | import System.Environment |
8 | import Control.Monad.Reader | 8 | import Control.Monad.Reader |
9 | 9 | import Data.IORef | |
10 | 10 | ||
11 | main :: IO () | 11 | main :: IO () |
12 | main = do | 12 | main = do |
@@ -16,6 +16,8 @@ main = do | |||
16 | client <- newClient [] | 16 | client <- newClient [] |
17 | swarm <- newLeacher client torrent | 17 | swarm <- newLeacher client torrent |
18 | 18 | ||
19 | ref <- newIORef 0 | ||
20 | |||
19 | discover swarm $ do | 21 | discover swarm $ do |
20 | addr <- asks connectedPeerAddr | 22 | addr <- asks connectedPeerAddr |
21 | liftIO $ print $ "connected to" ++ show addr | 23 | liftIO $ print $ "connected to" ++ show addr |
@@ -27,7 +29,14 @@ main = do | |||
27 | | Just m <- findMin bf -> yieldEvent (Want (BlockIx m 0 10)) | 29 | | Just m <- findMin bf -> yieldEvent (Want (BlockIx m 0 10)) |
28 | | otherwise -> return () | 30 | | otherwise -> return () |
29 | Want bix -> liftIO $ print bix | 31 | Want bix -> liftIO $ print bix |
30 | Fragment blk -> liftIO $ print (ppBlock blk) | 32 | Fragment blk -> do |
33 | |||
34 | liftIO $ do | ||
35 | readIORef ref >>= print | ||
36 | modifyIORef ref succ | ||
37 | print (ppBlock blk) | ||
38 | |||
39 | yieldEvent (Want (BlockIx 0 0 (16 * 1024))) | ||
31 | 40 | ||
32 | 41 | ||
33 | print "Bye-bye! =_=" \ No newline at end of file | 42 | print "Bye-bye! =_=" \ No newline at end of file |
diff --git a/src/Network/BitTorrent.hs b/src/Network/BitTorrent.hs index f7c4c004..7ec0a067 100644 --- a/src/Network/BitTorrent.hs +++ b/src/Network/BitTorrent.hs | |||
@@ -29,9 +29,14 @@ module Network.BitTorrent | |||
29 | , awaitEvent, yieldEvent | 29 | , awaitEvent, yieldEvent |
30 | ) where | 30 | ) where |
31 | 31 | ||
32 | import Control.Concurrent | ||
33 | import Control.Exception | ||
32 | import Control.Monad | 34 | import Control.Monad |
35 | |||
33 | import Data.IORef | 36 | import Data.IORef |
34 | 37 | ||
38 | import Network | ||
39 | |||
35 | import Data.Torrent | 40 | import Data.Torrent |
36 | import Network.BitTorrent.Internal | 41 | import Network.BitTorrent.Internal |
37 | import Network.BitTorrent.Exchange | 42 | import Network.BitTorrent.Exchange |
@@ -45,6 +50,8 @@ import Network.BitTorrent.Tracker | |||
45 | 50 | ||
46 | discover :: SwarmSession -> P2P () -> IO () | 51 | discover :: SwarmSession -> P2P () -> IO () |
47 | discover swarm action = do | 52 | discover swarm action = do |
53 | port <- listener swarm action | ||
54 | |||
48 | let conn = TConnection (tAnnounce (torrentMeta swarm)) | 55 | let conn = TConnection (tAnnounce (torrentMeta swarm)) |
49 | (tInfoHash (torrentMeta swarm)) | 56 | (tInfoHash (torrentMeta swarm)) |
50 | (clientPeerID (clientSession swarm)) | 57 | (clientPeerID (clientSession swarm)) |
@@ -57,8 +64,14 @@ discover swarm action = do | |||
57 | forever $ do | 64 | forever $ do |
58 | addr <- getPeerAddr tses | 65 | addr <- getPeerAddr tses |
59 | putStrLn "connecting to peer" | 66 | putStrLn "connecting to peer" |
60 | withPeer swarm addr action | 67 | handle handler (withPeer swarm addr action) |
61 | |||
62 | 68 | ||
69 | where | ||
70 | handler :: IOException -> IO () | ||
71 | handler _ = return () | ||
63 | 72 | ||
64 | port = 10000 | 73 | listener :: SwarmSession -> P2P () -> IO PortNumber |
74 | listener _ _ = do | ||
75 | -- TODO: | ||
76 | -- forkIO loop | ||
77 | return 10000 | ||
diff --git a/src/Network/BitTorrent/Exchange.hs b/src/Network/BitTorrent/Exchange.hs index de13d4ce..0e2799b2 100644 --- a/src/Network/BitTorrent/Exchange.hs +++ b/src/Network/BitTorrent/Exchange.hs | |||
@@ -28,6 +28,7 @@ import Control.Exception | |||
28 | import Control.Lens | 28 | import Control.Lens |
29 | import Control.Monad.Reader | 29 | import Control.Monad.Reader |
30 | import Control.Monad.State | 30 | import Control.Monad.State |
31 | import Control.Monad.Trans.Resource | ||
31 | 32 | ||
32 | import Data.IORef | 33 | import Data.IORef |
33 | import Data.Function | 34 | import Data.Function |
@@ -77,7 +78,7 @@ awaitMessage = P2P (ReaderT go) | |||
77 | liftIO $ putStrLn "trying recv:" | 78 | liftIO $ putStrLn "trying recv:" |
78 | mmsg <- await | 79 | mmsg <- await |
79 | case mmsg of | 80 | case mmsg of |
80 | Nothing -> go se | 81 | Nothing -> monadThrow SessionException |
81 | Just msg -> do | 82 | Just msg -> do |
82 | -- liftIO $ updateIncoming se | 83 | -- liftIO $ updateIncoming se |
83 | liftIO $ print ("recv:" <+> ppMessage msg) | 84 | liftIO $ print ("recv:" <+> ppMessage msg) |
@@ -250,8 +251,18 @@ awaitEvent = awaitMessage >>= go | |||
250 | -- @ | 251 | -- @ |
251 | -- | 252 | -- |
252 | yieldEvent :: Event -> P2P () | 253 | yieldEvent :: Event -> P2P () |
253 | yieldEvent (Available bf) = undefined | 254 | yieldEvent (Available bf) = undefined |
254 | yieldEvent _ = undefined | 255 | yieldEvent (Want bix) = do |
256 | offer <- peerOffer | ||
257 | if ixPiece bix `BF.member` offer | ||
258 | then yieldMessage (Request bix) | ||
259 | else return () | ||
260 | |||
261 | yieldEvent (Fragment blk) = do | ||
262 | offer <- clientOffer | ||
263 | if blkPiece blk `BF.member` offer | ||
264 | then yieldMessage (Piece blk) | ||
265 | else return () | ||
255 | 266 | ||
256 | --flushBroadcast :: P2P () | 267 | --flushBroadcast :: P2P () |
257 | --flushBroadcast = nextBroadcast >>= maybe (return ()) go | 268 | --flushBroadcast = nextBroadcast >>= maybe (return ()) go |
@@ -265,11 +276,21 @@ checkPiece = undefined | |||
265 | P2P monad | 276 | P2P monad |
266 | -----------------------------------------------------------------------} | 277 | -----------------------------------------------------------------------} |
267 | 278 | ||
279 | -- | | ||
280 | -- Exceptions: | ||
281 | -- | ||
282 | -- * SessionException: is visible with one peer session. Use this | ||
283 | -- exception to terminate P2P session, but not the swarm session. | ||
284 | -- | ||
268 | newtype P2P a = P2P { | 285 | newtype P2P a = P2P { |
269 | runP2P :: ReaderT PeerSession PeerWire a | 286 | runP2P :: ReaderT PeerSession PeerWire a |
270 | } deriving (Functor, Applicative, Monad, MonadReader PeerSession, MonadIO) | 287 | } deriving ( Functor, Applicative, Monad |
288 | , MonadIO, MonadThrow, MonadActive | ||
289 | , MonadReader PeerSession | ||
290 | ) | ||
271 | 291 | ||
272 | withPeer :: SwarmSession -> PeerAddr -> P2P () -> IO () | 292 | withPeer :: SwarmSession -> PeerAddr -> P2P () -> IO () |
273 | withPeer se addr p2p = | 293 | withPeer se addr p2p = |
274 | withPeerSession se addr $ \(sock, pses) -> do | 294 | withPeerSession se addr $ \(sock, pses) -> do |
275 | runConduit sock (runReaderT (runP2P p2p) pses) | 295 | handle putSessionException $ |
296 | runConduit sock (runReaderT (runP2P p2p) pses) | ||
diff --git a/src/Network/BitTorrent/Internal.hs b/src/Network/BitTorrent/Internal.hs index da104240..a3e1a5cd 100644 --- a/src/Network/BitTorrent/Internal.hs +++ b/src/Network/BitTorrent/Internal.hs | |||
@@ -16,6 +16,7 @@ | |||
16 | {-# LANGUAGE OverloadedStrings #-} | 16 | {-# LANGUAGE OverloadedStrings #-} |
17 | {-# LANGUAGE RecordWildCards #-} | 17 | {-# LANGUAGE RecordWildCards #-} |
18 | {-# LANGUAGE TemplateHaskell #-} | 18 | {-# LANGUAGE TemplateHaskell #-} |
19 | {-# LANGUAGE DeriveDataTypeable #-} | ||
19 | {-# LANGUAGE FlexibleInstances #-} | 20 | {-# LANGUAGE FlexibleInstances #-} |
20 | {-# LANGUAGE FlexibleContexts #-} | 21 | {-# LANGUAGE FlexibleContexts #-} |
21 | {-# LANGUAGE MultiParamTypeClasses #-} | 22 | {-# LANGUAGE MultiParamTypeClasses #-} |
@@ -23,20 +24,31 @@ | |||
23 | module Network.BitTorrent.Internal | 24 | module Network.BitTorrent.Internal |
24 | ( Progress(..), startProgress | 25 | ( Progress(..), startProgress |
25 | 26 | ||
27 | -- * Client | ||
26 | , ClientSession (clientPeerID, allowedExtensions) | 28 | , ClientSession (clientPeerID, allowedExtensions) |
27 | , newClient, getCurrentProgress | 29 | , newClient, getCurrentProgress |
28 | 30 | ||
31 | -- * Swarm | ||
29 | , SwarmSession(SwarmSession, torrentMeta, clientSession) | 32 | , SwarmSession(SwarmSession, torrentMeta, clientSession) |
30 | , newLeacher, newSeeder | 33 | , newLeacher, newSeeder |
31 | 34 | ||
35 | -- * Peer | ||
32 | , PeerSession(PeerSession, connectedPeerAddr | 36 | , PeerSession(PeerSession, connectedPeerAddr |
33 | , swarmSession, enabledExtensions | 37 | , swarmSession, enabledExtensions |
34 | ) | 38 | ) |
35 | , SessionState | 39 | , SessionState |
40 | , withPeerSession | ||
41 | |||
42 | -- ** Exceptions | ||
43 | , SessionException(..) | ||
44 | , isSessionException | ||
45 | , putSessionException | ||
46 | , sessionError | ||
47 | |||
48 | -- ** Properties | ||
36 | , bitfield, status | 49 | , bitfield, status |
37 | , emptyBF, fullBF, singletonBF, adjustBF | 50 | , emptyBF, fullBF, singletonBF, adjustBF |
38 | , getPieceCount, getClientBF | 51 | , getPieceCount, getClientBF |
39 | , sessionError, withPeerSession | ||
40 | 52 | ||
41 | -- * Timeouts | 53 | -- * Timeouts |
42 | , updateIncoming, updateOutcoming | 54 | , updateIncoming, updateOutcoming |
@@ -55,6 +67,7 @@ import Data.Default | |||
55 | import Data.Function | 67 | import Data.Function |
56 | import Data.Ord | 68 | import Data.Ord |
57 | import Data.Set as S | 69 | import Data.Set as S |
70 | import Data.Typeable | ||
58 | 71 | ||
59 | import Data.Serialize hiding (get) | 72 | import Data.Serialize hiding (get) |
60 | import Text.PrettyPrint | 73 | import Text.PrettyPrint |
@@ -212,6 +225,7 @@ data PeerSession = PeerSession { | |||
212 | -- to avoid reduntant KA messages. | 225 | -- to avoid reduntant KA messages. |
213 | , outcomingTimeout :: TimeoutKey | 226 | , outcomingTimeout :: TimeoutKey |
214 | 227 | ||
228 | -- TODO use dupChan for broadcasting | ||
215 | , broadcastMessages :: Chan [Message] | 229 | , broadcastMessages :: Chan [Message] |
216 | , sessionState :: IORef SessionState | 230 | , sessionState :: IORef SessionState |
217 | } | 231 | } |
@@ -234,6 +248,17 @@ instance (MonadIO m, MonadReader PeerSession m) | |||
234 | get = asks sessionState >>= liftIO . readIORef | 248 | get = asks sessionState >>= liftIO . readIORef |
235 | put s = asks sessionState >>= \ref -> liftIO $ writeIORef ref s | 249 | put s = asks sessionState >>= \ref -> liftIO $ writeIORef ref s |
236 | 250 | ||
251 | data SessionException = SessionException | ||
252 | deriving (Show, Typeable) | ||
253 | |||
254 | instance Exception SessionException | ||
255 | |||
256 | isSessionException :: Monad m => SessionException -> m () | ||
257 | isSessionException _ = return () | ||
258 | |||
259 | putSessionException :: SessionException -> IO () | ||
260 | putSessionException = print | ||
261 | |||
237 | sessionError :: MonadIO m => Doc -> m () | 262 | sessionError :: MonadIO m => Doc -> m () |
238 | sessionError msg | 263 | sessionError msg |
239 | = liftIO $ throwIO $ userError $ render $ msg <+> "in session" | 264 | = liftIO $ throwIO $ userError $ render $ msg <+> "in session" |
diff --git a/src/Network/BitTorrent/Tracker.hs b/src/Network/BitTorrent/Tracker.hs index 179dae1d..11bc52de 100644 --- a/src/Network/BitTorrent/Tracker.hs +++ b/src/Network/BitTorrent/Tracker.hs | |||
@@ -145,6 +145,7 @@ data TSession = TSession { | |||
145 | , sePeers :: Chan PeerAddr | 145 | , sePeers :: Chan PeerAddr |
146 | -- TODO use something like 'TVar (Set PeerAddr)' | 146 | -- TODO use something like 'TVar (Set PeerAddr)' |
147 | -- otherwise we might get space leak | 147 | -- otherwise we might get space leak |
148 | -- TODO or maybe BoundedChan? | ||
148 | } | 149 | } |
149 | 150 | ||
150 | newSession :: Progress -> Int -> [PeerAddr] -> IO TSession | 151 | newSession :: Progress -> Int -> [PeerAddr] -> IO TSession |