2a9077f4e9
Avoid crashing when "git annex get" fails to download from one location, and falls back to downloading from a second location. The problem is that git annex get calls download recursively from within itself if the first download attempt fails. So the first time through, it writes a transfer info file, which is then overwritten on the second, recursive call. Then on cleanup, it tries to delete the file twice, which of course doesn't work. Fixed both by not crashing if the transfer file is removed, and by changing Get to not run download recursively like that. It's the only thing that did so, and it just seems like a bad idea.
188 lines
5.9 KiB
Haskell
188 lines
5.9 KiB
Haskell
{- git-annex transfer information files and lock files
|
|
-
|
|
- Copyright 2012 Joey Hess <joey@kitenet.net>
|
|
-
|
|
- Licensed under the GNU GPL version 3 or higher.
|
|
-}
|
|
|
|
module Logs.Transfer where
|
|
|
|
import Common.Annex
|
|
import Annex.Perms
|
|
import Annex.Exception
|
|
import qualified Git
|
|
import Types.Remote
|
|
import qualified Fields
|
|
|
|
import System.Posix.Types
|
|
import Data.Time.Clock
|
|
import Data.Time.Clock.POSIX
|
|
import Data.Time
|
|
import System.Locale
|
|
import Control.Concurrent
|
|
|
|
{- Enough information to uniquely identify a transfer, used as the filename
|
|
- of the transfer information file. -}
|
|
data Transfer = Transfer
|
|
{ transferDirection :: Direction
|
|
, transferUUID :: UUID
|
|
, transferKey :: Key
|
|
}
|
|
deriving (Show, Eq, Ord)
|
|
|
|
{- Information about a Transfer, stored in the transfer information file.
|
|
-
|
|
- Note that the associatedFile may not correspond to a file in the local
|
|
- git repository. It's some file, possibly relative to some directory,
|
|
- of some repository, that was acted on to initiate the transfer.
|
|
-}
|
|
data TransferInfo = TransferInfo
|
|
{ startedTime :: Maybe POSIXTime
|
|
, transferPid :: Maybe ProcessID
|
|
, transferTid :: Maybe ThreadId
|
|
, transferRemote :: Maybe Remote
|
|
, bytesComplete :: Maybe Integer
|
|
, associatedFile :: Maybe FilePath
|
|
}
|
|
deriving (Show, Eq, Ord)
|
|
|
|
data Direction = Upload | Download
|
|
deriving (Eq, Ord)
|
|
|
|
instance Show Direction where
|
|
show Upload = "upload"
|
|
show Download = "download"
|
|
|
|
readDirection :: String -> Maybe Direction
|
|
readDirection "upload" = Just Upload
|
|
readDirection "download" = Just Download
|
|
readDirection _ = Nothing
|
|
|
|
upload :: UUID -> Key -> AssociatedFile -> Annex a -> Annex a
|
|
upload u key file a = transfer (Transfer Upload u key) file a
|
|
|
|
download :: UUID -> Key -> AssociatedFile -> Annex a -> Annex a
|
|
download u key file a = transfer (Transfer Download u key) file a
|
|
|
|
fieldTransfer :: Direction -> Key -> Annex a -> Annex a
|
|
fieldTransfer direction key a = do
|
|
afile <- Fields.getField Fields.associatedFile
|
|
maybe a (\u -> transfer (Transfer direction (toUUID u) key) afile a)
|
|
=<< Fields.getField Fields.remoteUUID
|
|
|
|
{- Runs a transfer action. Creates and locks the lock file while the
|
|
- action is running, and stores info in the transfer information
|
|
- file. Will throw an error if the transfer is already in progress.
|
|
-}
|
|
transfer :: Transfer -> Maybe FilePath -> Annex a -> Annex a
|
|
transfer t file a = do
|
|
tfile <- fromRepo $ transferFile t
|
|
createAnnexDirectory $ takeDirectory tfile
|
|
mode <- annexFileMode
|
|
info <- liftIO $ TransferInfo
|
|
<$> (Just . utcTimeToPOSIXSeconds <$> getCurrentTime)
|
|
<*> pure Nothing -- pid not stored in file, so omitted for speed
|
|
<*> pure Nothing -- tid ditto
|
|
<*> pure Nothing -- not 0; transfer may be resuming
|
|
<*> pure Nothing
|
|
<*> pure file
|
|
bracketIO (prep tfile mode info) (cleanup tfile) a
|
|
where
|
|
prep tfile mode info = do
|
|
fd <- openFd (transferLockFile tfile) ReadWrite (Just mode)
|
|
defaultFileFlags { trunc = True }
|
|
locked <- catchMaybeIO $
|
|
setLock fd (WriteLock, AbsoluteSeek, 0, 0)
|
|
when (locked == Nothing) $
|
|
error $ "transfer already in progress"
|
|
writeFile tfile $ writeTransferInfo info
|
|
return fd
|
|
cleanup tfile fd = do
|
|
void $ tryIO $ removeFile tfile
|
|
void $ tryIO $ removeFile $ transferLockFile tfile
|
|
closeFd fd
|
|
|
|
{- If a transfer is still running, returns its TransferInfo. -}
|
|
checkTransfer :: Transfer -> Annex (Maybe TransferInfo)
|
|
checkTransfer t = do
|
|
mode <- annexFileMode
|
|
tfile <- fromRepo $ transferFile t
|
|
mfd <- liftIO $ catchMaybeIO $
|
|
openFd (transferLockFile tfile) ReadOnly (Just mode) defaultFileFlags
|
|
case mfd of
|
|
Nothing -> return Nothing -- failed to open file; not running
|
|
Just fd -> do
|
|
locked <- liftIO $
|
|
getLock fd (WriteLock, AbsoluteSeek, 0, 0)
|
|
liftIO $ closeFd fd
|
|
case locked of
|
|
Nothing -> return Nothing
|
|
Just (pid, _) -> liftIO $
|
|
flip catchDefaultIO Nothing $ do
|
|
readTransferInfo pid
|
|
<$> readFile tfile
|
|
|
|
{- Gets all currently running transfers. -}
|
|
getTransfers :: Annex [(Transfer, TransferInfo)]
|
|
getTransfers = do
|
|
transfers <- catMaybes . map parseTransferFile <$> findfiles
|
|
infos <- mapM checkTransfer transfers
|
|
return $ map (\(t, Just i) -> (t, i)) $
|
|
filter running $ zip transfers infos
|
|
where
|
|
findfiles = liftIO . dirContentsRecursive
|
|
=<< fromRepo gitAnnexTransferDir
|
|
running (_, i) = isJust i
|
|
|
|
{- The transfer information file to use for a given Transfer. -}
|
|
transferFile :: Transfer -> Git.Repo -> FilePath
|
|
transferFile (Transfer direction u key) r = gitAnnexTransferDir r
|
|
</> show direction
|
|
</> fromUUID u
|
|
</> keyFile key
|
|
|
|
{- The transfer lock file corresponding to a given transfer info file. -}
|
|
transferLockFile :: FilePath -> FilePath
|
|
transferLockFile infofile = let (d,f) = splitFileName infofile in
|
|
combine d ("lck." ++ f)
|
|
|
|
{- Parses a transfer information filename to a Transfer. -}
|
|
parseTransferFile :: FilePath -> Maybe Transfer
|
|
parseTransferFile file
|
|
| "lck." `isPrefixOf` (takeFileName file) = Nothing
|
|
| otherwise = case drop (length bits - 3) bits of
|
|
[direction, u, key] -> Transfer
|
|
<$> readDirection direction
|
|
<*> pure (toUUID u)
|
|
<*> fileKey key
|
|
_ -> Nothing
|
|
where
|
|
bits = splitDirectories file
|
|
|
|
writeTransferInfo :: TransferInfo -> String
|
|
writeTransferInfo info = unlines
|
|
-- transferPid is not included; instead obtained by looking at
|
|
-- the process that locks the file.
|
|
[ maybe "" show $ startedTime info
|
|
-- bytesComplete is not included; changes too fast
|
|
, fromMaybe "" $ associatedFile info -- comes last; arbitrary content
|
|
]
|
|
|
|
readTransferInfo :: ProcessID -> String -> Maybe TransferInfo
|
|
readTransferInfo pid s =
|
|
case bits of
|
|
[time] -> TransferInfo
|
|
<$> (Just <$> parsePOSIXTime time)
|
|
<*> pure (Just pid)
|
|
<*> pure Nothing
|
|
<*> pure Nothing
|
|
<*> pure Nothing
|
|
<*> pure (if null filename then Nothing else Just filename)
|
|
_ -> Nothing
|
|
where
|
|
(bits, filebits) = splitAt 1 $ lines s
|
|
filename = join "\n" filebits
|
|
|
|
parsePOSIXTime :: String -> Maybe POSIXTime
|
|
parsePOSIXTime s = utcTimeToPOSIXSeconds
|
|
<$> parseTime defaultTimeLocale "%s%Qs" s
|