module Darcs.Util.Tree.Hashed
(
readDarcsHashed
, writeDarcsHashed
, hashedTreeIO
, readDarcsHashedDir
, readDarcsHashedNosize
, darcsAddMissingHashes
, darcsLocation
, darcsTreeHash
, decodeDarcsHash
, decodeDarcsSize
, darcsUpdateHashes
) where
import Prelude hiding ( lookup, (<$>) )
import System.FilePath ( (</>) )
import System.Directory( doesFileExist )
import Codec.Compression.GZip( decompress, compress )
import Control.Applicative( (<$>) )
import qualified Data.ByteString.Char8 as BS8
import qualified Data.ByteString.Lazy.Char8 as BL8
import qualified Data.ByteString.Lazy as BL
import qualified Data.ByteString as BS
import Data.List( sortBy )
import Data.Maybe( fromJust, isJust )
import Control.Monad.State.Strict
import Darcs.Util.Path
import Darcs.Util.ByteString ( FileSegment, readSegment )
import Darcs.Util.Hash
import Darcs.Util.Tree
import Darcs.Util.Tree.Monad
darcsEncodeWhiteBS :: BS8.ByteString -> BS8.ByteString
darcsEncodeWhiteBS = BS8.pack . encodeWhite . BS8.unpack
decodeDarcsHash :: BS8.ByteString -> Hash
decodeDarcsHash bs = case BS8.split '-' bs of
[s, h] | BS8.length s == 10 -> decodeBase16 h
_ -> decodeBase16 bs
decodeDarcsSize :: BS8.ByteString -> Maybe Int
decodeDarcsSize bs = case BS8.split '-' bs of
[s, _] | BS8.length s == 10 ->
case reads (BS8.unpack s) of
[(x, _)] -> Just x
_ -> Nothing
_ -> Nothing
darcsLocation :: FilePath -> (Maybe Int, Hash) -> FileSegment
darcsLocation dir (s,h) = case hash of
"" -> error "darcsLocation: invalid hash"
_ -> (dir </> prefix s ++ hash, Nothing)
where prefix Nothing = ""
prefix (Just s') = formatSize s' ++ "-"
formatSize s' = let n = show s' in replicate (10 length n) '0' ++ n
hash = BS8.unpack (encodeBase16 h)
darcsFormatDir :: Tree m -> Maybe BL8.ByteString
darcsFormatDir t = BL8.fromChunks . concat <$>
mapM string (sortBy cmp $ listImmediate t)
where cmp (Name a, _) (Name b, _) = compare a b
string (Name name, item) =
do header <- case item of
File _ -> Just $ BS8.pack "file:\n"
_ -> Just $ BS8.pack "directory:\n"
hash <- case itemHash item of
NoHash -> Nothing
x -> Just $ encodeBase16 x
return [ header
, darcsEncodeWhiteBS name
, BS8.singleton '\n'
, hash, BS8.singleton '\n' ]
darcsParseDir :: BL8.ByteString -> [(ItemType, Name, Maybe Int, Hash)]
darcsParseDir content = parse (BL8.split '\n' content)
where
parse (t:n:h':r) = (header t,
Name $ BS8.pack $ decodeWhite (BL8.unpack n),
decodeDarcsSize hash,
decodeDarcsHash hash) : parse r
where hash = BS8.concat $ BL8.toChunks h'
parse _ = []
header x
| x == BL8.pack "file:" = BlobType
| x == BL8.pack "directory:" = TreeType
| otherwise = error $ "Error parsing darcs hashed dir: " ++ BL8.unpack x
darcsTreeHash :: Tree m -> Hash
darcsTreeHash t = case darcsFormatDir t of
Nothing -> NoHash
Just x -> sha256 x
darcsUpdateDirHashes :: Tree m -> Tree m
darcsUpdateDirHashes = updateSubtrees update
where update t = t { treeHash = darcsTreeHash t }
darcsUpdateHashes :: (Monad m, Functor m) => Tree m -> m (Tree m)
darcsUpdateHashes = updateTree update
where update (SubTree t) = return . SubTree $ t { treeHash = darcsTreeHash t }
update (File blob@(Blob con _)) =
do hash <- sha256 <$> readBlob blob
return $ File (Blob con hash)
update stub = return stub
darcsHash :: (Monad m, Functor m) => TreeItem m -> m Hash
darcsHash (SubTree t) = return $ darcsTreeHash t
darcsHash (File blob) = sha256 <$> readBlob blob
darcsHash _ = return NoHash
darcsAddMissingHashes :: (Monad m, Functor m) => Tree m -> m (Tree m)
darcsAddMissingHashes = addMissingHashes darcsHash
readDarcsHashedDir :: FilePath -> (Maybe Int, Hash) -> IO [(ItemType, Name, Maybe Int, Hash)]
readDarcsHashedDir dir h = do
exist <- doesFileExist $ fst (darcsLocation dir h)
unless exist $ fail $ "error opening " ++ fst (darcsLocation dir h)
compressed <- readSegment $ darcsLocation dir h
let content = decompress compressed
return $ if BL8.null compressed
then []
else darcsParseDir content
readDarcsHashed' :: Bool -> FilePath -> (Maybe Int, Hash) -> IO (Tree IO)
readDarcsHashed' _ _ (_, NoHash) = fail "Cannot readDarcsHashed NoHash"
readDarcsHashed' sizefail dir root@(_, hash) = do
items' <- readDarcsHashedDir dir root
subs <- sequence [
do when (sizefail && isJust s) $
fail ("Unexpectedly encountered size-prefixed hash in " ++ dir)
case tp of
BlobType -> return (d, File $
Blob (readBlob' (s, h)) h)
TreeType ->
do let t = readDarcsHashed dir (s, h)
return (d, Stub t h)
| (tp, d, s, h) <- items' ]
return $ makeTreeWithHash subs hash
where readBlob' = fmap decompress . readSegment . darcsLocation dir
readDarcsHashed :: FilePath -> (Maybe Int, Hash) -> IO (Tree IO)
readDarcsHashed = readDarcsHashed' False
readDarcsHashedNosize :: FilePath -> Hash -> IO (Tree IO)
readDarcsHashedNosize dir hash = readDarcsHashed' True dir (Nothing, hash)
writeDarcsHashed :: Tree IO -> FilePath -> IO Hash
writeDarcsHashed tree' dir =
do t <- darcsUpdateDirHashes <$> expand tree'
sequence_ [ dump =<< readBlob b | (_, File b) <- list t ]
let dirs = darcsFormatDir t : [ darcsFormatDir d | (_, SubTree d) <- list t ]
_ <- mapM (dump . fromJust) dirs
return $ darcsTreeHash t
where dump bits =
do let name = dir </> BS8.unpack (encodeBase16 $ sha256 bits)
exist <- doesFileExist name
unless exist $ BL.writeFile name (compress bits)
fsCreateHashedFile :: FilePath -> BL8.ByteString -> TreeIO ()
fsCreateHashedFile fn content =
liftIO $ do
exist <- doesFileExist fn
unless exist $ BL.writeFile fn content
hashedTreeIO :: TreeIO a
-> Tree IO
-> FilePath
-> IO (a, Tree IO)
hashedTreeIO action t dir =
runTreeMonad action $ initialState t darcsHash updateItem
where updateItem _ (File b) = File <$> updateFile b
updateItem _ (SubTree s) = SubTree <$> updateSub s
updateItem _ x = return x
updateFile b@(Blob _ !h) = do
content <- liftIO $ readBlob b
let fn = dir </> BS8.unpack (encodeBase16 h)
nblob = Blob (decompress <$> rblob) h
rblob = BL.fromChunks . return <$> BS.readFile fn
newcontent = compress content
fsCreateHashedFile fn newcontent
return nblob
updateSub s = do
let !hash = treeHash s
Just dirdata = darcsFormatDir s
fn = dir </> BS8.unpack (encodeBase16 hash)
fsCreateHashedFile fn (compress dirdata)
return s