aboutsummaryrefslogtreecommitdiff
path: root/Utility/DirWatcher.hs
blob: d28381fae9eeb9f6331e01b64dbc110f3c974a31 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
{- generic directory watching interface
 -
 - Uses inotify, or kqueue, or fsevents to watch a directory
 - (and subdirectories) for changes, and runs hooks for different
 - sorts of events as they occur.
 -
 - Copyright 2012 Joey Hess <joey@kitenet.net>
 -
 - Licensed under the GNU GPL version 3 or higher.
 -}

{-# LANGUAGE CPP #-}

module Utility.DirWatcher where

import Utility.DirWatcher.Types

#if WITH_INOTIFY
import qualified Utility.INotify as INotify
import qualified System.INotify as INotify
#endif
#if WITH_KQUEUE
import qualified Utility.Kqueue as Kqueue
import Control.Concurrent
#endif
#if WITH_FSEVENTS
import qualified Utility.FSEvents as FSEvents
import qualified System.OSX.FSEvents as FSEvents
#endif

type Pruner = FilePath -> Bool

canWatch :: Bool
#if (WITH_INOTIFY || WITH_KQUEUE || WITH_FSEVENTS)
canWatch = True
#else
#if defined linux_HOST_OS
#warning "Building without inotify support"
#endif
canWatch = False
#endif

{- With inotify, discrete events will be received when making multiple changes
 - to the same filename. For example, adding it, deleting it, and adding it
 - again will be three events.
 - 
 - OTOH, with kqueue, often only one event is received, indicating the most
 - recent state of the file. -}
eventsCoalesce :: Bool
#if WITH_INOTIFY
eventsCoalesce = False
#else
#if (WITH_KQUEUE || WITH_FSEVENTS)
eventsCoalesce = True
#else
eventsCoalesce = undefined
#endif
#endif

{- With inotify, file closing is tracked to some extent, so an add event
 - will always be received for a file once its writer closes it, and
 - (typically) not before. This may mean multiple add events for the same file.
 - 
 - fsevents behaves similarly, although different event types are used for
 - creating and modification of the file.
 -
 - OTOH, with kqueue, add events will often be received while a file is
 - still being written to, and then no add event will be received once the
 - writer closes it. -}
closingTracked :: Bool
#if (WITH_INOTIFY || WITH_FSEVENTS)
closingTracked = True
#else
#if WITH_KQUEUE
closingTracked = False
#else
closingTracked = undefined
#endif
#endif

{- With inotify, modifications to existing files can be tracked.
 - Kqueue does not support this.
 - Fsevents generates events when an existing file is reopened and rewritten,
 - but not necessarily when it's opened once and modified repeatedly. -}
modifyTracked :: Bool
#if (WITH_INOTIFY || WITH_FSEVENTS)
modifyTracked = True
#else
#if WITH_KQUEUE
modifyTracked = False
#else
modifyTracked = undefined
#endif
#endif

{- Starts a watcher thread. The runstartup action is passed a scanner action
 - to run, that will return once the initial directory scan is complete.
 - Once runstartup returns, the watcher thread continues running,
 - and processing events. Returns a DirWatcherHandle that can be used
 - to shutdown later. -}
#if WITH_INOTIFY
type DirWatcherHandle = INotify.INotify
watchDir :: FilePath -> Pruner -> WatchHooks -> (IO () -> IO ()) -> IO DirWatcherHandle
watchDir dir prune hooks runstartup = do
	i <- INotify.initINotify
	runstartup $ INotify.watchDir i dir prune hooks
	return i
#else
#if WITH_KQUEUE
type DirWatcherHandle = ThreadId
watchDir :: FilePath -> Pruner -> WatchHooks -> (IO Kqueue.Kqueue -> IO Kqueue.Kqueue) -> IO DirWatcherHandle
watchDir dir prune hooks runstartup = do
	kq <- runstartup $ Kqueue.initKqueue dir prune
	forkIO $ Kqueue.runHooks kq hooks
#else
#if WITH_FSEVENTS
type DirWatcherHandle = FSEvents.EventStream
watchDir :: FilePath -> Pruner -> WatchHooks -> (IO FSEvents.EventStream -> IO FSEvents.EventStream) -> IO DirWatcherHandle
watchDir dir prune hooks runstartup =
	runstartup $ FSEvents.watchDir dir prune hooks
#else
type DirWatcherHandle = ()
watchDir :: FilePath -> Pruner -> WatchHooks -> (IO () -> IO ()) -> IO DirWatcherHandle
watchDir = undefined
#endif
#endif
#endif

#if WITH_INOTIFY
stopWatchDir :: DirWatcherHandle -> IO ()
stopWatchDir = INotify.killINotify
#else
#if WITH_KQUEUE
stopWatchDir :: DirWatcherHandle -> IO ()
stopWatchDir = killThread
#else
#if WITH_FSEVENTS
stopWatchDir :: DirWatcherHandle -> IO ()
stopWatchDir = FSEvents.eventStreamDestroy
#else
stopWatchDir :: DirWatcherHandle -> IO ()
stopWatchDir = undefined
#endif
#endif
#endif