Skip to content
Snippets Groups Projects
Backend.hs 5.24 KiB
Newer Older
Joey Hess's avatar
Joey Hess committed
{- git-annex key-value storage backends
Joey Hess's avatar
Joey Hess committed
 -
Joey Hess's avatar
Joey Hess committed
 - git-annex uses a key-value abstraction layer to allow files contents to be
 - stored in different ways. In theory, any key-value storage system could be
Joey Hess's avatar
Joey Hess committed
 - used to store the file contents, and git-annex would then retrieve them
 - as needed and put them in `.git/annex/`.
 - 
 - When a file is annexed, a key is generated from its content and/or metadata.
 - This key can later be used to retrieve the file's content (its value). This
 - key generation must be stable for a given file content, name, and size.
 - 
 - Multiple pluggable backends are supported, and more than one can be used
 - to store different files' contents in a given repository.
Joey Hess's avatar
Joey Hess committed
 -
 - Copyright 2010 Joey Hess <joey@kitenet.net>
 -
 - Licensed under the GNU GPL version 3 or higher.
 -}
Joey Hess's avatar
Joey Hess committed

Joey Hess's avatar
Joey Hess committed
module Backend (
Joey Hess's avatar
Joey Hess committed
	storeFileKey,
	retrieveKeyFile,
Joey Hess's avatar
Joey Hess committed
	removeKey,
	hasKey,
Joey Hess's avatar
Joey Hess committed
	fsckKey,
	lookupFile,
Joey Hess's avatar
Joey Hess committed
	chooseBackends,
	keyBackend,
	lookupBackendName
Joey Hess's avatar
Joey Hess committed
) where
Joey Hess's avatar
Joey Hess committed

Joey Hess's avatar
Joey Hess committed
import Control.Monad.State
Joey Hess's avatar
Joey Hess committed
import System.IO.Error (try)
import System.FilePath
import System.Posix.Files
Joey Hess's avatar
Joey Hess committed

import Locations
import qualified GitRepo as Git
import qualified Annex
import Types
import qualified BackendTypes as B
import Messages
Joey Hess's avatar
Joey Hess committed

{- List of backends in the order to try them when storing a new key. -}
Joey Hess's avatar
Joey Hess committed
list :: Annex [Backend Annex]
	l <- Annex.getState Annex.backends -- list is cached here
Joey Hess's avatar
Joey Hess committed
	if not $ null l
Joey Hess's avatar
Joey Hess committed
		then return l
		else do
Joey Hess's avatar
Joey Hess committed
			s <- getstandard
			d <- Annex.getState Annex.defaultbackend
			handle d s
	where
		parseBackendList l [] = l
		parseBackendList bs s = map (lookupBackendName bs) $ words s
		handle Nothing s = return s
		handle (Just "") s = return s
		handle (Just name) s = do
			bs <- Annex.getState Annex.supportedBackends
