@@ -0,0 +1,991 @@
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.fromString\# "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+--
24+-- =Design notes
25+--
26+-- For reference documentation see:
27+--
28+-- *
29+-- "<https://developer.apple.com/documentation/coreservices/file_system_events?language=objc Apple FS Events Reference>"
30+-- * "<https://developer.apple.com/library/archive/documentation/Darwin/Conceptual/FSEvents_ProgGuide/UsingtheFSEventsFramework/UsingtheFSEventsFramework.html Apple FS Events Programming Guide>"
31+--
32+-- We try to keep the macOS\/Linux/Windows event handling APIs and defaults
33+-- semantically and syntactically as close as possible.
34+--
35+-- =TODO
36+--
37+-- APIs for persistent event-id related functionality, to get events from
38+-- specific event-id etc are not implemented.
39+
40+-- Need a real cpp to process Availability.h
41+{-# OPTIONS_GHC -pgmP cpp -optP -Wno-unused-command-line-argument #-}
42+
43+#include <Availability.h>
44+
45+#if defined(__MAC_OS_X_VERSION_MIN_REQUIRED) \
46+ && __MAC_OS_X_VERSION_MIN_REQUIRED >= 10700
47+
48+module Streamly.Internal.FileSystem.Event.Darwin
49+ (
50+ -- * Creating a Watch
51+
52+ -- ** Default configuration
53+ Config
54+ , Toggle (..)
55+ , defaultConfig
56+
57+ -- ** Watch Behavior
58+ , BatchInfo (..)
59+ , setEventBatching
60+
61+ -- ** Events of Interest
62+ , setRootChanged
63+ , setFileEvents
64+ , setIgnoreSelf
65+#if __MAC_OS_X_VERSION_MIN_REQUIRED >= 101500
66+ , setFullHistory
67+#endif
68+
69+ -- ** Watch APIs
70+ , watchTrees
71+ , watchTreesWith
72+
73+ -- * Handling Events
74+ , Event
75+ , getEventId
76+ , getAbsPath
77+
78+ -- ** Exception Conditions
79+ , isEventIdWrapped
80+ , isOverflow
81+ , isOverflowKernel
82+ , isOverflowUser
83+
84+ -- ** Root Level Events
85+ -- | Events that belong to the root path as a whole and not to specific
86+ -- itmes contained in it.
87+ , isMount
88+ , isUnmount
89+ , isHistoryDone
90+ , isRootChanged
91+
92+ -- ** Item Level Metadata change
93+ , isOwnerGroupModeChanged
94+ , isInodeAttrsChanged
95+ , isFinderInfoChanged
96+ , isXAttrsChanged
97+
98+ -- ** Item Level CRUD events
99+ -- | Note: Sometimes the "Deleted", "Renamed", "Modified" events have
100+ -- "Created" flag also present, this may perhaps be due to temporal
101+ -- coalescing of events. Though it is not clear why different types of
102+ -- events should be coalesced together. Therefore, it may be a good idea to
103+ -- check the presence of other flags before "Created" and to give
104+ -- precedence to those flags in such case. For example, on macOS 10.15.1,
105+ -- try "touch x; rm x" or "rm x; touch x" or "touch x; mv x y". Sometimes
106+ -- even just an "rm x" produces both flags.
107+ --
108+ -- When @rm -rf@ is used on the watched directory, only one 'isDeleted'
109+ -- event occurs and that is for the watch root. However, @rm -rf@ on a
110+ -- directory inside the watch root produces 'isDeleted' events for all
111+ -- files.
112+
113+ , isCreated
114+ , isDeleted
115+ , isMoved
116+ , isModified
117+#if __MAC_OS_X_VERSION_MIN_REQUIRED >= 101300
118+ , isCloned
119+#endif
120+
121+ -- ** Item Path info
122+ , isDir
123+ , isFile
124+ , isSymLink
125+#if __MAC_OS_X_VERSION_MIN_REQUIRED >= 101000
126+ , isHardLink
127+ , isLastHardLink
128+#endif
129+
130+ -- * Debugging
131+ , showEvent
132+ )
133+where
134+
135+import Control.Concurrent (MVar, newMVar, takeMVar, putMVar)
136+import Control.Monad (when)
137+import Control.Monad.IO.Class (liftIO)
138+import Data.Bits ((.|.), (.&.), complement)
139+import Data.Functor.Identity (runIdentity)
140+import Data.Word (Word8, Word32, Word64)
141+import Foreign.C.Types (CInt(..), CDouble(..), CSize(..), CUChar(..))
142+import Foreign.Marshal.Alloc (alloca)
143+import Foreign.Marshal.Array (withArray)
144+import Foreign.Ptr (Ptr, castPtr)
145+import Foreign.Storable (Storable(..))
146+import GHC.IO.Handle.FD (fdToHandle)
147+import Streamly (SerialT)
148+import Streamly.Internal.Data.Cont (contListMap)
149+import Streamly.Internal.Data.Parser (Parser)
150+import Streamly.Internal.Memory.Array.Types (Array(..))
151+import System.IO (Handle, hClose)
152+
153+import qualified Streamly.Internal.Data.Parser as PR
154+import qualified Streamly.Internal.Data.Stream.IsStream as S
155+import qualified Streamly.Internal.Data.Unicode.Stream as U
156+import qualified Streamly.Internal.FileSystem.Handle as FH
157+import qualified Streamly.Internal.Memory.Array as A
158+
159+-------------------------------------------------------------------------------
160+-- Subscription to events
161+-------------------------------------------------------------------------------
162+
163+-- | Watch configuration, used to specify the events of interest and the
164+-- behavior of the watch.
165+--
166+-- /Internal/
167+--
168+data Config = Config
169+ { latency :: Double
170+ , createFlags :: Word32
171+ }
172+
173+-------------------------------------------------------------------------------
174+-- Batching Events
175+-------------------------------------------------------------------------------
176+
177+foreign import capi
178+ "CoreServices/CoreServices.h value kFSEventStreamCreateFlagNoDefer"
179+ kFSEventStreamCreateFlagNoDefer :: Word32
180+
181+data BatchInfo =
182+ Throttle Double -- ^ Deliver an event immediately but suppress the
183+ -- following events upto the specified time.
184+ | Batch Double -- ^ Collapse all events that occurred in the specified
185+ -- time window (in seconds) and deliver them as a single
186+ -- batch.
187+
188+-- | Set how the events should be batched. See 'BatchInfo' for details. A
189+-- negative value for time is treated as 0.
190+--
191+-- /default: Batch 0.1/
192+--
193+-- /macOS 10.5+/
194+--
195+-- /Internal/
196+--
197+setEventBatching :: BatchInfo -> Config -> Config
198+setEventBatching batchInfo cfg@Config{..} =
199+ let (t, status) =
200+ case batchInfo of
201+ Throttle sec | sec < 0 -> (0, On)
202+ Throttle sec -> (sec, On)
203+ Batch sec | sec < 0 -> (0, Off)
204+ Batch sec -> (sec, Off)
205+ in setFlag kFSEventStreamCreateFlagNoDefer status $ cfg {latency = t}
206+
207+-------------------------------------------------------------------------------
208+-- Boolean settings
209+-------------------------------------------------------------------------------
210+
211+-- | Whether a setting is 'On' or 'Off'.
212+--
213+-- /Internal/
214+--
215+data Toggle = On | Off
216+
217+setFlag :: Word32 -> Toggle -> Config -> Config
218+setFlag mask status cfg@Config{..} =
219+ let flags =
220+ case status of
221+ On -> createFlags .|. mask
222+ Off -> createFlags .&. complement mask
223+ in cfg {createFlags = flags}
224+
225+foreign import capi
226+ "CoreServices/CoreServices.h value kFSEventStreamCreateFlagWatchRoot"
227+ kFSEventStreamCreateFlagWatchRoot :: Word32
228+
229+-- | Watch the changes to the path of the top level file system objects being
230+-- watched.
231+--
232+-- /default: Off/
233+--
234+-- /macOS 10.5+/
235+--
236+-- /Internal/
237+--
238+setRootChanged :: Toggle -> Config -> Config
239+setRootChanged = setFlag kFSEventStreamCreateFlagWatchRoot
240+
241+foreign import capi
242+ "CoreServices/CoreServices.h value kFSEventStreamCreateFlagFileEvents"
243+ kFSEventStreamCreateFlagFileEvents :: Word32
244+
245+-- | When this is 'Off' only events for the watched directories are reported.
246+-- For example, when a file is created inside a directory it is reported as an
247+-- event, the path name in the event is the path of the directory being
248+-- watched. Events cannot be distinguished based on the type of event.
249+--
250+-- When it is 'On' the path reported in the event is the path of the item and
251+-- not the of the directory in which it is contained. We can use the event
252+-- accessor functions to determine the event type and the path type etc.
253+--
254+-- /default: On/
255+--
256+-- /macOS 10.7+/
257+--
258+-- /Internal/
259+--
260+setFileEvents :: Toggle -> Config -> Config
261+setFileEvents = setFlag kFSEventStreamCreateFlagFileEvents
262+
263+foreign import capi
264+ "CoreServices/CoreServices.h value kFSEventStreamCreateFlagIgnoreSelf"
265+ kFSEventStreamCreateFlagIgnoreSelf :: Word32
266+
267+-- | When this is 'On' events generated by the current process are not
268+-- reported.
269+--
270+-- /default: Off/
271+--
272+-- /macOS 10.6+/
273+--
274+-- /Internal/
275+--
276+setIgnoreSelf :: Toggle -> Config -> Config
277+setIgnoreSelf = setFlag kFSEventStreamCreateFlagIgnoreSelf
278+
279+#if __MAC_OS_X_VERSION_MIN_REQUIRED >= 101500
280+foreign import capi
281+ "CoreServices/CoreServices.h value kFSEventStreamCreateFlagFullHistory"
282+ kFSEventStreamCreateFlagFullHistory :: Word32
283+
284+-- | When this is 'On' all events since the beginning of time are reported.
285+--
286+-- /default: Off/
287+--
288+-- /macOS 10.15+/
289+--
290+-- /Internal/
291+--
292+setFullHistory :: Toggle -> Config -> Config
293+setFullHistory = setFlag kFSEventStreamCreateFlagFullHistory
294+#endif
295+
296+-------------------------------------------------------------------------------
297+-- Default config
298+-------------------------------------------------------------------------------
299+
300+-- | The default settings are:
301+--
302+-- * 'setEventBatching' ('Batch' 0.1)
303+-- * 'setFileEvents' 'On'
304+-- * 'setRootChanged' 'Off'
305+-- * 'setIgnoreSelf' 'Off'
306+#if __MAC_OS_X_VERSION_MIN_REQUIRED >= 101500
307+-- * 'setFullHistory' 'Off'
308+#endif
309+--
310+-- /Internal/
311+--
312+defaultConfig :: Config
313+defaultConfig = setFileEvents On $ Config
314+ { latency = 0.1
315+ , createFlags = 0
316+ }
317+
318+-------------------------------------------------------------------------------
319+-- Open an event stream (C FFI)
320+-------------------------------------------------------------------------------
321+
322+-- See FileSystem/Event/Darwin.h
323+
324+data CWatch
325+
326+-- Representation of "struct pathName" in "FileSystem/Event/Darwin.h"
327+data PathName = PathName
328+ { pathBytes :: Ptr CUChar
329+ , pathLen :: CSize
330+ }
331+
332+instance Storable PathName where
333+ alignment _ = sizeOf (undefined :: Ptr a)
334+ sizeOf _ = sizeOf (undefined :: Ptr CUChar) + sizeOf (undefined :: CSize)
335+ peek ptr = do
336+ bptr <- peekByteOff ptr 0
337+ len <- peekByteOff ptr (sizeOf (undefined :: Ptr CUChar))
338+ return $ PathName bptr len
339+ poke ptr PathName{..} = do
340+ pokeByteOff ptr 0 pathBytes
341+ pokeByteOff ptr (sizeOf (undefined :: Ptr CUChar)) pathLen
342+
343+foreign import ccall safe "FileSystem/Event/Darwin.h createWatch" createWatch
344+ :: Ptr PathName
345+ -> CInt
346+ -> Word32
347+ -> Word64
348+ -> CDouble
349+ -> Ptr (CInt)
350+ -> Ptr (Ptr CWatch)
351+ -> IO CInt
352+
353+foreign import ccall safe "FileSystem/Event/Darwin.h destroyWatch" destroyWatch
354+ :: Ptr CWatch -> IO ()
355+
356+-------------------------------------------------------------------------------
357+-- Open an event stream
358+-------------------------------------------------------------------------------
359+
360+-- | A handle for a watch.
361+data Watch = Watch Handle (Ptr CWatch) (MVar Bool)
362+
363+-- | Given a 'Config' and @paths@ start monitoring the paths for file system
364+-- events. Returns a 'Watch' handle which can then be used to read the event
365+-- stream or to close the watch.
366+--
367+-- Implementation Note: This API creates an OS level thread where an event loop
368+-- runs and writes the events to the write side fd of a POSIX pipe. Event
369+-- stream APIs read the events from the read end of this pipe. Since an OS
370+-- level thread is forked, creating a new watch stream is expensive. It is
371+-- advised to minimize the number of watches.
372+--
373+-- /Internal/
374+--
375+openWatch :: Config -> [Array Word8] -> IO Watch
376+openWatch Config{..} paths = do
377+ withPathNames paths $ \arraysPtr ->
378+ alloca $ \fdPtr -> do
379+ alloca $ \watchPPtr -> do
380+ let nArrays = fromIntegral (length paths)
381+ seconds = (realToFrac latency)
382+ r <- createWatch
383+ arraysPtr nArrays createFlags 0 seconds fdPtr watchPPtr
384+ when (r /= 0) $
385+ ioError (userError "openWatch: failed to create watch.")
386+ fd <- peek fdPtr
387+ h <- fdToHandle fd
388+ watchPtr <- peek watchPPtr
389+ closeLock <- newMVar False
390+ return $ Watch h watchPtr closeLock
391+
392+ where
393+
394+ withPathName :: Array Word8 -> (PathName -> IO a) -> IO a
395+ withPathName arr act = do
396+ A.withPtr arr $ \ptr ->
397+ let pname = PathName (castPtr ptr) (fromIntegral (A.length arr))
398+ in act pname
399+
400+ withPathNames = contListMap withPathName withArray
401+
402+-- | Close a 'Watch' handle.
403+--
404+-- /Internal/
405+--
406+closeWatch :: Watch -> IO ()
407+closeWatch (Watch h watchPtr mvar) = do
408+ hClose h
409+ closed <- takeMVar mvar
410+ when (not closed) $ destroyWatch watchPtr
411+ putMVar mvar True
412+
413+-------------------------------------------------------------------------------
414+-- Raw events read from the watch file handle
415+-------------------------------------------------------------------------------
416+
417+-- | An Event generated by the file system. Use the accessor functions to
418+-- examine the event.
419+--
420+-- /Internal/
421+--
422+data Event = Event
423+ { eventId :: Word64
424+ , eventFlags :: Word64
425+ , eventAbsPath :: Array Word8
426+ } deriving (Show, Ord, Eq)
427+
428+-- XXX We can perhaps use parseD monad instance for fusing with parseMany? Need
429+-- to measure the perf.
430+--
431+-- XXX should we use a magic in the header to avoid any issues due to
432+-- misalignment of records? Can that ever happen?
433+--
434+readOneEvent :: Parser IO Word8 Event
435+readOneEvent = do
436+ arr <- PR.takeEQ 24 (A.writeN 24)
437+ let arr1 = A.unsafeCast arr :: Array Word64
438+ eid = A.unsafeIndex arr1 0
439+ eflags = A.unsafeIndex arr1 1
440+ pathLen = fromIntegral $ A.unsafeIndex arr1 2
441+ path <- PR.takeEQ pathLen (A.writeN pathLen)
442+ return $ Event
443+ { eventId = eid
444+ , eventFlags = eflags
445+ , eventAbsPath = path
446+ }
447+
448+watchToStream :: Watch -> SerialT IO Event
449+watchToStream (Watch handle _ _) =
450+ S.parseMany readOneEvent $ S.unfold FH.read handle
451+
452+-- | Start monitoring a list of file system paths for file system events with
453+-- the supplied configuration operation over the 'defaultConfig'. The
454+-- paths could be files or directories. When the path is a directory, the
455+-- whole directory tree under it is watched recursively. Monitoring starts from
456+-- the current time onwards. The paths are specified as UTF-8 encoded 'Array'
457+-- of 'Word8'.
458+--
459+-- If the path name to be watched is a symbolic link then the target of the
460+-- link is watched instead of the symbolic link itself. If the symbolic link is
461+-- removed later the target is still watched as usual.
462+--
463+-- When 'setFileEvents' is 'Off' then events occurring inside the watch root
464+-- cannot be distinguished from each other. For example, we cannot determine
465+-- if an event is a "create" or "delete", we only know that some event ocurred
466+-- under the watched directory hierarchy. Also, no path information for the
467+-- target item of the event is generated.
468+--
469+-- If the watched path is deleted and created again the events start coming
470+-- again for that path. A file type path being watched could be deleted and
471+-- recreated as a directory. If the watched path is moved to another path (or
472+-- is deleted and recreated as a symbolic link to another path) then no events
473+-- are reported for any changes under the new path.
474+--
475+-- If a watch is started on a non-existing path then the path is not watched
476+-- even if it is created later. The macOS API does not fail for a non-existing
477+-- path. If a non-existing path is watched with 'setRootChanged' then an
478+-- 'isRootChanged' event is reported if the path is created later and the
479+-- "path" field in the event is set to the dirname of the path rather than the
480+-- full absolute path. This is the observed behavior on macOS 10.15.1.
481+--
482+-- @
483+-- {-\# LANGUAGE MagicHash #-}
484+-- watchTreesWith ('setIgnoreSelf' 'On' . 'setRootChanged' 'On') [Array.fromString\# "path"#]
485+-- @
486+--
487+-- /Internal/
488+--
489+watchTreesWith :: (Config -> Config) -> [Array Word8] -> SerialT IO Event
490+watchTreesWith f paths = S.bracket before after watchToStream
491+
492+ where
493+
494+ before = liftIO $ openWatch (f defaultConfig) paths
495+ after = (liftIO . closeWatch)
496+
497+-- | Like 'watchTreesWith' but uses the 'defaultConfig' options.
498+--
499+-- @
500+-- watchTrees = watchTreesWith id
501+-- @
502+--
503+watchTrees :: [Array Word8] -> SerialT IO Event
504+watchTrees = watchTreesWith id
505+
506+-------------------------------------------------------------------------------
507+-- Examine the event stream
508+-------------------------------------------------------------------------------
509+
510+-- | Get the event id of an 'Event'. Event-id is a monotonically increasing
511+-- 64-bit integer identifying an event uniquely.
512+--
513+-- /Internal/
514+--
515+getEventId :: Event -> Word64
516+getEventId Event{..} = eventId
517+
518+foreign import capi
519+ "CoreServices/CoreServices.h value kFSEventStreamEventFlagEventIdsWrapped"
520+ kFSEventStreamEventFlagEventIdsWrapped :: Word64
521+
522+-- | Determine whether the event id has wrapped. This is impossible on any
523+-- traditional hardware in any reasonable amount of time unless the event-id
524+-- starts from a very high value, because the event-id is a 64-bit integer.
525+--
526+-- /macOS 10.5+/
527+--
528+-- /Internal/
529+--
530+isEventIdWrapped :: Event -> Bool
531+isEventIdWrapped = getFlag kFSEventStreamEventFlagEventIdsWrapped
532+
533+-- | Get the absolute path of the file system object for which the event is
534+-- generated. The path is a UTF-8 encoded array of bytes.
535+--
536+-- /Internal/
537+--
538+getAbsPath :: Event -> Array Word8
539+getAbsPath Event{..} = eventAbsPath
540+
541+-------------------------------------------------------------------------------
542+-- Event types
543+-------------------------------------------------------------------------------
544+
545+getFlag :: Word64 -> Event -> Bool
546+getFlag mask Event{..} = eventFlags .&. mask /= 0
547+
548+-------------------------------------------------------------------------------
549+-- Error events
550+-------------------------------------------------------------------------------
551+
552+foreign import capi
553+ "CoreServices/CoreServices.h value kFSEventStreamEventFlagMustScanSubDirs"
554+ kFSEventStreamEventFlagMustScanSubDirs :: Word64
555+
556+-- | Events may be dropped if the kernel or the user process is not able to
557+-- cope up with the speed, in that case multiple events from child nodes may be
558+-- coalesced into an event for the parent node thus losing some information
559+-- about where the event came from. The user application must then scan
560+-- everything under the parent node if it wants to know the current state.
561+--
562+-- The event path in such case points to the highest common path under which
563+-- the events were coaleseced together.
564+--
565+-- /macOS 10.5+/
566+--
567+-- /Internal/
568+--
569+isOverflow :: Event -> Bool
570+isOverflow = getFlag kFSEventStreamEventFlagMustScanSubDirs
571+
572+foreign import capi
573+ "CoreServices/CoreServices.h value kFSEventStreamEventFlagKernelDropped"
574+ kFSEventStreamEventFlagKernelDropped :: Word64
575+
576+-- | Did an overflow occur due to a kernel processing issue? Set only if an
577+-- overflow occurred.
578+--
579+-- /macOS 10.5+/
580+--
581+-- /Internal/
582+--
583+isOverflowKernel :: Event -> Bool
584+isOverflowKernel = getFlag kFSEventStreamEventFlagKernelDropped
585+
586+foreign import capi
587+ "CoreServices/CoreServices.h value kFSEventStreamEventFlagUserDropped"
588+ kFSEventStreamEventFlagUserDropped :: Word64
589+
590+-- | Did an overflow occur due to a user process issue? Set only if an overflow
591+-- occurred.
592+--
593+-- /macOS 10.5+/
594+--
595+-- /Internal/
596+--
597+isOverflowUser :: Event -> Bool
598+isOverflowUser = getFlag kFSEventStreamEventFlagUserDropped
599+
600+-------------------------------------------------------------------------------
601+-- Global Sentinel Events
602+-------------------------------------------------------------------------------
603+
604+foreign import capi
605+ "CoreServices/CoreServices.h value kFSEventStreamEventFlagHistoryDone"
606+ kFSEventStreamEventFlagHistoryDone :: Word64
607+
608+-- | Determine whether the event is a history done marker event. A history done
609+-- event is generated when the historical events from before the current time
610+-- are done. Historical events are generated when the "since" parameter in the
611+-- watch config is set to before the current time.
612+--
613+-- /macOS 10.5+/
614+--
615+-- /Internal/
616+--
617+isHistoryDone :: Event -> Bool
618+isHistoryDone = getFlag kFSEventStreamEventFlagHistoryDone
619+
620+-------------------------------------------------------------------------------
621+-- Events affecting the watched path only
622+-------------------------------------------------------------------------------
623+
624+foreign import capi
625+ "CoreServices/CoreServices.h value kFSEventStreamEventFlagRootChanged"
626+ kFSEventStreamEventFlagRootChanged :: Word64
627+
628+-- | Determine whether the event indicates a change of path of the monitored
629+-- object itself. Note that the object may become unreachable or deleted after
630+-- a change of path.
631+--
632+-- /Applicable only when 'setRootChanged' is 'On'/
633+--
634+-- /Occurs only for the watched path/
635+--
636+-- /macOS 10.5+/
637+--
638+-- /Internal/
639+--
640+isRootChanged :: Event -> Bool
641+isRootChanged = getFlag kFSEventStreamEventFlagRootChanged
642+
643+-------------------------------------------------------------------------------
644+-- Global events under the watched path
645+-------------------------------------------------------------------------------
646+
647+foreign import capi
648+ "CoreServices/CoreServices.h value kFSEventStreamEventFlagMount"
649+ kFSEventStreamEventFlagMount :: Word64
650+
651+-- | Determine whether the event is a mount event. A mount event is generated
652+-- if a volume is mounted under the path being watched.
653+--
654+-- /macOS 10.5+/
655+--
656+-- /Internal/
657+--
658+isMount :: Event -> Bool
659+isMount = getFlag kFSEventStreamEventFlagMount
660+
661+foreign import capi
662+ "CoreServices/CoreServices.h value kFSEventStreamEventFlagUnmount"
663+ kFSEventStreamEventFlagUnmount :: Word64
664+
665+-- | Determine whether the event is an unmount event. An unmount event is
666+-- generated if a volume mounted under the path being watched is unmounted.
667+--
668+-- /macOS 10.5+/
669+--
670+-- /Internal/
671+--
672+isUnmount :: Event -> Bool
673+isUnmount = getFlag kFSEventStreamEventFlagUnmount
674+
675+-------------------------------------------------------------------------------
676+-- Metadata change Events (applicable only when 'setFileEvents' is 'On')
677+-------------------------------------------------------------------------------
678+
679+foreign import capi
680+ "CoreServices/CoreServices.h value kFSEventStreamEventFlagItemChangeOwner"
681+ kFSEventStreamEventFlagItemChangeOwner :: Word64
682+
683+-- | Determine whether the event is ownership, group, permissions or ACL change
684+-- event of an object contained within the monitored path.
685+--
686+-- Note that this event may be generated even if the metadata is changed to the
687+-- same value again.
688+--
689+-- /Applicable only when 'setFileEvents' is 'On'/
690+--
691+-- /Can occur for watched path or a file inside it/
692+--
693+-- /macOS 10.7+/
694+--
695+-- /Internal/
696+--
697+isOwnerGroupModeChanged :: Event -> Bool
698+isOwnerGroupModeChanged = getFlag kFSEventStreamEventFlagItemChangeOwner
699+
700+foreign import capi
701+ "CoreServices/CoreServices.h value kFSEventStreamEventFlagItemInodeMetaMod"
702+ kFSEventStreamEventFlagItemInodeMetaMod :: Word64
703+
704+-- | Determine whether the event indicates inode metadata change for an object
705+-- contained within the monitored path. This event is generated when inode
706+-- attributes other than the owner, group or permissions are changed e.g. file
707+-- modification time. It does not occur when the link count of a file changes
708+-- (as of macOS 10.15.1).
709+--
710+-- /Applicable only when 'setFileEvents' is 'On'/
711+--
712+-- /Can occur for watched path or a file inside it/
713+--
714+-- /macOS 10.7+/
715+--
716+-- /Internal/
717+--
718+isInodeAttrsChanged :: Event -> Bool
719+isInodeAttrsChanged = getFlag kFSEventStreamEventFlagItemInodeMetaMod
720+
721+foreign import capi
722+ "CoreServices/CoreServices.h value kFSEventStreamEventFlagItemFinderInfoMod"
723+ kFSEventStreamEventFlagItemFinderInfoMod :: Word64
724+
725+-- | Determine whether the event indicates finder information metadata change
726+-- for an object contained within the monitored path.
727+--
728+-- /Applicable only when 'setFileEvents' is 'On'/
729+--
730+-- /macOS 10.7+/
731+--
732+-- /Internal/
733+--
734+isFinderInfoChanged :: Event -> Bool
735+isFinderInfoChanged = getFlag kFSEventStreamEventFlagItemFinderInfoMod
736+
737+foreign import capi
738+ "CoreServices/CoreServices.h value kFSEventStreamEventFlagItemXattrMod"
739+ kFSEventStreamEventFlagItemXattrMod :: Word64
740+
741+-- | Determine whether the event indicates extended attributes metadata change
742+-- for an object contained within the monitored path.
743+--
744+-- /Applicable only when 'setFileEvents' is 'On'/
745+--
746+-- /Can occur for watched path or a file inside it/
747+--
748+-- /macOS 10.7+/
749+--
750+-- /Internal/
751+--
752+isXAttrsChanged :: Event -> Bool
753+isXAttrsChanged = getFlag kFSEventStreamEventFlagItemXattrMod
754+
755+-------------------------------------------------------------------------------
756+-- CRUD Events (applicable only when 'setFileEvents' is 'On')
757+-------------------------------------------------------------------------------
758+
759+foreign import capi
760+ "CoreServices/CoreServices.h value kFSEventStreamEventFlagItemCreated"
761+ kFSEventStreamEventFlagItemCreated :: Word64
762+
763+-- | Determine whether the event indicates creation of an object within the
764+-- monitored path. This event is generated when any file system object other
765+-- than a hard link is created. On hard linking only an 'isInodeAttrsChanged'
766+-- event on the directory is generated, it is not a create event. However, when
767+-- a hard link is deleted 'isDeleted' and 'isHardLink' both are true.
768+--
769+-- This event can occur for the watched path if the path was deleted/moved and
770+-- created again (tested on macOS 10.15.1). However, if a watch is started on a
771+-- non-existing path and the path is created later, then the this event is not
772+-- generated, the path is not watched.
773+--
774+-- /Applicable only when 'setFileEvents' is 'On'/
775+--
776+-- /Can occur for watched path or a file inside it/
777+--
778+-- /macOS 10.7+/
779+--
780+-- /Internal/
781+--
782+isCreated :: Event -> Bool
783+isCreated = getFlag kFSEventStreamEventFlagItemCreated
784+
785+foreign import capi
786+ "CoreServices/CoreServices.h value kFSEventStreamEventFlagItemRemoved"
787+ kFSEventStreamEventFlagItemRemoved :: Word64
788+
789+-- | Determine whether the event indicates deletion of an object within the
790+-- monitored path. This is true when a file or a hardlink is deleted.
791+--
792+-- Applicable only when 'setFileEvents' is 'On'.
793+--
794+-- /Can occur for watched path or a file inside it/
795+--
796+-- /macOS 10.7+/
797+--
798+-- /Internal/
799+--
800+isDeleted :: Event -> Bool
801+isDeleted = getFlag kFSEventStreamEventFlagItemRemoved
802+
803+foreign import capi
804+ "CoreServices/CoreServices.h value kFSEventStreamEventFlagItemRenamed"
805+ kFSEventStreamEventFlagItemRenamed :: Word64
806+
807+-- | Determine whether the event indicates rename of an object within the
808+-- monitored path. This event is generated when a file is renamed within the
809+-- watched directory or if it is moved out of or in the watched directory.
810+--
811+-- /Applicable only when 'setFileEvents' is 'On'/
812+--
813+-- /Can occur for watched path or a file inside it/
814+--
815+-- /macOS 10.7+/
816+--
817+-- /Internal/
818+--
819+isMoved :: Event -> Bool
820+isMoved = getFlag kFSEventStreamEventFlagItemRenamed
821+
822+foreign import capi
823+ "CoreServices/CoreServices.h value kFSEventStreamEventFlagItemModified"
824+ kFSEventStreamEventFlagItemModified :: Word64
825+
826+-- | Determine whether the event indicates modification of an object within the
827+-- monitored path. This event is generated only for files and not directories.
828+--
829+-- Applicable only when 'setFileEvents' is 'On'.
830+--
831+-- /macOS 10.7+/
832+--
833+-- /Internal/
834+--
835+isModified :: Event -> Bool
836+isModified = getFlag kFSEventStreamEventFlagItemModified
837+
838+#if __MAC_OS_X_VERSION_MIN_REQUIRED >= 101300
839+foreign import capi
840+ "CoreServices/CoreServices.h value kFSEventStreamEventFlagItemCloned"
841+ kFSEventStreamEventFlagItemCloned :: Word64
842+
843+-- | Determine whether the event indicates cloning of an object within the
844+-- monitored path.
845+--
846+-- Applicable only when 'setFileEvents' is 'On'.
847+--
848+-- /macOS 10.13+/
849+--
850+-- /Internal/
851+--
852+isCloned :: Event -> Bool
853+isCloned = getFlag kFSEventStreamEventFlagItemCloned
854+#endif
855+
856+-------------------------------------------------------------------------------
857+-- Information about path type (applicable only when 'setFileEvents' is 'On')
858+-------------------------------------------------------------------------------
859+
860+foreign import capi
861+ "CoreServices/CoreServices.h value kFSEventStreamEventFlagItemIsDir"
862+ kFSEventStreamEventFlagItemIsDir :: Word64
863+
864+-- | Determine whether the event is for a directory path.
865+--
866+-- Applicable only when 'setFileEvents' is 'On'.
867+--
868+-- /macOS 10.7+/
869+--
870+-- /Internal/
871+--
872+isDir :: Event -> Bool
873+isDir = getFlag kFSEventStreamEventFlagItemIsDir
874+
875+foreign import capi
876+ "CoreServices/CoreServices.h value kFSEventStreamEventFlagItemIsFile"
877+ kFSEventStreamEventFlagItemIsFile :: Word64
878+
879+-- | Determine whether the event is for a file path.
880+--
881+-- Applicable only when 'setFileEvents' is 'On'.
882+--
883+-- /macOS 10.7+/
884+--
885+-- /Internal/
886+--
887+isFile :: Event -> Bool
888+isFile = getFlag kFSEventStreamEventFlagItemIsFile
889+
890+foreign import capi
891+ "CoreServices/CoreServices.h value kFSEventStreamEventFlagItemIsSymlink"
892+ kFSEventStreamEventFlagItemIsSymlink :: Word64
893+
894+-- | Determine whether the event is for a symbolic link path.
895+--
896+-- Applicable only when 'setFileEvents' is 'On'.
897+--
898+-- /macOS 10.7+/
899+--
900+-- /Internal/
901+--
902+isSymLink :: Event -> Bool
903+isSymLink = getFlag kFSEventStreamEventFlagItemIsSymlink
904+
905+#if __MAC_OS_X_VERSION_MIN_REQUIRED >= 101000
906+foreign import capi
907+ "CoreServices/CoreServices.h value kFSEventStreamEventFlagItemIsHardlink"
908+ kFSEventStreamEventFlagItemIsHardlink :: Word64
909+
910+-- | Determine whether the event is for a file with more than one hard link.
911+-- When 'isFile' is true we can check for 'isHardLink'. Note that 'isHardLink'
912+-- is not true when a hard link is created, however, it is true when a file
913+-- which has or had more than one link in the past is removed. This is not true
914+-- if a file never had more than one link.
915+--
916+-- Applicable only when 'setFileEvents' is 'On'.
917+--
918+-- /macOS 10.10+/
919+--
920+-- /Internal/
921+--
922+isHardLink :: Event -> Bool
923+isHardLink = getFlag kFSEventStreamEventFlagItemIsHardlink
924+
925+foreign import capi
926+ "CoreServices/CoreServices.h value kFSEventStreamEventFlagItemIsLastHardlink"
927+ kFSEventStreamEventFlagItemIsLastHardlink :: Word64
928+
929+-- | Determine whether the event is for a hard link path with only one hard
930+-- link. If 'isHardLink' is true then we can check for 'isLastHardLink'. This
931+-- is true when a file has had more than one link and now the last link is
932+-- removed. In that case both 'isHardLink' and 'isLastHardLink" would be true.
933+--
934+-- Applicable only when 'setFileEvents' is 'On'.
935+--
936+-- /macOS 10.10+/
937+--
938+-- /Internal/
939+--
940+isLastHardLink :: Event -> Bool
941+isLastHardLink = getFlag kFSEventStreamEventFlagItemIsLastHardlink
942+#endif
943+
944+-------------------------------------------------------------------------------
945+-- Debugging
946+-------------------------------------------------------------------------------
947+
948+-- | Convert an 'Event' record to a String representation.
949+showEvent :: Event -> String
950+showEvent ev@Event{..} =
951+ let path = runIdentity $ S.toList $ U.decodeUtf8 $ A.toStream eventAbsPath
952+ in "--------------------------"
953+ ++ "\nId = " ++ show eventId
954+ ++ "\nPath = " ++ show path
955+ ++ "\nFlags " ++ show eventFlags
956+
957+ ++ showev isOverflow "Overflow"
958+ ++ showev isOverflowKernel "OverflowKernel"
959+ ++ showev isOverflowUser "OverflowUser"
960+
961+ ++ showev isRootChanged "RootChanged"
962+ ++ showev isMount "Mount"
963+ ++ showev isUnmount "Unmount"
964+ ++ showev isHistoryDone "HistoryDone"
965+
966+ ++ showev isOwnerGroupModeChanged "OwnerGroupModeChanged"
967+ ++ showev isInodeAttrsChanged "InodeAttrsChanged"
968+ ++ showev isFinderInfoChanged "FinderInfoChanged"
969+ ++ showev isXAttrsChanged "XAttrsChanged"
970+
971+ ++ showev isCreated "Created"
972+ ++ showev isDeleted "Deleted"
973+ ++ showev isModified "Modified"
974+ ++ showev isMoved "Moved"
975+#if __MAC_OS_X_VERSION_MIN_REQUIRED >= 101300
976+ ++ showev isCloned "Cloned"
977+#endif
978+
979+ ++ showev isDir "Dir"
980+ ++ showev isFile "File"
981+ ++ showev isSymLink "SymLink"
982+#if __MAC_OS_X_VERSION_MIN_REQUIRED >= 101000
983+ ++ showev isHardLink "HardLink"
984+ ++ showev isLastHardLink "LastHardLink"
985+#endif
986+ ++ "\n"
987+
988+ where showev f str = if f ev then "\n" ++ str else ""
989+#else
990+module Streamly.Internal.FileSystem.Event.Darwin () where
991+#endif