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 : pre-release
11-- Use 'watchRecursive' 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') $ 'watchRecursive' [Array.fromList "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.
25-- For reference documentation see:
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>"
30-- We try to keep the macOS\/Linux/Windows event handling APIs and defaults
31-- semantically and syntactically as close as possible.
35-- XXX Check the behavior for Windows/Linux where applicable. Mention these in
36-- the common module so that users are aware of the portability issues.
38-- There may be some idiosyncrasies in event reporting.
40-- 1. Multiple events may be coalesced into a single event having multiple
41-- flags set. For example, on macOS 10.15.6, "touch x; rm x" or "touch x; mv x
42-- y" produces an event with both "Created" and "Deleted/Moved" flags set.
44-- 2. The same event can occur multiple times. For example, "touch x; sleep 1;
45-- rm x" generates a "Created" event followed by an event with both "Created"
46-- and "Deleted" flags set. Similarly, a cloned event can also occur multiple
49-- 3. Some events can go missing and only the latest one is reported. See
50-- 'isCreated' for one such case.
52-- XXX write a test case for this, and check on all platforms.
54-- 4. When @rm -rf@ is used on the watched root directory, only one 'isDeleted'
55-- event occurs and that is for the root. However, @rm -rf@ on a directory
56-- inside the watch root produces 'isDeleted' events for all files.
58-- XXX write a test case for this, and check on all platforms.
60-- 5. When a file is moved, a move event occurs for both the source and the
61-- destination files. There seems to be no way to distinguish the source and
62-- the destination. Perhaps the lower eventId can be considered as source.
64-- XXX cloned event is specific to macOS. Translate it to the same events as
67-- 6. When a file is cloned both source and destination generate a cloned
68-- event, however the source has lower eventId and destination may have
69-- OwnerGroupModeChanged, InodeAttrsChanged, Created flags as well.
71-- You may have to stat the path of the event to know more information about
72-- what might have happened. It may be a good idea to watch the behavior of
73-- events you are handling before you can rely on it. See the individual event
74-- APIs for some of the known issues marked with `BUGS`. Also see the "Handling
75-- Events" section in the "Apple FS Events Programming Guide".
77-- * "<https://stackoverflow.com/questions/18415285/osx-fseventstreameventflags-not-working-correctly Stack overflow question on event coalescing>"
81-- APIs for persistent event-id related functionality, to get events from
82-- specific event-id etc are not implemented.
87#if HAVE_DECL_KFSEVENTSTREAMCREATEFLAGFILEEVENTS
89module Streamly.Internal.FileSystem.Event.Darwin
93 -- ** Default configuration
101 -- ** Events of Interest
105#if HAVE_DECL_KFSEVENTSTREAMCREATEFLAGFULLHISTORY
120 -- ** Root Level Events
121 -- | Events that belong to the root path as a whole and not to specific
122 -- items contained in it.
128 -- ** Item Level Metadata change
132 , isFinderInfoModified
134 -- ** Item Level CRUD events
139#if HAVE_DECL_KFSEVENTSTREAMEVENTFLAGITEMCLONED
147#if HAVE_DECL_KFSEVENTSTREAMEVENTFLAGITEMISHARDLINK
152 -- ** Exception Conditions
163import Control.Concurrent (MVar, newMVar, takeMVar, putMVar)
164import Control.Monad (when)
165import Data.Bits ((.|.), (.&.), complement)
166import Data.Functor.Identity (runIdentity)
167import Data.List.NonEmpty (NonEmpty)
168import Data.Word (Word8, Word32, Word64)
169import Foreign.C.Types (CInt(..), CDouble(..), CSize(..), CUChar(..))
170import Foreign.Marshal.Alloc (alloca)
171import Foreign.Marshal.Array (withArray)
172import Foreign.Ptr (Ptr, castPtr)
173import Foreign.Storable (Storable(..))
174import GHC.IO.Handle.FD (fdToHandle)
175import Streamly.Internal.Data.Stream (Stream)
176import Streamly.Internal.Data.Cont (contListMap)
177import Streamly.Internal.Data.Parser (Parser)
178import Streamly.Internal.Data.Array (Array(..))
179import System.IO (Handle, hClose)
181import qualified Data.List.NonEmpty as NonEmpty
182import qualified Streamly.Data.Fold as FL
183import qualified Streamly.Internal.Data.Parser as PR
184import qualified Streamly.Internal.Data.Stream as S
185import qualified Streamly.Internal.Unicode.Stream as U
186import qualified Streamly.Internal.FileSystem.Handle as FH
187import qualified Streamly.Internal.Data.Array as A
189-------------------------------------------------------------------------------
190-- Subscription to events
191-------------------------------------------------------------------------------
193-- XXX Add a recursive option to Config
194-- Keep setRecursiveMode False as undefined for now until we implement it.
196-- | Watch configuration, used to specify the events of interest and the
197-- behavior of the watch.
203 , createFlags :: Word32
206-------------------------------------------------------------------------------
208-------------------------------------------------------------------------------
210foreign import ccall safe
211 "FileSystem/Event/Darwin.h FSEventStreamCreateFlagNoDefer"
212 kFSEventStreamCreateFlagNoDefer :: Word32
214-- | Determines how multiple events are batched or throttled.
216 Throttle Double -- ^ Deliver an event immediately but suppress the
217 -- following events upto the specified time.
218 | Batch Double -- ^ Collapse all events that occurred in the specified
219 -- time window (in seconds) and deliver them as a single
222-- | Set how the events should be batched. See 'BatchInfo' for details. A
223-- negative value for time is treated as 0.
225-- /default: Batch 0.0/
231setEventBatching :: BatchInfo -> Config -> Config
232setEventBatching batchInfo cfg =
235 Throttle sec | sec < 0 -> (0, True)
236 Throttle sec -> (sec, True)
237 Batch sec | sec < 0 -> (0, False)
238 Batch sec -> (sec, False)
239 in setFlag kFSEventStreamCreateFlagNoDefer status $ cfg {latency = t}
241-------------------------------------------------------------------------------
243-------------------------------------------------------------------------------
245setFlag :: Word32 -> Bool -> Config -> Config
246setFlag mask status cfg@Config{..} =
249 then createFlags .|. mask
250 else createFlags .&. complement mask
251 in cfg {createFlags = flags}
253foreign import ccall safe
254 "FSEventStreamCreateFlagWatchRoot"
255 kFSEventStreamCreateFlagWatchRoot :: Word32
257-- XXX write a test case for this, and check on all platforms. The behavior on
258-- macOS is slightly different as a create event is also generated.
260-- | Any changes to the path (delete, rename, or create) of an object are
261-- reported as events for its parent directory. Such events are not reported
262-- for the watch root itself because its parent is not being watched. When
263-- 'setRootPathEvents' is 'True', an 'isRootPathEvent' event is generated with an
264-- eventId 0 if the watch root is deleted, renamed or created.
266-- Note: We have observed that an 'isDeleted' event occurs for the root path
267-- (when it is a regular dir and not symlink) even when this option is off.
275setRootPathEvents :: Bool -> Config -> Config
276setRootPathEvents = setFlag kFSEventStreamCreateFlagWatchRoot
278foreign import ccall safe
279 "FSEventStreamCreateFlagFileEvents"
280 kFSEventStreamCreateFlagFileEvents :: Word32
282-- | When this is 'False' only events for the watched directories are reported.
283-- For example, when a file is created inside a directory it is reported as an
284-- event, the path name in the event is the path of the directory being watched
285-- and not of the specific item for which the event occurred. Events cannot be
286-- distinguished based on the type of event. For example, we cannot determine
287-- if an event is a "create" or "delete", we only know that some event ocurred
288-- under the watched directory hierarchy.
290-- When it is 'True' the path reported in the event is the path of the item and
291-- not the of the directory in which it is contained. We can use the event
292-- accessor functions to determine the event type and the path type etc.
300setFileEvents :: Bool -> Config -> Config
301setFileEvents = setFlag kFSEventStreamCreateFlagFileEvents
303foreign import ccall safe
304 "FSEventStreamCreateFlagIgnoreSelf"
305 kFSEventStreamCreateFlagIgnoreSelf :: Word32
307-- | When this is 'True' events generated by the current process are not
316setIgnoreSelf :: Bool -> Config -> Config
317setIgnoreSelf = setFlag kFSEventStreamCreateFlagIgnoreSelf
319#if HAVE_DECL_KFSEVENTSTREAMCREATEFLAGFULLHISTORY
320foreign import ccall safe
321 "FSEventStreamCreateFlagFullHistory"
322 kFSEventStreamCreateFlagFullHistory :: Word32
324-- | When this is 'True' all events since the beginning of time are reported.
332setFullHistory :: Bool -> Config -> Config
333setFullHistory = setFlag kFSEventStreamCreateFlagFullHistory
336-- | Set all tunable events 'True' or 'False'. Equivalent to setting:
338-- * setRootPathEvents
343setAllEvents :: Bool -> Config -> Config
348-------------------------------------------------------------------------------
350-------------------------------------------------------------------------------
352-- | The default settings are:
354-- * 'setEventBatching' ('Batch' 0.0)
355-- * 'setFileEvents' 'True'
356-- * 'setRootPathEvents' 'False'
357-- * 'setIgnoreSelf' 'False'
358#if HAVE_DECL_KFSEVENTSTREAMCREATEFLAGFULLHISTORY
359-- * 'setFullHistory' 'False'
364defaultConfig :: Config
365defaultConfig = setFileEvents True $ Config
370-------------------------------------------------------------------------------
371-- Open an event stream (C FFI)
372-------------------------------------------------------------------------------
374-- See FileSystem/Event/Darwin.h
378-- Representation of "struct pathName" in "FileSystem/Event/Darwin.h"
379data PathName = PathName
380 { pathBytes :: Ptr CUChar
384instance Storable PathName where
385 alignment _ = sizeOf (undefined :: Ptr a)
386 sizeOf _ = sizeOf (undefined :: Ptr CUChar) + sizeOf (undefined :: CSize)
388 bptr <- peekByteOff ptr 0
389 len <- peekByteOff ptr (sizeOf (undefined :: Ptr CUChar))
390 return $ PathName bptr len
391 poke ptr PathName{..} = do
392 pokeByteOff ptr 0 pathBytes
393 pokeByteOff ptr (sizeOf (undefined :: Ptr CUChar)) pathLen
395foreign import ccall safe "FileSystem/Event/Darwin.h createWatch" createWatch
405foreign import ccall safe "FileSystem/Event/Darwin.h destroyWatch" destroyWatch
406 :: Ptr CWatch -> IO ()
408-------------------------------------------------------------------------------
409-- Open an event stream
410-------------------------------------------------------------------------------
412-- | A handle for a watch.
413data Watch = Watch Handle (Ptr CWatch) (MVar Bool)
415-- | Given a 'Config' and @paths@ start monitoring the paths for file system
416-- events. Returns a 'Watch' handle which can then be used to read the event
417-- stream or to close the watch.
419-- Implementation Note: This API creates an OS level thread where an event loop
420-- runs and writes the events to the write side fd of a POSIX pipe. Event
421-- stream APIs read the events from the read end of this pipe. Since an OS
422-- level thread is forked, creating a new watch stream is expensive. It is
423-- advised to minimize the number of watches.
427openWatch :: Config -> NonEmpty (Array Word8) -> IO Watch
428openWatch Config{..} paths = do
429 -- XXX write a test case when path does not exist
430 -- XXX write a test case when there are no permissions
431 -- XXX check whether the path exists. If the path does not exist and
432 -- is created later it cannot be properly monitored. Also a user may
433 -- inadvertently provide a path for which the user may not have permission
434 -- and then later wonder why events are not being reported.
435 withPathNames (NonEmpty.toList paths) $ \arraysPtr ->
436 alloca $ \fdPtr -> do
437 alloca $ \watchPPtr -> do
438 let nArrays = fromIntegral (NonEmpty.length paths)
439 seconds = realToFrac latency
441 arraysPtr nArrays createFlags 0 seconds fdPtr watchPPtr
443 ioError (userError "openWatch: failed to create watch.")
446 watchPtr <- peek watchPPtr
447 closeLock <- newMVar False
448 return $ Watch h watchPtr closeLock
452 withPathName :: Array Word8 -> (PathName -> IO a) -> IO a
453 withPathName arr act = do
454 A.unsafePinnedAsPtr arr $ \ptr ->
455 let pname = PathName (castPtr ptr) (fromIntegral (A.length arr))
458 withPathNames = contListMap withPathName withArray
460-- | Close a 'Watch' handle.
464closeWatch :: Watch -> IO ()
465closeWatch (Watch h watchPtr mvar) = do
467 closed <- takeMVar mvar
468 when (not closed) $ destroyWatch watchPtr
471-------------------------------------------------------------------------------
472-- Raw events read from the watch file handle
473-------------------------------------------------------------------------------
475-- | An Event generated by the file system. Use the accessor functions to
482 , eventFlags :: Word32
483 , eventAbsPath :: Array Word8
484 } deriving (Show, Ord, Eq)
486-- XXX We can perhaps use parseD monad instance for fusing with parseMany? Need
487-- to measure the perf.
489-- XXX should we use a magic in the header to avoid any issues due to
490-- misalignment of records? Can that ever happen?
492readOneEvent :: Parser Word8 IO Event
494 arr <- PR.takeEQ 24 (A.writeN 24)
495 let arr1 = A.castUnsafe arr :: Array Word64
496 eid = A.getIndexUnsafe 0 arr1
497 eflags = A.getIndexUnsafe 1 arr1
498 pathLen = fromIntegral $ A.getIndexUnsafe 2 arr1
499 path <- PR.takeEQ pathLen (A.writeN pathLen)
502 , eventFlags = fromIntegral eflags
503 , eventAbsPath = path
506watchToStream :: Watch -> Stream IO Event
507watchToStream (Watch handle _ _) =
508 S.catRights $ S.parseMany readOneEvent $ S.unfold FH.reader handle
510-- XXX Write tests for all the points in macOS specific behavior.
512-- | Start monitoring a list of file system paths for file system events with
513-- the supplied configuration operation over the 'defaultConfig'. The paths
514-- could be files or directories. When the path is a directory and recursive
515-- mode is set, the whole directory tree under it is watched recursively.
516-- Monitoring starts from the current time onwards. The paths are specified as
517-- UTF-8 encoded 'Array' of 'Word8'.
519-- Important notes for macOS specific behavior:
521-- From the observed behavior it seems macOS watches the paths, whatever they
522-- are pointing to at any given point of time:
524-- /Watch root deletion:/ If the the watch root is deleted and then recreated,
525-- the newly created file or directory is automatically watched.
527-- /Watch root moved:/ If the watch root is moved to a new path, the object
528-- will no longer be watched unless the new path is also being watched and was
529-- pointing to an existing file at the time of starting the watch (see notes
530-- about non-existing paths below).
532-- /Symbolic link watch root:/ If the path name to be watched is a symbolic
533-- link then the target of the link is watched instead of the symbolic link
534-- itself. It is equivalent to as if the target of the symbolic link itself was
535-- directly added to the watch API. That is, the symbolic link is resolved at
536-- the time of adding the watch.
538-- Note that if a watched path is deleted and recreated as a symbolic link
539-- pointing to another path then the symbolic link file itself is watched, it
540-- won't be resolved. The symbolic link resolution happens only at the time of
543-- /Non-existing Paths:/ If a watch is started on a non-existing path then the
544-- path is not watched even if it is created later. The macOS API does not
545-- fail for a non-existing path.
547-- If a non-existing path is watched with 'setRootPathEvents' then an
548-- 'isRootPathEvent' event is reported if the path is created later and the
549-- "path" field in the event is set to the dirname of the path rather than the
550-- full absolute path. This is the observed behavior on macOS 10.15.1.
553-- {-\# LANGUAGE MagicHash #-}
555-- ('setIgnoreSelf' 'True' . 'setRootPathEvents' 'True')
556-- [Array.fromList "path"]
562 (Config -> Config) -> NonEmpty (Array Word8) -> Stream IO Event
563watchWith f paths = S.bracketIO before after watchToStream
567 before = openWatch (f defaultConfig) paths
570-- | Same as 'watchWith' using 'defaultConfig' and recursive mode.
572-- >>> watchRecursive = watchWith id
574watchRecursive :: NonEmpty (Array Word8) -> Stream IO Event
575watchRecursive = watchWith id
577-- | Same as 'watchWith' using defaultConfig and non-recursive mode.
581watch :: NonEmpty (Array Word8) -> Stream IO Event
582watch _paths = undefined
584-------------------------------------------------------------------------------
585-- Examine the event stream
586-------------------------------------------------------------------------------
588-- | Get the event id of an 'Event'. Event-id is a monotonically increasing
589-- 64-bit integer identifying an event uniquely.
593getEventId :: Event -> Word64
594getEventId Event{..} = eventId
596foreign import ccall safe
597 "FSEventStreamEventFlagEventIdsWrapped"
598 kFSEventStreamEventFlagEventIdsWrapped :: Word32
600-- | Determine whether the event id has wrapped. This is impossible on any
601-- traditional hardware in any reasonable amount of time unless the event-id
602-- starts from a very high value, because the event-id is a 64-bit integer.
603-- However, apple still recommends to check and handle this flag.
609isEventIdWrapped :: Event -> Bool
610isEventIdWrapped = getFlag kFSEventStreamEventFlagEventIdsWrapped
612-- | Get the absolute path of the file system object for which the event is
613-- generated. The path is a UTF-8 encoded array of bytes.
615-- When the watch root is a symlink, the absolute path returned is via the real
616-- path of the root after resolving the symlink.
620getAbsPath :: Event -> Array Word8
621getAbsPath Event{..} = eventAbsPath
623-------------------------------------------------------------------------------
625-------------------------------------------------------------------------------
627getFlag :: Word32 -> Event -> Bool
628getFlag mask Event{..} = eventFlags .&. mask /= 0
630-------------------------------------------------------------------------------
632-------------------------------------------------------------------------------
634foreign import ccall safe
635 "FSEventStreamEventFlagMustScanSubDirs"
636 kFSEventStreamEventFlagMustScanSubDirs :: Word32
638-- | Apple documentation says that "If an event in a directory occurs at about
639-- the same time as one or more events in a subdirectory of that directory, the
640-- events may be coalesced into a single event." In that case you will recieve
641-- 'isEventsLost' event. In that case the path listed in the event is
642-- invalidated and you must rescan it to know the current state.
644-- This event can also occur if a communication error (overflow) occurs in
645-- sending the event and the event gets dropped. In that case 'isKernelDropped'
646-- and/or 'isUserDropped' attributes will also be set.
652isEventsLost :: Event -> Bool
653isEventsLost = getFlag kFSEventStreamEventFlagMustScanSubDirs
655foreign import ccall safe
656 "FSEventStreamEventFlagKernelDropped"
657 kFSEventStreamEventFlagKernelDropped :: Word32
659-- | Did an event get dropped due to a kernel processing issue? Set only when
660-- 'isEventsLost' is also true.
666isKernelDropped :: Event -> Bool
667isKernelDropped = getFlag kFSEventStreamEventFlagKernelDropped
669foreign import ccall safe
670 "FSEventStreamEventFlagUserDropped"
671 kFSEventStreamEventFlagUserDropped :: Word32
673-- | Did an event get dropped due to a user process issue? Set only when
674-- 'isEventsLost' is also true.
680isUserDropped :: Event -> Bool
681isUserDropped = getFlag kFSEventStreamEventFlagUserDropped
683-------------------------------------------------------------------------------
684-- Global Sentinel Events
685-------------------------------------------------------------------------------
687foreign import ccall safe
688 "FSEventStreamEventFlagHistoryDone"
689 kFSEventStreamEventFlagHistoryDone :: Word32
691-- | Determine whether the event is a history done marker event. A history done
692-- event is generated when the historical events from before the current time
693-- are done. Historical events are generated when the "since" parameter in the
694-- watch config is set to before the current time.
700isHistoryDone :: Event -> Bool
701isHistoryDone = getFlag kFSEventStreamEventFlagHistoryDone
703-------------------------------------------------------------------------------
704-- Events affecting the watched path only
705-------------------------------------------------------------------------------
707foreign import ccall safe
708 "FSEventStreamEventFlagRootChanged"
709 kFSEventStreamEventFlagRootChanged :: Word32
711-- | Determine whether the event indicates a change of path of the monitored
712-- object itself. Note that the object may become unreachable or deleted after
715-- /Applicable only when 'setRootPathEvents' is 'True'/
717-- /Occurs only for the watched path/
723isRootPathEvent :: Event -> Bool
724isRootPathEvent = getFlag kFSEventStreamEventFlagRootChanged
726-------------------------------------------------------------------------------
727-- Global events under the watched path
728-------------------------------------------------------------------------------
730foreign import ccall safe
731 "FSEventStreamEventFlagMount"
732 kFSEventStreamEventFlagMount :: Word32
734-- | Determine whether the event is a mount event. A mount event is generated
735-- if a volume is mounted under the path being watched.
741isMounted :: Event -> Bool
742isMounted = getFlag kFSEventStreamEventFlagMount
744foreign import ccall safe
745 "FSEventStreamEventFlagUnmount"
746 kFSEventStreamEventFlagUnmount :: Word32
748-- | Determine whether the event is an unmount event. An unmount event is
749-- generated if a volume mounted under the path being watched is unmounted.
755isUnmounted :: Event -> Bool
756isUnmounted = getFlag kFSEventStreamEventFlagUnmount
758-------------------------------------------------------------------------------
759-- Metadata change Events (applicable only when 'setFileEvents' is 'True')
760-------------------------------------------------------------------------------
762foreign import ccall safe
763 "FSEventStreamEventFlagItemChangeOwner"
764 kFSEventStreamEventFlagItemChangeOwner :: Word32
766-- | Determine whether the event is ownership, group, permissions or ACL change
767-- event of an object contained within the monitored path.
769-- Note that this event may be generated even if the metadata is changed to the
772-- /Applicable only when 'setFileEvents' is 'True'/
774-- /Can occur for watched path or a file inside it/
780isSecurityModified :: Event -> Bool
781isSecurityModified = getFlag kFSEventStreamEventFlagItemChangeOwner
783foreign import ccall safe
784 "FSEventStreamEventFlagItemInodeMetaMod"
785 kFSEventStreamEventFlagItemInodeMetaMod :: Word32
787-- | Determine whether the event indicates inode metadata change for an object
788-- contained within the monitored path. This event is generated when inode
789-- attributes other than the owner, group or permissions are changed e.g. file
790-- modification time. It does not occur when the link count of a file changes
791-- (as of macOS 10.15.1).
793-- /Applicable only when 'setFileEvents' is 'True'/
795-- /Can occur for watched path or a file inside it/
801isAttrsModified :: Event -> Bool
802isAttrsModified = getFlag kFSEventStreamEventFlagItemInodeMetaMod
804foreign import ccall safe
805 "FSEventStreamEventFlagItemFinderInfoMod"
806 kFSEventStreamEventFlagItemFinderInfoMod :: Word32
808-- | Determine whether the event indicates finder information metadata change
809-- for an object contained within the monitored path.
811-- /Applicable only when 'setFileEvents' is 'True'/
817isFinderInfoModified :: Event -> Bool
818isFinderInfoModified = getFlag kFSEventStreamEventFlagItemFinderInfoMod
820foreign import ccall safe
821 "FSEventStreamEventFlagItemXattrMod"
822 kFSEventStreamEventFlagItemXattrMod :: Word32
824-- | Determine whether the event indicates extended attributes metadata change
825-- for an object contained within the monitored path.
827-- /Applicable only when 'setFileEvents' is 'True'/
829-- /Can occur for watched path or a file inside it/
835isXAttrsModified :: Event -> Bool
836isXAttrsModified = getFlag kFSEventStreamEventFlagItemXattrMod
838-------------------------------------------------------------------------------
839-- CRUD Events (applicable only when 'setFileEvents' is 'True')
840-------------------------------------------------------------------------------
842foreign import ccall safe
843 "FSEventStreamEventFlagItemCreated"
844 kFSEventStreamEventFlagItemCreated :: Word32
846-- | Determine whether the event indicates creation of an object within the
847-- monitored path. This event is generated when any file system object other
848-- than a hard link is created. On hard linking only an 'isAttrsModified'
849-- event on the directory is generated, it is not a create event. However, when
850-- a hard link is deleted 'isDeleted' and 'isHardLink' both are true.
852-- This event can occur for the watched path if the path was deleted/moved and
853-- created again (tested on macOS 10.15.1). However, if a watch is started on a
854-- non-existing path and the path is created later, then this event is not
855-- generated, the path is not watched.
857-- BUGS: On 10.15.1 when we use a "touch x" to create a file for the first time
858-- only an 'isAttrsModified' event occurs and there is no 'isCreated'
859-- event. However, this seems to have been fixed on 10.15.6.
861-- /Applicable only when 'setFileEvents' is 'True'/
863-- /Can occur for watched path or a file inside it/
869isCreated :: Event -> Bool
870isCreated = getFlag kFSEventStreamEventFlagItemCreated
872foreign import ccall safe
873 "FSEventStreamEventFlagItemRemoved"
874 kFSEventStreamEventFlagItemRemoved :: Word32
876-- | Determine whether the event indicates deletion of an object within the
877-- monitored path. This is true when a file or a hardlink is deleted.
879-- Applicable only when 'setFileEvents' is 'True'.
881-- /Can occur for watched path or a file inside it/
887isDeleted :: Event -> Bool
888isDeleted = getFlag kFSEventStreamEventFlagItemRemoved
890foreign import ccall safe
891 "FSEventStreamEventFlagItemRenamed"
892 kFSEventStreamEventFlagItemRenamed :: Word32
894-- | Determine whether the event indicates rename of an object within the
895-- monitored path. This event is generated when a file is renamed within the
896-- watched directory or if it is moved out of or in the watched directory.
898-- /Applicable only when 'setFileEvents' is 'True'/
900-- /Can occur for watched path or a file inside it/
906isMoved :: Event -> Bool
907isMoved = getFlag kFSEventStreamEventFlagItemRenamed
909foreign import ccall safe
910 "FSEventStreamEventFlagItemModified"
911 kFSEventStreamEventFlagItemModified :: Word32
913-- | Determine whether the event indicates modification of an object within the
914-- monitored path. This event is generated only for files and not directories.
916-- Applicable only when 'setFileEvents' is 'True'.
922isModified :: Event -> Bool
923isModified = getFlag kFSEventStreamEventFlagItemModified
925#if HAVE_DECL_KFSEVENTSTREAMEVENTFLAGITEMCLONED
926foreign import ccall safe
927 "FSEventStreamEventFlagItemCloned"
928 kFSEventStreamEventFlagItemCloned :: Word32
930-- | Determine whether the event indicates cloning of an object within the
931-- monitored path. The "Duplicate" command in the "File" menu of the "Finder"
932-- application generates a "clone" event.
934-- Applicable only when 'setFileEvents' is 'True'.
940isCloned :: Event -> Bool
941isCloned = getFlag kFSEventStreamEventFlagItemCloned
944-------------------------------------------------------------------------------
945-- Information about path type (applicable only when 'setFileEvents' is 'True')
946-------------------------------------------------------------------------------
948foreign import ccall safe
949 "FSEventStreamEventFlagItemIsDir"
950 kFSEventStreamEventFlagItemIsDir :: Word32
952-- | Determine whether the event is for a directory path.
954-- Applicable only when 'setFileEvents' is 'True'.
960isDir :: Event -> Bool
961isDir = getFlag kFSEventStreamEventFlagItemIsDir
963foreign import ccall safe
964 "FSEventStreamEventFlagItemIsFile"
965 kFSEventStreamEventFlagItemIsFile :: Word32
967-- | Determine whether the event is for a file path.
969-- Applicable only when 'setFileEvents' is 'True'.
975isFile :: Event -> Bool
976isFile = getFlag kFSEventStreamEventFlagItemIsFile
978foreign import ccall safe
979 "FSEventStreamEventFlagItemIsSymlink"
980 kFSEventStreamEventFlagItemIsSymlink :: Word32
982-- | Determine whether the event is for a symbolic link path.
984-- Applicable only when 'setFileEvents' is 'True'.
990isSymLink :: Event -> Bool
991isSymLink = getFlag kFSEventStreamEventFlagItemIsSymlink
993#if HAVE_DECL_KFSEVENTSTREAMEVENTFLAGITEMISHARDLINK
994foreign import ccall safe
995 "FSEventStreamEventFlagItemIsHardlink"
996 kFSEventStreamEventFlagItemIsHardlink :: Word32
998-- | Determine whether the event is for a file with more than one hard link.
999-- When 'isFile' is true we can check for 'isHardLink'. Note that 'isHardLink'
1000-- is not true when a hard link is created, however, it is true when a file
1001-- which has or had more than one link in the past is removed. This is not true
1002-- if a file never had more than one link.
1004-- Applicable only when 'setFileEvents' is 'True'.
1010isHardLink :: Event -> Bool
1011isHardLink = getFlag kFSEventStreamEventFlagItemIsHardlink
1013foreign import ccall safe
1014 "FSEventStreamEventFlagItemIsLastHardlink"
1015 kFSEventStreamEventFlagItemIsLastHardlink :: Word32
1017-- | Determine whether the event is for a hard link path with only one hard
1018-- link. If 'isHardLink' is true then we can check for 'isLastHardLink'. This
1019-- is true when a file has had more than one link and now the last link is
1020-- removed. In that case both 'isHardLink' and 'isLastHardLink" would be true.
1022-- Applicable only when 'setFileEvents' is 'True'.
1028isLastHardLink :: Event -> Bool
1029isLastHardLink = getFlag kFSEventStreamEventFlagItemIsLastHardlink
1032-------------------------------------------------------------------------------
1034-------------------------------------------------------------------------------
1036-- | Convert an 'Event' record to a String representation.
1037showEvent :: Event -> String
1038showEvent ev@Event{..} =
1039 let path = runIdentity $ S.fold FL.toList $ U.decodeUtf8' $ A.toStream eventAbsPath
1040 in "--------------------------"
1041 ++ "\nId = " ++ show eventId
1042 ++ "\nPath = " ++ show path
1043 ++ "\nFlags " ++ show eventFlags
1045 ++ showev isEventsLost "MustScanSubdirs"
1046 ++ showev isKernelDropped "KernelDropped"
1047 ++ showev isUserDropped "UserDropped"
1049 ++ showev isRootPathEvent "RootPathEvent"
1050 ++ showev isMounted "Mounted"
1051 ++ showev isUnmounted "Unmounted"
1052 ++ showev isHistoryDone "HistoryDone"
1054 ++ showev isSecurityModified "SecurityModified"
1055 ++ showev isAttrsModified "AttrsModified"
1056 ++ showev isFinderInfoModified "FinderInfoChanged"
1057 ++ showev isXAttrsModified "XAttrsModified"
1059 ++ showev isCreated "Created"
1060 ++ showev isDeleted "Deleted"
1061 ++ showev isModified "Modified"
1062 ++ showev isMoved "Moved"
1063#if HAVE_DECL_KFSEVENTSTREAMEVENTFLAGITEMCLONED
1064 ++ showev isCloned "Cloned"
1067 ++ showev isDir "Dir"
1068 ++ showev isFile "File"
1069 ++ showev isSymLink "SymLink"
1070#if HAVE_DECL_KFSEVENTSTREAMEVENTFLAGITEMISHARDLINK
1071 ++ showev isHardLink "HardLink"
1072 ++ showev isLastHardLink "LastHardLink"
1076 where showev f str = if f ev then "\n" ++ str else ""
1078module Streamly.Internal.FileSystem.Event.Darwin () where
1079#warning "Autoconf did not find the definition \
1080kFSEventStreamCreateFlagFileEvents in Darwin header files.\
1081Do you have Cocoa framework header files installed?\
1082Not compiling the Streamly.Internal.FileSystem.Event.Darwin module. \
1083Programs depending on this module may not compile. \
1084Check if HAVE_DECL_KFSEVENTSTREAMCREATEFLAGFILEEVENTS is defined in config.h \
1085generated from src/config.h.in"
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list