lifted out the kqueue and inotify to a generic DirWatcher interface

Kqueue code for dispatching events is not tested and probably doesn't
build.
This commit is contained in:
Joey Hess 2012-06-18 23:47:48 -04:00
parent 9b7f929e96
commit 7a09d74319
6 changed files with 115 additions and 54 deletions

View file

@ -13,7 +13,8 @@ import Common.Annex
import Assistant.ThreadedMonad
import Assistant.DaemonStatus
import Assistant.Committer
import Utility.ThreadScheduler
import Utility.DirWatcher
import Utility.Types.DirWatcher
import qualified Annex.Queue
import qualified Git.Command
import qualified Git.UpdateIndex
@ -29,25 +30,12 @@ import Control.Concurrent.STM
import Data.Bits.Utils
import qualified Data.ByteString.Lazy as L
#ifdef WITH_INOTIFY
import Utility.Inotify
import System.INotify
#endif
#ifdef WITH_KQUEUE
import qualified Utility.Kqueue as Kqueue
#endif
checkCanWatch :: Annex ()
checkCanWatch = do
#if (WITH_INOTIFY || WITH_KQUEUE)
unlessM (liftIO (inPath "lsof") <||> Annex.getState Annex.force) $
needLsof
#else
#if defined linux_HOST_OS
#warning "Building without inotify support; watch mode will be disabled."
#endif
error "watch mode is not available on this system"
#endif
checkCanWatch
| canWatch =
unlessM (liftIO (inPath "lsof") <||> Annex.getState Annex.force) $
needLsof
| otherwise = error "watch mode is not available on this system"
needLsof :: Annex ()
needLsof = error $ unlines
@ -58,13 +46,9 @@ needLsof = error $ unlines
]
watchThread :: ThreadState -> DaemonStatusHandle -> ChangeChan -> IO ()
#ifdef WITH_INOTIFY
watchThread st dstatus changechan = withINotify $ \i -> do
statupScan st dstatus $
watchDir i "." ignored hooks
-- Let the inotify thread run.
waitForTermination
watchThread st dstatus changechan = watchDir "." ignored hooks startup
where
startup = statupScan st dstatus
hook a = Just $ runHandler st dstatus changechan a
hooks = WatchHooks
{ addHook = hook onAdd
@ -73,21 +57,6 @@ watchThread st dstatus changechan = withINotify $ \i -> do
, delDirHook = hook onDelDir
, errHook = hook onErr
}
#else
#ifdef WITH_KQUEUE
watchThread st dstatus changechan = do
kq <- statupScan st dstatus $
Kqueue.initKqueue "." ignored
go kq
where
go kq = do
(kq', changes) <- Kqueue.waitChange kq
print $ "detected a change in " ++ show changes
go kq'
#else
watchThread = undefined
#endif /* WITH_KQUEUE */
#endif /* WITH_INOTIFY */
{- Initial scartup scan. The action should return once the scan is complete. -}
statupScan :: ThreadState -> DaemonStatusHandle -> IO a -> IO a

53
Utility/DirWatcher.hs Normal file
View file

@ -0,0 +1,53 @@
{- generic directory watching interface
-
- Uses either inotify or kqueue to watch a directory (and subdirectories)
- for changes, and runs hooks for different sorts of events as they occur.
-
- Copyright 2012 Joey Hess <joey@kitenet.net>
-
- Licensed under the GNU GPL version 3 or higher.
-}
{-# LANGUAGE CPP #-}
module Utility.DirWatcher where
import Utility.Types.DirWatcher
#if WITH_INOTIFY
import qualified Utility.INotify as INotify
import qualified System.INotify as INotify
import Utility.ThreadScheduler
#endif
#if WITH_KQUEUE
import qualified Utility.Kqueue as Kqueue
#endif
type Pruner = FilePath -> Bool
canWatch :: Bool
#if (WITH_INOTIFY || WITH_KQUEUE)
canWatch = True
#else
#if defined linux_HOST_OS
#warning "Building without inotify support"
#endif
canWatch = False
#endif
#if WITH_INOTIFY
watchDir :: FilePath -> Pruner -> WatchHooks -> (IO () -> IO ()) -> IO ()
watchDir dir prune hooks runstartup = INotify.withINotify $ \i -> do
runstartup $ INotify.watchDir i dir prune hooks
waitForTermination -- Let the inotify thread run.
#else
#if WITH_KQUEUE
watchDir :: FilePath -> Pruner -> WatchHooks -> (IO Kqueue.Kqueue -> IO Kqueue.Kqueue) -> IO ()
watchDir dir ignored hooks runstartup = do
kq <- runstartup $ Kqueue.initKqueue dir ignored
Kqueue.runHooks kq hooks
#else
watchDir :: FilePath -> Pruner -> WatchHooks -> (IO () -> IO ()) -> IO ()
watchDir = undefined
#endif
#endif

View file

@ -5,26 +5,17 @@
- Licensed under the GNU GPL version 3 or higher.
-}
module Utility.Inotify where
module Utility.INotify where
import Common hiding (isDirectory)
import Utility.ThreadLock
import Utility.Types.DirWatcher
import System.INotify
import qualified System.Posix.Files as Files
import System.IO.Error
import Control.Exception (throw)
type Hook a = Maybe (a -> Maybe FileStatus -> IO ())
data WatchHooks = WatchHooks
{ addHook :: Hook FilePath
, addSymlinkHook :: Hook FilePath
, delHook :: Hook FilePath
, delDirHook :: Hook FilePath
, errHook :: Hook String -- error message
}
{- Watches for changes to files in a directory, and all its subdirectories
- that are not ignored, using inotify. This function returns after
- its initial scan is complete, leaving a thread running. Callbacks are

View file

@ -15,9 +15,11 @@ module Utility.Kqueue (
changedFile,
isAdd,
isDelete,
runHooks,
) where
import Common
import Utility.Types.DirWatcher
import System.Posix.Types
import Foreign.C.Types
@ -187,3 +189,26 @@ handleChange (Kqueue h dirmap pruner) fd olddirinfo =
-- remove it from our map.
newmap <- removeSubDir dirmap (dirName olddirinfo)
return (Kqueue h newmap pruner, [])
{- Processes changes on the Kqueue, calling the hooks as appropriate.
- Never returns. -}
runHooks :: Kqueue -> WatchHooks -> IO ()
runHooks kq hooks = do
(kq', changes) <- Kqueue.waitChange kq
forM_ changes $ dispatch kq'
runHooks kq' hooks
where
-- Kqueue returns changes for both whole directories
-- being added and deleted, and individual files being
-- added and deleted.
dispatch q change status
| isAdd change = withstatus s (dispatchadd q)
| isDelete change = callhook delDirHook change
dispatchadd q change s
| Files.isSymbolicLink = callhook addSymlinkHook change
| Files.isDirectory = print $ "TODO: recursive directory add: " ++ show change
| Files.isRegularFile = callhook addHook change
| otherwise = noop
callhook h change = hooks h $ changedFile change
withstatus change a = maybe noop (a change) =<<
(catchMaybeIO (getSymbolicLinkStatus (changedFile change)

View file

@ -0,0 +1,22 @@
{- generic directory watching types
-
- Copyright 2012 Joey Hess <joey@kitenet.net>
-
- Licensed under the GNU GPL version 3 or higher.
-}
{-# LANGUAGE CPP #-}
module Utility.Types.DirWatcher where
import Common
type Hook a = Maybe (a -> Maybe FileStatus -> IO ())
data WatchHooks = WatchHooks
{ addHook :: Hook FilePath
, addSymlinkHook :: Hook FilePath
, delHook :: Hook FilePath
, delDirHook :: Hook FilePath
, errHook :: Hook String -- error message
}

7
debian/changelog vendored
View file

@ -1,8 +1,9 @@
git-annex (3.20120616) UNRELEASED; urgency=low
* watch: New subcommand, which uses inotify to watch for changes to
files and automatically annexes new files, etc, so you don't need
to manually run git commands when manipulating files.
* watch: New subcommand, a daemon which notices changes to
files and automatically annexes new files, etc, so you don't
need to manually run git commands when manipulating files.
Available on Linux, BSDs, and OSX!
* Enable diskfree on kfreebsd, using statvfs.
-- Joey Hess <joeyh@debian.org> Tue, 12 Jun 2012 11:35:59 -0400