2020-09-02 14:41:27 +00:00
|
|
|
{- HttpAlso remote (readonly).
|
2020-09-01 19:16:35 +00:00
|
|
|
-
|
|
|
|
- Copyright 2020 Joey Hess <id@joeyh.name>
|
|
|
|
-
|
|
|
|
- Licensed under the GNU AGPL version 3 or higher.
|
|
|
|
-}
|
|
|
|
|
2020-09-02 14:41:27 +00:00
|
|
|
module Remote.HttpAlso (remote) where
|
2020-09-01 19:16:35 +00:00
|
|
|
|
|
|
|
import Annex.Common
|
|
|
|
import Types.Remote
|
|
|
|
import Types.ProposedAccepted
|
2020-09-02 15:14:50 +00:00
|
|
|
import Types.Export
|
2020-09-01 19:16:35 +00:00
|
|
|
import Remote.Helper.Messages
|
|
|
|
import Remote.Helper.ExportImport
|
|
|
|
import Remote.Helper.Special
|
|
|
|
import qualified Git
|
|
|
|
import Config.Cost
|
|
|
|
import Config
|
|
|
|
import Logs.Web
|
|
|
|
import Creds
|
2020-09-02 16:01:50 +00:00
|
|
|
import Messages.Progress
|
2020-09-01 19:16:35 +00:00
|
|
|
import Utility.Metered
|
|
|
|
import qualified Annex.Url as Url
|
|
|
|
import Annex.SpecialRemote.Config
|
|
|
|
|
2020-09-02 16:01:50 +00:00
|
|
|
import Data.Either
|
2020-09-01 19:16:35 +00:00
|
|
|
import qualified Data.Map as M
|
|
|
|
import System.FilePath.Posix as P
|
|
|
|
import Control.Concurrent.STM
|
|
|
|
|
|
|
|
remote :: RemoteType
|
|
|
|
remote = RemoteType
|
2020-09-02 14:41:27 +00:00
|
|
|
{ typename = "httpalso"
|
|
|
|
, enumerate = const (findSpecialRemotes "httpalso")
|
2020-09-01 19:16:35 +00:00
|
|
|
, generate = gen
|
|
|
|
, configParser = mkRemoteConfigParser
|
|
|
|
[ optionalStringParser urlField
|
|
|
|
(FieldDesc "(required) url to the remote content")
|
|
|
|
]
|
2020-09-02 14:41:27 +00:00
|
|
|
, setup = httpAlsoSetup
|
2020-09-02 15:14:50 +00:00
|
|
|
, exportSupported = exportIsSupported
|
2020-09-01 19:16:35 +00:00
|
|
|
, importSupported = importUnsupported
|
|
|
|
}
|
|
|
|
|
|
|
|
urlField :: RemoteConfigField
|
|
|
|
urlField = Accepted "url"
|
|
|
|
|
|
|
|
gen :: Git.Repo -> UUID -> RemoteConfig -> RemoteGitConfig -> RemoteStateHandle -> Annex (Maybe Remote)
|
|
|
|
gen r u rc gc rs = do
|
|
|
|
c <- parsedRemoteConfig remote rc
|
|
|
|
cst <- remoteCost gc expensiveRemoteCost
|
|
|
|
let url = getRemoteConfigValue urlField c
|
|
|
|
ll <- liftIO newLearnedLayout
|
|
|
|
return $ Just $ this url ll c cst
|
|
|
|
where
|
|
|
|
this url ll c cst = Remote
|
|
|
|
{ uuid = u
|
|
|
|
, cost = cst
|
|
|
|
, name = Git.repoDescribe r
|
2020-09-02 15:14:50 +00:00
|
|
|
, storeKey = cannotModify
|
2020-09-01 19:16:35 +00:00
|
|
|
, retrieveKeyFile = downloadKey url ll
|
|
|
|
, retrieveKeyFileCheap = Nothing
|
|
|
|
-- HttpManagerRestricted is used here, so this is
|
|
|
|
-- secure.
|
|
|
|
, retrievalSecurityPolicy = RetrievalAllKeysSecure
|
2020-09-02 15:14:50 +00:00
|
|
|
, removeKey = cannotModify
|
2020-09-01 19:16:35 +00:00
|
|
|
, lockContent = Nothing
|
|
|
|
, checkPresent = checkKey url ll (this url ll c cst)
|
|
|
|
, checkPresentCheap = False
|
2020-09-02 15:14:50 +00:00
|
|
|
, exportActions = ExportActions
|
|
|
|
{ storeExport = cannotModify
|
|
|
|
, retrieveExport = retriveExportHttpAlso url
|
|
|
|
, removeExport = cannotModify
|
|
|
|
, checkPresentExport = checkPresentExportHttpAlso url
|
|
|
|
, removeExportDirectory = Nothing
|
|
|
|
, renameExport = cannotModify
|
|
|
|
}
|
2020-09-01 19:16:35 +00:00
|
|
|
, importActions = importUnsupported
|
|
|
|
, whereisKey = Nothing
|
|
|
|
, remoteFsck = Nothing
|
|
|
|
, repairRepo = Nothing
|
|
|
|
, config = c
|
|
|
|
, gitconfig = gc
|
|
|
|
, localpath = Nothing
|
|
|
|
, getRepo = return r
|
|
|
|
, readonly = True
|
|
|
|
, appendonly = False
|
|
|
|
, availability = GloballyAvailable
|
|
|
|
, remotetype = remote
|
|
|
|
, mkUnavailable = return Nothing
|
|
|
|
, getInfo = return []
|
|
|
|
, claimUrl = Nothing
|
|
|
|
, checkUrl = Nothing
|
|
|
|
, remoteStateHandle = rs
|
|
|
|
}
|
|
|
|
|
2020-09-02 15:14:50 +00:00
|
|
|
cannotModify :: a
|
|
|
|
cannotModify = giveup "httpalso special remote is read only"
|
|
|
|
|
2020-09-02 14:41:27 +00:00
|
|
|
httpAlsoSetup :: SetupStage -> Maybe UUID -> Maybe CredPair -> RemoteConfig -> RemoteGitConfig -> Annex (RemoteConfig, UUID)
|
|
|
|
httpAlsoSetup _ Nothing _ _ _ =
|
|
|
|
error "Must use --sameas when initializing a httpalso remote."
|
|
|
|
httpAlsoSetup _ (Just u) _ c gc = do
|
2020-09-01 19:16:35 +00:00
|
|
|
_url <- maybe (giveup "Specify url=")
|
|
|
|
(return . fromProposedAccepted)
|
|
|
|
(M.lookup urlField c)
|
2020-09-29 17:56:27 +00:00
|
|
|
c' <- if isJust (M.lookup encryptionField c)
|
|
|
|
then fst <$> encryptionSetup c gc
|
|
|
|
else pure c
|
2020-09-02 14:41:27 +00:00
|
|
|
gitConfigSpecialRemote u c' [("httpalso", "true")]
|
2020-09-01 19:16:35 +00:00
|
|
|
return (c', u)
|
|
|
|
|
|
|
|
downloadKey :: Maybe URLString -> LearnedLayout -> Key -> AssociatedFile -> FilePath -> MeterUpdate -> Annex Verification
|
|
|
|
downloadKey baseurl ll key _af dest p = do
|
2020-09-02 16:01:50 +00:00
|
|
|
downloadAction dest p key (keyUrlAction baseurl ll key)
|
2020-09-01 19:16:35 +00:00
|
|
|
return UnVerified
|
|
|
|
|
2020-09-02 15:14:50 +00:00
|
|
|
retriveExportHttpAlso :: Maybe URLString -> Key -> ExportLocation -> FilePath -> MeterUpdate -> Annex ()
|
|
|
|
retriveExportHttpAlso baseurl key loc dest p =
|
2020-09-02 16:01:50 +00:00
|
|
|
downloadAction dest p key (exportLocationUrlAction baseurl loc)
|
|
|
|
|
2020-09-02 16:21:10 +00:00
|
|
|
downloadAction :: FilePath -> MeterUpdate -> Key -> ((URLString -> Annex (Either String ())) -> Annex (Either String ())) -> Annex ()
|
2020-09-02 16:01:50 +00:00
|
|
|
downloadAction dest p key run =
|
2020-09-02 16:21:10 +00:00
|
|
|
Url.withUrlOptions $ \uo ->
|
|
|
|
meteredFile dest (Just p) key $
|
|
|
|
run (\url -> Url.download' p url dest uo)
|
|
|
|
>>= either giveup (const (return ()))
|
2020-09-01 19:16:35 +00:00
|
|
|
|
|
|
|
checkKey :: Maybe URLString -> LearnedLayout -> Remote -> Key -> Annex Bool
|
|
|
|
checkKey baseurl ll r key = do
|
|
|
|
showChecking r
|
2020-09-02 16:21:10 +00:00
|
|
|
isRight <$> keyUrlAction baseurl ll key (checkKey' key)
|
2020-09-02 15:14:50 +00:00
|
|
|
|
2020-09-02 16:21:10 +00:00
|
|
|
checkKey' :: Key -> URLString -> Annex (Either String ())
|
|
|
|
checkKey' key url = ifM (Url.withUrlOptions $ Url.checkBoth url (fromKey keySize key))
|
|
|
|
( return (Right ())
|
|
|
|
, return (Left "content not found")
|
|
|
|
)
|
2020-09-02 15:14:50 +00:00
|
|
|
|
|
|
|
checkPresentExportHttpAlso :: Maybe URLString -> Key -> ExportLocation -> Annex Bool
|
|
|
|
checkPresentExportHttpAlso baseurl key loc =
|
2020-09-02 16:21:10 +00:00
|
|
|
isRight <$> exportLocationUrlAction baseurl loc (checkKey' key)
|
2020-09-01 19:16:35 +00:00
|
|
|
|
|
|
|
type LearnedLayout = TVar (Maybe [Key -> URLString])
|
|
|
|
|
|
|
|
newLearnedLayout :: IO LearnedLayout
|
|
|
|
newLearnedLayout = newTVarIO Nothing
|
|
|
|
|
|
|
|
-- Learns which layout the special remote uses, so the once any
|
|
|
|
-- action on an url succeeds, subsequent calls will continue to use that
|
|
|
|
-- layout (or related layouts).
|
2020-09-02 16:21:10 +00:00
|
|
|
keyUrlAction
|
|
|
|
:: Maybe URLString
|
|
|
|
-> LearnedLayout
|
|
|
|
-> Key
|
|
|
|
-> (URLString -> Annex (Either String ()))
|
|
|
|
-> Annex (Either String ())
|
|
|
|
keyUrlAction (Just baseurl) ll key downloader =
|
|
|
|
liftIO (readTVarIO ll) >>= \case
|
|
|
|
Just learned -> go Nothing False [learned]
|
|
|
|
Nothing -> go Nothing True (supportedLayouts baseurl)
|
2020-09-01 19:16:35 +00:00
|
|
|
where
|
2020-09-02 16:21:10 +00:00
|
|
|
go err learn [] = go' err learn [] []
|
|
|
|
go err learn (layouts:rest) = go' err learn layouts [] >>= \case
|
|
|
|
Right () -> return (Right ())
|
|
|
|
Left err' -> go (Just err') learn rest
|
2020-09-01 19:16:35 +00:00
|
|
|
|
2020-09-02 16:21:10 +00:00
|
|
|
go' (Just err) _ [] _ = pure (Left err)
|
|
|
|
go' Nothing _ [] _ = error "internal"
|
|
|
|
go' _err learn (layout:rest) prevs =
|
|
|
|
downloader (layout key) >>= \case
|
|
|
|
Right () -> do
|
2020-09-01 19:16:35 +00:00
|
|
|
when learn $ do
|
|
|
|
let learned = layout:prevs++rest
|
|
|
|
liftIO $ atomically $
|
|
|
|
writeTVar ll (Just learned)
|
2020-09-02 16:21:10 +00:00
|
|
|
return (Right ())
|
|
|
|
Left err -> go' (Just err) learn rest (layout:prevs)
|
2020-09-02 15:14:50 +00:00
|
|
|
keyUrlAction Nothing _ _ _ = noBaseUrlError
|
|
|
|
|
2020-09-02 16:21:10 +00:00
|
|
|
exportLocationUrlAction
|
|
|
|
:: Maybe URLString
|
|
|
|
-> ExportLocation
|
|
|
|
-> (URLString -> Annex (Either String ()))
|
|
|
|
-> Annex (Either String ())
|
2020-09-02 15:14:50 +00:00
|
|
|
exportLocationUrlAction (Just baseurl) loc a =
|
|
|
|
a (baseurl P.</> fromRawFilePath (fromExportLocation loc))
|
|
|
|
exportLocationUrlAction Nothing _ _ = noBaseUrlError
|
|
|
|
|
2020-09-01 19:16:35 +00:00
|
|
|
-- cannot normally happen
|
2020-09-02 15:14:50 +00:00
|
|
|
noBaseUrlError :: Annex a
|
|
|
|
noBaseUrlError = giveup "no url configured for httpalso special remote"
|
2020-09-01 19:16:35 +00:00
|
|
|
|
|
|
|
-- Different ways that keys can be laid out in the special remote,
|
|
|
|
-- with the more common first.
|
|
|
|
--
|
|
|
|
-- This is a nested list, because a single remote may use more than one
|
|
|
|
-- layout. In particular, old versions of git-annex used hashDirMixed
|
|
|
|
-- for some special remotes, before switching to hashDirLower for new data.
|
|
|
|
-- So, when learning the layout, both need to be tried.
|
|
|
|
supportedLayouts :: URLString -> [[Key -> URLString]]
|
|
|
|
supportedLayouts baseurl =
|
|
|
|
-- Layout used for bare git-annex repos, and for many
|
|
|
|
-- special remotes like directory.
|
|
|
|
[ [ \k -> mkurl k (hashDirLower (HashLevels 2)) P.</> kf k
|
|
|
|
-- Layout used for non-bare git-annex repos, and for some old
|
|
|
|
-- special remotes.
|
|
|
|
, \k -> mkurl k (hashDirMixed (HashLevels 2)) P.</> kf k
|
|
|
|
]
|
|
|
|
-- Special remotes that do not need hash directories.
|
|
|
|
, [ \k -> baseurl P.</> kf k ]
|
|
|
|
-- Layouts without a key directory, used by some special remotes.
|
|
|
|
, [ \k -> mkurl k (hashDirLower def)
|
|
|
|
, \k -> mkurl k (hashDirMixed def)
|
|
|
|
]
|
|
|
|
-- Layouts with only 1 level of hash directory,
|
|
|
|
-- rather than the default 2.
|
|
|
|
, [ \k -> mkurl k (hashDirLower (HashLevels 1))
|
|
|
|
, \k -> mkurl k (hashDirMixed (HashLevels 1))
|
|
|
|
]
|
|
|
|
]
|
|
|
|
where
|
|
|
|
mkurl k hasher = baseurl P.</> fromRawFilePath (hasher k) P.</> kf k
|
|
|
|
kf k = fromRawFilePath (keyFile k)
|