darcs-2.18.1: src/Darcs/Repository/Inventory.hs
module Darcs.Repository.Inventory
( module Darcs.Repository.Inventory.Format
, readPatchesFromInventoryFile
, readPatchesFromInventory
, readSinglePatch
, readOneInventory
, writeInventory
, writePatchIfNecessary
, writeHashFile
) where
import Darcs.Prelude
import Control.Exception ( catch )
import Control.Monad ( unless )
import System.FilePath.Posix ( (</>) )
import System.IO ( hPutStrLn, stderr )
import System.IO.Unsafe ( unsafeInterleaveIO )
import Darcs.Patch ( RepoPatch, readPatch, showPatch )
import Darcs.Patch.Format ( PatchListFormat )
import Darcs.Patch.Info ( PatchInfo, displayPatchInfo, piName )
import Darcs.Patch.PatchInfoAnd
( PatchInfoAnd
, PatchInfoAndG
, createHashed
, extractHash
, info
, patchInfoAndPatch
)
import Darcs.Patch.Read ( ReadPatch )
import Darcs.Patch.Set ( Origin, PatchSet(..), SealedPatchSet, Tagged(..) )
import Darcs.Patch.Show ( ShowPatchFor(..) )
import Darcs.Patch.Witnesses.Ordered ( RL(..), mapRL )
import Darcs.Patch.Witnesses.Sealed ( Sealed(..), mapSeal, seal, unseal )
import Darcs.Patch.Witnesses.Unsafe ( unsafeCoerceP )
import Darcs.Repository.InternalTypes ( Repository, repoCache, repoLocation )
import Darcs.Repository.Inventory.Format
import Darcs.Util.Cache
( Cache
, fetchFileUsingCache
, peekInCache
, speculateFilesUsingCache
, writeFileUsingCache
)
import Darcs.Util.File ( Cachable(Uncachable), gzFetchFilePS )
import Darcs.Util.Printer ( Doc, renderPS, renderString, text, ($$) )
import Darcs.Util.Progress ( debugMessage, finishedOneIO )
-- | Read a 'PatchSet' starting with a specific inventory inside a 'Repository'.
readPatchesFromInventoryFile
:: (PatchListFormat p, ReadPatch p)
=> FilePath
-> Repository rt p wU wR
-> IO (PatchSet p Origin wS)
readPatchesFromInventoryFile invPath repo = do
let repodir = repoLocation repo
Sealed ps <-
catch
(readInventoryPrivate (repodir </> invPath) >>=
readPatchesFromInventory (repoCache repo))
(\e -> hPutStrLn stderr ("Invalid repository: " ++ repodir) >> ioError e)
return $ unsafeCoerceP ps
-- | Read a complete 'PatchSet' from a 'Cache', by following the chain of
-- 'Inventory's, starting with the given one.
readPatchesFromInventory :: (PatchListFormat p, ReadPatch p)
=> Cache
-> Inventory
-> IO (SealedPatchSet p Origin)
readPatchesFromInventory cache = parseInv
where
parseInv :: (PatchListFormat p, ReadPatch p)
=> Inventory
-> IO (SealedPatchSet p Origin)
parseInv (Inventory Nothing ris) =
mapSeal (PatchSet NilRL) <$> readPatchesFromInventoryEntries cache ris
parseInv (Inventory (Just h) []) =
-- TODO could be more tolerant and create a larger PatchSet
error $ "bad inventory " ++ encodeValidHash h ++ " (no tag) in parseInv!"
parseInv (Inventory (Just h) (t : ris)) = do
Sealed ts <- delaySealed (read_ts t h)
Sealed ps <- delaySealed (readPatchesFromInventoryEntries cache ris)
return $ seal $ PatchSet ts ps
read_ts :: (PatchListFormat p, ReadPatch p) => InventoryEntry
-> InventoryHash -> IO (Sealed (RL (Tagged p) Origin))
read_ts tag0 h0 = do
contents <- unsafeInterleaveIO $ readTaggedInventory h0
let is = case contents of
Inventory (Just _) (_ : ris0) -> ris0
Inventory Nothing ris0 -> ris0
Inventory (Just _) [] -> error "inventory without tag!"
Sealed ts <-
delaySealed $
case contents of
Inventory (Just h') (t' : _) -> read_ts t' h'
Inventory (Just _) [] -> error "inventory without tag!"
Inventory Nothing _ -> return $ seal NilRL
Sealed ps <- delaySealed (readPatchesFromInventoryEntries cache is)
Sealed tag00 <- read_tag tag0
return $ seal $ ts :<: Tagged ps tag00 (Just h0)
read_tag :: (PatchListFormat p, ReadPatch p) => InventoryEntry
-> IO (Sealed (PatchInfoAnd p wX))
read_tag (i, h) =
mapSeal (patchInfoAndPatch i) <$> createHashed h (readSinglePatch cache i)
readTaggedInventory :: InventoryHash -> IO Inventory
readTaggedInventory invHash = do
(fileName, inventory) <- fetchFileUsingCache cache invHash
case parseInventory inventory of
Right r -> return r
Left e -> fail $ unlines [unwords ["parse error in file", fileName],e]
-- | Read patches from a 'Cache' as specified by a list of 'InventoryEntry'.
readPatchesFromInventoryEntries :: ReadPatch np
=> Cache
-> [InventoryEntry]
-> IO (Sealed (RL (PatchInfoAndG np) wX))
readPatchesFromInventoryEntries cache ris = read_patches (reverse ris)
where
read_patches [] = return $ seal NilRL
read_patches allis@((i1, h1) : is1) =
lift2Sealed (\p rest -> rest :<: i1 `patchInfoAndPatch` p) (rp is1)
(createHashed h1 (const $ speculateAndParse h1 allis i1))
where
rp [] = return $ seal NilRL
rp [(i, h), (il, hl)] =
lift2Sealed (\p rest -> rest :<: i `patchInfoAndPatch` p)
(rp [(il, hl)])
(createHashed h
(const $ speculateAndParse h (reverse allis) i))
rp ((i, h) : is) =
lift2Sealed (\p rest -> rest :<: i `patchInfoAndPatch` p)
(rp is)
(createHashed h (readSinglePatch cache i))
lift2Sealed :: (forall wY wZ . q wY wZ -> p wX wY -> r wX wZ)
-> IO (Sealed (p wX))
-> (forall wB . IO (Sealed (q wB)))
-> IO (Sealed (r wX))
lift2Sealed f iox ioy = do
Sealed x <- delaySealed iox
Sealed y <- delaySealed ioy
return $ seal $ f y x
speculateAndParse h is i = speculate h is >> readSinglePatch cache i h
speculate :: PatchHash -> [InventoryEntry] -> IO ()
speculate pHash is = do
already_got_one <- peekInCache cache pHash
unless already_got_one $
speculateFilesUsingCache cache (map snd is)
-- | We have to unseal and then reseal, otherwise the 'unsafeInterleaveIO' has
-- no effect.
delaySealed :: IO (Sealed (p wX)) -> IO (Sealed (p wX))
delaySealed = fmap (unseal seal) . unsafeInterleaveIO
-- | Read a single patch from a 'Cache', given its 'PatchInfo' and 'PatchHash'.
-- Fails with an error message if the patch file cannot be parsed.
readSinglePatch :: ReadPatch p
=> Cache
-> PatchInfo -> PatchHash -> IO (Sealed (p wX))
readSinglePatch cache i h = do
debugMessage $ "Reading patch file for: " ++ piName i
(fn, ps) <- fetchFileUsingCache cache h
case readPatch ps of
Right p -> return p
Left e -> fail $ unlines
[ "Couldn't parse file " ++ fn
, "which is patch"
, renderString $ displayPatchInfo i
, e
]
readOneInventory :: ReadPatch p
=> Cache -> FilePath -> IO (Sealed (RL (PatchInfoAndG p) wX))
readOneInventory cache path = do
Inventory _ invEntries <- readInventoryPrivate path
readPatchesFromInventoryEntries cache invEntries
-- | Read an 'Inventory' from a file. Fails with an error message if
-- file is not there or cannot be parsed.
readInventoryPrivate :: FilePath -> IO Inventory
readInventoryPrivate path = do
inv <- skipPristineHash <$> gzFetchFilePS path Uncachable
case parseInventory inv of
Right r -> return r
Left e -> fail $ unlines [unwords ["parse error in file", path],e]
writeInventory :: RepoPatch p => String -> Cache
-> PatchSet p Origin wX -> IO InventoryHash
writeInventory tediousName cache = go
where
go :: RepoPatch p => PatchSet p Origin wX -> IO InventoryHash
go (PatchSet ts ps) = do
entries <- sequence $ mapRL (writePatchIfNecessary cache) ps
content <- write_ts ts entries
writeHashFile cache content
write_ts NilRL entries = return $ showInventoryPatches (reverse entries)
write_ts (tts :<: Tagged tps t maybeHash) entries = do
-- if the Tagged has a hash, then we know that it has already been
-- written; otherwise recurse without the tag
parenthash <- maybe (go (PatchSet tts tps)) return maybeHash
let parenthash_str = encodeValidHash parenthash
finishedOneIO tediousName parenthash_str
tag_entry <- writePatchIfNecessary cache t
return $
text ("Starting with inventory:\n" ++ parenthash_str) $$
showInventoryPatches (tag_entry : reverse entries)
-- | Write a 'PatchInfoAnd' to disk and return an 'InventoryEntry' i.e. the
-- patch info and hash. However, if we patch already contains a hash, assume it
-- has already been written to disk at some point and merely return the info
-- and hash.
writePatchIfNecessary :: RepoPatch p => Cache
-> PatchInfoAnd p wX wY -> IO InventoryEntry
writePatchIfNecessary c hp = infohp `seq`
case extractHash hp of
Right h -> return (infohp, h)
Left p ->
(infohp,) <$>
writeHashFile c (showPatch ForStorage p)
where
infohp = info hp
-- | Wrapper around 'writeFileUsingCache' that takes a 'Doc' instead of a
-- 'ByteString'.
writeHashFile :: ValidHash h => Cache -> Doc -> IO h
writeHashFile c d = writeFileUsingCache c (renderPS d)