@@ -0,0 +1,991 @@
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
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.
16+-- {-\# LANGUAGE MagicHash #-}
17+-- Stream.mapM_ (putStrLn . 'showEvent') $ 'watchTrees' [Array.fromString\# "path"#]
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.
26+-- For reference documentation see:
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>"
32+-- We try to keep the macOS\/Linux/Windows event handling APIs and defaults
33+-- semantically and syntactically as close as possible.
37+-- APIs for persistent event-id related functionality, to get events from
38+-- specific event-id etc are not implemented.
40+-- Need a real cpp to process Availability.h
41+{-# OPTIONS_GHC -pgmP cpp -optP -Wno-unused-command-line-argument #-}
43+#include <Availability.h>
45+#if defined(__MAC_OS_X_VERSION_MIN_REQUIRED) \
46+ && __MAC_OS_X_VERSION_MIN_REQUIRED >= 10700
48+module Streamly.Internal.FileSystem.Event.Darwin
50+ -- * Creating a Watch
52+ -- ** Default configuration
61+ -- ** Events of Interest
65+#if __MAC_OS_X_VERSION_MIN_REQUIRED >= 101500
78+ -- ** Exception Conditions
84+ -- ** Root Level Events
85+ -- | Events that belong to the root path as a whole and not to specific
86+ -- itmes contained in it.
92+ -- ** Item Level Metadata change
93+ , isOwnerGroupModeChanged
94+ , isInodeAttrsChanged
95+ , isFinderInfoChanged
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.
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
117+#if __MAC_OS_X_VERSION_MIN_REQUIRED >= 101300
121+ -- ** Item Path info
125+#if __MAC_OS_X_VERSION_MIN_REQUIRED >= 101000
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)
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
159+-------------------------------------------------------------------------------
160+-- Subscription to events
161+-------------------------------------------------------------------------------
163+-- | Watch configuration, used to specify the events of interest and the
164+-- behavior of the watch.
170+ , createFlags :: Word32
173+-------------------------------------------------------------------------------
175+-------------------------------------------------------------------------------
178+ "CoreServices/CoreServices.h value kFSEventStreamCreateFlagNoDefer"
179+ kFSEventStreamCreateFlagNoDefer :: Word32
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
188+-- | Set how the events should be batched. See 'BatchInfo' for details. A
189+-- negative value for time is treated as 0.
191+-- /default: Batch 0.1/
197+setEventBatching :: BatchInfo -> Config -> Config
198+setEventBatching batchInfo cfg@Config{..} =
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}
207+-------------------------------------------------------------------------------
209+-------------------------------------------------------------------------------
211+-- | Whether a setting is 'On' or 'Off'.
215+data Toggle = On | Off
217+setFlag :: Word32 -> Toggle -> Config -> Config
218+setFlag mask status cfg@Config{..} =
221+ On -> createFlags .|. mask
222+ Off -> createFlags .&. complement mask
223+ in cfg {createFlags = flags}
226+ "CoreServices/CoreServices.h value kFSEventStreamCreateFlagWatchRoot"
227+ kFSEventStreamCreateFlagWatchRoot :: Word32
229+-- | Watch the changes to the path of the top level file system objects being
238+setRootChanged :: Toggle -> Config -> Config
239+setRootChanged = setFlag kFSEventStreamCreateFlagWatchRoot
242+ "CoreServices/CoreServices.h value kFSEventStreamCreateFlagFileEvents"
243+ kFSEventStreamCreateFlagFileEvents :: Word32
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.
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.
260+setFileEvents :: Toggle -> Config -> Config
261+setFileEvents = setFlag kFSEventStreamCreateFlagFileEvents
264+ "CoreServices/CoreServices.h value kFSEventStreamCreateFlagIgnoreSelf"
265+ kFSEventStreamCreateFlagIgnoreSelf :: Word32
267+-- | When this is 'On' events generated by the current process are not
276+setIgnoreSelf :: Toggle -> Config -> Config
277+setIgnoreSelf = setFlag kFSEventStreamCreateFlagIgnoreSelf
279+#if __MAC_OS_X_VERSION_MIN_REQUIRED >= 101500
281+ "CoreServices/CoreServices.h value kFSEventStreamCreateFlagFullHistory"
282+ kFSEventStreamCreateFlagFullHistory :: Word32
284+-- | When this is 'On' all events since the beginning of time are reported.
292+setFullHistory :: Toggle -> Config -> Config
293+setFullHistory = setFlag kFSEventStreamCreateFlagFullHistory
296+-------------------------------------------------------------------------------
298+-------------------------------------------------------------------------------
300+-- | The default settings are:
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'
312+defaultConfig :: Config
313+defaultConfig = setFileEvents On $ Config
318+-------------------------------------------------------------------------------
319+-- Open an event stream (C FFI)
320+-------------------------------------------------------------------------------
322+-- See FileSystem/Event/Darwin.h
326+-- Representation of "struct pathName" in "FileSystem/Event/Darwin.h"
327+data PathName = PathName
328+ { pathBytes :: Ptr CUChar
332+instance Storable PathName where
333+ alignment _ = sizeOf (undefined :: Ptr a)
334+ sizeOf _ = sizeOf (undefined :: Ptr CUChar) + sizeOf (undefined :: CSize)
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
343+foreign import ccall safe "FileSystem/Event/Darwin.h createWatch" createWatch
353+foreign import ccall safe "FileSystem/Event/Darwin.h destroyWatch" destroyWatch
354+ :: Ptr CWatch -> IO ()
356+-------------------------------------------------------------------------------
357+-- Open an event stream
358+-------------------------------------------------------------------------------
360+-- | A handle for a watch.
361+data Watch = Watch Handle (Ptr CWatch) (MVar Bool)
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.
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.
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)
383+ arraysPtr nArrays createFlags 0 seconds fdPtr watchPPtr
385+ ioError (userError "openWatch: failed to create watch.")
388+ watchPtr <- peek watchPPtr
389+ closeLock <- newMVar False
390+ return $ Watch h watchPtr closeLock
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))
400+ withPathNames = contListMap withPathName withArray
402+-- | Close a 'Watch' handle.
406+closeWatch :: Watch -> IO ()
407+closeWatch (Watch h watchPtr mvar) = do
409+ closed <- takeMVar mvar
410+ when (not closed) $ destroyWatch watchPtr
413+-------------------------------------------------------------------------------
414+-- Raw events read from the watch file handle
415+-------------------------------------------------------------------------------
417+-- | An Event generated by the file system. Use the accessor functions to
418+-- examine the event.
424+ , eventFlags :: Word64
425+ , eventAbsPath :: Array Word8
426+ } deriving (Show, Ord, Eq)
428+-- XXX We can perhaps use parseD monad instance for fusing with parseMany? Need
429+-- to measure the perf.
431+-- XXX should we use a magic in the header to avoid any issues due to
432+-- misalignment of records? Can that ever happen?
434+readOneEvent :: Parser IO Word8 Event
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)
444+ , eventFlags = eflags
445+ , eventAbsPath = path
448+watchToStream :: Watch -> SerialT IO Event
449+watchToStream (Watch handle _ _) =
450+ S.parseMany readOneEvent $ S.unfold FH.read handle
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'
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.
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.
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.
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.
483+-- {-\# LANGUAGE MagicHash #-}
484+-- watchTreesWith ('setIgnoreSelf' 'On' . 'setRootChanged' 'On') [Array.fromString\# "path"#]
489+watchTreesWith :: (Config -> Config) -> [Array Word8] -> SerialT IO Event
490+watchTreesWith f paths = S.bracket before after watchToStream
494+ before = liftIO $ openWatch (f defaultConfig) paths
495+ after = (liftIO . closeWatch)
497+-- | Like 'watchTreesWith' but uses the 'defaultConfig' options.
500+-- watchTrees = watchTreesWith id
503+watchTrees :: [Array Word8] -> SerialT IO Event
504+watchTrees = watchTreesWith id
506+-------------------------------------------------------------------------------
507+-- Examine the event stream
508+-------------------------------------------------------------------------------
510+-- | Get the event id of an 'Event'. Event-id is a monotonically increasing
511+-- 64-bit integer identifying an event uniquely.
515+getEventId :: Event -> Word64
516+getEventId Event{..} = eventId
519+ "CoreServices/CoreServices.h value kFSEventStreamEventFlagEventIdsWrapped"
520+ kFSEventStreamEventFlagEventIdsWrapped :: Word64
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.
530+isEventIdWrapped :: Event -> Bool
531+isEventIdWrapped = getFlag kFSEventStreamEventFlagEventIdsWrapped
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.
538+getAbsPath :: Event -> Array Word8
539+getAbsPath Event{..} = eventAbsPath
541+-------------------------------------------------------------------------------
543+-------------------------------------------------------------------------------
545+getFlag :: Word64 -> Event -> Bool
546+getFlag mask Event{..} = eventFlags .&. mask /= 0
548+-------------------------------------------------------------------------------
550+-------------------------------------------------------------------------------
553+ "CoreServices/CoreServices.h value kFSEventStreamEventFlagMustScanSubDirs"
554+ kFSEventStreamEventFlagMustScanSubDirs :: Word64
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.
562+-- The event path in such case points to the highest common path under which
563+-- the events were coaleseced together.
569+isOverflow :: Event -> Bool
570+isOverflow = getFlag kFSEventStreamEventFlagMustScanSubDirs
573+ "CoreServices/CoreServices.h value kFSEventStreamEventFlagKernelDropped"
574+ kFSEventStreamEventFlagKernelDropped :: Word64
576+-- | Did an overflow occur due to a kernel processing issue? Set only if an
577+-- overflow occurred.
583+isOverflowKernel :: Event -> Bool
584+isOverflowKernel = getFlag kFSEventStreamEventFlagKernelDropped
587+ "CoreServices/CoreServices.h value kFSEventStreamEventFlagUserDropped"
588+ kFSEventStreamEventFlagUserDropped :: Word64
590+-- | Did an overflow occur due to a user process issue? Set only if an overflow
597+isOverflowUser :: Event -> Bool
598+isOverflowUser = getFlag kFSEventStreamEventFlagUserDropped
600+-------------------------------------------------------------------------------
601+-- Global Sentinel Events
602+-------------------------------------------------------------------------------
605+ "CoreServices/CoreServices.h value kFSEventStreamEventFlagHistoryDone"
606+ kFSEventStreamEventFlagHistoryDone :: Word64
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.
617+isHistoryDone :: Event -> Bool
618+isHistoryDone = getFlag kFSEventStreamEventFlagHistoryDone
620+-------------------------------------------------------------------------------
621+-- Events affecting the watched path only
622+-------------------------------------------------------------------------------
625+ "CoreServices/CoreServices.h value kFSEventStreamEventFlagRootChanged"
626+ kFSEventStreamEventFlagRootChanged :: Word64
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
632+-- /Applicable only when 'setRootChanged' is 'On'/
634+-- /Occurs only for the watched path/
640+isRootChanged :: Event -> Bool
641+isRootChanged = getFlag kFSEventStreamEventFlagRootChanged
643+-------------------------------------------------------------------------------
644+-- Global events under the watched path
645+-------------------------------------------------------------------------------
648+ "CoreServices/CoreServices.h value kFSEventStreamEventFlagMount"
649+ kFSEventStreamEventFlagMount :: Word64
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.
658+isMount :: Event -> Bool
659+isMount = getFlag kFSEventStreamEventFlagMount
662+ "CoreServices/CoreServices.h value kFSEventStreamEventFlagUnmount"
663+ kFSEventStreamEventFlagUnmount :: Word64
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.
672+isUnmount :: Event -> Bool
673+isUnmount = getFlag kFSEventStreamEventFlagUnmount
675+-------------------------------------------------------------------------------
676+-- Metadata change Events (applicable only when 'setFileEvents' is 'On')
677+-------------------------------------------------------------------------------
680+ "CoreServices/CoreServices.h value kFSEventStreamEventFlagItemChangeOwner"
681+ kFSEventStreamEventFlagItemChangeOwner :: Word64
683+-- | Determine whether the event is ownership, group, permissions or ACL change
684+-- event of an object contained within the monitored path.
686+-- Note that this event may be generated even if the metadata is changed to the
689+-- /Applicable only when 'setFileEvents' is 'On'/
691+-- /Can occur for watched path or a file inside it/
697+isOwnerGroupModeChanged :: Event -> Bool
698+isOwnerGroupModeChanged = getFlag kFSEventStreamEventFlagItemChangeOwner
701+ "CoreServices/CoreServices.h value kFSEventStreamEventFlagItemInodeMetaMod"
702+ kFSEventStreamEventFlagItemInodeMetaMod :: Word64
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).
710+-- /Applicable only when 'setFileEvents' is 'On'/
712+-- /Can occur for watched path or a file inside it/
718+isInodeAttrsChanged :: Event -> Bool
719+isInodeAttrsChanged = getFlag kFSEventStreamEventFlagItemInodeMetaMod
722+ "CoreServices/CoreServices.h value kFSEventStreamEventFlagItemFinderInfoMod"
723+ kFSEventStreamEventFlagItemFinderInfoMod :: Word64
725+-- | Determine whether the event indicates finder information metadata change
726+-- for an object contained within the monitored path.
728+-- /Applicable only when 'setFileEvents' is 'On'/
734+isFinderInfoChanged :: Event -> Bool
735+isFinderInfoChanged = getFlag kFSEventStreamEventFlagItemFinderInfoMod
738+ "CoreServices/CoreServices.h value kFSEventStreamEventFlagItemXattrMod"
739+ kFSEventStreamEventFlagItemXattrMod :: Word64
741+-- | Determine whether the event indicates extended attributes metadata change
742+-- for an object contained within the monitored path.
744+-- /Applicable only when 'setFileEvents' is 'On'/
746+-- /Can occur for watched path or a file inside it/
752+isXAttrsChanged :: Event -> Bool
753+isXAttrsChanged = getFlag kFSEventStreamEventFlagItemXattrMod
755+-------------------------------------------------------------------------------
756+-- CRUD Events (applicable only when 'setFileEvents' is 'On')
757+-------------------------------------------------------------------------------
760+ "CoreServices/CoreServices.h value kFSEventStreamEventFlagItemCreated"
761+ kFSEventStreamEventFlagItemCreated :: Word64
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.
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.
774+-- /Applicable only when 'setFileEvents' is 'On'/
776+-- /Can occur for watched path or a file inside it/
782+isCreated :: Event -> Bool
783+isCreated = getFlag kFSEventStreamEventFlagItemCreated
786+ "CoreServices/CoreServices.h value kFSEventStreamEventFlagItemRemoved"
787+ kFSEventStreamEventFlagItemRemoved :: Word64
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.
792+-- Applicable only when 'setFileEvents' is 'On'.
794+-- /Can occur for watched path or a file inside it/
800+isDeleted :: Event -> Bool
801+isDeleted = getFlag kFSEventStreamEventFlagItemRemoved
804+ "CoreServices/CoreServices.h value kFSEventStreamEventFlagItemRenamed"
805+ kFSEventStreamEventFlagItemRenamed :: Word64
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.
811+-- /Applicable only when 'setFileEvents' is 'On'/
813+-- /Can occur for watched path or a file inside it/
819+isMoved :: Event -> Bool
820+isMoved = getFlag kFSEventStreamEventFlagItemRenamed
823+ "CoreServices/CoreServices.h value kFSEventStreamEventFlagItemModified"
824+ kFSEventStreamEventFlagItemModified :: Word64
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.
829+-- Applicable only when 'setFileEvents' is 'On'.
835+isModified :: Event -> Bool
836+isModified = getFlag kFSEventStreamEventFlagItemModified
838+#if __MAC_OS_X_VERSION_MIN_REQUIRED >= 101300
840+ "CoreServices/CoreServices.h value kFSEventStreamEventFlagItemCloned"
841+ kFSEventStreamEventFlagItemCloned :: Word64
843+-- | Determine whether the event indicates cloning of an object within the
846+-- Applicable only when 'setFileEvents' is 'On'.
852+isCloned :: Event -> Bool
853+isCloned = getFlag kFSEventStreamEventFlagItemCloned
856+-------------------------------------------------------------------------------
857+-- Information about path type (applicable only when 'setFileEvents' is 'On')
858+-------------------------------------------------------------------------------
861+ "CoreServices/CoreServices.h value kFSEventStreamEventFlagItemIsDir"
862+ kFSEventStreamEventFlagItemIsDir :: Word64
864+-- | Determine whether the event is for a directory path.
866+-- Applicable only when 'setFileEvents' is 'On'.
872+isDir :: Event -> Bool
873+isDir = getFlag kFSEventStreamEventFlagItemIsDir
876+ "CoreServices/CoreServices.h value kFSEventStreamEventFlagItemIsFile"
877+ kFSEventStreamEventFlagItemIsFile :: Word64
879+-- | Determine whether the event is for a file path.
881+-- Applicable only when 'setFileEvents' is 'On'.
887+isFile :: Event -> Bool
888+isFile = getFlag kFSEventStreamEventFlagItemIsFile
891+ "CoreServices/CoreServices.h value kFSEventStreamEventFlagItemIsSymlink"
892+ kFSEventStreamEventFlagItemIsSymlink :: Word64
894+-- | Determine whether the event is for a symbolic link path.
896+-- Applicable only when 'setFileEvents' is 'On'.
902+isSymLink :: Event -> Bool
903+isSymLink = getFlag kFSEventStreamEventFlagItemIsSymlink
905+#if __MAC_OS_X_VERSION_MIN_REQUIRED >= 101000
907+ "CoreServices/CoreServices.h value kFSEventStreamEventFlagItemIsHardlink"
908+ kFSEventStreamEventFlagItemIsHardlink :: Word64
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.
916+-- Applicable only when 'setFileEvents' is 'On'.
922+isHardLink :: Event -> Bool
923+isHardLink = getFlag kFSEventStreamEventFlagItemIsHardlink
926+ "CoreServices/CoreServices.h value kFSEventStreamEventFlagItemIsLastHardlink"
927+ kFSEventStreamEventFlagItemIsLastHardlink :: Word64
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.
934+-- Applicable only when 'setFileEvents' is 'On'.
940+isLastHardLink :: Event -> Bool
941+isLastHardLink = getFlag kFSEventStreamEventFlagItemIsLastHardlink
944+-------------------------------------------------------------------------------
946+-------------------------------------------------------------------------------
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
957+ ++ showev isOverflow "Overflow"
958+ ++ showev isOverflowKernel "OverflowKernel"
959+ ++ showev isOverflowUser "OverflowUser"
961+ ++ showev isRootChanged "RootChanged"
962+ ++ showev isMount "Mount"
963+ ++ showev isUnmount "Unmount"
964+ ++ showev isHistoryDone "HistoryDone"
966+ ++ showev isOwnerGroupModeChanged "OwnerGroupModeChanged"
967+ ++ showev isInodeAttrsChanged "InodeAttrsChanged"
968+ ++ showev isFinderInfoChanged "FinderInfoChanged"
969+ ++ showev isXAttrsChanged "XAttrsChanged"
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"
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"
988+ where showev f str = if f ev then "\n" ++ str else ""
990+module Streamly.Internal.FileSystem.Event.Darwin () where