sync-mht-0.2.1.0: src/main/hs/Sync/MerkleTree/Client.hs
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
module Sync.MerkleTree.Client where
import Control.Monad
import Control.Monad.IO.Class
import Data.Foldable(Foldable)
import Data.Monoid(Monoid, mappend, mempty, Sum(..))
import Data.Set(Set)
import Data.List
import Foreign.C.Types
import qualified Data.ByteString as BS
import qualified Data.Foldable as F
import qualified Data.Set as S
import qualified Data.Map as M
import qualified Data.Text as T
import Sync.MerkleTree.CommTypes
import Sync.MerkleTree.Trie
import Sync.MerkleTree.Types
import System.Directory
import System.IO
import System.Posix.Files
data Diff a = Diff (Set a) (Set a)
deriving Show
instance Ord a => Monoid (Diff a) where
mempty = Diff S.empty S.empty
mappend (Diff x1 y1) (Diff x2 y2) = Diff (x1 `S.union` x2) (y1 `S.union` y2)
showText :: (Show a) => a -> T.Text
showText = T.pack . show
dataSize :: (Foldable f) => f Entry -> FileSize
dataSize s = getSum $ F.foldMap sizeOf s
where
sizeOf (FileEntry f) = Sum $ f_size f
sizeOf (DirectoryEntry _) = Sum $ FileSize 0
dataSizeText :: (Foldable f) => f Entry -> T.Text
dataSizeText s = T.concat [showText $ unFileSize $ dataSize s, " bytes"]
class (Protocol m, MonadIO m) => (ClientMonad m) where
split :: (Monoid a) => [m a] -> m a
logClient :: (Protocol m) => T.Text -> m ()
logClient t =
do True <- logReq $ SerText t
return ()
analyseEntries :: Diff Entry -> ([Entry],[Entry],[Entry])
analyseEntries (Diff obsoleteEntries newEntries) =
(M.elems deleteMap, M.elems changeMap, M.elems newMap)
where
deleteMap = M.difference obsMap updMap
changeMap = M.intersection updMap obsMap
newMap = M.difference updMap obsMap
obsMap = M.fromList $ S.toList $ S.map keyValue obsoleteEntries
updMap = M.fromList $ S.toList $ S.map keyValue newEntries
keyValue x = (name x, x)
name (FileEntry f) = f_name f
name (DirectoryEntry f) = f
abstractClient :: (ClientMonad m) => ClientServerOptions -> FilePath -> Trie Entry -> m ()
abstractClient cs fp trie =
do logClient $ T.concat [ "Client finished directory traversal: ", showText $ t_hash trie ]
Diff oent nent <- nodeReq (rootLocation, trie)
let (delEntries, changedEntries, newEntries) = analyseEntries (Diff oent nent)
logClient $ T.concat
[ "Client has ", showText $ length delEntries, " superfluos files of size "
, dataSizeText delEntries, ", ", showText $ length changedEntries, " changed files "
, "of size ", dataSizeText changedEntries, " and ", showText $ length newEntries
, " missing files of size ", dataSizeText newEntries, "."
]
when (cs_delete cs) $
forM_ (reverse $ sort delEntries) $ \e ->
case e of
FileEntry f -> liftIO $ removeFile $ toFilePath fp $ f_name f
DirectoryEntry p -> liftIO $ removeDirectory $ toFilePath fp p
mapM_ (split . map (synchronizeNewOrChangedEntry fp))
$ splitEvery _CONCURRENT_FILETRANSFER_SIZE_ $ sort
$ [ e | cs_add cs, e <- newEntries ] ++ [ e | cs_update cs, e <- changedEntries ]
True <- terminateReq
return ()
_CONCURRENT_FILETRANSFER_SIZE_ :: Int
_CONCURRENT_FILETRANSFER_SIZE_ = 96
splitEvery :: Int -> [a] -> [[a]]
splitEvery n l
| null l = []
| (h,t) <- splitAt n l = h:(splitEvery n t)
synchronizeNewOrChangedEntry :: (ClientMonad m) => FilePath -> Entry -> m ()
synchronizeNewOrChangedEntry fp entry =
case entry of
FileEntry f ->
do firstResult <- queryFileReq (f_name f)
h <- liftIO $ openFile (toFilePath fp $ f_name f) WriteMode
let loop result =
case result of
Final -> return ()
ToBeContinued content contHandle ->
(liftIO $ BS.hPut h content) >> queryFileContReq contHandle >>= loop
loop firstResult
liftIO $ hClose h
let modTime = (CTime $ unModTime $ f_modtime f)
liftIO $ setFileTimes (toFilePath fp $ f_name f) modTime modTime
DirectoryEntry p -> liftIO $ createDirectory $ toFilePath fp p
nodeReq :: (ClientMonad m) => (TrieLocation, Trie Entry) -> m (Diff Entry)
nodeReq (loc,trie) =
do fp <- queryHashReq loc
case () of
() | fp == toFingerprint trie ->
return mempty
| Node arr <- t_node trie, NodeType == f_nodeType fp ->
split $ map nodeReq (expand loc arr)
| otherwise ->
do s' <- querySetReq loc
let s = getAll trie
return $ Diff (s `S.difference` s') (s' `S.difference` s)