-createDummyFile :: FilePath -> Int -> IO ()
-createDummyFile path size =
- writeFile path (BC.replicate size '\0')
-
--- loop1 :: shake hands with all peers, find out the pieces they have, form PieceData.
--- recvMsg :: Peer -> Handle -> Msg
-msgLoop :: PeerState -> PieceMap -> IO ()
-msgLoop state pieceStatus | meInterested state == False &&
- heChoking state == True = do
- -- if me NOT Interested and she is Choking, tell her that
- -- I am interested.
- let h = handle state
- sendMsg h InterestedMsg
- putStrLn $ "--> InterestedMsg to peer: " ++ show (peer state)
- msgLoop (state { meInterested = True }) pieceStatus
- | meInterested state == True &&
- heChoking state == False =
- -- 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)
- pBS <- downloadPiece (handle state) workPiece pLen
- -- sendMsg (handle state) (RequestMsg workPiece 0 pLen)
- -- putStrLn $ "--> RequestMsg for Piece " ++ (show workPiece) ++ "to peer: " ++ show (peer state) ++ " of length: " ++ show pLen
- -- msg <- getMsg (handle state)
- -- putStrLn $ "<-- " ++ show msg ++ "from peer: " ++ show (peer state)
- msgLoop state (adjust (\pieceData -> pieceData { state = Have }) workPiece pieceStatus)
- | otherwise = do
- msg <- getMsg (handle state)
- putStrLn $ "<-- " ++ show msg ++ "from peer: " ++ show (peer state)
- case msg of
- KeepAliveMsg -> do
- sendMsg (handle state) KeepAliveMsg
- putStrLn $ "--> " ++ "KeepAliveMsg to peer: " ++ show (peer state)
- msgLoop state pieceStatus
- BitFieldMsg bss -> do
- let pieceList = bitfieldToList (unpack bss)
- pieceStatus' = updatePieceAvailability pieceStatus (peer state) pieceList
- print pieceList
- -- 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 state pieceStatus'
- UnChokeMsg -> do
- msgLoop (state { heChoking = False }) pieceStatus
- _ -> do
- msgLoop state pieceStatus
-
--- 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 (\(k, v) -> state v == Pending) pieceList
- in
- case allPending of
- [] -> Nothing
- ((i, _):_) -> Just i
-
-updatePieceAvailability :: PieceMap -> Peer -> [Integer] -> PieceMap
-updatePieceAvailability pieceStatus p pieceList =
- mapWithKey (\k pd -> if k `elem` pieceList
- then (pd { peers = p : (peers pd) })
- else pd) pieceStatus
-
-handlePeerMsgs :: Peer -> Metainfo -> String -> (String -> IO ()) -> IO ()
-handlePeerMsgs p m peerId logFn = do
- h <- handShake p (infoHash m) peerId
- -- logFn "handShake"
- let state = PeerState { handle = h
- , peer = p
- , heInterested = False
- , heChoking = True
- , meInterested = False
- , meChoking = True }
- pieceHash = pieces (info m)
- numPieces = (toInteger . (`quot` 20) . BC.length) pieceHash
- pLen = pieceLength (info m)
- fileLen = lengthInBytes (info m)
- pieceStatus = mkPieceMap numPieces pieceHash (splitNum fileLen pLen)
- msgLoop state pieceStatus
-
-downloadPiece :: Handle -> Integer -> Integer -> IO [PeerMsg]
+-- 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 }
+
+handlePeerMsgs :: Peer -> String -> Metainfo -> PieceMap -> Bool -> FS.MsgChannel -> IO ()
+handlePeerMsgs p peerId m pieceMap isClient c = do
+ h <- connectToPeer p
+ doHandshake isClient h p (infoHash m) peerId
+ let pstate = toPeerState h p False False True True
+ _ <- runStateT (msgLoop pieceMap c) pstate
+ return ()
+
+msgLoop :: PieceMap -> FS.MsgChannel -> StateT PState IO ()
+msgLoop pieceStatus msgchannel = 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 msgchannel
+ PState { meInterested = True, heChoking = False } ->
+ 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"
+ else do
+ liftIO $ putStrLn $ "Write piece: " ++ show workPiece
+ liftIO $ FS.writePieceToDisk msgchannel workPiece pBS
+ msgLoop (adjust (\pieceData -> pieceData { dlstate = Have }) workPiece pieceStatus) msgchannel
+ _ -> 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 msgchannel
+ 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' msgchannel
+ UnChokeMsg -> do
+ modify (\st' -> st' {heChoking = False })
+ msgLoop pieceStatus msgchannel
+ ChokeMsg -> do
+ modify (\st' -> st' {heChoking = True })
+ msgLoop pieceStatus msgchannel
+ InterestedMsg -> do
+ modify (\st' -> st' {heInterested = True})
+ msgLoop pieceStatus msgchannel
+ NotInterestedMsg -> do
+ modify (\st' -> st' {heInterested = False})
+ msgLoop pieceStatus msgchannel
+ CancelMsg _ _ _ -> -- check if valid index, begin, length
+ msgLoop pieceStatus msgchannel
+ PortMsg _ ->
+ msgLoop pieceStatus msgchannel
+ HaveMsg idx -> do
+ p <- gets peer
+ let pieceStatus' = updatePieceAvailability pieceStatus p [idx]
+ msgLoop pieceStatus' msgchannel
+ _ -> do
+ liftIO $ putStrLn $ ".. not doing anything with the msg"
+ msgLoop pieceStatus msgchannel
+ -- No need to handle PieceMsg and RequestMsg here.
+
+
+downloadPiece :: Handle -> Integer -> Integer -> IO ByteString