2011-04-08 20:44:43 +00:00
|
|
|
{- Using bup as a remote.
|
|
|
|
-
|
|
|
|
- Copyright 2011 Joey Hess <joey@kitenet.net>
|
|
|
|
-
|
|
|
|
- Licensed under the GNU GPL version 3 or higher.
|
|
|
|
-}
|
|
|
|
|
|
|
|
module Remote.Bup (remote) where
|
|
|
|
|
|
|
|
import IO
|
|
|
|
import Control.Exception.Extensible (IOException)
|
|
|
|
import qualified Data.Map as M
|
2011-04-09 16:34:49 +00:00
|
|
|
import Control.Monad (unless, when)
|
2011-04-08 20:44:43 +00:00
|
|
|
import Control.Monad.State (liftIO)
|
|
|
|
import System.Process
|
|
|
|
import System.Exit
|
2011-04-09 16:34:49 +00:00
|
|
|
import System.FilePath
|
|
|
|
import Data.List.Utils
|
2011-04-08 20:44:43 +00:00
|
|
|
|
|
|
|
import RemoteClass
|
|
|
|
import Types
|
|
|
|
import qualified GitRepo as Git
|
|
|
|
import qualified Annex
|
|
|
|
import UUID
|
|
|
|
import Locations
|
|
|
|
import LocationLog
|
|
|
|
import Config
|
|
|
|
import Utility
|
|
|
|
import Messages
|
|
|
|
import Remote.Special
|
2011-04-09 16:34:49 +00:00
|
|
|
import Ssh
|
2011-04-08 20:44:43 +00:00
|
|
|
|
|
|
|
remote :: RemoteType Annex
|
|
|
|
remote = RemoteType {
|
|
|
|
typename = "bup",
|
2011-04-09 16:41:17 +00:00
|
|
|
enumerate = findSpecialRemotes "buprepo",
|
2011-04-08 20:44:43 +00:00
|
|
|
generate = gen,
|
|
|
|
setup = bupSetup
|
|
|
|
}
|
|
|
|
|
|
|
|
gen :: Git.Repo -> UUID -> Maybe (M.Map String String) -> Annex (Remote Annex)
|
|
|
|
gen r u c = do
|
2011-04-09 16:41:17 +00:00
|
|
|
buprepo <- getConfig r "buprepo" (error "missing buprepo")
|
|
|
|
cst <- remoteCost r (if bupLocal buprepo then semiCheapRemoteCost else expensiveRemoteCost)
|
|
|
|
-- u' <- getBupUUID r u
|
2011-04-09 01:37:59 +00:00
|
|
|
|
2011-04-09 16:41:17 +00:00
|
|
|
return $ this cst buprepo u'
|
2011-04-08 20:44:43 +00:00
|
|
|
where
|
2011-04-09 16:41:17 +00:00
|
|
|
this cst buprepo u' = Remote {
|
|
|
|
uuid = u',
|
2011-04-08 20:44:43 +00:00
|
|
|
cost = cst,
|
|
|
|
name = Git.repoDescribe r,
|
2011-04-09 16:41:17 +00:00
|
|
|
storeKey = store r buprepo,
|
|
|
|
retrieveKeyFile = retrieve buprepo,
|
2011-04-08 20:44:43 +00:00
|
|
|
removeKey = remove,
|
2011-04-09 16:41:17 +00:00
|
|
|
hasKey = checkPresent u',
|
2011-04-08 20:44:43 +00:00
|
|
|
hasKeyCheap = True,
|
|
|
|
config = c
|
|
|
|
}
|
|
|
|
|
|
|
|
bupSetup :: UUID -> M.Map String String -> Annex (M.Map String String)
|
|
|
|
bupSetup u c = do
|
|
|
|
-- verify configuration is sane
|
2011-04-09 16:41:17 +00:00
|
|
|
let buprepo = case M.lookup "remote" c of
|
2011-04-08 20:44:43 +00:00
|
|
|
Nothing -> error "Specify remote="
|
|
|
|
Just r -> r
|
|
|
|
case M.lookup "encryption" c of
|
|
|
|
Nothing -> error "Specify encryption=key or encryption=none"
|
|
|
|
Just "none" -> return ()
|
|
|
|
Just _ -> error "encryption keys not yet supported"
|
|
|
|
|
|
|
|
-- bup init will create the repository.
|
|
|
|
-- (If the repository already exists, bup init again appears safe.)
|
|
|
|
showNote "bup init"
|
2011-04-09 16:41:17 +00:00
|
|
|
ok <- bup "init" buprepo []
|
2011-04-08 20:44:43 +00:00
|
|
|
unless ok $ error "bup init failed"
|
|
|
|
|
2011-04-09 16:41:17 +00:00
|
|
|
storeBupUUID u buprepo
|
2011-04-09 16:34:49 +00:00
|
|
|
|
2011-04-09 16:41:17 +00:00
|
|
|
-- The buprepo is stored in git config, as well as this repo's
|
2011-04-08 20:44:43 +00:00
|
|
|
-- persistant state, so it can vary between hosts.
|
2011-04-09 16:41:17 +00:00
|
|
|
gitConfigSpecialRemote u c "buprepo" buprepo
|
2011-04-08 20:44:43 +00:00
|
|
|
|
|
|
|
return $ M.delete "directory" c
|
|
|
|
|
|
|
|
bupParams :: String -> String -> [CommandParam] -> [CommandParam]
|
2011-04-09 16:41:17 +00:00
|
|
|
bupParams command buprepo params =
|
|
|
|
(Param command) : [Param "-r", Param buprepo] ++ params
|
2011-04-08 20:44:43 +00:00
|
|
|
|
|
|
|
bup :: String -> String -> [CommandParam] -> Annex Bool
|
2011-04-09 16:41:17 +00:00
|
|
|
bup command buprepo params = do
|
2011-04-08 20:44:43 +00:00
|
|
|
showProgress -- make way for bup output
|
2011-04-09 16:41:17 +00:00
|
|
|
liftIO $ boolSystem "bup" $ bupParams command buprepo params
|
2011-04-08 20:44:43 +00:00
|
|
|
|
|
|
|
store :: Git.Repo -> String -> Key -> Annex Bool
|
2011-04-09 16:41:17 +00:00
|
|
|
store r buprepo k = do
|
2011-04-08 20:44:43 +00:00
|
|
|
g <- Annex.gitRepo
|
|
|
|
let src = gitAnnexLocation g k
|
|
|
|
o <- getConfig r "bup-split-options" ""
|
|
|
|
let os = map Param $ words o
|
2011-04-09 16:41:17 +00:00
|
|
|
bup "split" buprepo $ os ++ [Param "-n", Param (show k), File src]
|
2011-04-08 20:44:43 +00:00
|
|
|
|
|
|
|
retrieve :: String -> Key -> FilePath -> Annex Bool
|
2011-04-09 16:41:17 +00:00
|
|
|
retrieve buprepo k f = do
|
|
|
|
let params = bupParams "join" buprepo [Param $ show k]
|
2011-04-08 20:44:43 +00:00
|
|
|
ret <- liftIO $ try $ do
|
|
|
|
-- pipe bup's stdout directly to file
|
|
|
|
tofile <- openFile f WriteMode
|
|
|
|
p <- runProcess "bup" (toCommand params)
|
|
|
|
Nothing Nothing Nothing (Just tofile) Nothing
|
|
|
|
r <- waitForProcess p
|
|
|
|
case r of
|
|
|
|
ExitSuccess -> return True
|
|
|
|
_ -> return False
|
|
|
|
case ret of
|
|
|
|
Right r -> return r
|
2011-04-09 00:55:22 +00:00
|
|
|
Left _ -> return False
|
2011-04-08 20:44:43 +00:00
|
|
|
|
|
|
|
remove :: Key -> Annex Bool
|
|
|
|
remove _ = do
|
|
|
|
warning "content cannot be removed from bup remote"
|
|
|
|
return False
|
|
|
|
|
|
|
|
{- Bup does not provide a way to tell if a given dataset is present
|
|
|
|
- in a bup repository. One way it to check if the git repository has
|
|
|
|
- a branch matching the name (as created by bup split -n).
|
|
|
|
-
|
|
|
|
- However, git-annex's ususal reasons for checking if a remote really
|
|
|
|
- has a key also don't really apply in the case of bup, since, short
|
|
|
|
- of deleting bup's git repository, data cannot be removed from it.
|
|
|
|
-
|
|
|
|
- So, trust git-annex's location log; if it says a bup repository has
|
|
|
|
- content, assume it's right.
|
|
|
|
-}
|
|
|
|
checkPresent :: UUID -> Key -> Annex (Either IOException Bool)
|
|
|
|
checkPresent u k = do
|
|
|
|
g <- Annex.gitRepo
|
|
|
|
liftIO $ try $ do
|
|
|
|
uuids <- keyLocations g k
|
|
|
|
return $ u `elem` uuids
|
2011-04-09 16:34:49 +00:00
|
|
|
|
|
|
|
{- Store UUID in the annex.uuid setting of the bup repository. -}
|
|
|
|
storeBupUUID :: UUID -> FilePath -> Annex ()
|
2011-04-09 16:41:17 +00:00
|
|
|
storeBupUUID u buprepo = do
|
|
|
|
r <- liftIO $ bup2GitRemote buprepo
|
2011-04-09 16:34:49 +00:00
|
|
|
if Git.repoIsUrl r
|
|
|
|
then do
|
|
|
|
showNote "storing uuid"
|
|
|
|
let dir = shellEscape (Git.workTree r)
|
|
|
|
sshparams <- sshToRepo r
|
|
|
|
[Param $ "cd " ++ dir ++
|
|
|
|
" && git config annex.uuid " ++ u]
|
|
|
|
ok <- liftIO $ boolSystem "ssh" sshparams
|
|
|
|
unless ok $ do error "ssh failed"
|
|
|
|
else liftIO $ do
|
|
|
|
r' <- Git.configRead r
|
|
|
|
let olduuid = Git.configGet r' "annex.uuid" ""
|
|
|
|
when (olduuid == "") $
|
|
|
|
Git.run r' "config" [Param "annex.uuid", Param u]
|
|
|
|
|
2011-04-09 16:41:17 +00:00
|
|
|
{- Allow for bup repositories on removable media by checking
|
|
|
|
- local bup repositories -}
|
|
|
|
--getBupUUID :: UUID -> FilePath -> Annex ()
|
|
|
|
--getBupUUID u buprepo = do
|
|
|
|
|
2011-04-09 16:34:49 +00:00
|
|
|
{- Converts a bup remote path spec into a Git.Repo. There are some
|
|
|
|
- differences in path representation between git and bup. -}
|
|
|
|
bup2GitRemote :: FilePath -> IO Git.Repo
|
|
|
|
bup2GitRemote "" = do
|
|
|
|
-- bup -r "" operates on ~/.bup
|
|
|
|
h <- myHomeDir
|
|
|
|
Git.repoFromAbsPath $ h </> ".bup"
|
|
|
|
bup2GitRemote r
|
|
|
|
| bupLocal r =
|
|
|
|
if r !! 0 == '/'
|
|
|
|
then Git.repoFromAbsPath r
|
|
|
|
else error "please specify an absolute path"
|
|
|
|
| otherwise = Git.repoFromUrl $ "ssh://" ++ host ++ slash dir
|
|
|
|
where
|
|
|
|
bits = split ":" r
|
|
|
|
host = bits !! 0
|
|
|
|
dir = join ":" $ drop 1 bits
|
|
|
|
-- "host:~user/dir" is not supported specially by bup;
|
|
|
|
-- "host:dir" is relative to the home directory;
|
|
|
|
-- "host:" goes in ~/.bup
|
|
|
|
slash d
|
|
|
|
| d == "" = "/~/.bup"
|
|
|
|
| d !! 0 == '/' = d
|
|
|
|
| otherwise = "/~/" ++ d
|
|
|
|
|
|
|
|
bupLocal :: FilePath -> Bool
|
|
|
|
bupLocal = notElem ':'
|