]> git.rkrishnan.org Git - hs-rsync.git/blob - src/Lib.hs
Lib: remove redundant module imports
[hs-rsync.git] / src / Lib.hs
1 {-# LANGUAGE OverloadedStrings #-}
2 module Lib
3     ( fileSignatures
4     , genInstructions
5     , recreate
6     ) where
7
8 import Control.Monad.State
9 import qualified Data.ByteString.Char8 as BS
10 import qualified Data.ByteString.Lazy as BL
11 import Data.Digest.Adler32 (adler32)
12 import qualified Data.Map as M
13 import Data.Word (Word8, Word32)
14 import qualified Crypto.Hash.MD4 as MD4
15
16 type Md4digest       = BS.ByteString
17 type Adler32checksum = Word32
18
19 type Signature     = (Md4digest, Adler32checksum, Int)
20
21 fileSignatures :: BL.ByteString -> Integer -> [Signature]
22 fileSignatures bs blockSize = zip3 strongsigs weaksigs [0..]
23   where strongsigs = map blockSig (splitBS bs blockSize)
24         weaksigs   = map adler32 (splitBS bs blockSize)
25
26 splitBS :: BL.ByteString -> Integer -> [BL.ByteString]
27 splitBS bs blockSize | fromIntegral (BL.length bs) < blockSize = [bs]
28 splitBS bs blockSize = BL.take (fromIntegral blockSize) bs :
29   splitBS (BL.drop (fromIntegral blockSize) bs) blockSize
30
31 -- compute md4 digest (128 bits)
32 blockSig :: BL.ByteString -> BS.ByteString
33 blockSig = MD4.hash . BL.toStrict
34
35 weakSig :: BL.ByteString -> Adler32checksum
36 weakSig = adler32
37
38 data Instruction = RChar Word8
39                  | RBlk  Int
40                  deriving Show
41
42 genInstructions :: [Signature] -> Integer -> BL.ByteString -> [Instruction]
43 genInstructions f0sigs blockSize fnew =
44   evalState (go fnew) 0
45   where
46     go :: BL.ByteString -> State Word32 [Instruction]
47     go fnew | fnew == BL.empty = return []
48             | otherwise =
49                 let (blk, blks) = BL.splitAt (fromIntegral blockSize) fnew
50                     adlerSum    = weakSig blk
51                     matches     = M.lookup adlerSum f0AdlerTable >>
52                       M.lookup (blockSig blk) f0MD4Table
53                 in
54                   case matches of
55                     Just idxs -> do
56                       is <- go blks
57                       return $ RBlk (head idxs) : is
58                     Nothing -> do
59                       put adlerSum
60                       is <- go (BL.tail (blk `mappend` blks))
61                       return $ RChar (BL.head blk) : is
62     f0AdlerTable = toAdlerMap f0sigs
63     f0MD4Table   = toMD4Map f0sigs
64
65 toAdlerMap :: [Signature] -> M.Map Adler32checksum [Int]
66 toAdlerMap = foldr f M.empty
67   where f sig m = let (_, aSig, idx) = sig in M.insertWith (++) aSig [idx] m
68
69 toMD4Map :: [Signature] -> M.Map Md4digest [Int]
70 toMD4Map = foldr f M.empty
71   where f sig m = let (mSig, _, idx) = sig in M.insertWith (++) mSig [idx] m
72
73 recreate :: BL.ByteString -> Integer -> [Instruction] -> BL.ByteString
74 recreate f0 blockSize ins =
75   let f0blocks = splitBS f0 blockSize
76   in
77     go f0blocks ins
78   where go f0blocks [] = mempty
79         go f0blocks (inst:insts) =
80           case inst of
81             RBlk i  -> (f0blocks !! i) `mappend` go f0blocks insts
82             RChar w -> BL.singleton w `mappend` go f0blocks insts
83