move new interface to git-annex transfer
authorJoey Hess <joeyh@joeyh.name>
Wed, 9 Dec 2020 16:32:29 +0000 (12:32 -0400)
committerJoey Hess <joeyh@joeyh.name>
Wed, 9 Dec 2020 16:33:56 +0000 (12:33 -0400)
This is to avoid breakage when upgrading or downgrading git-annex with a
process running that uses the interface. It's better to keep the
compatability code for a few years than worry about such breakage.

This commit was sponsored by Brett Eisenberg on Patreon.

Annex/TransferrerPool.hs
CmdLine/GitAnnex.hs
Command/Transfer.hs [new file with mode: 0644]
Command/TransferKeys.hs
Types/TransferrerPool.hs
doc/git-annex-transfer.mdwn [new file with mode: 0644]
doc/git-annex-transferkeys.mdwn
doc/git-annex.mdwn
doc/todo/serialize_Messages_for_transfer.mdwn [new file with mode: 0644]
doc/todo/serialize_Messages_for_transferkeys.mdwn [deleted file]
git-annex.cabal

index 973f7562982f6e40c80a1b6def70ea45b5a9c15a..f44d46db908c9ac155a032280872955761f2baf6 100644 (file)
@@ -1,4 +1,4 @@
-{- A pool of "git-annex transferkeys" processes
+{- A pool of "git-annex transfer" processes
  -
  - Copyright 2013-2020 Joey Hess <id@joeyh.name>
  -
@@ -197,12 +197,12 @@ detectStalls (Just (StallDetection minsz duration)) metervar onstall = go Nothin
                                                then onstall
                                                else go (Just sofar)
 
-{- Starts a new git-annex transferkeys process, setting up handles
+{- Starts a new git-annex transfer process, setting up handles
  - that will be used to communicate with it. -}
 mkTransferrer :: FilePath -> BatchCommandMaker -> IO Transferrer
 mkTransferrer program batchmaker = do
        {- It runs as a batch job. -}
-       let (program', params') = batchmaker (program, [Param "transferkeys"])
+       let (program', params') = batchmaker (program, [Param "transfer"])
        {- It's put into its own group so that the whole group can be
         - killed to stop a transfer. -}
        (Just writeh, Just readh, _, pid) <- createProcess
@@ -243,10 +243,10 @@ readResponse h = do
        case readMaybe l of
                Just (TransferOutput so) -> return (Left so)
                Just (TransferResult r) -> return (Right r)
-               Nothing -> transferKeysProtocolError l
+               Nothing -> transferProtocolError l
 
-transferKeysProtocolError :: String -> a
-transferKeysProtocolError l = error $ "transferkeys protocol error: " ++ show l
+transferProtocolError :: String -> a
+transferProtocolError l = error $ "transfer protocol error: " ++ show l
 
 {- Closing the fds will shut down the transferrer, but only when it's
  - in between transfers. -}
index 9fc0d272ae6395cf28b8a854ae1f88a9b2255be2..2364df8bac3ea1c744fdb6cfd887be96c9d09cb3 100644 (file)
@@ -35,6 +35,7 @@ import qualified Command.FromKey
 import qualified Command.RegisterUrl
 import qualified Command.SetKey
 import qualified Command.DropKey
+import qualified Command.Transfer
 import qualified Command.TransferKey
 import qualified Command.TransferKeys
 import qualified Command.SetPresentKey
@@ -177,6 +178,7 @@ cmds testoptparser testrunner mkbenchmarkgenerator =
        , Command.RegisterUrl.cmd
        , Command.SetKey.cmd
        , Command.DropKey.cmd
+       , Command.Transfer.cmd
        , Command.TransferKey.cmd
        , Command.TransferKeys.cmd
        , Command.SetPresentKey.cmd
diff --git a/Command/Transfer.hs b/Command/Transfer.hs
new file mode 100644 (file)
index 0000000..f4a1cd2
--- /dev/null
@@ -0,0 +1,112 @@
+{- git-annex command
+ -
+ - Copyright 2012-2020 Joey Hess <id@joeyh.name>
+ -
+ - Licensed under the GNU AGPL version 3 or higher.
+ -}
+
+module Command.Transfer where
+
+import Command
+import qualified Annex
+import Annex.Content
+import Logs.Location
+import Annex.Transfer
+import qualified Remote
+import Utility.SimpleProtocol (dupIoHandles)
+import qualified Database.Keys
+import Annex.BranchState
+import Types.Messages
+import Annex.TransferrerPool
+
+import Text.Read (readMaybe)
+
+cmd :: Command
+cmd = command "transfer" SectionPlumbing "transfers content"
+       paramNothing (withParams seek)
+
+seek :: CmdParams -> CommandSeek
+seek = withNothing (commandAction start)
+
+start :: CommandStart
+start = do
+       enableInteractiveBranchAccess
+       (readh, writeh) <- liftIO dupIoHandles
+       Annex.setOutput $ SerializedOutput
+               (\v -> hPutStrLn writeh (show (TransferOutput v)) >> hFlush writeh)
+               (readMaybe <$> hGetLine readh)
+       runRequests readh writeh runner
+       stop
+  where
+       runner (TransferRequest AnnexLevel direction _ keydata file) remote
+               | direction == Upload =
+                       -- This is called by eg, Annex.Transfer.upload,
+                       -- so caller is responsible for doing notification,
+                       -- and for retrying.
+                       upload' (Remote.uuid remote) key file noRetry
+                               (Remote.action . Remote.storeKey remote key file)
+                               noNotification
+               | otherwise =
+                       -- This is called by eg, Annex.Transfer.download
+                       -- so caller is responsible for doing notification
+                       -- and for retrying.
+                       let go p = getViaTmp (Remote.retrievalSecurityPolicy remote) (RemoteVerify remote) key file $ \t -> do
+                               Remote.verifiedAction (Remote.retrieveKeyFile remote key file (fromRawFilePath t) p)
+                       in download' (Remote.uuid remote) key file noRetry go 
+                               noNotification
+         where
+               key = mkKey (const keydata)
+       runner (TransferRequest AssistantLevel direction _ keydata file) remote
+               | direction == Upload = notifyTransfer direction file $
+                       upload' (Remote.uuid remote) key file stdRetry $ \p -> do
+                               tryNonAsync (Remote.storeKey remote key file p) >>= \case
+                                       Left e -> do
+                                               warning (show e)
+                                               return False
+                                       Right () -> do
+                                               Remote.logStatus remote key InfoPresent
+                                               return True
+               | otherwise = notifyTransfer direction file $
+                       download' (Remote.uuid remote) key file stdRetry $ \p ->
+                               getViaTmp (Remote.retrievalSecurityPolicy remote) (RemoteVerify remote) key file $ \t -> do
+                                       r <- tryNonAsync (Remote.retrieveKeyFile remote key file (fromRawFilePath t) p) >>= \case
+                                               Left e -> do
+                                                       warning (show e)
+                                                       return (False, UnVerified)
+                                               Right v -> return (True, v)
+                                       -- Make sure we get the current
+                                       -- associated files data for the key,
+                                       -- not old cached data.
+                                       Database.Keys.closeDb                   
+                                       return r
+         where
+               key = mkKey (const keydata)
+
+runRequests
+       :: Handle
+       -> Handle
+       -> (TransferRequest -> Remote -> Annex Bool)
+       -> Annex ()
+runRequests readh writeh a = go Nothing Nothing
+  where
+       go lastremoteoruuid lastremote = unlessM (liftIO $ hIsEOF readh) $ do
+               l <- liftIO $ hGetLine readh
+               case readMaybe l of
+                       Just tr@(TransferRequest _ _ remoteoruuid _ _) -> do
+                               -- Often the same remote will be used
+                               -- repeatedly, so cache the last one to
+                               -- avoid looking up repeatedly.
+                               mremote <- if lastremoteoruuid == Just remoteoruuid
+                                       then pure lastremote
+                                       else eitherToMaybe <$> Remote.byName'
+                                               (either fromUUID id remoteoruuid)
+                               case mremote of
+                                       Just remote -> do
+                                               sendresult =<< a tr remote
+                                               go (Just remoteoruuid) mremote
+                                       Nothing -> transferProtocolError l
+                       Nothing -> transferProtocolError l
+
+       sendresult b = liftIO $ do
+               hPutStrLn writeh $ show $ TransferResult b
+               hFlush writeh
index 573530e98f5571473f40583861bfe5509527fdcd..aba5feabaad878795bed15fee930b9d31d89c386 100644 (file)
@@ -1,28 +1,31 @@
-{- git-annex command
+{- git-annex command, used internally by assistant in version
+ - 8.20201127 and older and provided only to avoid upgrade breakage.
+ - Remove at some point when such old versions of git-annex are unlikely
+ - to be running any longer.
  -
- - Copyright 2012-2020 Joey Hess <id@joeyh.name>
+ - Copyright 2012, 2013 Joey Hess <id@joeyh.name>
  -
  - Licensed under the GNU AGPL version 3 or higher.
  -}
 
+{-# LANGUAGE TypeSynonymInstances, FlexibleInstances #-}
+
 module Command.TransferKeys where
 
 import Command
-import qualified Annex
 import Annex.Content
 import Logs.Location
 import Annex.Transfer
 import qualified Remote
 import Utility.SimpleProtocol (dupIoHandles)
+import Git.Types (RemoteName)
 import qualified Database.Keys
 import Annex.BranchState
-import Types.Messages
-import Annex.TransferrerPool
 
-import Text.Read (readMaybe)
+data TransferRequest = TransferRequest Direction Remote Key AssociatedFile
 
 cmd :: Command
-cmd = command "transferkeys" SectionPlumbing "transfers keys"
+cmd = command "transferkeys" SectionPlumbing "transfers keys (deprecated)"
        paramNothing (withParams seek)
 
 seek :: CmdParams -> CommandSeek
@@ -32,31 +35,10 @@ start :: CommandStart
 start = do
        enableInteractiveBranchAccess
        (readh, writeh) <- liftIO dupIoHandles
-       Annex.setOutput $ SerializedOutput
-               (\v -> hPutStrLn writeh (show (TransferOutput v)) >> hFlush writeh)
-               (readMaybe <$> hGetLine readh)
        runRequests readh writeh runner
        stop
   where
-       runner (TransferRequest AnnexLevel direction _ keydata file) remote
-               | direction == Upload =
-                       -- This is called by eg, Annex.Transfer.upload,
-                       -- so caller is responsible for doing notification,
-                       -- and for retrying.
-                       upload' (Remote.uuid remote) key file noRetry
-                               (Remote.action . Remote.storeKey remote key file)
-                               noNotification
-               | otherwise =
-                       -- This is called by eg, Annex.Transfer.download
-                       -- so caller is responsible for doing notification
-                       -- and for retrying.
-                       let go p = getViaTmp (Remote.retrievalSecurityPolicy remote) (RemoteVerify remote) key file $ \t -> do
-                               Remote.verifiedAction (Remote.retrieveKeyFile remote key file (fromRawFilePath t) p)
-                       in download' (Remote.uuid remote) key file noRetry go 
-                               noNotification
-         where
-               key = mkKey (const keydata)
-       runner (TransferRequest AssistantLevel direction _ keydata file) remote
+       runner (TransferRequest direction remote key file)
                | direction == Upload = notifyTransfer direction file $
                        upload' (Remote.uuid remote) key file stdRetry $ \p -> do
                                tryNonAsync (Remote.storeKey remote key file p) >>= \case
@@ -79,34 +61,82 @@ start = do
                                        -- not old cached data.
                                        Database.Keys.closeDb                   
                                        return r
-         where
-               key = mkKey (const keydata)
 
 runRequests
        :: Handle
        -> Handle
-       -> (TransferRequest -> Remote -> Annex Bool)
+       -> (TransferRequest -> Annex Bool)
        -> Annex ()
-runRequests readh writeh a = go Nothing Nothing
+runRequests readh writeh a = do
+       liftIO $ hSetBuffering readh NoBuffering
+       go =<< readrequests
   where
-       go lastremoteoruuid lastremote = unlessM (liftIO $ hIsEOF readh) $ do
-               l <- liftIO $ hGetLine readh
-               case readMaybe l of
-                       Just tr@(TransferRequest _ _ remoteoruuid _ _) -> do
-                               -- Often the same remote will be used
-                               -- repeatedly, so cache the last one to
-                               -- avoid looking up repeatedly.
-                               mremote <- if lastremoteoruuid == Just remoteoruuid
-                                       then pure lastremote
-                                       else eitherToMaybe <$> Remote.byName'
-                                               (either fromUUID id remoteoruuid)
+       go (d:rn:k:f:rest) = do
+               case (deserialize d, deserialize rn, deserialize k, deserialize f) of
+                       (Just direction, Just remotename, Just key, Just file) -> do
+                               mremote <- Remote.byName' remotename
                                case mremote of
-                                       Just remote -> do
-                                               sendresult =<< a tr remote
-                                               go (Just remoteoruuid) mremote
-                                       Nothing -> transferKeysProtocolError l
-                       Nothing -> transferKeysProtocolError l
+                                       Left _ -> sendresult False
+                                       Right remote -> sendresult =<< a
+                                               (TransferRequest direction remote key file)
+                       _ -> sendresult False
+               go rest
+       go [] = noop
+       go [""] = noop
+       go v = error $ "transferkeys protocol error: " ++ show v
 
+       readrequests = liftIO $ split fieldSep <$> hGetContents readh
        sendresult b = liftIO $ do
-               hPutStrLn writeh $ show $ TransferResult b
+               hPutStrLn writeh $ serialize b
                hFlush writeh
+
+sendRequest :: Transfer -> TransferInfo -> Handle -> IO ()
+sendRequest t tinfo h = do
+       hPutStr h $ intercalate fieldSep
+               [ serialize (transferDirection t)
+               , maybe (serialize ((fromUUID (transferUUID t)) :: String))
+                       (serialize . Remote.name)
+                       (transferRemote tinfo)
+               , serialize (transferKey t)
+               , serialize (associatedFile tinfo)
+               , "" -- adds a trailing null
+               ]
+       hFlush h
+
+readResponse :: Handle -> IO Bool
+readResponse h = fromMaybe False . deserialize <$> hGetLine h
+
+fieldSep :: String
+fieldSep = "\0"
+
+class TCSerialized a where
+       serialize :: a -> String
+       deserialize :: String -> Maybe a
+
+instance TCSerialized Bool where
+       serialize True = "1"
+       serialize False = "0"
+       deserialize "1" = Just True
+       deserialize "0" = Just False
+       deserialize _ = Nothing
+
+instance TCSerialized Direction where
+       serialize Upload = "u"
+       serialize Download = "d"
+       deserialize "u" = Just Upload
+       deserialize "d" = Just Download
+       deserialize _ = Nothing
+
+instance TCSerialized AssociatedFile where
+       serialize (AssociatedFile (Just f)) = fromRawFilePath f
+       serialize (AssociatedFile Nothing) = ""
+       deserialize "" = Just (AssociatedFile Nothing)
+       deserialize f = Just (AssociatedFile (Just (toRawFilePath f)))
+
+instance TCSerialized RemoteName where
+       serialize n = n
+       deserialize n = Just n
+
+instance TCSerialized Key where
+       serialize = serializeKey
+       deserialize = deserializeKey
index 72d61a3b9abfd28b7496558d1fafeec4e198efd9..e2150e300866e3b68fe214e906c760c9fd67d69e 100644 (file)
@@ -1,4 +1,4 @@
-{- A pool of "git-annex transferkeys" processes available for use
+{- A pool of "git-annex transfer" processes available for use
  -
  - Copyright 2013-2020 Joey Hess <id@joeyh.name>
  -
diff --git a/doc/git-annex-transfer.mdwn b/doc/git-annex-transfer.mdwn
new file mode 100644 (file)
index 0000000..a318959
--- /dev/null
@@ -0,0 +1,24 @@
+# NAME
+
+git-annex transfer - transfers content
+
+# SYNOPSIS
+
+git annex transfer
+
+# DESCRIPTION
+
+This plumbing-level command is used to transfer data.
+It is a long-running process, which is fed instructions about
+what to transfer using an internal stdio protocol, which is
+intentionally not documented (as it may change at any time).
+
+# SEE ALSO
+
+[[git-annex]](1)
+
+# AUTHOR
+
+Joey Hess <id@joeyh.name>
+
+Warning: Automatically converted into a man page by mdwn2man. Edit with care.
index b1f983d5b8c917d9f85e5870ad0b12af9cb64a79..cdc97771d467232ae8f563ba40eeb509cc8da17f 100644 (file)
@@ -1,6 +1,6 @@
 # NAME
 
-git-annex transferkeys - transfers keys
+git-annex transferkeys - transfers keys (deprecated)
 
 # SYNOPSIS
 
@@ -8,7 +8,10 @@ git annex transferkeys
 
 # DESCRIPTION
 
-This plumbing-level command is used to transfer data.
+This plumbing-level command is used to transfer data, by the assistant
+in git-annex version 8.20201127 and older. It is still included only
+to prevent breakage during upgrades.
+
 It is a long-running process, which is fed instructions about the keys
 to transfer using an internal stdio protocol, which is
 intentionally not documented (as it may change at any time).
index b739cdafd202208c909a86b4546f584228dee917..b1be9055f1910c87e059306f564edb35f443aa33 100644 (file)
@@ -631,9 +631,15 @@ content from the key-value store.
   
   See [[git-annex-transferkey]](1) for details.
 
+* `transfer`
+  
+  Used internally by git-annex to transfer content.
+
+  See [[git-annex-transfer]](1) for details.
+
 * `transferkeys`
   
-  Used internally by the assistant.
+  Used internally by old versions of the assistant.
 
   See [[git-annex-transferkey]](1) for details.
 
diff --git a/doc/todo/serialize_Messages_for_transfer.mdwn b/doc/todo/serialize_Messages_for_transfer.mdwn
new file mode 100644 (file)
index 0000000..18a1aaf
--- /dev/null
@@ -0,0 +1,47 @@
+For [[todo/more_extensive_retries_to_mask_transient_failures]],
+multiple transferkeys processes need to be run. So all console IO needs to
+be serialized and sent back to the main git-annex process from them,
+to avoid concurrent output.
+
+Using --json and --json-progress and --json-error-messages is pretty close
+to what would be needed. That handles progress of transfers, and overall
+success/failure. 
+
+Using --json-error-messages makes most error messages be output in json.
+However, that uses an error-messages array in the json object,
+which error messages are added to as an action runs, and it's only sent at
+the end. So a problem for eg, a relay of stderr output from a command
+(eg ssh password prompt), or for anything that displays a warning that
+should be displayed before the transfer is complete.
+
+Since the existing json is not a perfect fit, it might be better to use a
+custom protocol, implemented as a new output type. Then anything in
+Messages and child modules that looks at output types will support it.
+(Although perhaps some of that stuff is not used by remotes.)
+
+A few notes on implementing that:
+
+* Messages.Progress.mkOutputHandler, which uses mkStderrEmitter,
+  outputs to stderr directly no matter the output type currently.
+  It would need to be changed to support the new output type.
+  (And probably should for concurrent output mode too actually!)
+
+  > It's true, this is not concurrent output safe. However, that's already
+  > the case, and output to stderr doesn't affect the piping of serialized
+  > messages on stdout. So, punted on this.
+
+* So does warningIO, though it's only used in a couple of remotes
+  and rarely. It would be good to find a way to eliminate it.
+
+  > Eliminated except for one call in a non-relevant code path.
+
+* Messages.prompt. Which is used by remotes, and would need to 
+  communicate over the pipe to the parent git-annex bidirectionally.
+  Eg, send a message saying the parent needs to prepare for prompt,
+  wait for it to reply saying it has, and then send a message when the
+  prompting is done. (Note that the parent would need to detect if the child
+  process crashed to avoid being locked waiting for the prompt.)
+
+  > Done.
+
+[[done]]
diff --git a/doc/todo/serialize_Messages_for_transferkeys.mdwn b/doc/todo/serialize_Messages_for_transferkeys.mdwn
deleted file mode 100644 (file)
index 18a1aaf..0000000
+++ /dev/null
@@ -1,47 +0,0 @@
-For [[todo/more_extensive_retries_to_mask_transient_failures]],
-multiple transferkeys processes need to be run. So all console IO needs to
-be serialized and sent back to the main git-annex process from them,
-to avoid concurrent output.
-
-Using --json and --json-progress and --json-error-messages is pretty close
-to what would be needed. That handles progress of transfers, and overall
-success/failure. 
-
-Using --json-error-messages makes most error messages be output in json.
-However, that uses an error-messages array in the json object,
-which error messages are added to as an action runs, and it's only sent at
-the end. So a problem for eg, a relay of stderr output from a command
-(eg ssh password prompt), or for anything that displays a warning that
-should be displayed before the transfer is complete.
-
-Since the existing json is not a perfect fit, it might be better to use a
-custom protocol, implemented as a new output type. Then anything in
-Messages and child modules that looks at output types will support it.
-(Although perhaps some of that stuff is not used by remotes.)
-
-A few notes on implementing that:
-
-* Messages.Progress.mkOutputHandler, which uses mkStderrEmitter,
-  outputs to stderr directly no matter the output type currently.
-  It would need to be changed to support the new output type.
-  (And probably should for concurrent output mode too actually!)
-
-  > It's true, this is not concurrent output safe. However, that's already
-  > the case, and output to stderr doesn't affect the piping of serialized
-  > messages on stdout. So, punted on this.
-
-* So does warningIO, though it's only used in a couple of remotes
-  and rarely. It would be good to find a way to eliminate it.
-
-  > Eliminated except for one call in a non-relevant code path.
-
-* Messages.prompt. Which is used by remotes, and would need to 
-  communicate over the pipe to the parent git-annex bidirectionally.
-  Eg, send a message saying the parent needs to prepare for prompt,
-  wait for it to reply saying it has, and then send a message when the
-  prompting is done. (Note that the parent would need to detect if the child
-  process crashed to avoid being locked waiting for the prompt.)
-
-  > Done.
-
-[[done]]
index 6f7a9ab2c89a533267ecd24a04713325f0c60837..a7d74579ee4e3bedba4b89812362f23c6ee8d435 100644 (file)
@@ -794,6 +794,7 @@ Executable git-annex
     Command.Test
     Command.TestRemote
     Command.TransferInfo
+    Command.Transfer
     Command.TransferKey
     Command.TransferKeys
     Command.Trust