remove empty directories when removing from export

The subtle part of this is what happens when the remote fails to remove
an empty directory. The removal from the export needs to fail in that
case, so the removal will be tried again later. However, removeExportLocation
has already been run and changed the export db, so if the next run
checks getExportLocation, it might decide nothing remains to be done,
leaving the empty directory.

Dealt with that by making removeEmptyDirectories, handle a failure
by calling addExportLocation, reverting the database changes so the next
run will be guaranteed to try deleting the empty directory again.

This commit was sponsored by Thomas Hochstein on Patreon.
This commit is contained in:
Joey Hess 2017-09-15 15:04:29 -04:00
parent e223cf568f
commit c633144d28
No known key found for this signature in database
GPG key ID: DB12DB0FF05F8F38
4 changed files with 58 additions and 29 deletions

View file

@ -25,6 +25,7 @@ import Annex.CatFile
import Logs.Location import Logs.Location
import Logs.Export import Logs.Export
import Database.Export import Database.Export
import Remote.Helper.Export
import Messages.Progress import Messages.Progress
import Utility.Tmp import Utility.Tmp
@ -252,24 +253,24 @@ startUnexport' r ea db f ek = do
performUnexport :: Remote -> ExportActions Annex -> ExportHandle -> [ExportKey] -> ExportLocation -> CommandPerform performUnexport :: Remote -> ExportActions Annex -> ExportHandle -> [ExportKey] -> ExportLocation -> CommandPerform
performUnexport r ea db eks loc = do performUnexport r ea db eks loc = do
ifM (allM (\ek -> removeExport ea (asKey ek) loc) eks) ifM (allM (\ek -> removeExport ea (asKey ek) loc) eks)
( next $ cleanupUnexport r db eks loc ( next $ cleanupUnexport r ea db eks loc
, stop , stop
) )
cleanupUnexport :: Remote -> ExportHandle -> [ExportKey] -> ExportLocation -> CommandCleanup cleanupUnexport :: Remote -> ExportActions Annex -> ExportHandle -> [ExportKey] -> ExportLocation -> CommandCleanup
cleanupUnexport r db eks loc = do cleanupUnexport r ea db eks loc = do
liftIO $ do liftIO $ do
forM_ eks $ \ek -> forM_ eks $ \ek ->
removeExportLocation db (asKey ek) loc removeExportLocation db (asKey ek) loc
-- Flush so that getExportLocation sees this and any
-- other removals of the key.
flushDbQueue db flushDbQueue db
remaininglocs <- liftIO $ remaininglocs <- liftIO $
concat <$> forM eks (\ek -> getExportLocation db (asKey ek)) concat <$> forM eks (\ek -> getExportLocation db (asKey ek))
when (null remaininglocs) $ when (null remaininglocs) $
forM_ eks $ \ek -> forM_ eks $ \ek ->
logChange (asKey ek) (uuid r) InfoMissing logChange (asKey ek) (uuid r) InfoMissing
return True
removeEmptyDirectories ea db loc (map asKey eks)
startRecoverIncomplete :: Remote -> ExportActions Annex -> ExportHandle -> Git.Sha -> TopFilePath -> CommandStart startRecoverIncomplete :: Remote -> ExportActions Annex -> ExportHandle -> Git.Sha -> TopFilePath -> CommandStart
startRecoverIncomplete r ea db sha oldf startRecoverIncomplete r ea db sha oldf
@ -306,7 +307,7 @@ startMoveFromTempName r ea db ek f = do
performRename :: Remote -> ExportActions Annex -> ExportHandle -> ExportKey -> ExportLocation -> ExportLocation -> CommandPerform performRename :: Remote -> ExportActions Annex -> ExportHandle -> ExportKey -> ExportLocation -> ExportLocation -> CommandPerform
performRename r ea db ek src dest = do performRename r ea db ek src dest = do
ifM (renameExport ea (asKey ek) src dest) ifM (renameExport ea (asKey ek) src dest)
( next $ cleanupRename db ek src dest ( next $ cleanupRename ea db ek src dest
-- In case the special remote does not support renaming, -- In case the special remote does not support renaming,
-- unexport the src instead. -- unexport the src instead.
, do , do
@ -314,11 +315,10 @@ performRename r ea db ek src dest = do
performUnexport r ea db [ek] src performUnexport r ea db [ek] src
) )
cleanupRename :: ExportHandle -> ExportKey -> ExportLocation -> ExportLocation -> CommandCleanup cleanupRename :: ExportActions Annex -> ExportHandle -> ExportKey -> ExportLocation -> ExportLocation -> CommandCleanup
cleanupRename db ek src dest = do cleanupRename ea db ek src dest = do
liftIO $ do liftIO $ do
removeExportLocation db (asKey ek) src removeExportLocation db (asKey ek) src
addExportLocation db (asKey ek) dest addExportLocation db (asKey ek) dest
-- Flush so that getExportLocation sees this.
flushDbQueue db flushDbQueue db
return True removeEmptyDirectories ea db src [asKey ek]

View file

@ -28,11 +28,10 @@ import qualified Database.Queue as H
import Database.Init import Database.Init
import Annex.Locations import Annex.Locations
import Annex.Common hiding (delete) import Annex.Common hiding (delete)
import Types.Remote (ExportLocation(..), ExportDirectory(..)) import Types.Remote (ExportLocation(..), ExportDirectory(..), exportedDirectories)
import Database.Persist.TH import Database.Persist.TH
import Database.Esqueleto hiding (Key) import Database.Esqueleto hiding (Key)
import qualified System.FilePath.Posix as Posix
newtype ExportHandle = ExportHandle H.DbQueue newtype ExportHandle = ExportHandle H.DbQueue
@ -114,12 +113,3 @@ isExportDirectoryEmpty (ExportHandle h) (ExportDirectory d) = H.queryDbQueue h $
return $ null l return $ null l
where where
ed = toSFilePath d ed = toSFilePath d
exportedDirectories :: ExportLocation -> [ExportDirectory]
exportedDirectories (ExportLocation f) =
map (ExportDirectory . Posix.joinPath . reverse) $
subs [] $ map Posix.dropTrailingPathSeparator $
Posix.splitPath $ Posix.takeDirectory f
where
subs _ [] = []
subs ps (d:ds) = (d:ps) : subs (d:ps) ds

View file

@ -120,12 +120,15 @@ adjustExportable r = case M.lookup "exporttree" (config r) of
, removeKey = \k -> do , removeKey = \k -> do
locs <- liftIO $ getExportLocation db k locs <- liftIO $ getExportLocation db k
ea <- exportActions r ea <- exportActions r
oks <- forM locs $ \loc -> do oks <- forM locs $ \loc ->
ok <- removeExport ea k loc ifM (removeExport ea k loc)
when ok $ ( do
liftIO $ removeExportLocation db k loc liftIO $ do
return ok removeExportLocation db k loc
liftIO $ flushDbQueue db flushDbQueue db
removeEmptyDirectories ea db loc [k]
, return False
)
return (and oks) return (and oks)
-- Can't lock content on exports, since they're -- Can't lock content on exports, since they're
-- not key/value stores, and someone else could -- not key/value stores, and someone else could
@ -143,3 +146,26 @@ adjustExportable r = case M.lookup "exporttree" (config r) of
is <- getInfo r is <- getInfo r
return (is++[("export", "yes")]) return (is++[("export", "yes")])
} }
-- | Remove empty directories from the export. Call after removing an
-- exported file, and after calling removeExportLocation and flushing the
-- database.
removeEmptyDirectories :: ExportActions Annex -> ExportHandle -> ExportLocation -> [Key] -> Annex Bool
removeEmptyDirectories ea db loc ks = case removeExportDirectory ea of
Nothing -> return True
Just removeexportdirectory -> do
ok <- allM (go removeexportdirectory)
(reverse (exportedDirectories loc))
unless ok $ liftIO $ do
-- Add back to export database, so this is
-- tried again next time.
forM_ ks $ \k ->
addExportLocation db k loc
flushDbQueue db
return ok
where
go removeexportdirectory d =
ifM (liftIO $ isExportDirectoryEmpty db d)
( removeexportdirectory d
, return True
)

View file

@ -22,11 +22,13 @@ module Types.Remote
, ExportDirectory(..) , ExportDirectory(..)
, isExportSupported , isExportSupported
, ExportActions(..) , ExportActions(..)
, exportedDirectories
) )
where where
import Data.Map as M import qualified Data.Map as M
import Data.Ord import Data.Ord
import qualified System.FilePath.Posix as Posix
import qualified Git import qualified Git
import Types.Key import Types.Key
@ -198,3 +200,14 @@ data ExportActions a = ExportActions
-- support renames. -- support renames.
, renameExport :: Key -> ExportLocation -> ExportLocation -> a Bool , renameExport :: Key -> ExportLocation -> ExportLocation -> a Bool
} }
-- | All directories down to the ExportLocation, with the deepest ones
-- last.
exportedDirectories :: ExportLocation -> [ExportDirectory]
exportedDirectories (ExportLocation f) =
map (ExportDirectory . Posix.joinPath . reverse) $
subs [] $ map Posix.dropTrailingPathSeparator $
Posix.splitPath $ Posix.takeDirectory f
where
subs _ [] = []
subs ps (d:ds) = (d:ps) : subs (d:ps) ds