]> git.rkrishnan.org Git - functorrent.git/blobdiff - src/FuncTorrent/Peer.hs
peer: more refactoring. Supports all messages as before
[functorrent.git] / src / FuncTorrent / Peer.hs
index e809f3f1c0c744e061e50ebf5b7f1ed44ff7b785..cf5b66d35f35e6069b26cf1f56ecb1d893984fd8 100644 (file)
@@ -10,7 +10,7 @@ import System.IO (Handle, BufferMode(..), hSetBuffering)
 import Data.ByteString (ByteString, unpack, concat, hGet, hPut, take, empty)
 import qualified Data.ByteString.Char8 as BC (length)
 import Network (connectTo, PortID(..))
-import Control.Monad (liftM, forM)
+import Control.Monad.State
 import Data.Bits
 import Data.Word (Word8)
 import Data.Map (Map, fromList, toList, (!), mapWithKey, adjust)
@@ -21,13 +21,14 @@ import FuncTorrent.Utils (splitN, splitNum)
 import FuncTorrent.Fileops (createDummyFile, writeFileAtOffset)
 import FuncTorrent.PeerMsgs (Peer(..), PeerMsg(..), sendMsg, getMsg, genHandshakeMsg)
 
--- PeerState is a misnomer
-data PeerState = PeerState { handle :: Handle
-                           , peer :: Peer
-                           , meChoking :: Bool
-                           , meInterested :: Bool
-                           , heChoking :: Bool
-                           , heInterested :: Bool}
+data PState = PState { handle :: Handle
+                     , peer :: Peer
+                     , meChoking :: Bool
+                     , meInterested :: Bool
+                     , heChoking :: Bool
+                     , heInterested :: Bool}
+
+type PeerState = State PState
 
 data PieceDlState = Pending
                   | InProgress
@@ -36,7 +37,7 @@ data PieceDlState = Pending
 
 -- todo - map with index to a new data structure (peers who have that piece amd state)
 data PieceData = PieceData { peers :: [Peer]        -- ^ list of peers who have this piece
-                           , state :: PieceDlState  -- ^ state of the piece from download perspective.
+                           , dlstate :: PieceDlState  -- ^ state of the piece from download perspective.
                            , hash  :: ByteString    -- ^ piece hash
                            , len :: Integer }       -- ^ piece length
 
@@ -49,7 +50,7 @@ type PieceMap = Map Integer PieceData
 mkPieceMap :: Integer -> ByteString -> [Integer] -> PieceMap
 mkPieceMap numPieces pieceHash pLengths = fromList kvs
   where kvs = [(i, PieceData { peers = []
-                             , state = Pending
+                             , dlstate = Pending
                              , hash = h
                              , len = pLen })
               | (i, h, pLen) <- zip3 [0..numPieces] hashes pLengths]
@@ -57,7 +58,7 @@ mkPieceMap numPieces pieceHash pLengths = fromList kvs
 
 havePiece :: PieceMap -> Integer -> Bool
 havePiece pm index =
-  state (pm ! index) == Have
+  dlstate (pm ! index) == Have
 
 connectToPeer :: Peer -> IO Handle
 connectToPeer (Peer _ ip port) = do
@@ -82,60 +83,28 @@ bitfieldToList bs = go bs 0
           in
            setBits ++ go bs' (pos + 1)
 
--- recvMsg :: Peer -> Handle -> Msg
-msgLoop :: PeerState -> PieceMap -> IO ()
-msgLoop pState@(PeerState { meInterested = False, heChoking = True }) pieceStatus =
-  do
-    -- if me NOT Interested and she is Choking, tell her that
-    -- I am interested.
-    let h = handle pState
-    sendMsg h InterestedMsg
-    putStrLn $ "--> InterestedMsg to peer: " ++ show (peer pState)
-    msgLoop (pState { meInterested = True }) pieceStatus
-msgLoop pState@(PeerState { meInterested = True, heChoking = False }) pieceStatus =
-  -- if me Interested and she not Choking, send her a request
-  -- for a piece.
-  case pickPiece pieceStatus of
-   Nothing -> putStrLn "Nothing to download"
-   Just workPiece -> do
-     let pLen = len (pieceStatus ! workPiece)
-     putStrLn $ "piece length = " ++ show pLen
-     pBS <- downloadPiece (handle pState) workPiece pLen
-     if not $ verifyHash pBS (hash (pieceStatus ! workPiece))
-       then
-       putStrLn $ "Hash mismatch: " ++ show (hash (pieceStatus ! workPiece)) ++ " vs " ++ show (take 20 (SHA1.hash pBS))
-       else do
-       let fileOffset = if workPiece == 0 then 0 else workPiece * len (pieceStatus ! (workPiece - 1))
-       putStrLn $ "Write into file at offset: " ++ show fileOffset
-       writeFileAtOffset "/tmp/download.file" fileOffset pBS
-       msgLoop pState (adjust (\pieceData -> pieceData { state = Have }) workPiece pieceStatus)
-msgLoop pState pieceStatus = do
-  msg <- getMsg (handle pState)
-  putStrLn $ "<-- " ++ show msg ++ "from peer: " ++ show (peer pState)
-  case msg of
-   KeepAliveMsg -> do
-     sendMsg (handle pState) KeepAliveMsg
-     putStrLn $ "--> " ++ "KeepAliveMsg to peer: " ++ show (peer pState)
-     msgLoop pState pieceStatus
-   BitFieldMsg bss -> do
-     let pieceList = bitfieldToList (unpack bss)
-         pieceStatus' = updatePieceAvailability pieceStatus (peer pState) pieceList
-     putStrLn $ show (length pieceList) ++ " Pieces"
-     -- for each pieceIndex in pieceList, make an entry in the pieceStatus
-     -- map with pieceIndex as the key and modify the value to add the peer.
-     -- download each of the piece in order
-     msgLoop pState pieceStatus'
-   UnChokeMsg ->
-     msgLoop (pState { heChoking = False }) pieceStatus
-   _ ->
-     msgLoop pState pieceStatus
+-- helper functions to manipulate PeerState
+toPeerState :: Handle
+            -> Peer
+            -> Bool  -- ^ meChoking
+            -> Bool  -- ^ meInterested
+            -> Bool  -- ^ heChoking
+            -> Bool  -- ^ heInterested
+            -> PState
+toPeerState h p meCh meIn heCh heIn =
+  PState { handle = h
+         , peer = p
+         , heChoking = heCh
+         , heInterested = heIn
+         , meChoking = meCh
+         , meInterested = meIn }
 
 -- simple algorithm to pick piece.
 -- pick the first piece from 0 that is not downloaded yet.
 pickPiece :: PieceMap -> Maybe Integer
 pickPiece m =
   let pieceList = toList m
