plumb file status through to event handlers
The idea, not yet done, is to use this to detect when a file has an old change time, and avoid expensive restaging of the file. If git-annex watch keeps track of the last time it finished a full scan, then any symlink that is older than that time must have been scanned before, so need not be added. (Relying on moving, copying, etc of a file all updating its change time.) Anyway, this info is available for free since inotify already checks it, so it might as well make it available.
This commit is contained in:
parent
ab076b2e81
commit
12dbb9d1d0
2 changed files with 37 additions and 30 deletions
|
@ -68,7 +68,7 @@ import System.INotify
|
||||||
|
|
||||||
type ChangeChan = TChan Change
|
type ChangeChan = TChan Change
|
||||||
|
|
||||||
type Handler = FilePath -> Annex (Maybe Change)
|
type Handler = FilePath -> Maybe FileStatus -> Annex (Maybe Change)
|
||||||
|
|
||||||
data Change = Change
|
data Change = Change
|
||||||
{ changeTime :: UTCTime
|
{ changeTime :: UTCTime
|
||||||
|
@ -181,9 +181,9 @@ runChangeChan = atomically
|
||||||
-
|
-
|
||||||
- Exceptions are ignored, otherwise a whole watcher thread could be crashed.
|
- Exceptions are ignored, otherwise a whole watcher thread could be crashed.
|
||||||
-}
|
-}
|
||||||
runHandler :: MVar Annex.AnnexState -> ChangeChan -> Handler -> FilePath -> IO ()
|
runHandler :: MVar Annex.AnnexState -> ChangeChan -> Handler -> FilePath -> Maybe FileStatus -> IO ()
|
||||||
runHandler st changechan handler file = void $ do
|
runHandler st changechan handler file filestatus = void $ do
|
||||||
r <- tryIO (runStateMVar st $ handler file)
|
r <- tryIO (runStateMVar st $ handler file filestatus)
|
||||||
case r of
|
case r of
|
||||||
Left e -> print e
|
Left e -> print e
|
||||||
Right Nothing -> noop
|
Right Nothing -> noop
|
||||||
|
@ -214,7 +214,7 @@ noChange = return Nothing
|
||||||
- startup.
|
- startup.
|
||||||
-}
|
-}
|
||||||
onAdd :: Handler
|
onAdd :: Handler
|
||||||
onAdd file = do
|
onAdd file _filestatus = do
|
||||||
ifM (Annex.getState Annex.fast)
|
ifM (Annex.getState Annex.fast)
|
||||||
( go -- initial directory scan is complete
|
( go -- initial directory scan is complete
|
||||||
, do -- expensive check done only during startup scan
|
, do -- expensive check done only during startup scan
|
||||||
|
@ -243,7 +243,7 @@ onAdd file = do
|
||||||
- already exist.
|
- already exist.
|
||||||
-}
|
-}
|
||||||
onAddSymlink :: Handler
|
onAddSymlink :: Handler
|
||||||
onAddSymlink file = go =<< Backend.lookupFile file
|
onAddSymlink file filestatus = go =<< Backend.lookupFile file
|
||||||
where
|
where
|
||||||
go Nothing = addlink =<< liftIO (readSymbolicLink file)
|
go Nothing = addlink =<< liftIO (readSymbolicLink file)
|
||||||
go (Just (key, _)) = do
|
go (Just (key, _)) = do
|
||||||
|
@ -270,7 +270,7 @@ onAddSymlink file = go =<< Backend.lookupFile file
|
||||||
madeChange file "link"
|
madeChange file "link"
|
||||||
|
|
||||||
onDel :: Handler
|
onDel :: Handler
|
||||||
onDel file = do
|
onDel file _filestatus = do
|
||||||
Annex.Queue.addUpdateIndex =<<
|
Annex.Queue.addUpdateIndex =<<
|
||||||
inRepo (Git.UpdateIndex.unstageFile file)
|
inRepo (Git.UpdateIndex.unstageFile file)
|
||||||
madeChange file "rm"
|
madeChange file "rm"
|
||||||
|
@ -283,14 +283,14 @@ onDel file = do
|
||||||
- command to get the recursive list of files in the directory, so rm is
|
- command to get the recursive list of files in the directory, so rm is
|
||||||
- just as good. -}
|
- just as good. -}
|
||||||
onDelDir :: Handler
|
onDelDir :: Handler
|
||||||
onDelDir dir = do
|
onDelDir dir _filestatus = do
|
||||||
Annex.Queue.addCommand "rm"
|
Annex.Queue.addCommand "rm"
|
||||||
[Params "--quiet -r --cached --ignore-unmatch --"] [dir]
|
[Params "--quiet -r --cached --ignore-unmatch --"] [dir]
|
||||||
madeChange dir "rmdir"
|
madeChange dir "rmdir"
|
||||||
|
|
||||||
{- Called when there's an error with inotify. -}
|
{- Called when there's an error with inotify. -}
|
||||||
onErr :: Handler
|
onErr :: Handler
|
||||||
onErr msg = do
|
onErr msg _ = do
|
||||||
warning msg
|
warning msg
|
||||||
return Nothing
|
return Nothing
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,7 @@ import qualified System.Posix.Files as Files
|
||||||
import System.IO.Error
|
import System.IO.Error
|
||||||
import Control.Exception (throw)
|
import Control.Exception (throw)
|
||||||
|
|
||||||
type Hook a = Maybe (a -> IO ())
|
type Hook a = Maybe (a -> Maybe FileStatus -> IO ())
|
||||||
|
|
||||||
data WatchHooks = WatchHooks
|
data WatchHooks = WatchHooks
|
||||||
{ addHook :: Hook FilePath
|
{ addHook :: Hook FilePath
|
||||||
|
@ -84,15 +84,18 @@ watchDir i dir ignored hooks
|
||||||
| otherwise = []
|
| otherwise = []
|
||||||
|
|
||||||
scan f = unless (ignored f) $ do
|
scan f = unless (ignored f) $ do
|
||||||
let fullf = indir f
|
ms <- getstatus f
|
||||||
r <- catchMaybeIO $ getSymbolicLinkStatus fullf
|
case ms of
|
||||||
case r of
|
|
||||||
Nothing -> return ()
|
Nothing -> return ()
|
||||||
Just s
|
Just s
|
||||||
| Files.isDirectory s -> recurse fullf
|
| Files.isDirectory s ->
|
||||||
| Files.isSymbolicLink s -> addSymlinkHook <@> f
|
recurse $ indir f
|
||||||
| Files.isRegularFile s -> addHook <@> f
|
| Files.isSymbolicLink s ->
|
||||||
| otherwise -> return ()
|
runhook addSymlinkHook f ms
|
||||||
|
| Files.isRegularFile s ->
|
||||||
|
runhook addHook f ms
|
||||||
|
| otherwise ->
|
||||||
|
noop
|
||||||
|
|
||||||
-- Ignore creation events for regular files, which won't be
|
-- Ignore creation events for regular files, which won't be
|
||||||
-- done being written when initially created, but handle for
|
-- done being written when initially created, but handle for
|
||||||
|
@ -100,39 +103,43 @@ watchDir i dir ignored hooks
|
||||||
go (Created { isDirectory = isd, filePath = f })
|
go (Created { isDirectory = isd, filePath = f })
|
||||||
| isd = recurse $ indir f
|
| isd = recurse $ indir f
|
||||||
| hashook addSymlinkHook =
|
| hashook addSymlinkHook =
|
||||||
whenM (filetype Files.isSymbolicLink f) $
|
checkfiletype Files.isSymbolicLink addSymlinkHook f
|
||||||
addSymlinkHook <@> f
|
|
||||||
| otherwise = noop
|
| otherwise = noop
|
||||||
-- Closing a file is assumed to mean it's done being written.
|
-- Closing a file is assumed to mean it's done being written.
|
||||||
go (Closed { isDirectory = False, maybeFilePath = Just f }) =
|
go (Closed { isDirectory = False, maybeFilePath = Just f }) =
|
||||||
whenM (filetype Files.isRegularFile f) $
|
checkfiletype Files.isRegularFile addHook f
|
||||||
addHook <@> f
|
|
||||||
-- When a file or directory is moved in, scan it to add new
|
-- When a file or directory is moved in, scan it to add new
|
||||||
-- stuff.
|
-- stuff.
|
||||||
go (MovedIn { filePath = f }) = scan f
|
go (MovedIn { filePath = f }) = scan f
|
||||||
go (MovedOut { isDirectory = isd, filePath = f })
|
go (MovedOut { isDirectory = isd, filePath = f })
|
||||||
| isd = delDirHook <@> f
|
| isd = runhook delDirHook f Nothing
|
||||||
| otherwise = delHook <@> f
|
| otherwise = runhook delHook f Nothing
|
||||||
-- Verify that the deleted item really doesn't exist,
|
-- Verify that the deleted item really doesn't exist,
|
||||||
-- since there can be spurious deletion events for items
|
-- since there can be spurious deletion events for items
|
||||||
-- in a directory that has been moved out, but is still
|
-- in a directory that has been moved out, but is still
|
||||||
-- being watched.
|
-- being watched.
|
||||||
go (Deleted { isDirectory = isd, filePath = f })
|
go (Deleted { isDirectory = isd, filePath = f })
|
||||||
| isd = guarded $ delDirHook <@> f
|
| isd = guarded $ runhook delDirHook f Nothing
|
||||||
| otherwise = guarded $ delHook <@> f
|
| otherwise = guarded $ runhook delHook f Nothing
|
||||||
where
|
where
|
||||||
guarded = unlessM (filetype (const True) f)
|
guarded = unlessM (filetype (const True) f)
|
||||||
go _ = noop
|
go _ = noop
|
||||||
|
|
||||||
hashook h = isJust $ h hooks
|
hashook h = isJust $ h hooks
|
||||||
|
|
||||||
runhook h f
|
runhook h f s
|
||||||
| ignored f = noop
|
| ignored f = noop
|
||||||
| otherwise = maybe noop (\a -> a $ indir f) (h hooks)
|
| otherwise = maybe noop (\a -> a (indir f) s) (h hooks)
|
||||||
h <@> f = runhook h f
|
|
||||||
|
|
||||||
indir f = dir </> f
|
indir f = dir </> f
|
||||||
|
|
||||||
|
getstatus f = catchMaybeIO $ getSymbolicLinkStatus $ indir f
|
||||||
|
checkfiletype check h f = do
|
||||||
|
ms <- getstatus f
|
||||||
|
case ms of
|
||||||
|
Just s
|
||||||
|
| check s -> runhook h f ms
|
||||||
|
_ -> noop
|
||||||
filetype t f = catchBoolIO $ t <$> getSymbolicLinkStatus (indir f)
|
filetype t f = catchBoolIO $ t <$> getSymbolicLinkStatus (indir f)
|
||||||
|
|
||||||
-- Inotify fails when there are too many watches with a
|
-- Inotify fails when there are too many watches with a
|
||||||
|
@ -144,10 +151,10 @@ watchDir i dir ignored hooks
|
||||||
Just hook -> tooManyWatches hook dir
|
Just hook -> tooManyWatches hook dir
|
||||||
| otherwise = throw e
|
| otherwise = throw e
|
||||||
|
|
||||||
tooManyWatches :: (String -> IO ()) -> FilePath -> IO ()
|
tooManyWatches :: (String -> Maybe FileStatus -> IO ()) -> FilePath -> IO ()
|
||||||
tooManyWatches hook dir = do
|
tooManyWatches hook dir = do
|
||||||
sysctlval <- querySysctl [Param maxwatches] :: IO (Maybe Integer)
|
sysctlval <- querySysctl [Param maxwatches] :: IO (Maybe Integer)
|
||||||
hook $ unlines $ basewarning : maybe withoutsysctl withsysctl sysctlval
|
hook (unlines $ basewarning : maybe withoutsysctl withsysctl sysctlval) Nothing
|
||||||
where
|
where
|
||||||
maxwatches = "fs.inotify.max_user_watches"
|
maxwatches = "fs.inotify.max_user_watches"
|
||||||
basewarning = "Too many directories to watch! (Not watching " ++ dir ++")"
|
basewarning = "Too many directories to watch! (Not watching " ++ dir ++")"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue