import Prelude hiding (lookup, concat, replicate, splitAt, take, filter)
import System.IO (Handle, BufferMode(..), hSetBuffering)
+import System.Directory (doesFileExist)
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.State
import Data.Bits
import Data.Word (Word8)
-import Data.Map (Map, fromList, toList, (!), mapWithKey, adjust, filter)
+import Data.Map (Map, fromList, toList, (!), mapWithKey, traverseWithKey, adjust, filter)
import qualified Crypto.Hash.SHA1 as SHA1 (hash)
import Safe (headMay)
import FuncTorrent.Metainfo (Info(..), Metainfo(..))
import FuncTorrent.Utils (splitN, splitNum)
-import FuncTorrent.Fileops (createDummyFile, writeFileAtOffset)
+import FuncTorrent.Fileops (createDummyFile, writeFileAtOffset, readFileAtOffset)
import FuncTorrent.PeerMsgs (Peer(..), PeerMsg(..), sendMsg, getMsg, genHandshakeMsg)
data PState = PState { handle :: Handle
-- Make the initial Piece map, with the assumption that no peer has the
-- piece and that every piece is pending download.
-mkPieceMap :: Integer -> ByteString -> [Integer] -> PieceMap
-mkPieceMap numPieces pieceHash pLengths = fromList kvs
- where kvs = [(i, PieceData { peers = []
- , dlstate = Pending
- , hash = h
- , len = pLen })
- | (i, h, pLen) <- zip3 [0..numPieces] hashes pLengths]
- hashes = splitN 20 pieceHash
+initPieceMap :: ByteString -> Integer -> Integer -> PieceMap
+initPieceMap pieceHash fileLen pieceLen = fromList kvs
+ where
+ numPieces = (toInteger . (`quot` 20) . BC.length) pieceHash
+ kvs = [(i, PieceData { peers = []
+ , dlstate = Pending
+ , hash = h
+ , len = pLen })
+ | (i, h, pLen) <- zip3 [0..numPieces] hashes pLengths]
+ hashes = splitN 20 pieceHash
+ pLengths = (splitNum fileLen pieceLen)
+
+updatePieceMap :: FilePath -> PieceMap -> IO PieceMap
+updatePieceMap filePath pieceMap = do
+ dfe <- doesFileExist filePath
+ -- TODO: this is not enough, file should have the same size as well
+ if dfe
+ then pieceMapFromFile filePath pieceMap
+ else return pieceMap
+
+pieceMapFromFile :: FilePath -> PieceMap -> IO PieceMap
+pieceMapFromFile filePath pieceMap = do
+ traverseWithKey f pieceMap
+ where
+ f k v = do
+ let offset = if k == 0 then 0 else k * len (pieceMap ! (k - 1))
+ isHashValid <- (flip verifyHash) (hash v) <$> (readFileAtOffset filePath offset (len v))
+ if isHashValid
+ then return $ v { dlstate = Have }
+ else return $ v
havePiece :: PieceMap -> Integer -> Bool
havePiece pm index =
bytesDownloaded :: PieceMap -> Integer
bytesDownloaded =
- sum . (map (len . snd)) . toList . filter (\v -> dlstate v == Have)
+ sum . map (len . snd) . toList . filter (\v -> dlstate v == Have)
updatePieceAvailability :: PieceMap -> Peer -> [Integer] -> PieceMap
updatePieceAvailability pieceStatus p pieceList =
then (pd { peers = p : peers pd })
else pd) pieceStatus
-handlePeerMsgs :: Peer -> Metainfo -> String -> IO ()
-handlePeerMsgs p m peerId = do
+handlePeerMsgs :: Peer -> String -> Metainfo -> IO ()
+handlePeerMsgs p peerId m = do
h <- connectToPeer p
doHandshake h p (infoHash m) peerId
let pstate = toPeerState h p False False True 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)
+ pieceStatus = initPieceMap pieceHash fileLen pLen
+ pieceStatus' <- updatePieceMap fileName pieceStatus
createDummyFile fileName (fromIntegral fileLen)
- _ <- runStateT (msgLoop pieceStatus fileName) pstate
+ _ <- runStateT (msgLoop pieceStatus' fileName) pstate
return ()
msgLoop :: PieceMap -> FilePath -> StateT PState IO ()
downloadPiece :: Handle -> Integer -> Integer -> IO ByteString
downloadPiece h index pieceLength = do
let chunks = splitNum pieceLength 16384
- liftM concat $ forM (zip [0..] chunks) (\(i, pLen) -> do
- sendMsg h (RequestMsg index (i*pLen) pLen)
- putStrLn $ "--> " ++ "RequestMsg for Piece "
- ++ show index ++ ", part: " ++ show i ++ " of length: "
- ++ show pLen
- msg <- getMsg h
- case msg of
- PieceMsg index begin block -> do
- putStrLn $ " <-- PieceMsg for Piece: "
- ++ show index
- ++ ", offset: "
- ++ show begin
- return block
- _ -> do
- putStrLn "ignoring irrelevant msg"
- return empty)
+ concat `liftM` forM (zip [0..] chunks) (\(i, pLen) -> do
+ sendMsg h (RequestMsg index (i*pLen) pLen)
+ putStrLn $ "--> " ++ "RequestMsg for Piece "
+ ++ show index ++ ", part: " ++ show i ++ " of length: "
+ ++ show pLen
+ msg <- getMsg h
+ case msg of
+ PieceMsg index begin block -> do
+ putStrLn $ " <-- PieceMsg for Piece: "
+ ++ show index
+ ++ ", offset: "
+ ++ show begin
+ return block
+ _ -> do
+ putStrLn "ignoring irrelevant msg"
+ return empty)
verifyHash :: ByteString -> ByteString -> Bool
verifyHash bs pieceHash =