-      allPending = filter (\(_, v) -> state v == Pending) pieceList
+      allPending = filter (\(_, v) -> dlstate v == Pending) pieceList
   in
    case allPending of
     [] -> Nothing
@@ -151,20 +120,64 @@ handlePeerMsgs :: Peer -> Metainfo -> String -> IO ()
 handlePeerMsgs p m peerId = do
   h <- connectToPeer p
   doHandshake h p (infoHash m) peerId
-  let state = PeerState { handle = h
-                        , peer = p
-                        , heInterested = False
-                        , heChoking = True
-                        , meInterested = False
-                        , meChoking = True }
+  let pstate = toPeerState h p False True False True
       pieceHash = pieces (info m)
       numPieces = (toInteger . (`quot` 20) . BC.length) pieceHash
       pLen = pieceLength (info m)
       fileLen = lengthInBytes (info m)
+      fileName = name (info m)
       pieceStatus = mkPieceMap numPieces pieceHash (splitNum fileLen pLen)
-  createDummyFile "/tmp/download.file" (fromIntegral fileLen)
-  msgLoop state pieceStatus
-  
+  createDummyFile fileName (fromIntegral fileLen)
+  (r, _) <- runStateT (msgLoop pieceStatus fileName) pstate
+  return ()
+
+msgLoop :: PieceMap -> FilePath -> StateT PState IO ()
+msgLoop pieceStatus file = do
+  h <- gets handle
+  st <- get
+  case st of
+    PState { meInterested = False, heChoking = True } -> do
+      liftIO $ sendMsg h InterestedMsg
+      gets peer >>= (\p -> liftIO $ putStrLn $ "--> InterestedMsg to peer: " ++ show p)
+      modify (\st -> st { meInterested = True })
+      msgLoop pieceStatus file
+    PState { meInterested = True, heChoking = False } -> do
+      case pickPiece pieceStatus of
+        Nothing -> liftIO $ putStrLn "Nothing to download"
+        Just workPiece -> do
+          let pLen = len (pieceStatus ! workPiece)
+          liftIO $ putStrLn $ "piece length = " ++ show pLen
+          pBS <- liftIO $ downloadPiece h workPiece pLen
+          if not $ verifyHash pBS (hash (pieceStatus ! workPiece))
+            then
+            liftIO $ putStrLn $ "Hash mismatch: " ++ show (hash (pieceStatus ! workPiece)) ++ " vs " ++ show (take 20 (SHA1.hash pBS))
+            else do
+            let fileOffset = if workPiece == 0 then 0 else workPiece * len (pieceStatus ! (workPiece - 1))
+            liftIO $ putStrLn $ "Write into file at offset: " ++ show fileOffset
+            liftIO $ writeFileAtOffset file fileOffset pBS
+            msgLoop (adjust (\pieceData -> pieceData { dlstate = Have }) workPiece pieceStatus) file
+    _ -> do
+      msg <- liftIO $ getMsg h
+      gets peer >>= (\p -> liftIO $ putStrLn $ "<-- " ++ show msg ++ "from peer: " ++ show p)
+      case msg of
+        KeepAliveMsg -> do
+          liftIO $ sendMsg h KeepAliveMsg
+          gets peer >>= (\p -> liftIO $ putStrLn $ "--> " ++ "KeepAliveMsg to peer: " ++ show p)
+          msgLoop pieceStatus file
+        BitFieldMsg bss -> do
+          p <- gets peer
+          let pieceList = bitfieldToList (unpack bss)
+              pieceStatus' = updatePieceAvailability pieceStatus p pieceList
+          liftIO $ putStrLn $ show (length pieceList) ++ " Pieces"
+          -- for each pieceIndex in pieceList, make an entry in the pieceStatus
+          -- map with pieceIndex as the key and modify the value to add the peer.
+          -- download each of the piece in order
+          msgLoop pieceStatus' file
+        UnChokeMsg -> do
+          modify (\st -> st {heChoking = False })
+          msgLoop pieceStatus file
+
+
 downloadPiece :: Handle -> Integer -> Integer -> IO ByteString
 downloadPiece h index pieceLength = do
   let chunks = splitNum pieceLength 16384