Joey Hess's avatar
Joey Hess committed
			let l' = (lookupBackendName bs name):s
			Annex.changeState $ \state -> state { Annex.backends = l' }
Joey Hess's avatar
Joey Hess committed
		getstandard = do
			bs <- Annex.getState Annex.supportedBackends
			g <- Annex.gitRepo
			return $ parseBackendList bs $
				Git.configGet g "annex.backends" ""
{- Looks up a backend in a list. May fail if unknown. -}
Joey Hess's avatar
Joey Hess committed
lookupBackendName :: [Backend Annex] -> String -> Backend Annex
Joey Hess's avatar
Joey Hess committed
lookupBackendName bs s =
	case maybeLookupBackendName bs s of
		Just b -> b
		Nothing -> error $ "unknown backend " ++ s
Joey Hess's avatar
Joey Hess committed
maybeLookupBackendName :: [Backend Annex] -> String -> Maybe (Backend Annex)
maybeLookupBackendName bs s =
Joey Hess's avatar
Joey Hess committed
	if 1 /= length matches
		then Nothing
Joey Hess's avatar
Joey Hess committed
		else Just $ head matches
	where matches = filter (\b -> s == B.name b) bs
Joey Hess's avatar
Joey Hess committed

Joey Hess's avatar
Joey Hess committed
{- Attempts to store a file in one of the backends. -}
Joey Hess's avatar
Joey Hess committed
storeFileKey :: FilePath -> Maybe (Backend Annex) -> Annex (Maybe (Key, Backend Annex))
storeFileKey file trybackend = do
	bs <- list
	let bs' = case trybackend of
		Nothing -> bs
		Just backend -> backend:bs
Joey Hess's avatar
Joey Hess committed
storeFileKey' :: [Backend Annex] -> FilePath -> Annex (Maybe (Key, Backend Annex))
storeFileKey' [] _ = return Nothing
storeFileKey' (b:bs) file = do
	result <- (B.getKey b) file
Joey Hess's avatar
Joey Hess committed
	case result of
		Nothing -> nextbackend
Joey Hess's avatar
Joey Hess committed
		Just key -> do
			stored <- (B.storeFileKey b) file key
			if (not stored)
				then nextbackend
Joey Hess's avatar
Joey Hess committed
				else return $ Just (key, b)
		nextbackend = storeFileKey' bs file
Joey Hess's avatar
Joey Hess committed

Joey Hess's avatar
Joey Hess committed
{- Attempts to retrieve an key from one of the backends, saving it to
Joey Hess's avatar
Joey Hess committed
 - a specified location. -}
Joey Hess's avatar
Joey Hess committed
retrieveKeyFile :: Backend Annex -> Key -> FilePath -> Annex Bool
retrieveKeyFile backend key dest = (B.retrieveKeyFile backend) key dest
Joey Hess's avatar
Joey Hess committed

Joey Hess's avatar
Joey Hess committed
{- Removes a key from a backend. -}
Joey Hess's avatar
Joey Hess committed
removeKey :: Backend Annex -> Key -> Maybe Int -> Annex Bool
removeKey backend key numcopies = (B.removeKey backend) key numcopies
Joey Hess's avatar
Joey Hess committed

Joey Hess's avatar
Joey Hess committed
{- Checks if a key is present in its backend. -}
Joey Hess's avatar
Joey Hess committed
hasKey :: Key -> Annex Bool
hasKey key = do
Joey Hess's avatar
Joey Hess committed
	backend <- keyBackend key
	(B.hasKey backend) key
Joey Hess's avatar
Joey Hess committed

Joey Hess's avatar
Joey Hess committed
{- Checks a key's backend for problems. -}
fsckKey :: Backend Annex -> Key -> Maybe FilePath -> Maybe Int -> Annex Bool
fsckKey backend key file numcopies = (B.fsckKey backend) key file numcopies
Joey Hess's avatar
Joey Hess committed

Joey Hess's avatar
Joey Hess committed
{- Looks up the key and backend corresponding to an annexed file,
 - by examining what the file symlinks to. -}
Joey Hess's avatar
Joey Hess committed
lookupFile :: FilePath -> Annex (Maybe (Key, Backend Annex))
Joey Hess's avatar
Joey Hess committed
lookupFile file = do
	bs <- Annex.getState Annex.supportedBackends
	tl <- liftIO $ try getsymlink
	case tl of
Joey Hess's avatar
Joey Hess committed
		Left _ -> return Nothing
		Right l -> makekey bs l
	where
		getsymlink = do
Joey Hess's avatar
Joey Hess committed
			l <- readSymbolicLink file
			return $ takeFileName l
Joey Hess's avatar
Joey Hess committed
			case maybeLookupBackendName bs bname of
				Nothing -> do
					unless (null kname || null bname ||
					        not (isLinkToAnnex l)) $
						warning skip
					return Nothing
				Just backend -> return $ Just (k, backend)
Joey Hess's avatar
Joey Hess committed
			where
				k = fileKey l
				bname = backendName k
				kname = keyName k
				skip = "skipping " ++ file ++ 
					" (unknown backend " ++ bname ++ ")"

{- Looks up the backends that should be used for each file in a list.
 - That can be configured on a per-file basis in the gitattributes file.
 -}
Joey Hess's avatar
Joey Hess committed
chooseBackends :: [FilePath] -> Annex [(FilePath, Maybe (Backend Annex))]
chooseBackends fs = do
	bs <- Annex.getState Annex.supportedBackends
	pairs <- liftIO $ Git.checkAttr g "annex.backend" fs
	return $ map (\(f,b) -> (f, maybeLookupBackendName bs b)) pairs
Joey Hess's avatar
Joey Hess committed

{- Returns the backend to use for a key. -}
Joey Hess's avatar
Joey Hess committed
keyBackend :: Key -> Annex (Backend Annex)
Joey Hess's avatar
Joey Hess committed
keyBackend key = do
	bs <- Annex.getState Annex.supportedBackends
Joey Hess's avatar
Joey Hess committed
	return $ lookupBackendName bs $ backendName key