1 {-# LANGUAGE OverloadedStrings #-}
2 {-# LANGUAGE FlexibleContexts #-}
3 module FuncTorrent.FileSystem
15 import Control.Concurrent.Chan (Chan, newChan, readChan, writeChan)
16 import Control.Concurrent.MVar (MVar, newEmptyMVar, putMVar)
17 import Control.Monad (forever)
18 import Control.Monad.State (StateT, liftIO, get, runStateT, modify)
19 import qualified Data.ByteString as BS
20 import Data.Map (traverseWithKey, (!))
21 import System.IO (Handle, IOMode (ReadWriteMode), withFile)
22 import System.Directory (doesFileExist)
24 import FuncTorrent.PieceManager (PieceDlState(..), PieceData(..), PieceMap, pieceNumToOffset)
25 import FuncTorrent.Utils (createDummyFile, readFileAtOffset, writeFileAtOffset, verifyHash)
27 type PieceNum = Integer
28 data Piece = Piece PieceNum BS.ByteString
30 data Msg = ReadPiece PieceNum Integer (MVar Piece)
32 | VerifyPiece PieceNum (MVar Bool)
33 | GetStats (MVar Stats)
35 type MsgChannel = Chan Msg
37 data Stats = Stats { bytesRead :: Integer
38 , bytesWritten :: Integer
41 createMsgChannel :: IO (Chan Msg)
42 createMsgChannel = newChan
44 run :: PieceMap -> MsgChannel -> Handle -> IO ()
45 run pieceMap c handle = forever $ do
46 _ <- runStateT (run' pieceMap c handle) initialStats
48 where initialStats = Stats { bytesRead = 0
51 run' :: PieceMap -> MsgChannel -> Handle -> StateT Stats IO ()
52 run' pieceMap c handle = do
55 liftIO $ sendResponse msg stats
59 sendResponse msg stats =
61 ReadPiece n len' var -> do
62 bs <- readPiece n len'
63 putMVar var (Piece n bs)
64 WritePiece (Piece n bs) ->
66 VerifyPiece n var -> do
67 isHashValid <- verifyPiece n
68 putMVar var isHashValid
72 let offset = pieceNumToOffset pieceMap n
73 readFileAtOffset handle offset len'
74 writePiece n piece = do
75 let offset = pieceNumToOffset pieceMap n
76 writeFileAtOffset handle offset piece
78 let offset = pieceNumToOffset pieceMap n
79 hash' = hash (pieceMap ! n)
80 len' = len (pieceMap ! n)
81 bs' <- readFileAtOffset handle offset len'
82 return $ verifyHash bs' hash'
83 updateStats (ReadPiece _ l _) =
84 modify (\st -> st {bytesRead = bytesRead st + l})
85 updateStats (WritePiece (Piece _ bs)) =
86 modify (\st -> st {bytesWritten = bytesWritten st + fromIntegral (BS.length bs)})
87 updateStats _ = modify id
89 pieceMapFromFile :: FilePath -> Integer -> PieceMap -> IO PieceMap
90 pieceMapFromFile filePath fileLen pieceMap = do
91 dfe <- doesFileExist filePath
93 then traverseWithKey f pieceMap
94 else createDummyFile filePath (fromIntegral fileLen) >> return pieceMap
97 let offset = pieceNumToOffset pieceMap k
98 isHashValid <- flip verifyHash (hash v) <$> withFile filePath ReadWriteMode (\handle -> readFileAtOffset handle offset (len v))
100 then return $ v { dlstate = Have }
103 writePieceToDisk :: MsgChannel -> PieceNum -> BS.ByteString -> IO ()
104 writePieceToDisk c pieceNum bs =
105 writeChan c $ WritePiece (Piece pieceNum bs)
107 getStats :: MsgChannel -> IO (MVar Stats)
110 writeChan c $ GetStats v