git-annex/Remote.hs

217 lines
5.4 KiB
Haskell
Raw Normal View History

{- git-annex remotes
-
- Copyright 2011 Joey Hess <joey@kitenet.net>
-
- Licensed under the GNU GPL version 3 or higher.
-}
module Remote (
Remote,
uuid,
name,
storeKey,
retrieveKeyFile,
removeKey,
hasKey,
hasKeyCheap,
2011-03-29 18:55:59 +00:00
remoteTypes,
byName,
nameToUUID,
keyPossibilities,
remotesWithUUID,
2011-03-29 03:22:31 +00:00
remotesWithoutUUID,
2011-03-29 18:55:59 +00:00
remoteLog,
2011-03-29 17:49:54 +00:00
readRemoteLog,
2011-03-29 03:22:31 +00:00
configSet,
keyValToConfig,
configToKeyVal,
prop_idempotent_configEscape
) where
import Control.Monad.State (liftIO)
2011-03-29 22:28:37 +00:00
import Control.Monad (when, liftM, filterM)
import Data.List
2011-03-29 03:22:31 +00:00
import qualified Data.Map as M
import Data.Maybe
import Data.Char
import RemoteClass
import Types
import UUID
import qualified Annex
import Trust
import LocationLog
2011-03-29 03:22:31 +00:00
import Locations
2011-03-29 18:55:59 +00:00
import Utility
2011-03-29 22:28:37 +00:00
import Config
2011-03-29 18:55:59 +00:00
import qualified Remote.Git
import qualified Remote.S3
import qualified Remote.Bup
2011-03-30 17:18:46 +00:00
import qualified Remote.Directory
import qualified Remote.Rsync
import qualified Remote.Hook
2011-03-29 03:51:07 +00:00
remoteTypes :: [RemoteType Annex]
remoteTypes =
[ Remote.Git.remote
, Remote.S3.remote
, Remote.Bup.remote
2011-03-30 17:18:46 +00:00
, Remote.Directory.remote
, Remote.Rsync.remote
, Remote.Hook.remote
2011-03-28 02:00:44 +00:00
]
{- Builds a list of all available Remotes.
2011-03-29 21:20:22 +00:00
- Since doing so can be expensive, the list is cached. -}
genList :: Annex [Remote Annex]
genList = do
rs <- Annex.getState Annex.remotes
if null rs
then do
2011-03-29 21:57:20 +00:00
m <- readRemoteLog
l <- mapM (process m) remoteTypes
let rs' = concat l
2011-03-29 20:21:21 +00:00
Annex.changeState $ \s -> s { Annex.remotes = rs' }
return rs'
else return rs
2011-03-29 21:57:20 +00:00
where
2011-05-15 19:27:49 +00:00
process m t =
enumerate t >>=
filterM remoteNotIgnored >>=
mapM (gen m t)
2011-03-29 21:57:20 +00:00
gen m t r = do
u <- getUUID r
2011-03-30 19:15:46 +00:00
generate t r u (M.lookup u m)
{- Looks up a remote by name. (Or by UUID.) -}
byName :: String -> Annex (Remote Annex)
byName "" = error "no remote specified"
byName n = do
allremotes <- genList
let match = filter matching allremotes
when (null match) $ error $
"there is no git remote named \"" ++ n ++ "\""
return $ head match
where
matching r = n == name r || n == uuid r
{- Looks up a remote by name (or by UUID), and returns its UUID. -}
nameToUUID :: String -> Annex UUID
2011-05-15 19:27:49 +00:00
nameToUUID "." = getUUID =<< Annex.gitRepo -- special case for current repo
nameToUUID n = liftM uuid (byName n)
{- Cost ordered lists of remotes that the LocationLog indicate may have a key.
-
- Also returns a list of UUIDs that are trusted to have the key
- (some may not have configured remotes).
-}
keyPossibilities :: Key -> Annex ([Remote Annex], [UUID])
keyPossibilities key = do
g <- Annex.gitRepo
u <- getUUID g
trusted <- trustGet Trusted
-- get uuids of all remotes that are recorded to have the key
uuids <- liftIO $ keyLocations g key
let validuuids = filter (/= u) uuids
-- note that validuuids is assumed to not have dups
let validtrusteduuids = intersect validuuids trusted
-- remotes that match uuids that have the key
allremotes <- genList
let validremotes = remotesWithUUID allremotes validuuids
return (sort validremotes, validtrusteduuids)
{- Filters a list of remotes to ones that have the listed uuids. -}
remotesWithUUID :: [Remote Annex] -> [UUID] -> [Remote Annex]
remotesWithUUID rs us = filter (\r -> uuid r `elem` us) rs
{- Filters a list of remotes to ones that do not have the listed uuids. -}
remotesWithoutUUID :: [Remote Annex] -> [UUID] -> [Remote Annex]
remotesWithoutUUID rs us = filter (\r -> uuid r `notElem` us) rs
2011-03-29 03:22:31 +00:00
{- Filename of remote.log. -}
remoteLog :: Annex FilePath
remoteLog = do
g <- Annex.gitRepo
return $ gitStateDir g ++ "remote.log"
2011-03-29 17:49:54 +00:00
{- Adds or updates a remote's config in the log. -}
2011-04-15 19:09:36 +00:00
configSet :: UUID -> RemoteConfig -> Annex ()
2011-03-29 17:49:54 +00:00
configSet u c = do
m <- readRemoteLog
2011-03-29 03:22:31 +00:00
l <- remoteLog
2011-03-29 18:55:59 +00:00
liftIO $ safeWriteFile l $ unlines $ sort $
map toline $ M.toList $ M.insert u c m
2011-03-29 03:22:31 +00:00
where
2011-04-15 19:09:36 +00:00
toline (u', c') = u' ++ " " ++ (unwords $ configToKeyVal c')
2011-03-29 03:22:31 +00:00
2011-03-29 17:49:54 +00:00
{- Map of remotes by uuid containing key/value config maps. -}
2011-04-15 19:09:36 +00:00
readRemoteLog :: Annex (M.Map UUID RemoteConfig)
2011-03-29 17:49:54 +00:00
readRemoteLog = do
2011-03-29 03:22:31 +00:00
l <- remoteLog
2011-03-29 17:49:54 +00:00
s <- liftIO $ catch (readFile l) ignoreerror
return $ remoteLogParse s
2011-03-29 03:22:31 +00:00
where
2011-03-29 17:49:54 +00:00
ignoreerror _ = return ""
2011-03-29 03:22:31 +00:00
2011-04-15 19:09:36 +00:00
remoteLogParse :: String -> M.Map UUID RemoteConfig
2011-03-29 17:49:54 +00:00
remoteLogParse s =
M.fromList $ catMaybes $ map parseline $ filter (not . null) $ lines s
2011-03-29 03:22:31 +00:00
where
parseline l
2011-03-29 17:49:54 +00:00
| length w > 2 = Just (u, c)
2011-03-29 03:22:31 +00:00
| otherwise = Nothing
where
w = words l
u = w !! 0
2011-04-15 19:09:36 +00:00
c = keyValToConfig $ tail w
2011-03-29 03:22:31 +00:00
2011-04-15 19:09:36 +00:00
{- Given Strings like "key=value", generates a RemoteConfig. -}
keyValToConfig :: [String] -> RemoteConfig
keyValToConfig ws = M.fromList $ map (/=/) ws
2011-03-29 03:22:31 +00:00
where
(/=/) s = (k, v)
where
k = takeWhile (/= '=') s
v = configUnEscape $ drop (1 + length k) s
2011-03-29 03:22:31 +00:00
2011-04-15 19:09:36 +00:00
configToKeyVal :: M.Map String String -> [String]
configToKeyVal m = map toword $ sort $ M.toList m
2011-03-29 03:22:31 +00:00
where
toword (k, v) = k ++ "=" ++ configEscape v
configEscape :: String -> String
2011-05-16 19:01:05 +00:00
configEscape = (>>= escape)
where
escape c
| isSpace c || c `elem` "&" = "&" ++ show (ord c) ++ ";"
| otherwise = [c]
configUnEscape :: String -> String
configUnEscape = unescape
where
unescape [] = []
unescape (c:rest)
| c == '&' = entity rest
| otherwise = c : unescape rest
entity s = if ok
then chr (read num) : unescape rest
else '&' : unescape s
where
num = takeWhile isNumber s
r = drop (length num) s
rest = drop 1 r
ok = not (null num) &&
not (null r) && r !! 0 == ';'
{- for quickcheck -}
prop_idempotent_configEscape :: String -> Bool
prop_idempotent_configEscape s = s == (configUnEscape $ configEscape s)