diff options
author | Joey Hess <joeyh@oberon.tam-lin.net> | 2012-12-27 14:19:12 -0500 |
---|---|---|
committer | Joey Hess <joey@kitenet.net> | 2012-12-27 15:22:29 -0400 |
commit | 4c6096e2f92db212c2172e5567e5eaa53629ab9a (patch) | |
tree | 625ab2f9fce363477e7f0385c659ca5b6fe2f23f /Utility | |
parent | eeee547d7f9a4247d82954b823c1649e115d76d9 (diff) |
OSX FSEvents support
Needs work to deal with directory renames better; otherwise seems to
basically work.
Diffstat (limited to 'Utility')
-rw-r--r-- | Utility/DirWatcher.hs | 40 | ||||
-rw-r--r-- | Utility/FSEvents.hs | 65 |
2 files changed, 96 insertions, 9 deletions
diff --git a/Utility/DirWatcher.hs b/Utility/DirWatcher.hs index d564f8bcf..ba420401e 100644 --- a/Utility/DirWatcher.hs +++ b/Utility/DirWatcher.hs @@ -1,7 +1,8 @@ {- 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. + - Uses inotify, or kqueue, or fsevents 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> - @@ -22,11 +23,15 @@ import qualified System.INotify as INotify import qualified Utility.Kqueue as Kqueue import Control.Concurrent #endif +#if WITH_FSEVENTS +import qualified Utility.FSEvents as FSEvents +import qualified System.OSX.FSEvents as FSEvents +#endif type Pruner = FilePath -> Bool canWatch :: Bool -#if (WITH_INOTIFY || WITH_KQUEUE) +#if (WITH_INOTIFY || WITH_KQUEUE || WITH_FSEVENTS) canWatch = True #else #if defined linux_HOST_OS @@ -42,7 +47,7 @@ canWatch = False - OTOH, with kqueue, often only one event is received, indicating the most - recent state of the file. -} eventsCoalesce :: Bool -#if WITH_INOTIFY +#if (WITH_INOTIFY || WITH_FSEVENTS) eventsCoalesce = False #else #if WITH_KQUEUE @@ -55,12 +60,15 @@ eventsCoalesce = undefined {- With inotify, file closing is tracked to some extent, so an add event - will always be received for a file once its writer closes it, and - (typically) not before. This may mean multiple add events for the same file. + - + - fsevents behaves similarly, although different event types are used for + - creating and modification of the file. - - OTOH, with kqueue, add events will often be received while a file is - still being written to, and then no add event will be received once the - writer closes it. -} closingTracked :: Bool -#if WITH_INOTIFY +#if (WITH_INOTIFY || WITH_FSEVENTS) closingTracked = True #else #if WITH_KQUEUE @@ -71,9 +79,11 @@ closingTracked = undefined #endif {- With inotify, modifications to existing files can be tracked. - - Kqueue does not support this. -} + - Kqueue does not support this. + - Fsevents generates events when an existing file is reopened and rewritten, + - but not necessarily when it's opened once and modified repeatedly. -} modifyTracked :: Bool -#if WITH_INOTIFY +#if (WITH_INOTIFY || WITH_FSEVENTS) modifyTracked = True #else #if WITH_KQUEUE @@ -83,9 +93,9 @@ modifyTracked = undefined #endif #endif -{- Starts a watcher thread. The runStartup action is passed a scanner action +{- Starts a watcher thread. The runstartup action is passed a scanner action - to run, that will return once the initial directory scan is complete. - - Once runStartup returns, the watcher thread continues running, + - Once runstartup returns, the watcher thread continues running, - and processing events. Returns a DirWatcherHandle that can be used - to shutdown later. -} #if WITH_INOTIFY @@ -103,11 +113,18 @@ watchDir dir prune hooks runstartup = do kq <- runstartup $ Kqueue.initKqueue dir prune forkIO $ Kqueue.runHooks kq hooks #else +#if WITH_FSEVENTS +type DirWatcherHandle = FSEvents.EventStream +watchDir :: FilePath -> Pruner -> WatchHooks -> (IO FSEvents.EventStream -> IO FSEvents.EventStream) -> IO DirWatcherHandle +watchDir dir prune hooks runstartup = + runstartup $ FSEvents.watchDir dir prune hooks +#else type DirWatcherHandle = () watchDir :: FilePath -> Pruner -> WatchHooks -> (IO () -> IO ()) -> IO DirWatcherHandle watchDir = undefined #endif #endif +#endif #if WITH_INOTIFY stopWatchDir :: DirWatcherHandle -> IO () @@ -117,7 +134,12 @@ stopWatchDir = INotify.killINotify stopWatchDir :: DirWatcherHandle -> IO () stopWatchDir = killThread #else +#if WITH_FSEVENTS +stopWatchDir :: DirWatcherHandle -> IO () +stopWatchDir = FSEvents.eventStreamDestroy +#else stopWatchDir :: DirWatcherHandle -> IO () stopWatchDir = undefined #endif #endif +#endif diff --git a/Utility/FSEvents.hs b/Utility/FSEvents.hs new file mode 100644 index 000000000..2f0ada955 --- /dev/null +++ b/Utility/FSEvents.hs @@ -0,0 +1,65 @@ +{- FSEvents interface + - + - Copyright 2012 Joey Hess <joey@kitenet.net> + - + - Licensed under the GNU GPL version 3 or higher. + -} + +module Utility.FSEvents where + +import Common hiding (isDirectory) +import Utility.Types.DirWatcher + +import System.OSX.FSEvents +import qualified System.Posix.Files as Files +import Data.Bits ((.&.)) + +watchDir :: FilePath -> (FilePath -> Bool) -> WatchHooks -> IO EventStream +watchDir dir ignored hooks = do + unlessM fileLevelEventsSupported $ + error "Need at least OSX 10.7.0 for file-level FSEvents" + eventStreamCreate [dir] 1.0 True False True handle + where + handle evt + | not (hasflag eventFlagItemIsFile) = noop + | ignoredPath ignored (eventPath evt) = noop + | otherwise = do + {- More than one flag may be set, if events occurred + - close together. + - + - Order is important.. + - If a file is added and then deleted, we'll see it's + - not present, and addHook won't run. + - OTOH, if a file is deleted and then re-added, + - the delHook will run first, followed by the addHook. + -} + + {- Deletion events are received for both directories + - and files, with no way to differentiate between + - them. Deleting a directory always first yields + - events deleting its contents though, so we + - just always call delHook, and never delDirHook. -} + when (hasflag eventFlagItemRemoved) $ + runhook delHook Nothing + {- TODO deal with moving whole directories -} + when (hasflag eventFlagItemCreated || hasflag eventFlagItemRenamed) $ do + ms <- getstatus $ eventPath evt + case ms of + Nothing -> noop + Just s + | Files.isSymbolicLink s -> + runhook addSymlinkHook ms + | Files.isRegularFile s -> + runhook addHook ms + | otherwise -> noop + when (hasflag eventFlagItemModified) $ do + ms <- getstatus $ eventPath evt + runhook modifyHook ms + where + getstatus = catchMaybeIO . getSymbolicLinkStatus + hasflag f = eventFlags evt .&. f /= 0 + runhook h s = maybe noop (\a -> a (eventPath evt) s) (h hooks) + +{- Check each component of the path to see if it's ignored. -} +ignoredPath :: (FilePath -> Bool) -> FilePath -> Bool +ignoredPath ignored = any ignored . map dropTrailingPathSeparator . splitPath |