module Darcs.Repository.Packs
( fetchAndUnpackBasic
, fetchAndUnpackPatches
, packsDir
) where
import qualified Codec.Archive.Tar as Tar
import Codec.Compression.GZip as GZ ( compress, decompress )
import Control.Concurrent.Async ( withAsync )
import Control.Exception ( Exception, IOException, throwIO, catch )
import Control.Monad ( void )
import System.IO.Error ( isAlreadyExistsError )
import qualified Data.ByteString.Lazy.Char8 as BL
import Data.List ( isPrefixOf )
import Data.Maybe( catMaybes, listToMaybe )
import System.Directory ( createDirectoryIfMissing
, renameFile
, doesFileExist
)
import System.FilePath ( (</>)
, takeFileName
, splitPath
, joinPath
, takeDirectory
)
import System.Posix.Files ( createLink )
import Darcs.Util.Lock ( withTemp )
import Darcs.Util.External ( Cachable(..), fetchFileLazyPS )
import Darcs.Repository.Cache ( fetchFileUsingCache
, HashedDir(..)
, Cache(..)
, CacheLoc(..)
, WritableOrNot(..)
, hashedDir
, bucketFolder
, CacheType(Directory)
)
import Darcs.Util.Global ( darcsdir )
import Darcs.Util.Progress ( debugMessage )
packsDir :: String
packsDir = "packs"
fetchAndUnpack :: FilePath
-> HashedDir
-> Cache
-> FilePath
-> IO ()
fetchAndUnpack filename dir cache remote = do
unpackTar cache dir . Tar.read . GZ.decompress =<<
fetchFileLazyPS (remote </> darcsdir </> packsDir </> filename) Uncachable
fetchAndUnpackPatches :: [String] -> Cache -> FilePath -> IO ()
fetchAndUnpackPatches paths cache remote =
withAsync (fetchAndUnpack "patches.tar.gz" HashedInventoriesDir cache remote) $ \_ -> do
fetchFilesUsingCache cache HashedPatchesDir paths
fetchAndUnpackBasic :: Cache -> FilePath -> IO ()
fetchAndUnpackBasic = fetchAndUnpack "basic.tar.gz" HashedPristineDir
unpackTar :: Exception e => Cache -> HashedDir -> Tar.Entries e -> IO ()
unpackTar _ _ Tar.Done = return ()
unpackTar _ _ (Tar.Fail e) = throwIO e
unpackTar c dir (Tar.Next e es) = case Tar.entryContent e of
Tar.NormalFile bs _ -> do
let p = Tar.entryPath e
if "meta-" `isPrefixOf` takeFileName p
then unpackTar c dir es
else do
ex <- doesFileExist p
if ex
then debugMessage $ "TAR thread: exists " ++ p ++ "\nStopping TAR thread."
else do
if p == darcsdir </> "hashed_inventory"
then writeFile' Nothing p bs
else writeFile' (cacheDir c) p $ GZ.compress bs
debugMessage $ "TAR thread: GET " ++ p
unpackTar c dir es
_ -> fail "Unexpected non-file tar entry"
where
writeFile' Nothing path content = withTemp $ \tmp -> do
BL.writeFile tmp content
renameFile tmp path
writeFile' (Just ca) path content = do
let fileFullPath = case splitPath path of
_:hDir:hFile:_ -> joinPath [ca, hDir, bucketFolder hFile, hFile]
_ -> fail "Unexpected file path"
createDirectoryIfMissing True $ takeDirectory path
createLink fileFullPath path `catch` (\(ex :: IOException) -> do
if isAlreadyExistsError ex then
return ()
else
writeFile' Nothing path content)
fetchFilesUsingCache :: Cache -> HashedDir -> [FilePath] -> IO ()
fetchFilesUsingCache cache dir = mapM_ go where
go path = do
ex <- doesFileExist $ darcsdir </> hashedDir dir </> path
if ex
then debugMessage $ "FILE thread: exists " ++ path
else void $ fetchFileUsingCache cache dir path
cacheDir :: Cache -> Maybe String
cacheDir (Ca cs) = listToMaybe . catMaybes .flip map cs $ \x -> case x of
Cache Directory Writable x' -> Just x'
_ -> Nothing