on 03218ad help sign in
1030 lines 34 kB history raw
1-- |
2-- Module : Streamly.Internal.FileSystem.Event.Darwin
3-- Copyright : (c) 2020 Composewell Technologies
4-- License : BSD-3-Clause
5-- Maintainer : streamly@composewell.com
6-- Stability : experimental
7-- Portability : GHC
8--
9-- =Overview
10--
11-- Use 'watchTrees' with a list of file system paths you want to watch as
12-- argument. It returns a stream of 'Event' representing the file system events
13-- occurring under the watched paths.
14--
15-- @
16-- {-\# LANGUAGE MagicHash #-}
17-- Stream.mapM_ (putStrLn . 'showEvent') $ 'watchTrees' [Array.fromCString\# "path"#]
18-- @
19--
20-- 'Event' is an opaque type. Accessor functions (e.g. 'showEvent' above)
21-- provided in this module are used to determine the attributes of the event.
22--
23-- =Design notes
24--
25-- For reference documentation see:
26--
27-- * "<https://developer.apple.com/library/archive/documentation/Darwin/Conceptual/FSEvents_ProgGuide/UsingtheFSEventsFramework/UsingtheFSEventsFramework.html Apple FS Events Programming Guide>"
28-- * "<https://developer.apple.com/documentation/coreservices/file_system_events?language=objc Apple FS Events Reference>"
29--
30-- We try to keep the macOS\/Linux/Windows event handling APIs and defaults
31-- semantically and syntactically as close as possible.
32--
33-- =BUGS
34--
35-- There may be some idiosyncrasies in event reporting.
36--
37-- 1. Multiple events may be coalesced into a single event having multiple
38-- flags set. For example, on macOS 10.15.6, "touch x; rm x" or "touch x; mv x
39-- y" produces an event with both "Created" and "Deleted/Moved" flags set.
40--
41-- 2. The same event can occur multiple times. For example, "touch x; sleep 1;
42-- rm x" generates a "Created" event followed by an event with both "Created"
43-- and "Deleted" flags set. Similarly, a cloned event can also occur multiple
44-- times.
45--
46-- 3. Some events can go missing and only the latest one is reported. See
47-- 'isCreated' for one such case.
48--
49-- 4. When @rm -rf@ is used on the watched root directory, only one 'isDeleted'
50-- event occurs and that is for the root. However, @rm -rf@ on a directory
51-- inside the watch root produces 'isDeleted' events for all files.
52--
53-- 5. When a file is moved, a move event occurs for both the source and the
54-- destination files. There seems to be no way to distinguish the source and
55-- the destination. Perhaps the lower eventId can be considered as source.
56--
57-- 6. When a file is cloned both source and destination generate a cloned
58-- event, however the source has lower eventId and destination may have
59-- OwnerGroupModeChanged, InodeAttrsChanged, Created flags as well.
60--
61-- You may have to stat the path of the event to know more information about
62-- what might have happened. It may be a good idea to watch the behavior of
63-- events you are handling before you can rely on it. See the individual event
64-- APIs for some of the known issues marked with `BUGS`. Also see the "Handling
65-- Events" section in the "Apple FS Events Programming Guide".
66--
67-- * "<https://stackoverflow.com/questions/18415285/osx-fseventstreameventflags-not-working-correctly Stack overflow question on event coalescing>"
68--
69-- =TODO
70--
71-- APIs for persistent event-id related functionality, to get events from
72-- specific event-id etc are not implemented.
74#include <config.h>
76-- macOS 10.7+
77#if HAVE_DECL_KFSEVENTSTREAMCREATEFLAGFILEEVENTS
79module Streamly.Internal.FileSystem.Event.Darwin
80 (
81 -- * Creating a Watch
83 -- ** Default configuration
84 Config
85 , Toggle (..)
86 , defaultConfig
88 -- ** Watch Behavior
89 , BatchInfo (..)
90 , setEventBatching
92 -- ** Events of Interest
93 , setRootChanged
94 , setFileEvents
95 , setIgnoreSelf
96#if HAVE_DECL_KFSEVENTSTREAMCREATEFLAGFULLHISTORY
97 , setFullHistory
98#endif
100 -- ** Watch APIs
101 , watchTrees
102 , watchTreesWith
104 -- * Handling Events
105 , Event
106 , getEventId
107 , getAbsPath
109 -- ** Exception Conditions
110 , isEventIdWrapped
111 , isMustScanSubdirs
112 , isKernelDropped
113 , isUserDropped
115 -- ** Root Level Events
116 -- | Events that belong to the root path as a whole and not to specific
117 -- itmes contained in it.
118 , isMount
119 , isUnmount
120 , isHistoryDone
121 , isRootChanged
123 -- ** Item Level Metadata change
124 , isOwnerGroupModeChanged
125 , isInodeAttrsChanged
126 , isFinderInfoChanged
127 , isXAttrsChanged
129 -- ** Item Level CRUD events
130 , isCreated
131 , isDeleted
132 , isMoved
133 , isModified
134#if HAVE_DECL_KFSEVENTSTREAMEVENTFLAGITEMCLONED
135 , isCloned
136#endif
138 -- ** Item Path info
139 , isDir
140 , isFile
141 , isSymLink
142#if HAVE_DECL_KFSEVENTSTREAMEVENTFLAGITEMISHARDLINK
143 , isHardLink
144 , isLastHardLink
145#endif
147 -- * Debugging
148 , showEvent
149 )
150where
152import Control.Concurrent (MVar, newMVar, takeMVar, putMVar)
153import Control.Monad (when)
154import Control.Monad.IO.Class (liftIO)
155import Data.Bits ((.|.), (.&.), complement)
156import Data.Functor.Identity (runIdentity)
157import Data.List.NonEmpty (NonEmpty)
158import Data.Word (Word8, Word32, Word64)
159import Foreign.C.Types (CInt(..), CDouble(..), CSize(..), CUChar(..))
160import Foreign.Marshal.Alloc (alloca)
161import Foreign.Marshal.Array (withArray)
162import Foreign.Ptr (Ptr, castPtr)
163import Foreign.Storable (Storable(..))
164import GHC.IO.Handle.FD (fdToHandle)
165import Streamly.Prelude (SerialT)
166import Streamly.Internal.Data.Cont (contListMap)
167import Streamly.Internal.Data.Parser (Parser)
168import Streamly.Internal.Data.Array.Foreign.Type (Array(..))
169import System.IO (Handle, hClose)
171import qualified Data.List.NonEmpty as NonEmpty
172import qualified Streamly.Internal.Data.Parser as PR
173import qualified Streamly.Internal.Data.Stream.IsStream as S
174import qualified Streamly.Internal.Unicode.Stream as U
175import qualified Streamly.Internal.FileSystem.Handle as FH
176import qualified Streamly.Internal.Data.Array.Foreign as A
178-------------------------------------------------------------------------------
179-- Subscription to events
180-------------------------------------------------------------------------------
182-- | Watch configuration, used to specify the events of interest and the
183-- behavior of the watch.
184--
185-- /Pre-release/
186--
187data Config = Config
188 { latency :: Double
189 , createFlags :: Word32
190 }
192-------------------------------------------------------------------------------
193-- Batching Events
194-------------------------------------------------------------------------------
196foreign import ccall safe
197 "FileSystem/Event/Darwin.h FSEventStreamCreateFlagNoDefer"
198 kFSEventStreamCreateFlagNoDefer :: Word32
200data BatchInfo =
201 Throttle Double -- ^ Deliver an event immediately but suppress the
202 -- following events upto the specified time.
203 | Batch Double -- ^ Collapse all events that occurred in the specified
204 -- time window (in seconds) and deliver them as a single
205 -- batch.
207-- | Set how the events should be batched. See 'BatchInfo' for details. A
208-- negative value for time is treated as 0.
209--
210-- /default: Batch 0.0/
211--
212-- /macOS 10.5+/
213--
214-- /Pre-release/
215--
216setEventBatching :: BatchInfo -> Config -> Config
217setEventBatching batchInfo cfg@Config{..} =
218 let (t, status) =
219 case batchInfo of
220 Throttle sec | sec < 0 -> (0, On)
221 Throttle sec -> (sec, On)
222 Batch sec | sec < 0 -> (0, Off)
223 Batch sec -> (sec, Off)
224 in setFlag kFSEventStreamCreateFlagNoDefer status $ cfg {latency = t}
226-------------------------------------------------------------------------------
227-- Boolean settings
228-------------------------------------------------------------------------------
230-- | Whether a setting is 'On' or 'Off'.
231--
232-- /Pre-release/
233--
234data Toggle = On | Off
236setFlag :: Word32 -> Toggle -> Config -> Config
237setFlag mask status cfg@Config{..} =
238 let flags =
239 case status of
240 On -> createFlags .|. mask
241 Off -> createFlags .&. complement mask
242 in cfg {createFlags = flags}
244foreign import ccall safe
245 "FSEventStreamCreateFlagWatchRoot"
246 kFSEventStreamCreateFlagWatchRoot :: Word32
248-- | Watch the changes to the path of the top level file system objects being
249-- watched. If the root directory is deleted, moved or renamed you will receive
250-- an 'isRootChanged' event with an eventId 0.
251--
252-- /default: Off/
253--
254-- /macOS 10.5+/
255--
256-- /Pre-release/
257--
258setRootChanged :: Toggle -> Config -> Config
259setRootChanged = setFlag kFSEventStreamCreateFlagWatchRoot
261foreign import ccall safe
262 "FSEventStreamCreateFlagFileEvents"
263 kFSEventStreamCreateFlagFileEvents :: Word32
265-- | When this is 'Off' only events for the watched directories are reported.
266-- For example, when a file is created inside a directory it is reported as an
267-- event, the path name in the event is the path of the directory being
268-- watched. Events cannot be distinguished based on the type of event.
269--
270-- When it is 'On' the path reported in the event is the path of the item and
271-- not the of the directory in which it is contained. We can use the event
272-- accessor functions to determine the event type and the path type etc.
273--
274-- /default: On/
275--
276-- /macOS 10.7+/
277--
278-- /Pre-release/
279--
280setFileEvents :: Toggle -> Config -> Config
281setFileEvents = setFlag kFSEventStreamCreateFlagFileEvents
283foreign import ccall safe
284 "FSEventStreamCreateFlagIgnoreSelf"
285 kFSEventStreamCreateFlagIgnoreSelf :: Word32
287-- | When this is 'On' events generated by the current process are not
288-- reported.
289--
290-- /default: Off/
291--
292-- /macOS 10.6+/
293--
294-- /Pre-release/
295--
296setIgnoreSelf :: Toggle -> Config -> Config
297setIgnoreSelf = setFlag kFSEventStreamCreateFlagIgnoreSelf
299#if HAVE_DECL_KFSEVENTSTREAMCREATEFLAGFULLHISTORY
300foreign import ccall safe
301 "FSEventStreamCreateFlagFullHistory"
302 kFSEventStreamCreateFlagFullHistory :: Word32
304-- | When this is 'On' all events since the beginning of time are reported.
305--
306-- /default: Off/
307--
308-- /macOS 10.15+/
309--
310-- /Pre-release/
311--
312setFullHistory :: Toggle -> Config -> Config
313setFullHistory = setFlag kFSEventStreamCreateFlagFullHistory
314#endif
316-------------------------------------------------------------------------------
317-- Default config
318-------------------------------------------------------------------------------
320-- | The default settings are:
321--
322-- * 'setEventBatching' ('Batch' 0.0)
323-- * 'setFileEvents' 'On'
324-- * 'setRootChanged' 'Off'
325-- * 'setIgnoreSelf' 'Off'
326#if HAVE_DECL_KFSEVENTSTREAMCREATEFLAGFULLHISTORY
327-- * 'setFullHistory' 'Off'
328#endif
329--
330-- /Pre-release/
331--
332defaultConfig :: Config
333defaultConfig = setFileEvents On $ Config
334 { latency = 0.0
335 , createFlags = 0
336 }
338-------------------------------------------------------------------------------
339-- Open an event stream (C FFI)
340-------------------------------------------------------------------------------
342-- See FileSystem/Event/Darwin.h
344data CWatch
346-- Representation of "struct pathName" in "FileSystem/Event/Darwin.h"
347data PathName = PathName
348 { pathBytes :: Ptr CUChar
349 , pathLen :: CSize
350 }
352instance Storable PathName where
353 alignment _ = sizeOf (undefined :: Ptr a)
354 sizeOf _ = sizeOf (undefined :: Ptr CUChar) + sizeOf (undefined :: CSize)
355 peek ptr = do
356 bptr <- peekByteOff ptr 0
357 len <- peekByteOff ptr (sizeOf (undefined :: Ptr CUChar))
358 return $ PathName bptr len
359 poke ptr PathName{..} = do
360 pokeByteOff ptr 0 pathBytes
361 pokeByteOff ptr (sizeOf (undefined :: Ptr CUChar)) pathLen
363foreign import ccall safe "FileSystem/Event/Darwin.h createWatch" createWatch
364 :: Ptr PathName
365 -> CInt
366 -> Word32
367 -> Word64
368 -> CDouble
369 -> Ptr (CInt)
370 -> Ptr (Ptr CWatch)
371 -> IO CInt
373foreign import ccall safe "FileSystem/Event/Darwin.h destroyWatch" destroyWatch
374 :: Ptr CWatch -> IO ()
376-------------------------------------------------------------------------------
377-- Open an event stream
378-------------------------------------------------------------------------------
380-- | A handle for a watch.
381data Watch = Watch Handle (Ptr CWatch) (MVar Bool)
383-- | Given a 'Config' and @paths@ start monitoring the paths for file system
384-- events. Returns a 'Watch' handle which can then be used to read the event
385-- stream or to close the watch.
386--
387-- Implementation Note: This API creates an OS level thread where an event loop
388-- runs and writes the events to the write side fd of a POSIX pipe. Event
389-- stream APIs read the events from the read end of this pipe. Since an OS
390-- level thread is forked, creating a new watch stream is expensive. It is
391-- advised to minimize the number of watches.
392--
393-- /Pre-release/
394--
395openWatch :: Config -> NonEmpty (Array Word8) -> IO Watch
396openWatch Config{..} paths = do
397 -- XXX check whether the path exists. If the path does not exist and
398 -- is created later it cannot be properly monitored. Also a user may
399 -- inadvertently provide a path for which the user may not have permission
400 -- and then later wonder why events are not being reported.
401 withPathNames (NonEmpty.toList paths) $ \arraysPtr ->
402 alloca $ \fdPtr -> do
403 alloca $ \watchPPtr -> do
404 let nArrays = fromIntegral (NonEmpty.length paths)
405 seconds = (realToFrac latency)
406 r <- createWatch
407 arraysPtr nArrays createFlags 0 seconds fdPtr watchPPtr
408 when (r /= 0) $
409 ioError (userError "openWatch: failed to create watch.")
410 fd <- peek fdPtr
411 h <- fdToHandle fd
412 watchPtr <- peek watchPPtr
413 closeLock <- newMVar False
414 return $ Watch h watchPtr closeLock
416 where
418 withPathName :: Array Word8 -> (PathName -> IO a) -> IO a
419 withPathName arr act = do
420 A.asPtr arr $ \ptr ->
421 let pname = PathName (castPtr ptr) (fromIntegral (A.length arr))
422 in act pname
424 withPathNames = contListMap withPathName withArray
426-- | Close a 'Watch' handle.
427--
428-- /Pre-release/
429--
430closeWatch :: Watch -> IO ()
431closeWatch (Watch h watchPtr mvar) = do
432 hClose h
433 closed <- takeMVar mvar
434 when (not closed) $ destroyWatch watchPtr
435 putMVar mvar True
437-------------------------------------------------------------------------------
438-- Raw events read from the watch file handle
439-------------------------------------------------------------------------------
441-- | An Event generated by the file system. Use the accessor functions to
442-- examine the event.
443--
444-- /Pre-release/
445--
446data Event = Event
447 { eventId :: Word64
448 , eventFlags :: Word32
449 , eventAbsPath :: Array Word8
450 } deriving (Show, Ord, Eq)
452-- XXX We can perhaps use parseD monad instance for fusing with parseMany? Need
453-- to measure the perf.
454--
455-- XXX should we use a magic in the header to avoid any issues due to
456-- misalignment of records? Can that ever happen?
457--
458readOneEvent :: Parser IO Word8 Event
459readOneEvent = do
460 arr <- PR.takeEQ 24 (A.writeN 24)
461 let arr1 = A.unsafeCast arr :: Array Word64
462 eid = A.unsafeIndex arr1 0
463 eflags = A.unsafeIndex arr1 1
464 pathLen = fromIntegral $ A.unsafeIndex arr1 2
465 path <- PR.takeEQ pathLen (A.writeN pathLen)
466 return $ Event
467 { eventId = eid
468 , eventFlags = fromIntegral eflags
469 , eventAbsPath = path
470 }
472watchToStream :: Watch -> SerialT IO Event
473watchToStream (Watch handle _ _) =
474 S.parseMany readOneEvent $ S.unfold FH.read handle
476-- | Start monitoring a list of file system paths for file system events with
477-- the supplied configuration operation over the 'defaultConfig'. The
478-- paths could be files or directories. When the path is a directory, the
479-- whole directory tree under it is watched recursively. Monitoring starts from
480-- the current time onwards. The paths are specified as UTF-8 encoded 'Array'
481-- of 'Word8'.
482--
483-- If the path name to be watched is a symbolic link then the target of the
484-- link is watched instead of the symbolic link itself. If the symbolic link is
485-- removed later the target is still watched as usual.
486--
487-- When 'setFileEvents' is 'Off' then events occurring inside the watch root
488-- cannot be distinguished from each other. For example, we cannot determine
489-- if an event is a "create" or "delete", we only know that some event ocurred
490-- under the watched directory hierarchy. Also, no path information for the
491-- target item of the event is generated.
492--
493-- If the watched path is deleted and created again the events start coming
494-- again for that path. A file type path being watched could be deleted and
495-- recreated as a directory. If the watched path is moved to another path (or
496-- is deleted and recreated as a symbolic link to another path) then no events
497-- are reported for any changes under the new path.
498--
499-- BUGS: If a watch is started on a non-existing path then the path is not
500-- watched even if it is created later. The macOS API does not fail for a
501-- non-existing path. If a non-existing path is watched with 'setRootChanged'
502-- then an 'isRootChanged' event is reported if the path is created later and
503-- the "path" field in the event is set to the dirname of the path rather than
504-- the full absolute path. This is the observed behavior on macOS 10.15.1.
505--
506-- @
507-- {-\# LANGUAGE MagicHash #-}
508-- watchTreesWith ('setIgnoreSelf' 'On' . 'setRootChanged' 'On') [Array.fromCString\# "path"#]
509-- @
510--
511-- /Pre-release/
512--
513watchTreesWith ::
514 (Config -> Config) -> NonEmpty (Array Word8) -> SerialT IO Event
515watchTreesWith f paths = S.bracket before after watchToStream
517 where
519 before = liftIO $ openWatch (f defaultConfig) paths
520 after = (liftIO . closeWatch)
522-- | Like 'watchTreesWith' but uses the 'defaultConfig' options.
523--
524-- @
525-- watchTrees = watchTreesWith id
526-- @
527--
528watchTrees :: NonEmpty (Array Word8) -> SerialT IO Event
529watchTrees = watchTreesWith id
531-------------------------------------------------------------------------------
532-- Examine the event stream
533-------------------------------------------------------------------------------
535-- | Get the event id of an 'Event'. Event-id is a monotonically increasing
536-- 64-bit integer identifying an event uniquely.
537--
538-- /Pre-release/
539--
540getEventId :: Event -> Word64
541getEventId Event{..} = eventId
543foreign import ccall safe
544 "FSEventStreamEventFlagEventIdsWrapped"
545 kFSEventStreamEventFlagEventIdsWrapped :: Word32
547-- | Determine whether the event id has wrapped. This is impossible on any
548-- traditional hardware in any reasonable amount of time unless the event-id
549-- starts from a very high value, because the event-id is a 64-bit integer.
550-- However, apple still recommends to check and handle this flag.
551--
552-- /macOS 10.5+/
553--
554-- /Pre-release/
555--
556isEventIdWrapped :: Event -> Bool
557isEventIdWrapped = getFlag kFSEventStreamEventFlagEventIdsWrapped
559-- | Get the absolute path of the file system object for which the event is
560-- generated. The path is a UTF-8 encoded array of bytes.
561--
562-- /Pre-release/
563--
564getAbsPath :: Event -> Array Word8
565getAbsPath Event{..} = eventAbsPath
567-------------------------------------------------------------------------------
568-- Event types
569-------------------------------------------------------------------------------
571getFlag :: Word32 -> Event -> Bool
572getFlag mask Event{..} = eventFlags .&. mask /= 0
574-------------------------------------------------------------------------------
575-- Error events
576-------------------------------------------------------------------------------
578foreign import ccall safe
579 "FSEventStreamEventFlagMustScanSubDirs"
580 kFSEventStreamEventFlagMustScanSubDirs :: Word32
582-- | Apple documentation says that "If an event in a directory occurs at about
583-- the same time as one or more events in a subdirectory of that directory, the
584-- events may be coalesced into a single event." In that case you will recieve
585-- 'isMustScanSubdirs' event. In that case the path listed in the event is
586-- invalidated and you must rescan it to know the current state.
587--
588-- This event can also occur if a communication error (overflow) occurs in
589-- sending the event and the event gets dropped. In that case 'isKernelDropped'
590-- and/or 'isUserDropped' attributes will also be set.
591--
592-- /macOS 10.5+/
593--
594-- /Pre-release/
595--
596isMustScanSubdirs :: Event -> Bool
597isMustScanSubdirs = getFlag kFSEventStreamEventFlagMustScanSubDirs
599foreign import ccall safe
600 "FSEventStreamEventFlagKernelDropped"
601 kFSEventStreamEventFlagKernelDropped :: Word32
603-- | Did an event get dropped due to a kernel processing issue? Set only when
604-- 'isMustScanSubdirs' is also true.
605--
606-- /macOS 10.5+/
607--
608-- /Pre-release/
609--
610isKernelDropped :: Event -> Bool
611isKernelDropped = getFlag kFSEventStreamEventFlagKernelDropped
613foreign import ccall safe
614 "FSEventStreamEventFlagUserDropped"
615 kFSEventStreamEventFlagUserDropped :: Word32
617-- | Did an event get dropped due to a user process issue? Set only when
618-- 'isMustScanSubdirs' is also true.
619--
620-- /macOS 10.5+/
621--
622-- /Pre-release/
623--
624isUserDropped :: Event -> Bool
625isUserDropped = getFlag kFSEventStreamEventFlagUserDropped
627-------------------------------------------------------------------------------
628-- Global Sentinel Events
629-------------------------------------------------------------------------------
631foreign import ccall safe
632 "FSEventStreamEventFlagHistoryDone"
633 kFSEventStreamEventFlagHistoryDone :: Word32
635-- | Determine whether the event is a history done marker event. A history done
636-- event is generated when the historical events from before the current time
637-- are done. Historical events are generated when the "since" parameter in the
638-- watch config is set to before the current time.
639--
640-- /macOS 10.5+/
641--
642-- /Pre-release/
643--
644isHistoryDone :: Event -> Bool
645isHistoryDone = getFlag kFSEventStreamEventFlagHistoryDone
647-------------------------------------------------------------------------------
648-- Events affecting the watched path only
649-------------------------------------------------------------------------------
651foreign import ccall safe
652 "FSEventStreamEventFlagRootChanged"
653 kFSEventStreamEventFlagRootChanged :: Word32
655-- | Determine whether the event indicates a change of path of the monitored
656-- object itself. Note that the object may become unreachable or deleted after
657-- a change of path.
658--
659-- /Applicable only when 'setRootChanged' is 'On'/
660--
661-- /Occurs only for the watched path/
662--
663-- /macOS 10.5+/
664--
665-- /Pre-release/
666--
667isRootChanged :: Event -> Bool
668isRootChanged = getFlag kFSEventStreamEventFlagRootChanged
670-------------------------------------------------------------------------------
671-- Global events under the watched path
672-------------------------------------------------------------------------------
674foreign import ccall safe
675 "FSEventStreamEventFlagMount"
676 kFSEventStreamEventFlagMount :: Word32
678-- | Determine whether the event is a mount event. A mount event is generated
679-- if a volume is mounted under the path being watched.
680--
681-- /macOS 10.5+/
682--
683-- /Pre-release/
684--
685isMount :: Event -> Bool
686isMount = getFlag kFSEventStreamEventFlagMount
688foreign import ccall safe
689 "FSEventStreamEventFlagUnmount"
690 kFSEventStreamEventFlagUnmount :: Word32
692-- | Determine whether the event is an unmount event. An unmount event is
693-- generated if a volume mounted under the path being watched is unmounted.
694--
695-- /macOS 10.5+/
696--
697-- /Pre-release/
698--
699isUnmount :: Event -> Bool
700isUnmount = getFlag kFSEventStreamEventFlagUnmount
702-------------------------------------------------------------------------------
703-- Metadata change Events (applicable only when 'setFileEvents' is 'On')
704-------------------------------------------------------------------------------
706foreign import ccall safe
707 "FSEventStreamEventFlagItemChangeOwner"
708 kFSEventStreamEventFlagItemChangeOwner :: Word32
710-- | Determine whether the event is ownership, group, permissions or ACL change
711-- event of an object contained within the monitored path.
712--
713-- Note that this event may be generated even if the metadata is changed to the
714-- same value again.
715--
716-- /Applicable only when 'setFileEvents' is 'On'/
717--
718-- /Can occur for watched path or a file inside it/
719--
720-- /macOS 10.7+/
721--
722-- /Pre-release/
723--
724isOwnerGroupModeChanged :: Event -> Bool
725isOwnerGroupModeChanged = getFlag kFSEventStreamEventFlagItemChangeOwner
727foreign import ccall safe
728 "FSEventStreamEventFlagItemInodeMetaMod"
729 kFSEventStreamEventFlagItemInodeMetaMod :: Word32
731-- | Determine whether the event indicates inode metadata change for an object
732-- contained within the monitored path. This event is generated when inode
733-- attributes other than the owner, group or permissions are changed e.g. file
734-- modification time. It does not occur when the link count of a file changes
735-- (as of macOS 10.15.1).
736--
737-- /Applicable only when 'setFileEvents' is 'On'/
738--
739-- /Can occur for watched path or a file inside it/
740--
741-- /macOS 10.7+/
742--
743-- /Pre-release/
744--
745isInodeAttrsChanged :: Event -> Bool
746isInodeAttrsChanged = getFlag kFSEventStreamEventFlagItemInodeMetaMod
748foreign import ccall safe
749 "FSEventStreamEventFlagItemFinderInfoMod"
750 kFSEventStreamEventFlagItemFinderInfoMod :: Word32
752-- | Determine whether the event indicates finder information metadata change
753-- for an object contained within the monitored path.
754--
755-- /Applicable only when 'setFileEvents' is 'On'/
756--
757-- /macOS 10.7+/
758--
759-- /Pre-release/
760--
761isFinderInfoChanged :: Event -> Bool
762isFinderInfoChanged = getFlag kFSEventStreamEventFlagItemFinderInfoMod
764foreign import ccall safe
765 "FSEventStreamEventFlagItemXattrMod"
766 kFSEventStreamEventFlagItemXattrMod :: Word32
768-- | Determine whether the event indicates extended attributes metadata change
769-- for an object contained within the monitored path.
770--
771-- /Applicable only when 'setFileEvents' is 'On'/
772--
773-- /Can occur for watched path or a file inside it/
774--
775-- /macOS 10.7+/
776--
777-- /Pre-release/
778--
779isXAttrsChanged :: Event -> Bool
780isXAttrsChanged = getFlag kFSEventStreamEventFlagItemXattrMod
782-------------------------------------------------------------------------------
783-- CRUD Events (applicable only when 'setFileEvents' is 'On')
784-------------------------------------------------------------------------------
786foreign import ccall safe
787 "FSEventStreamEventFlagItemCreated"
788 kFSEventStreamEventFlagItemCreated :: Word32
790-- | Determine whether the event indicates creation of an object within the
791-- monitored path. This event is generated when any file system object other
792-- than a hard link is created. On hard linking only an 'isInodeAttrsChanged'
793-- event on the directory is generated, it is not a create event. However, when
794-- a hard link is deleted 'isDeleted' and 'isHardLink' both are true.
795--
796-- This event can occur for the watched path if the path was deleted/moved and
797-- created again (tested on macOS 10.15.1). However, if a watch is started on a
798-- non-existing path and the path is created later, then this event is not
799-- generated, the path is not watched.
800--
801-- BUGS: On 10.15.1 when we use a "touch x" to create a file for the first time
802-- only an 'isInodeAttrsChanged' event occurs and there is no 'isCreated'
803-- event. However, this seems to have been fixed on 10.15.6.
804--
805-- /Applicable only when 'setFileEvents' is 'On'/
806--
807-- /Can occur for watched path or a file inside it/
808--
809-- /macOS 10.7+/
810--
811-- /Pre-release/
812--
813isCreated :: Event -> Bool
814isCreated = getFlag kFSEventStreamEventFlagItemCreated
816foreign import ccall safe
817 "FSEventStreamEventFlagItemRemoved"
818 kFSEventStreamEventFlagItemRemoved :: Word32
820-- | Determine whether the event indicates deletion of an object within the
821-- monitored path. This is true when a file or a hardlink is deleted.
822--
823-- Applicable only when 'setFileEvents' is 'On'.
824--
825-- /Can occur for watched path or a file inside it/
826--
827-- /macOS 10.7+/
828--
829-- /Pre-release/
830--
831isDeleted :: Event -> Bool
832isDeleted = getFlag kFSEventStreamEventFlagItemRemoved
834foreign import ccall safe
835 "FSEventStreamEventFlagItemRenamed"
836 kFSEventStreamEventFlagItemRenamed :: Word32
838-- | Determine whether the event indicates rename of an object within the
839-- monitored path. This event is generated when a file is renamed within the
840-- watched directory or if it is moved out of or in the watched directory.
841--
842-- /Applicable only when 'setFileEvents' is 'On'/
843--
844-- /Can occur for watched path or a file inside it/
845--
846-- /macOS 10.7+/
847--
848-- /Pre-release/
849--
850isMoved :: Event -> Bool
851isMoved = getFlag kFSEventStreamEventFlagItemRenamed
853foreign import ccall safe
854 "FSEventStreamEventFlagItemModified"
855 kFSEventStreamEventFlagItemModified :: Word32
857-- | Determine whether the event indicates modification of an object within the
858-- monitored path. This event is generated only for files and not directories.
859--
860-- Applicable only when 'setFileEvents' is 'On'.
861--
862-- /macOS 10.7+/
863--
864-- /Pre-release/
865--
866isModified :: Event -> Bool
867isModified = getFlag kFSEventStreamEventFlagItemModified
869#if HAVE_DECL_KFSEVENTSTREAMEVENTFLAGITEMCLONED
870foreign import ccall safe
871 "FSEventStreamEventFlagItemCloned"
872 kFSEventStreamEventFlagItemCloned :: Word32
874-- | Determine whether the event indicates cloning of an object within the
875-- monitored path. The "Duplicate" command in the "File" menu of the "Finder"
876-- application generates a "clone" event.
877--
878-- Applicable only when 'setFileEvents' is 'On'.
879--
880-- /macOS 10.13+/
881--
882-- /Pre-release/
883--
884isCloned :: Event -> Bool
885isCloned = getFlag kFSEventStreamEventFlagItemCloned
886#endif
888-------------------------------------------------------------------------------
889-- Information about path type (applicable only when 'setFileEvents' is 'On')
890-------------------------------------------------------------------------------
892foreign import ccall safe
893 "FSEventStreamEventFlagItemIsDir"
894 kFSEventStreamEventFlagItemIsDir :: Word32
896-- | Determine whether the event is for a directory path.
897--
898-- Applicable only when 'setFileEvents' is 'On'.
899--
900-- /macOS 10.7+/
901--
902-- /Pre-release/
903--
904isDir :: Event -> Bool
905isDir = getFlag kFSEventStreamEventFlagItemIsDir
907foreign import ccall safe
908 "FSEventStreamEventFlagItemIsFile"
909 kFSEventStreamEventFlagItemIsFile :: Word32
911-- | Determine whether the event is for a file path.
912--
913-- Applicable only when 'setFileEvents' is 'On'.
914--
915-- /macOS 10.7+/
916--
917-- /Pre-release/
918--
919isFile :: Event -> Bool
920isFile = getFlag kFSEventStreamEventFlagItemIsFile
922foreign import ccall safe
923 "FSEventStreamEventFlagItemIsSymlink"
924 kFSEventStreamEventFlagItemIsSymlink :: Word32
926-- | Determine whether the event is for a symbolic link path.
927--
928-- Applicable only when 'setFileEvents' is 'On'.
929--
930-- /macOS 10.7+/
931--
932-- /Pre-release/
933--
934isSymLink :: Event -> Bool
935isSymLink = getFlag kFSEventStreamEventFlagItemIsSymlink
937#if HAVE_DECL_KFSEVENTSTREAMEVENTFLAGITEMISHARDLINK
938foreign import ccall safe
939 "FSEventStreamEventFlagItemIsHardlink"
940 kFSEventStreamEventFlagItemIsHardlink :: Word32
942-- | Determine whether the event is for a file with more than one hard link.
943-- When 'isFile' is true we can check for 'isHardLink'. Note that 'isHardLink'
944-- is not true when a hard link is created, however, it is true when a file
945-- which has or had more than one link in the past is removed. This is not true
946-- if a file never had more than one link.
947--
948-- Applicable only when 'setFileEvents' is 'On'.
949--
950-- /macOS 10.10+/
951--
952-- /Pre-release/
953--
954isHardLink :: Event -> Bool
955isHardLink = getFlag kFSEventStreamEventFlagItemIsHardlink
957foreign import ccall safe
958 "FSEventStreamEventFlagItemIsLastHardlink"
959 kFSEventStreamEventFlagItemIsLastHardlink :: Word32
961-- | Determine whether the event is for a hard link path with only one hard
962-- link. If 'isHardLink' is true then we can check for 'isLastHardLink'. This
963-- is true when a file has had more than one link and now the last link is
964-- removed. In that case both 'isHardLink' and 'isLastHardLink" would be true.
965--
966-- Applicable only when 'setFileEvents' is 'On'.
967--
968-- /macOS 10.10+/
969--
970-- /Pre-release/
971--
972isLastHardLink :: Event -> Bool
973isLastHardLink = getFlag kFSEventStreamEventFlagItemIsLastHardlink
974#endif
976-------------------------------------------------------------------------------
977-- Debugging
978-------------------------------------------------------------------------------
980-- | Convert an 'Event' record to a String representation.
981showEvent :: Event -> String
982showEvent ev@Event{..} =
983 let path = runIdentity $ S.toList $ U.decodeUtf8' $ A.toStream eventAbsPath
984 in "--------------------------"
985 ++ "\nId = " ++ show eventId
986 ++ "\nPath = " ++ show path
987 ++ "\nFlags " ++ show eventFlags
989 ++ showev isMustScanSubdirs "MustScanSubdirs"
990 ++ showev isKernelDropped "KernelDropped"
991 ++ showev isUserDropped "UserDropped"
993 ++ showev isRootChanged "RootChanged"
994 ++ showev isMount "Mount"
995 ++ showev isUnmount "Unmount"
996 ++ showev isHistoryDone "HistoryDone"
998 ++ showev isOwnerGroupModeChanged "OwnerGroupModeChanged"
999 ++ showev isInodeAttrsChanged "InodeAttrsChanged"
1000 ++ showev isFinderInfoChanged "FinderInfoChanged"
1001 ++ showev isXAttrsChanged "XAttrsChanged"
1003 ++ showev isCreated "Created"
1004 ++ showev isDeleted "Deleted"
1005 ++ showev isModified "Modified"
1006 ++ showev isMoved "Moved"
1007#if HAVE_DECL_KFSEVENTSTREAMEVENTFLAGITEMCLONED
1008 ++ showev isCloned "Cloned"
1009#endif
1011 ++ showev isDir "Dir"
1012 ++ showev isFile "File"
1013 ++ showev isSymLink "SymLink"
1014#if HAVE_DECL_KFSEVENTSTREAMEVENTFLAGITEMISHARDLINK
1015 ++ showev isHardLink "HardLink"
1016 ++ showev isLastHardLink "LastHardLink"
1017#endif
1018 ++ "\n"
1020 where showev f str = if f ev then "\n" ++ str else ""
1021#else
1022module Streamly.Internal.FileSystem.Event.Darwin () where
1023#warning "Autoconf did not find the definition \
1024kFSEventStreamCreateFlagFileEvents in Darwin header files.\
1025Do you have Cocoa framework header files installed?\
1026Not compiling the Streamly.Internal.FileSystem.Event.Darwin module. \
1027Programs depending on this module may not compile. \
1028Check if HAVE_DECL_KFSEVENTSTREAMCREATEFLAGFILEEVENTS is defined in config.h \
1029generated from src/config.h.in"
1030#endif