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.fromCString\# "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
102 -- ** Events of Interest
106#if HAVE_DECL_KFSEVENTSTREAMCREATEFLAGFULLHISTORY
121 -- ** Root Level Events
122 -- | Events that belong to the root path as a whole and not to specific
123 -- items contained in it.
129 -- ** Item Level Metadata change
133 , isFinderInfoModified
135 -- ** Item Level CRUD events
140#if HAVE_DECL_KFSEVENTSTREAMEVENTFLAGITEMCLONED
148#if HAVE_DECL_KFSEVENTSTREAMEVENTFLAGITEMISHARDLINK
153 -- ** Exception Conditions
164import Control.Concurrent (MVar, newMVar, takeMVar, putMVar)
165import Control.Monad (when)
166import Control.Monad.IO.Class (liftIO)
167import Data.Bits ((.|.), (.&.), complement)
168import Data.Functor.Identity (runIdentity)
169import Data.List.NonEmpty (NonEmpty)
170import Data.Word (Word8, Word32, Word64)
171import Foreign.C.Types (CInt(..), CDouble(..), CSize(..), CUChar(..))
172import Foreign.Marshal.Alloc (alloca)
173import Foreign.Marshal.Array (withArray)
174import Foreign.Ptr (Ptr, castPtr)
175import Foreign.Storable (Storable(..))
176import GHC.IO.Handle.FD (fdToHandle)
177import Streamly.Prelude (SerialT)
178import Streamly.Internal.Data.Cont (contListMap)
179import Streamly.Internal.Data.Parser (Parser)
180import Streamly.Internal.Data.Array.Foreign.Type (Array(..))
181import System.IO (Handle, hClose)
183import qualified Data.List.NonEmpty as NonEmpty
184import qualified Streamly.Internal.Data.Parser as PR
185import qualified Streamly.Internal.Data.Stream.IsStream as S
186import qualified Streamly.Internal.Unicode.Stream as U
187import qualified Streamly.Internal.FileSystem.Handle as FH
188import qualified Streamly.Internal.Data.Array.Foreign as A
190-------------------------------------------------------------------------------
191-- Subscription to events
192-------------------------------------------------------------------------------
194-- XXX Add a recursive option to Config
195-- Keep setRecursiveMode Off as undefined for now until we implement it.
197-- | Watch configuration, used to specify the events of interest and the
198-- behavior of the watch.
204 , createFlags :: Word32
207-------------------------------------------------------------------------------
209-------------------------------------------------------------------------------
211foreign import ccall safe
212 "FileSystem/Event/Darwin.h FSEventStreamCreateFlagNoDefer"
213 kFSEventStreamCreateFlagNoDefer :: Word32
215-- | Determines how multiple events are batched or throttled.
217 Throttle Double -- ^ Deliver an event immediately but suppress the
218 -- following events upto the specified time.
219 | Batch Double -- ^ Collapse all events that occurred in the specified
220 -- time window (in seconds) and deliver them as a single
223-- | Set how the events should be batched. See 'BatchInfo' for details. A
224-- negative value for time is treated as 0.
226-- /default: Batch 0.0/
232setEventBatching :: BatchInfo -> Config -> Config
233setEventBatching batchInfo cfg =
236 Throttle sec | sec < 0 -> (0, On)
237 Throttle sec -> (sec, On)
238 Batch sec | sec < 0 -> (0, Off)
239 Batch sec -> (sec, Off)
240 in setFlag kFSEventStreamCreateFlagNoDefer status $ cfg {latency = t}
242-------------------------------------------------------------------------------
244-------------------------------------------------------------------------------
246-- | Whether a setting is 'On' or 'Off'.
250data Switch = On | Off
252setFlag :: Word32 -> Switch -> Config -> Config
253setFlag mask status cfg@Config{..} =
256 On -> createFlags .|. mask
257 Off -> createFlags .&. complement mask
258 in cfg {createFlags = flags}
260foreign import ccall safe
261 "FSEventStreamCreateFlagWatchRoot"
262 kFSEventStreamCreateFlagWatchRoot :: Word32
264-- XXX write a test case for this, and check on all platforms. The behavior on
265-- macOS is slightly different as a create event is also generated.
267-- | Any changes to the path (delete, rename, or create) of an object are
268-- reported as events for its parent directory. Such events are not reported
269-- for the watch root itself because its parent is not being watched. When
270-- 'setRootPathEvents' is 'On', an 'isRootPathEvent' event is generated with an
271-- eventId 0 if the watch root is deleted, renamed or created.
273-- Note: We have observed that an 'isDeleted' event occurs for the root path
274-- (when it is a regular dir and not symlink) even when this option is off.
282setRootPathEvents :: Switch -> Config -> Config
283setRootPathEvents = setFlag kFSEventStreamCreateFlagWatchRoot
285foreign import ccall safe
286 "FSEventStreamCreateFlagFileEvents"
287 kFSEventStreamCreateFlagFileEvents :: Word32
289-- | When this is 'Off' only events for the watched directories are reported.
290-- For example, when a file is created inside a directory it is reported as an
291-- event, the path name in the event is the path of the directory being watched
292-- and not of the specific item for which the event occurred. Events cannot be
293-- distinguished based on the type of event. For example, we cannot determine
294-- if an event is a "create" or "delete", we only know that some event ocurred
295-- under the watched directory hierarchy.
297-- When it is 'On' the path reported in the event is the path of the item and
298-- not the of the directory in which it is contained. We can use the event
299-- accessor functions to determine the event type and the path type etc.
307setFileEvents :: Switch -> Config -> Config
308setFileEvents = setFlag kFSEventStreamCreateFlagFileEvents
310foreign import ccall safe
311 "FSEventStreamCreateFlagIgnoreSelf"
312 kFSEventStreamCreateFlagIgnoreSelf :: Word32
314-- | When this is 'On' events generated by the current process are not
323setIgnoreSelf :: Switch -> Config -> Config
324setIgnoreSelf = setFlag kFSEventStreamCreateFlagIgnoreSelf
326#if HAVE_DECL_KFSEVENTSTREAMCREATEFLAGFULLHISTORY
327foreign import ccall safe
328 "FSEventStreamCreateFlagFullHistory"
329 kFSEventStreamCreateFlagFullHistory :: Word32
331-- | When this is 'On' all events since the beginning of time are reported.
339setFullHistory :: Switch -> Config -> Config
340setFullHistory = setFlag kFSEventStreamCreateFlagFullHistory
343-- | Set all tunable events 'On' or 'Off'. Equivalent to setting:
345-- * setRootPathEvents
350setAllEvents :: Switch -> Config -> Config
355-------------------------------------------------------------------------------
357-------------------------------------------------------------------------------
359-- | The default settings are:
361-- * 'setEventBatching' ('Batch' 0.0)
362-- * 'setFileEvents' 'On'
363-- * 'setRootPathEvents' 'Off'
364-- * 'setIgnoreSelf' 'Off'
365#if HAVE_DECL_KFSEVENTSTREAMCREATEFLAGFULLHISTORY
366-- * 'setFullHistory' 'Off'
371defaultConfig :: Config
372defaultConfig = setFileEvents On $ Config
377-------------------------------------------------------------------------------
378-- Open an event stream (C FFI)
379-------------------------------------------------------------------------------
381-- See FileSystem/Event/Darwin.h
385-- Representation of "struct pathName" in "FileSystem/Event/Darwin.h"
386data PathName = PathName
387 { pathBytes :: Ptr CUChar
391instance Storable PathName where
392 alignment _ = sizeOf (undefined :: Ptr a)
393 sizeOf _ = sizeOf (undefined :: Ptr CUChar) + sizeOf (undefined :: CSize)
395 bptr <- peekByteOff ptr 0
396 len <- peekByteOff ptr (sizeOf (undefined :: Ptr CUChar))
397 return $ PathName bptr len
398 poke ptr PathName{..} = do
399 pokeByteOff ptr 0 pathBytes
400 pokeByteOff ptr (sizeOf (undefined :: Ptr CUChar)) pathLen
402foreign import ccall safe "FileSystem/Event/Darwin.h createWatch" createWatch
412foreign import ccall safe "FileSystem/Event/Darwin.h destroyWatch" destroyWatch
413 :: Ptr CWatch -> IO ()
415-------------------------------------------------------------------------------
416-- Open an event stream
417-------------------------------------------------------------------------------
419-- | A handle for a watch.
420data Watch = Watch Handle (Ptr CWatch) (MVar Bool)
422-- | Given a 'Config' and @paths@ start monitoring the paths for file system
423-- events. Returns a 'Watch' handle which can then be used to read the event
424-- stream or to close the watch.
426-- Implementation Note: This API creates an OS level thread where an event loop
427-- runs and writes the events to the write side fd of a POSIX pipe. Event
428-- stream APIs read the events from the read end of this pipe. Since an OS
429-- level thread is forked, creating a new watch stream is expensive. It is
430-- advised to minimize the number of watches.
434openWatch :: Config -> NonEmpty (Array Word8) -> IO Watch
435openWatch Config{..} paths = do
436 -- XXX write a test case when path does not exist
437 -- XXX write a test case when there are no permissions
438 -- XXX check whether the path exists. If the path does not exist and
439 -- is created later it cannot be properly monitored. Also a user may
440 -- inadvertently provide a path for which the user may not have permission
441 -- and then later wonder why events are not being reported.
442 withPathNames (NonEmpty.toList paths) $ \arraysPtr ->
443 alloca $ \fdPtr -> do
444 alloca $ \watchPPtr -> do
445 let nArrays = fromIntegral (NonEmpty.length paths)
446 seconds = realToFrac latency
448 arraysPtr nArrays createFlags 0 seconds fdPtr watchPPtr
450 ioError (userError "openWatch: failed to create watch.")
453 watchPtr <- peek watchPPtr
454 closeLock <- newMVar False
455 return $ Watch h watchPtr closeLock
459 withPathName :: Array Word8 -> (PathName -> IO a) -> IO a
460 withPathName arr act = do
461 A.asPtrUnsafe arr $ \ptr ->
462 let pname = PathName (castPtr ptr) (fromIntegral (A.length arr))
465 withPathNames = contListMap withPathName withArray
467-- | Close a 'Watch' handle.
471closeWatch :: Watch -> IO ()
472closeWatch (Watch h watchPtr mvar) = do
474 closed <- takeMVar mvar
475 when (not closed) $ destroyWatch watchPtr
478-------------------------------------------------------------------------------
479-- Raw events read from the watch file handle
480-------------------------------------------------------------------------------
482-- | An Event generated by the file system. Use the accessor functions to
489 , eventFlags :: Word32
490 , eventAbsPath :: Array Word8
491 } deriving (Show, Ord, Eq)
493-- XXX We can perhaps use parseD monad instance for fusing with parseMany? Need
494-- to measure the perf.
496-- XXX should we use a magic in the header to avoid any issues due to
497-- misalignment of records? Can that ever happen?
499readOneEvent :: Parser IO Word8 Event
501 arr <- PR.takeEQ 24 (A.writeN 24)
502 let arr1 = A.castUnsafe arr :: Array Word64
503 eid = A.unsafeIndex 0 arr1
504 eflags = A.unsafeIndex 1 arr1
505 pathLen = fromIntegral $ A.unsafeIndex 2 arr1
506 path <- PR.takeEQ pathLen (A.writeN pathLen)
509 , eventFlags = fromIntegral eflags
510 , eventAbsPath = path
513watchToStream :: Watch -> SerialT IO Event
514watchToStream (Watch handle _ _) =
515 S.parseMany readOneEvent $ S.unfold FH.read handle
517-- XXX Write tests for all the points in macOS specific behavior.
519-- | Start monitoring a list of file system paths for file system events with
520-- the supplied configuration operation over the 'defaultConfig'. The paths
521-- could be files or directories. When the path is a directory and recursive
522-- mode is set, the whole directory tree under it is watched recursively.
523-- Monitoring starts from the current time onwards. The paths are specified as
524-- UTF-8 encoded 'Array' of 'Word8'.
526-- Important notes for macOS specific behavior:
528-- From the observed behavior it seems macOS watches the paths, whatever they
529-- are pointing to at any given point of time:
531-- /Watch root deletion:/ If the the watch root is deleted and then recreated,
532-- the newly created file or directory is automatically watched.
534-- /Watch root moved:/ If the watch root is moved to a new path, the object
535-- will no longer be watched unless the new path is also being watched and was
536-- pointing to an existing file at the time of starting the watch (see notes
537-- about non-existing paths below).
539-- /Symbolic link watch root:/ If the path name to be watched is a symbolic
540-- link then the target of the link is watched instead of the symbolic link
541-- itself. It is equivalent to as if the target of the symbolic link itself was
542-- directly added to the watch API. That is, the symbolic link is resolved at
543-- the time of adding the watch.
545-- Note that if a watched path is deleted and recreated as a symbolic link
546-- pointing to another path then the symbolic link file itself is watched, it
547-- won't be resolved. The symbolic link resolution happens only at the time of
550-- /Non-existing Paths:/ If a watch is started on a non-existing path then the
551-- path is not watched even if it is created later. The macOS API does not
552-- fail for a non-existing path.
554-- If a non-existing path is watched with 'setRootPathEvents' then an
555-- 'isRootPathEvent' event is reported if the path is created later and the
556-- "path" field in the event is set to the dirname of the path rather than the
557-- full absolute path. This is the observed behavior on macOS 10.15.1.
560-- {-\# LANGUAGE MagicHash #-}
562-- ('setIgnoreSelf' 'On' . 'setRootPathEvents' 'On')
563-- [Array.fromCString\# "path"#]
569 (Config -> Config) -> NonEmpty (Array Word8) -> SerialT IO Event
570watchWith f paths = S.bracket before after watchToStream
574 before = liftIO $ openWatch (f defaultConfig) paths
575 after = liftIO . closeWatch
577-- | Same as 'watchWith' using 'defaultConfig' and recursive mode.
579-- >>> watchRecursive = watchWith id
581watchRecursive :: NonEmpty (Array Word8) -> SerialT IO Event
582watchRecursive = watchWith id
584-- | Same as 'watchWith' using defaultConfig and non-recursive mode.
588watch :: NonEmpty (Array Word8) -> SerialT IO Event
589watch _paths = undefined
591-------------------------------------------------------------------------------
592-- Examine the event stream
593-------------------------------------------------------------------------------
595-- | Get the event id of an 'Event'. Event-id is a monotonically increasing
596-- 64-bit integer identifying an event uniquely.
600getEventId :: Event -> Word64
601getEventId Event{..} = eventId
603foreign import ccall safe
604 "FSEventStreamEventFlagEventIdsWrapped"
605 kFSEventStreamEventFlagEventIdsWrapped :: Word32
607-- | Determine whether the event id has wrapped. This is impossible on any
608-- traditional hardware in any reasonable amount of time unless the event-id
609-- starts from a very high value, because the event-id is a 64-bit integer.
610-- However, apple still recommends to check and handle this flag.
616isEventIdWrapped :: Event -> Bool
617isEventIdWrapped = getFlag kFSEventStreamEventFlagEventIdsWrapped
619-- | Get the absolute path of the file system object for which the event is
620-- generated. The path is a UTF-8 encoded array of bytes.
622-- When the watch root is a symlink, the absolute path returned is via the real
623-- path of the root after resolving the symlink.
627getAbsPath :: Event -> Array Word8
628getAbsPath Event{..} = eventAbsPath
630-------------------------------------------------------------------------------
632-------------------------------------------------------------------------------
634getFlag :: Word32 -> Event -> Bool
635getFlag mask Event{..} = eventFlags .&. mask /= 0
637-------------------------------------------------------------------------------
639-------------------------------------------------------------------------------
641foreign import ccall safe
642 "FSEventStreamEventFlagMustScanSubDirs"
643 kFSEventStreamEventFlagMustScanSubDirs :: Word32
645-- | Apple documentation says that "If an event in a directory occurs at about
646-- the same time as one or more events in a subdirectory of that directory, the
647-- events may be coalesced into a single event." In that case you will recieve
648-- 'isEventsLost' event. In that case the path listed in the event is
649-- invalidated and you must rescan it to know the current state.
651-- This event can also occur if a communication error (overflow) occurs in
652-- sending the event and the event gets dropped. In that case 'isKernelDropped'
653-- and/or 'isUserDropped' attributes will also be set.
659isEventsLost :: Event -> Bool
660isEventsLost = getFlag kFSEventStreamEventFlagMustScanSubDirs
662foreign import ccall safe
663 "FSEventStreamEventFlagKernelDropped"
664 kFSEventStreamEventFlagKernelDropped :: Word32
666-- | Did an event get dropped due to a kernel processing issue? Set only when
667-- 'isEventsLost' is also true.
673isKernelDropped :: Event -> Bool
674isKernelDropped = getFlag kFSEventStreamEventFlagKernelDropped
676foreign import ccall safe
677 "FSEventStreamEventFlagUserDropped"
678 kFSEventStreamEventFlagUserDropped :: Word32
680-- | Did an event get dropped due to a user process issue? Set only when
681-- 'isEventsLost' is also true.
687isUserDropped :: Event -> Bool
688isUserDropped = getFlag kFSEventStreamEventFlagUserDropped
690-------------------------------------------------------------------------------
691-- Global Sentinel Events
692-------------------------------------------------------------------------------
694foreign import ccall safe
695 "FSEventStreamEventFlagHistoryDone"
696 kFSEventStreamEventFlagHistoryDone :: Word32
698-- | Determine whether the event is a history done marker event. A history done
699-- event is generated when the historical events from before the current time
700-- are done. Historical events are generated when the "since" parameter in the
701-- watch config is set to before the current time.
707isHistoryDone :: Event -> Bool
708isHistoryDone = getFlag kFSEventStreamEventFlagHistoryDone
710-------------------------------------------------------------------------------
711-- Events affecting the watched path only
712-------------------------------------------------------------------------------
714foreign import ccall safe
715 "FSEventStreamEventFlagRootChanged"
716 kFSEventStreamEventFlagRootChanged :: Word32
718-- | Determine whether the event indicates a change of path of the monitored
719-- object itself. Note that the object may become unreachable or deleted after
722-- /Applicable only when 'setRootPathEvents' is 'On'/
724-- /Occurs only for the watched path/
730isRootPathEvent :: Event -> Bool
731isRootPathEvent = getFlag kFSEventStreamEventFlagRootChanged
733-------------------------------------------------------------------------------
734-- Global events under the watched path
735-------------------------------------------------------------------------------
737foreign import ccall safe
738 "FSEventStreamEventFlagMount"
739 kFSEventStreamEventFlagMount :: Word32
741-- | Determine whether the event is a mount event. A mount event is generated
742-- if a volume is mounted under the path being watched.
748isMounted :: Event -> Bool
749isMounted = getFlag kFSEventStreamEventFlagMount
751foreign import ccall safe
752 "FSEventStreamEventFlagUnmount"
753 kFSEventStreamEventFlagUnmount :: Word32
755-- | Determine whether the event is an unmount event. An unmount event is
756-- generated if a volume mounted under the path being watched is unmounted.
762isUnmounted :: Event -> Bool
763isUnmounted = getFlag kFSEventStreamEventFlagUnmount
765-------------------------------------------------------------------------------
766-- Metadata change Events (applicable only when 'setFileEvents' is 'On')
767-------------------------------------------------------------------------------
769foreign import ccall safe
770 "FSEventStreamEventFlagItemChangeOwner"
771 kFSEventStreamEventFlagItemChangeOwner :: Word32
773-- | Determine whether the event is ownership, group, permissions or ACL change
774-- event of an object contained within the monitored path.
776-- Note that this event may be generated even if the metadata is changed to the
779-- /Applicable only when 'setFileEvents' is 'On'/
781-- /Can occur for watched path or a file inside it/
787isSecurityModified :: Event -> Bool
788isSecurityModified = getFlag kFSEventStreamEventFlagItemChangeOwner
790foreign import ccall safe
791 "FSEventStreamEventFlagItemInodeMetaMod"
792 kFSEventStreamEventFlagItemInodeMetaMod :: Word32
794-- | Determine whether the event indicates inode metadata change for an object
795-- contained within the monitored path. This event is generated when inode
796-- attributes other than the owner, group or permissions are changed e.g. file
797-- modification time. It does not occur when the link count of a file changes
798-- (as of macOS 10.15.1).
800-- /Applicable only when 'setFileEvents' is 'On'/
802-- /Can occur for watched path or a file inside it/
808isAttrsModified :: Event -> Bool
809isAttrsModified = getFlag kFSEventStreamEventFlagItemInodeMetaMod
811foreign import ccall safe
812 "FSEventStreamEventFlagItemFinderInfoMod"
813 kFSEventStreamEventFlagItemFinderInfoMod :: Word32
815-- | Determine whether the event indicates finder information metadata change
816-- for an object contained within the monitored path.
818-- /Applicable only when 'setFileEvents' is 'On'/
824isFinderInfoModified :: Event -> Bool
825isFinderInfoModified = getFlag kFSEventStreamEventFlagItemFinderInfoMod
827foreign import ccall safe
828 "FSEventStreamEventFlagItemXattrMod"
829 kFSEventStreamEventFlagItemXattrMod :: Word32
831-- | Determine whether the event indicates extended attributes metadata change
832-- for an object contained within the monitored path.
834-- /Applicable only when 'setFileEvents' is 'On'/
836-- /Can occur for watched path or a file inside it/
842isXAttrsModified :: Event -> Bool
843isXAttrsModified = getFlag kFSEventStreamEventFlagItemXattrMod
845-------------------------------------------------------------------------------
846-- CRUD Events (applicable only when 'setFileEvents' is 'On')
847-------------------------------------------------------------------------------
849foreign import ccall safe
850 "FSEventStreamEventFlagItemCreated"
851 kFSEventStreamEventFlagItemCreated :: Word32
853-- | Determine whether the event indicates creation of an object within the
854-- monitored path. This event is generated when any file system object other
855-- than a hard link is created. On hard linking only an 'isAttrsModified'
856-- event on the directory is generated, it is not a create event. However, when
857-- a hard link is deleted 'isDeleted' and 'isHardLink' both are true.
859-- This event can occur for the watched path if the path was deleted/moved and
860-- created again (tested on macOS 10.15.1). However, if a watch is started on a
861-- non-existing path and the path is created later, then this event is not
862-- generated, the path is not watched.
864-- BUGS: On 10.15.1 when we use a "touch x" to create a file for the first time
865-- only an 'isAttrsModified' event occurs and there is no 'isCreated'
866-- event. However, this seems to have been fixed on 10.15.6.
868-- /Applicable only when 'setFileEvents' is 'On'/
870-- /Can occur for watched path or a file inside it/
876isCreated :: Event -> Bool
877isCreated = getFlag kFSEventStreamEventFlagItemCreated
879foreign import ccall safe
880 "FSEventStreamEventFlagItemRemoved"
881 kFSEventStreamEventFlagItemRemoved :: Word32
883-- | Determine whether the event indicates deletion of an object within the
884-- monitored path. This is true when a file or a hardlink is deleted.
886-- Applicable only when 'setFileEvents' is 'On'.
888-- /Can occur for watched path or a file inside it/
894isDeleted :: Event -> Bool
895isDeleted = getFlag kFSEventStreamEventFlagItemRemoved
897foreign import ccall safe
898 "FSEventStreamEventFlagItemRenamed"
899 kFSEventStreamEventFlagItemRenamed :: Word32
901-- | Determine whether the event indicates rename of an object within the
902-- monitored path. This event is generated when a file is renamed within the
903-- watched directory or if it is moved out of or in the watched directory.
905-- /Applicable only when 'setFileEvents' is 'On'/
907-- /Can occur for watched path or a file inside it/
913isMoved :: Event -> Bool
914isMoved = getFlag kFSEventStreamEventFlagItemRenamed
916foreign import ccall safe
917 "FSEventStreamEventFlagItemModified"
918 kFSEventStreamEventFlagItemModified :: Word32
920-- | Determine whether the event indicates modification of an object within the
921-- monitored path. This event is generated only for files and not directories.
923-- Applicable only when 'setFileEvents' is 'On'.
929isModified :: Event -> Bool
930isModified = getFlag kFSEventStreamEventFlagItemModified
932#if HAVE_DECL_KFSEVENTSTREAMEVENTFLAGITEMCLONED
933foreign import ccall safe
934 "FSEventStreamEventFlagItemCloned"
935 kFSEventStreamEventFlagItemCloned :: Word32
937-- | Determine whether the event indicates cloning of an object within the
938-- monitored path. The "Duplicate" command in the "File" menu of the "Finder"
939-- application generates a "clone" event.
941-- Applicable only when 'setFileEvents' is 'On'.
947isCloned :: Event -> Bool
948isCloned = getFlag kFSEventStreamEventFlagItemCloned
951-------------------------------------------------------------------------------
952-- Information about path type (applicable only when 'setFileEvents' is 'On')
953-------------------------------------------------------------------------------
955foreign import ccall safe
956 "FSEventStreamEventFlagItemIsDir"
957 kFSEventStreamEventFlagItemIsDir :: Word32
959-- | Determine whether the event is for a directory path.
961-- Applicable only when 'setFileEvents' is 'On'.
967isDir :: Event -> Bool
968isDir = getFlag kFSEventStreamEventFlagItemIsDir
970foreign import ccall safe
971 "FSEventStreamEventFlagItemIsFile"
972 kFSEventStreamEventFlagItemIsFile :: Word32
974-- | Determine whether the event is for a file path.
976-- Applicable only when 'setFileEvents' is 'On'.
982isFile :: Event -> Bool
983isFile = getFlag kFSEventStreamEventFlagItemIsFile
985foreign import ccall safe
986 "FSEventStreamEventFlagItemIsSymlink"
987 kFSEventStreamEventFlagItemIsSymlink :: Word32
989-- | Determine whether the event is for a symbolic link path.
991-- Applicable only when 'setFileEvents' is 'On'.
997isSymLink :: Event -> Bool
998isSymLink = getFlag kFSEventStreamEventFlagItemIsSymlink
1000#if HAVE_DECL_KFSEVENTSTREAMEVENTFLAGITEMISHARDLINK
1001foreign import ccall safe
1002 "FSEventStreamEventFlagItemIsHardlink"
1003 kFSEventStreamEventFlagItemIsHardlink :: Word32
1005-- | Determine whether the event is for a file with more than one hard link.
1006-- When 'isFile' is true we can check for 'isHardLink'. Note that 'isHardLink'
1007-- is not true when a hard link is created, however, it is true when a file
1008-- which has or had more than one link in the past is removed. This is not true
1009-- if a file never had more than one link.
1011-- Applicable only when 'setFileEvents' is 'On'.
1017isHardLink :: Event -> Bool
1018isHardLink = getFlag kFSEventStreamEventFlagItemIsHardlink
1020foreign import ccall safe
1021 "FSEventStreamEventFlagItemIsLastHardlink"
1022 kFSEventStreamEventFlagItemIsLastHardlink :: Word32
1024-- | Determine whether the event is for a hard link path with only one hard
1025-- link. If 'isHardLink' is true then we can check for 'isLastHardLink'. This
1026-- is true when a file has had more than one link and now the last link is
1027-- removed. In that case both 'isHardLink' and 'isLastHardLink" would be true.
1029-- Applicable only when 'setFileEvents' is 'On'.
1035isLastHardLink :: Event -> Bool
1036isLastHardLink = getFlag kFSEventStreamEventFlagItemIsLastHardlink
1039-------------------------------------------------------------------------------
1041-------------------------------------------------------------------------------
1043-- | Convert an 'Event' record to a String representation.
1044showEvent :: Event -> String
1045showEvent ev@Event{..} =
1046 let path = runIdentity $ S.toList $ U.decodeUtf8' $ A.toStream eventAbsPath
1047 in "--------------------------"
1048 ++ "\nId = " ++ show eventId
1049 ++ "\nPath = " ++ show path
1050 ++ "\nFlags " ++ show eventFlags
1052 ++ showev isEventsLost "MustScanSubdirs"
1053 ++ showev isKernelDropped "KernelDropped"
1054 ++ showev isUserDropped "UserDropped"
1056 ++ showev isRootPathEvent "RootPathEvent"
1057 ++ showev isMounted "Mounted"
1058 ++ showev isUnmounted "Unmounted"
1059 ++ showev isHistoryDone "HistoryDone"
1061 ++ showev isSecurityModified "SecurityModified"
1062 ++ showev isAttrsModified "AttrsModified"
1063 ++ showev isFinderInfoModified "FinderInfoChanged"
1064 ++ showev isXAttrsModified "XAttrsModified"
1066 ++ showev isCreated "Created"
1067 ++ showev isDeleted "Deleted"
1068 ++ showev isModified "Modified"
1069 ++ showev isMoved "Moved"
1070#if HAVE_DECL_KFSEVENTSTREAMEVENTFLAGITEMCLONED
1071 ++ showev isCloned "Cloned"
1074 ++ showev isDir "Dir"
1075 ++ showev isFile "File"
1076 ++ showev isSymLink "SymLink"
1077#if HAVE_DECL_KFSEVENTSTREAMEVENTFLAGITEMISHARDLINK
1078 ++ showev isHardLink "HardLink"
1079 ++ showev isLastHardLink "LastHardLink"
1083 where showev f str = if f ev then "\n" ++ str else ""
1085module Streamly.Internal.FileSystem.Event.Darwin () where
1086#warning "Autoconf did not find the definition \
1087kFSEventStreamCreateFlagFileEvents in Darwin header files.\
1088Do you have Cocoa framework header files installed?\
1089Not compiling the Streamly.Internal.FileSystem.Event.Darwin module. \
1090Programs depending on this module may not compile. \
1091Check if HAVE_DECL_KFSEVENTSTREAMCREATEFLAGFILEEVENTS is defined in config.h \
1092generated 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