git-annex/Remote/Directory.hs

268 lines
9.1 KiB
Haskell
Raw Normal View History

{- A "remote" that is just a filesystem directory.
2011-03-30 17:18:46 +00:00
-
- Copyright 2011-2012 Joey Hess <joey@kitenet.net>
2011-03-30 17:18:46 +00:00
-
- Licensed under the GNU GPL version 3 or higher.
-}
{-# LANGUAGE CPP #-}
2011-03-30 17:18:46 +00:00
module Remote.Directory (remote) where
import qualified Data.ByteString.Lazy as L
import qualified Data.ByteString as S
2011-03-30 17:18:46 +00:00
import qualified Data.Map as M
2011-10-05 20:02:51 +00:00
import Common.Annex
import Types.Remote
import Types.Creds
import qualified Git
import Config.Cost
import Config
2011-09-23 22:13:24 +00:00
import Utility.FileMode
2011-08-17 00:49:54 +00:00
import Remote.Helper.Special
import Remote.Helper.Encryptable
import Remote.Helper.Chunked
import qualified Remote.Helper.Chunked.Legacy as Legacy
import Crypto
import Annex.Content
import Annex.UUID
import Utility.Metered
2011-03-30 17:18:46 +00:00
2011-12-31 08:11:39 +00:00
remote :: RemoteType
2011-03-30 17:18:46 +00:00
remote = RemoteType {
typename = "directory",
2011-03-30 18:00:54 +00:00
enumerate = findSpecialRemotes "directory",
2011-03-30 17:18:46 +00:00
generate = gen,
2011-03-30 18:00:54 +00:00
setup = directorySetup
2011-03-30 17:18:46 +00:00
}
gen :: Git.Repo -> UUID -> RemoteConfig -> RemoteGitConfig -> Annex (Maybe Remote)
gen r u c gc = do
cst <- remoteCost gc cheapRemoteCost
let chunkconfig = chunkConfig c
return $ Just $ encryptableRemote c
(storeEncrypted dir (getGpgEncParams (c,gc)) chunkconfig)
(retrieveEncrypted dir chunkconfig)
2011-04-17 01:41:14 +00:00
Remote {
uuid = u,
cost = cst,
name = Git.repoDescribe r,
storeKey = store dir chunkconfig,
retrieveKeyFile = retrieve dir chunkconfig,
retrieveKeyFileCheap = retrieveCheap dir chunkconfig,
removeKey = remove dir,
hasKey = checkPresent dir chunkconfig,
2011-04-17 01:41:14 +00:00
hasKeyCheap = True,
whereisKey = Nothing,
remoteFsck = Nothing,
repairRepo = Nothing,
config = c,
repo = r,
gitconfig = gc,
2012-08-26 18:26:43 +00:00
localpath = Just dir,
readonly = False,
availability = LocallyAvailable,
remotetype = remote
2011-04-17 01:41:14 +00:00
}
where
dir = fromMaybe (error "missing directory") $ remoteAnnexDirectory gc
directorySetup :: Maybe UUID -> Maybe CredPair -> RemoteConfig -> Annex (RemoteConfig, UUID)
directorySetup mu _ c = do
u <- maybe (liftIO genUUID) return mu
2011-03-30 17:18:46 +00:00
-- verify configuration is sane
2011-07-15 16:47:14 +00:00
let dir = fromMaybe (error "Specify directory=") $
2011-05-15 06:49:43 +00:00
M.lookup "directory" c
absdir <- liftIO $ absPath dir
liftIO $ unlessM (doesDirectoryExist absdir) $
error $ "Directory does not exist: " ++ absdir
c' <- encryptionSetup c
2011-03-30 17:18:46 +00:00
-- The directory is stored in git config, not in this remote's
-- persistant state, so it can vary between hosts.
gitConfigSpecialRemote u c' "directory" absdir
return (M.delete "directory" c', u)
2011-03-30 17:18:46 +00:00
{- Locations to try to access a given Key in the Directory.
- We try more than since we used to write to different hash directories. -}
locations :: FilePath -> Key -> [FilePath]
locations d k = map (d </>) (keyPaths k)
2011-03-30 17:18:46 +00:00
{- Directory where the file(s) for a key are stored. -}
storeDir :: FilePath -> Key -> FilePath
storeDir d k = addTrailingPathSeparator $ d </> hashDirLower k </> keyFile k
{- Where we store temporary data for a key as it's being uploaded. -}
tmpDir :: FilePath -> Key -> FilePath
tmpDir d k = addTrailingPathSeparator $ d </> "tmp" </> keyFile k
withCheckedFiles :: (FilePath -> IO Bool) -> ChunkConfig -> FilePath -> Key -> ([FilePath] -> IO Bool) -> IO Bool
withCheckedFiles _ _ [] _ _ = return False
2014-07-24 19:08:07 +00:00
withCheckedFiles check (LegacyChunks _) d k a = go $ locations d k
2012-11-11 04:51:07 +00:00
where
go [] = return False
go (f:fs) = do
let chunkcount = f ++ Legacy.chunkCount
2012-11-11 04:51:07 +00:00
ifM (check chunkcount)
( do
chunks <- Legacy.listChunks f <$> readFile chunkcount
ifM (allM check chunks)
2012-11-11 04:51:07 +00:00
( a chunks , return False )
, do
chunks <- Legacy.probeChunks f check
if null chunks
then go fs
else a chunks
2012-11-11 04:51:07 +00:00
)
withCheckedFiles check _ d k a = go $ locations d k
where
go [] = return False
go (f:fs) = ifM (check f) ( a [f] , go fs )
withStoredFiles :: ChunkConfig -> FilePath -> Key -> ([FilePath] -> IO Bool) -> IO Bool
withStoredFiles = withCheckedFiles doesFileExist
store :: FilePath -> ChunkConfig -> Key -> AssociatedFile -> MeterUpdate -> Annex Bool
store d chunkconfig k _f p = sendAnnex k (void $ remove d k) $ \src ->
metered (Just p) k $ \meterupdate ->
storeHelper d chunkconfig k k $ \dests ->
case chunkconfig of
2014-07-24 19:08:07 +00:00
LegacyChunks chunksize ->
storeLegacyChunked meterupdate chunksize dests
=<< L.readFile src
_ -> do
let dest = Prelude.head dests
meteredWriteFile meterupdate dest
=<< L.readFile src
return [dest]
storeEncrypted :: FilePath -> [CommandParam] -> ChunkConfig -> (Cipher, Key) -> Key -> MeterUpdate -> Annex Bool
storeEncrypted d gpgOpts chunkconfig (cipher, enck) k p = sendAnnex k (void $ remove d enck) $ \src ->
metered (Just p) k $ \meterupdate ->
storeHelper d chunkconfig enck k $ \dests ->
encrypt gpgOpts cipher (feedFile src) $ readBytes $ \b ->
case chunkconfig of
2014-07-24 19:08:07 +00:00
LegacyChunks chunksize ->
storeLegacyChunked meterupdate chunksize dests b
_ -> do
let dest = Prelude.head dests
meteredWriteFile meterupdate dest b
return [dest]
{- Splits a ByteString into chunks and writes to dests, obeying configured
- chunk size (not to be confused with the L.ByteString chunk size).
- Note: Must always write at least one file, even for empty ByteString. -}
2014-07-24 19:08:07 +00:00
storeLegacyChunked :: MeterUpdate -> ChunkSize -> [FilePath] -> L.ByteString -> IO [FilePath]
storeLegacyChunked _ _ [] _ = error "bad storeLegacyChunked call"
storeLegacyChunked meterupdate chunksize alldests@(firstdest:_) b
| L.null b = do
-- must always write at least one file, even for empty
L.writeFile firstdest b
return [firstdest]
| otherwise = storeLegacyChunked' meterupdate chunksize alldests (L.toChunks b) []
2014-07-24 19:08:07 +00:00
storeLegacyChunked' :: MeterUpdate -> ChunkSize -> [FilePath] -> [S.ByteString] -> [FilePath] -> IO [FilePath]
storeLegacyChunked' _ _ [] _ _ = error "ran out of dests"
storeLegacyChunked' _ _ _ [] c = return $ reverse c
storeLegacyChunked' meterupdate chunksize (d:dests) bs c = do
2013-09-26 03:19:01 +00:00
bs' <- withFile d WriteMode $
feed zeroBytesProcessed chunksize bs
storeLegacyChunked' meterupdate chunksize dests bs' (d:c)
2012-11-11 04:51:07 +00:00
where
feed _ _ [] _ = return []
feed bytes sz (l:ls) h = do
let len = S.length l
let s = fromIntegral len
if s <= sz || sz == chunksize
2012-11-11 04:51:07 +00:00
then do
S.hPut h l
let bytes' = addBytesProcessed bytes len
meterupdate bytes'
feed bytes' (sz - s) ls h
2012-11-11 04:51:07 +00:00
else return (l:ls)
storeHelper :: FilePath -> ChunkConfig -> Key -> Key -> ([FilePath] -> IO [FilePath]) -> Annex Bool
storeHelper d chunkconfig key origkey storer = check <&&> liftIO go
2012-11-11 04:51:07 +00:00
where
tmpdir = tmpDir d key
destdir = storeDir d key
{- An encrypted key does not have a known size,
- so check that the size of the original key is available as free
- space. -}
check = do
liftIO $ createDirectoryIfMissing True tmpdir
checkDiskSpace (Just tmpdir) origkey 0
go = case chunkconfig of
NoChunks -> flip catchNonAsync (\e -> print e >> return False) $ do
let tmpf = tmpdir </> keyFile key
void $ storer [tmpf]
finalizer tmpdir destdir
return True
2014-07-24 19:08:07 +00:00
UnpaddedChunks _ -> error "TODO: storeHelper with UnpaddedChunks"
LegacyChunks _ -> Legacy.storeChunks key tmpdir destdir storer recorder finalizer
finalizer tmp dest = do
void $ tryIO $ allowWrite dest -- may already exist
void $ tryIO $ removeDirectoryRecursive dest -- or not exist
createDirectoryIfMissing True (parentDir dest)
renameDirectory tmp dest
-- may fail on some filesystems
void $ tryIO $ do
mapM_ preventWrite =<< dirContents dest
preventWrite dest
recorder f s = do
void $ tryIO $ allowWrite f
writeFile f s
void $ tryIO $ preventWrite f
retrieve :: FilePath -> ChunkConfig -> Key -> AssociatedFile -> FilePath -> MeterUpdate -> Annex Bool
retrieve d chunkconfig k _ f p = metered (Just p) k $ \meterupdate ->
liftIO $ withStoredFiles chunkconfig d k $ \files ->
catchBoolIO $ do
meteredWriteFileChunks meterupdate f files L.readFile
return True
2011-04-17 01:41:14 +00:00
retrieveEncrypted :: FilePath -> ChunkConfig -> (Cipher, Key) -> Key -> FilePath -> MeterUpdate -> Annex Bool
retrieveEncrypted d chunkconfig (cipher, enck) k f p = metered (Just p) k $ \meterupdate ->
liftIO $ withStoredFiles chunkconfig d enck $ \files ->
catchBoolIO $ do
decrypt cipher (feeder files) $
readBytes $ meteredWriteFile meterupdate f
return True
where
2013-09-26 03:19:01 +00:00
feeder files h = forM_ files $ L.hPut h <=< L.readFile
2011-03-30 17:18:46 +00:00
retrieveCheap :: FilePath -> ChunkConfig -> Key -> FilePath -> Annex Bool
-- no cheap retrieval for chunks
2014-07-24 19:08:07 +00:00
retrieveCheap _ (UnpaddedChunks _) _ _ = return False
retrieveCheap _ (LegacyChunks _) _ _ = return False
#ifndef mingw32_HOST_OS
retrieveCheap d ck k f = liftIO $ withStoredFiles ck d k go
2012-11-11 04:51:07 +00:00
where
go [file] = catchBoolIO $ createSymbolicLink file f >> return True
go _files = return False
#else
retrieveCheap _ _ _ _ = return False
#endif
remove :: FilePath -> Key -> Annex Bool
remove d k = liftIO $ do
void $ tryIO $ allowWrite dir
#ifdef mingw32_HOST_OS
{- Windows needs the files inside the directory to be writable
- before it can delete them. -}
void $ tryIO $ mapM_ allowWrite =<< dirContents dir
#endif
catchBoolIO $ do
removeDirectoryRecursive dir
return True
2012-11-11 04:51:07 +00:00
where
dir = storeDir d k
2011-03-30 17:18:46 +00:00
checkPresent :: FilePath -> ChunkConfig -> Key -> Annex (Either String Bool)
checkPresent d chunkconfig k = liftIO $ catchMsgIO $ withStoredFiles chunkconfig d k $
const $ return True -- withStoredFiles checked that it exists