e55e445a36
Implemented so far for the directory special remote. Several remotes don't make sense to export to. Regular Git remotes, obviously, do not. Bup remotes almost certianly do not, since bup would need to be used to extract the export; same store for Ddar. Web and Bittorrent are download-only. GCrypt is always encrypted so exporting to it would be pointless. There's probably no point complicating the Hook remotes with exporting at this point. External, S3, Glacier, WebDAV, Rsync, and possibly Tahoe should be modified to support export. Thought about trying to reuse the storeKey/retrieveKeyFile/removeKey interface, rather than adding a new interface. But, it seemed better to keep it separate, to avoid a complicated interface that sometimes encrypts/chunks key/value storage and sometimes users non-key/value storage. Any common parts can be factored out. Note that storeExport is not atomic. doc/design/exporting_trees_to_special_remotes.mdwn has some things in the "resuming exports" section that bear on this decision. Basically, I don't think, at this time, that an atomic storeExport would help with resuming, because exports are not key/value storage, and we can't be sure that a partially uploaded file is the same content we're currently trying to export. Also, note that ExportLocation will always use unix path separators. This is important, because users may export from a mix of windows and unix, and it avoids complicating the API with path conversions, and ensures that in such a mix, they always use the same locations for exports. This commit was sponsored by Bruno BEAUFILS on Patreon.
201 lines
6.1 KiB
Haskell
201 lines
6.1 KiB
Haskell
{- git remotes using the git-annex P2P protocol
|
|
-
|
|
- Copyright 2016 Joey Hess <id@joeyh.name>
|
|
-
|
|
- Licensed under the GNU GPL version 3 or higher.
|
|
-}
|
|
|
|
module Remote.P2P (
|
|
remote,
|
|
chainGen
|
|
) where
|
|
|
|
import Annex.Common
|
|
import qualified Annex
|
|
import qualified P2P.Protocol as P2P
|
|
import P2P.Address
|
|
import P2P.Annex
|
|
import P2P.IO
|
|
import P2P.Auth
|
|
import Types.Remote
|
|
import Types.GitConfig
|
|
import qualified Git
|
|
import Annex.UUID
|
|
import Config
|
|
import Config.Cost
|
|
import Remote.Helper.Git
|
|
import Messages.Progress
|
|
import Utility.Metered
|
|
import Utility.AuthToken
|
|
import Types.NumCopies
|
|
|
|
import Control.Concurrent
|
|
import Control.Concurrent.STM
|
|
|
|
remote :: RemoteType
|
|
remote = RemoteType {
|
|
typename = "p2p",
|
|
-- Remote.Git takes care of enumerating P2P remotes,
|
|
-- and will call chainGen on them.
|
|
enumerate = const (return []),
|
|
generate = \_ _ _ _ -> return Nothing,
|
|
setup = error "P2P remotes are set up using git-annex p2p"
|
|
}
|
|
|
|
chainGen :: P2PAddress -> Git.Repo -> UUID -> RemoteConfig -> RemoteGitConfig -> Annex (Maybe Remote)
|
|
chainGen addr r u c gc = do
|
|
connpool <- mkConnectionPool
|
|
cst <- remoteCost gc veryExpensiveRemoteCost
|
|
let this = Remote
|
|
{ uuid = u
|
|
, cost = cst
|
|
, name = Git.repoDescribe r
|
|
, storeKey = store u addr connpool
|
|
, retrieveKeyFile = retrieve u addr connpool
|
|
, retrieveKeyFileCheap = \_ _ _ -> return False
|
|
, removeKey = remove u addr connpool
|
|
, lockContent = Just (lock u addr connpool)
|
|
, checkPresent = checkpresent u addr connpool
|
|
, checkPresentCheap = False
|
|
, storeExport = Nothing
|
|
, retrieveExport = Nothing
|
|
, removeExport = Nothing
|
|
, checkPresentExport = Nothing
|
|
, renameExport = Nothing
|
|
, whereisKey = Nothing
|
|
, remoteFsck = Nothing
|
|
, repairRepo = Nothing
|
|
, config = c
|
|
, localpath = Nothing
|
|
, repo = r
|
|
, gitconfig = gc { remoteGitConfig = extractGitConfig r }
|
|
, readonly = False
|
|
, availability = GloballyAvailable
|
|
, remotetype = remote
|
|
, mkUnavailable = return Nothing
|
|
, getInfo = gitRepoInfo this
|
|
, claimUrl = Nothing
|
|
, checkUrl = Nothing
|
|
}
|
|
return (Just this)
|
|
|
|
store :: UUID -> P2PAddress -> ConnectionPool -> Key -> AssociatedFile -> MeterUpdate -> Annex Bool
|
|
store u addr connpool k af p =
|
|
metered (Just p) k $ \p' -> fromMaybe False
|
|
<$> runProto u addr connpool (P2P.put k af p')
|
|
|
|
retrieve :: UUID -> P2PAddress -> ConnectionPool -> Key -> AssociatedFile -> FilePath -> MeterUpdate -> Annex (Bool, Verification)
|
|
retrieve u addr connpool k af dest p = unVerified $
|
|
metered (Just p) k $ \p' -> fromMaybe False
|
|
<$> runProto u addr connpool (P2P.get dest k af p')
|
|
|
|
remove :: UUID -> P2PAddress -> ConnectionPool -> Key -> Annex Bool
|
|
remove u addr connpool k = fromMaybe False
|
|
<$> runProto u addr connpool (P2P.remove k)
|
|
|
|
checkpresent :: UUID -> P2PAddress -> ConnectionPool -> Key -> Annex Bool
|
|
checkpresent u addr connpool k = maybe unavail return
|
|
=<< runProto u addr connpool (P2P.checkPresent k)
|
|
where
|
|
unavail = giveup "can't connect to peer"
|
|
|
|
lock :: UUID -> P2PAddress -> ConnectionPool -> Key -> (VerifiedCopy -> Annex r) -> Annex r
|
|
lock u addr connpool k callback =
|
|
withConnection u addr connpool $ \conn -> do
|
|
connv <- liftIO $ newMVar conn
|
|
let runproto d p = do
|
|
c <- liftIO $ takeMVar connv
|
|
(c', mr) <- runProto' p c
|
|
liftIO $ putMVar connv c'
|
|
return (fromMaybe d mr)
|
|
r <- P2P.lockContentWhile runproto k go
|
|
conn' <- liftIO $ takeMVar connv
|
|
return (conn', r)
|
|
where
|
|
go False = giveup "can't lock content"
|
|
go True = withVerifiedCopy LockedCopy u (return True) callback
|
|
|
|
-- | A connection to the peer.
|
|
data Connection
|
|
= OpenConnection P2PConnection
|
|
| ClosedConnection
|
|
|
|
type ConnectionPool = TVar [Connection]
|
|
|
|
mkConnectionPool :: Annex ConnectionPool
|
|
mkConnectionPool = liftIO $ newTVarIO []
|
|
|
|
-- Runs the Proto action.
|
|
runProto :: UUID -> P2PAddress -> ConnectionPool -> P2P.Proto a -> Annex (Maybe a)
|
|
runProto u addr connpool a = withConnection u addr connpool (runProto' a)
|
|
|
|
runProto' :: P2P.Proto a -> Connection -> Annex (Connection, Maybe a)
|
|
runProto' _ ClosedConnection = return (ClosedConnection, Nothing)
|
|
runProto' a (OpenConnection conn) = do
|
|
v <- runFullProto Client conn a
|
|
-- When runFullProto fails, the connection is no longer usable,
|
|
-- so close it.
|
|
case v of
|
|
Left e -> do
|
|
warning $ "Lost connection to peer (" ++ e ++ ")"
|
|
liftIO $ closeConnection conn
|
|
return (ClosedConnection, Nothing)
|
|
Right r -> return (OpenConnection conn, Just r)
|
|
|
|
-- Uses an open connection if one is available in the ConnectionPool;
|
|
-- otherwise opens a new connection.
|
|
--
|
|
-- Once the action is done, the connection is added back to the
|
|
-- ConnectionPool, unless it's no longer open.
|
|
withConnection :: UUID -> P2PAddress -> ConnectionPool -> (Connection -> Annex (Connection, a)) -> Annex a
|
|
withConnection u addr connpool a = bracketOnError get cache go
|
|
where
|
|
get = do
|
|
mc <- liftIO $ atomically $ do
|
|
l <- readTVar connpool
|
|
case l of
|
|
[] -> do
|
|
writeTVar connpool []
|
|
return Nothing
|
|
(c:cs) -> do
|
|
writeTVar connpool cs
|
|
return (Just c)
|
|
maybe (openConnection u addr) return mc
|
|
|
|
cache ClosedConnection = return ()
|
|
cache conn = liftIO $ atomically $ modifyTVar' connpool (conn:)
|
|
|
|
go conn = do
|
|
(conn', r) <- a conn
|
|
cache conn'
|
|
return r
|
|
|
|
openConnection :: UUID -> P2PAddress -> Annex Connection
|
|
openConnection u addr = do
|
|
g <- Annex.gitRepo
|
|
v <- liftIO $ tryNonAsync $ connectPeer g addr
|
|
case v of
|
|
Right conn -> do
|
|
myuuid <- getUUID
|
|
authtoken <- fromMaybe nullAuthToken
|
|
<$> loadP2PRemoteAuthToken addr
|
|
res <- liftIO $ runNetProto conn $
|
|
P2P.auth myuuid authtoken
|
|
case res of
|
|
Right (Just theiruuid)
|
|
| u == theiruuid -> return (OpenConnection conn)
|
|
| otherwise -> do
|
|
liftIO $ closeConnection conn
|
|
warning "Remote peer uuid seems to have changed."
|
|
return ClosedConnection
|
|
Right Nothing -> do
|
|
warning "Unable to authenticate with peer."
|
|
liftIO $ closeConnection conn
|
|
return ClosedConnection
|
|
Left e -> do
|
|
warning $ "Problem communicating with peer. (" ++ e ++ ")"
|
|
liftIO $ closeConnection conn
|
|
return ClosedConnection
|
|
Left e -> do
|
|
warning $ "Unable to connect to peer. (" ++ show e ++ ")"
|
|
return ClosedConnection
|