From: Ramakrishnan Muthukrishnan Date: Tue, 15 Sep 2015 16:11:36 +0000 (+0530) Subject: Resume download from where it was left off last time X-Git-Url: https://git.rkrishnan.org/?p=functorrent.git;a=commitdiff_plain;h=dfffc353671f9cb53f4c95fcd81cac807a07201e Resume download from where it was left off last time This commit looks at the downloaded file, verifies the hashes of the downloaded pieces, marks them as "have" pieces and proceeds to download those that are not in its posession. --- diff --git a/src/FuncTorrent/Fileops.hs b/src/FuncTorrent/Fileops.hs index f1bbefe..cab73c7 100644 --- a/src/FuncTorrent/Fileops.hs +++ b/src/FuncTorrent/Fileops.hs @@ -1,17 +1,23 @@ module FuncTorrent.Fileops (createDummyFile, - writeFileAtOffset + writeFileAtOffset, + readFileAtOffset ) where import Prelude hiding (writeFile) import System.IO (withFile, hSeek, IOMode(..), SeekMode(..)) -import Data.ByteString (ByteString, writeFile, hPut) +import System.Directory (doesFileExist) +import Data.ByteString (ByteString, writeFile, hPut, hGet) import qualified Data.ByteString.Char8 as BC (replicate) createDummyFile :: FilePath -> Int -> IO () -createDummyFile path size = - writeFile path (BC.replicate size '\0') +createDummyFile path size = do + dfe <- doesFileExist path + if dfe + then return () + else + writeFile path (BC.replicate size '\0') -- write into a file at a specific offet writeFileAtOffset :: FilePath -> Integer -> ByteString -> IO () @@ -19,3 +25,8 @@ writeFileAtOffset path offset block = withFile path ReadWriteMode (\h -> do hSeek h AbsoluteSeek offset hPut h block) +readFileAtOffset :: FilePath -> Integer -> Integer -> IO ByteString +readFileAtOffset path offset len = + withFile path ReadWriteMode (\h -> do + hSeek h AbsoluteSeek offset + hGet h (fromInteger len)) diff --git a/src/FuncTorrent/Peer.hs b/src/FuncTorrent/Peer.hs index 61b5112..2b9ff5f 100644 --- a/src/FuncTorrent/Peer.hs +++ b/src/FuncTorrent/Peer.hs @@ -8,19 +8,20 @@ module FuncTorrent.Peer 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 @@ -61,6 +62,25 @@ initPieceMap pieceHash fileLen pieceLen = fromList kvs 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 = dlstate (pm ! index) == Have @@ -130,8 +150,9 @@ handlePeerMsgs p peerId m = do fileLen = lengthInBytes (info m) fileName = name (info m) 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 () diff --git a/src/main/Main.hs b/src/main/Main.hs index 0a51410..ae3c035 100644 --- a/src/main/Main.hs +++ b/src/main/Main.hs @@ -57,9 +57,13 @@ main = do case torrentToMetainfo torrentStr of Left e -> logError e log Right m -> do - log "Input File OK" - log $ "Downloading file : " ++ name (info m) - + let p = name (info m) + log $ "Downloading file : " ++ p + -- if we had downloaded the file before (partly or completely) + -- then we should check the current directory for the existence + -- of the file and then update the map of each piece' availability. + -- This can be donw by reading each piece and verifying the checksum. + -- If the checksum does not match, we don't have that piece. log $ "starting server" (serverSock, (PortNumber portnum)) <- Server.start log $ "server started on " ++ show portnum