1
1
"""
2
2
WatchedFile
3
3
4
- Struct for a file being watched containing the path to the file as well as the time of last
5
- modification.
4
+ Struct for a file being watched containing the path to the file as well as the
5
+ time of last modification.
6
6
"""
7
7
mutable struct WatchedFile{T<: AbstractString }
8
8
path:: T
15
15
Construct a new `WatchedFile` object around a file `f_path`.
16
16
"""
17
17
WatchedFile (f_path:: AbstractString ) = WatchedFile (f_path, mtime (f_path))
18
-
18
+
19
19
20
20
"""
21
21
has_changed(wf::WatchedFile)
22
22
23
- Check if a `WatchedFile` has changed. Returns -1 if the file does not exist, 0 if it does exist but
24
- has not changed, and 1 if it has changed.
23
+ Check if a `WatchedFile` has changed. Returns -1 if the file does not exist, 0
24
+ if it does exist but has not changed, and 1 if it has changed.
25
25
"""
26
- function has_changed (wf:: WatchedFile )
26
+ function has_changed (wf:: WatchedFile ):: Int
27
27
if ! isfile (wf. path)
28
28
# isfile may return false for a file
29
29
# currently being written. Wait for 0.1s
37
37
"""
38
38
set_unchanged!(wf::WatchedFile)
39
39
40
- Set the current state of a `WatchedFile` as unchanged"
40
+ Set the current state of a `WatchedFile` as unchanged
41
+ """
42
+ set_unchanged! (wf:: WatchedFile ) = (wf. mtime = mtime (wf. path);)
43
+
44
+ """
45
+ set_unchanged!(wf::WatchedFile)
46
+
47
+ Set the current state of a `WatchedFile` as deleted (if it re-appears it will
48
+ immediately be marked as changed and trigger the callback).
41
49
"""
42
- set_unchanged ! (wf:: WatchedFile ) = (wf. mtime = mtime (wf . path) )
50
+ set_deleted ! (wf:: WatchedFile ) = (wf. mtime = - Inf ; )
43
51
44
52
45
53
"""
@@ -53,69 +61,70 @@ abstract type FileWatcher end
53
61
"""
54
62
SimpleWatcher([callback]; sleeptime::Float64=0.1) <: FileWatcher
55
63
56
- A simple file watcher. You can specify a callback function, receiving the path of each file that
57
- has changed as an `AbstractString`, at construction or later by the API function [`set_callback!`](@ref).
58
- The `sleeptime` is the time waited between two runs of the loop looking for changed files, it is
59
- constrained to be at least 0.05s.
64
+ A simple file watcher. You can specify a callback function, receiving the path
65
+ of each file that has changed as an `AbstractString`, at construction or later
66
+ by the API function [`set_callback!`](@ref).
67
+ The `sleeptime` is the time waited between two runs of the loop looking for
68
+ changed files, it is constrained to be at least 0.05s.
60
69
"""
61
70
mutable struct SimpleWatcher <: FileWatcher
62
- callback:: Union{Nothing,Function} # callback function triggered upon file change
71
+ callback:: Union{Nothing,Function} # callback triggered upon file change
63
72
task:: Union{Nothing,Task} # asynchronous file-watching task
64
- sleeptime:: Float64 # sleep-time before checking for file changes
73
+ sleeptime:: Float64 # sleep before checking for file changes
65
74
watchedfiles:: Vector{WatchedFile} # list of files being watched
66
- status:: Symbol # set to :interrupted as appropriate ( caught by server)
75
+ status:: Symbol # flag caught by server
67
76
end
68
77
69
- SimpleWatcher (callback:: Union{Nothing,Function} = nothing ; sleeptime:: Float64 = 0.1 ) =
70
- SimpleWatcher (callback, nothing , max (0.05 , sleeptime), Vector {WatchedFile} (), :runnable )
78
+ function SimpleWatcher (
79
+ callback:: Union{Nothing,Function} = nothing ;
80
+ sleeptime:: Float64 = 0.1
81
+ )
82
+ return SimpleWatcher (
83
+ callback,
84
+ nothing ,
85
+ max (0.05 , sleeptime),
86
+ Vector {WatchedFile} (),
87
+ :runnable
88
+ )
89
+ end
71
90
72
91
73
92
"""
74
93
file_watcher_task!(w::FileWatcher)
75
94
76
- Helper function that's spawned as an asynchronous task and checks for file changes. This task
77
- is normally terminated upon an `InterruptException` and shows a warning in the presence of
78
- any other exception.
95
+ Helper function that's spawned as an asynchronous task and checks for file
96
+ changes. This task is normally terminated upon an `InterruptException` and
97
+ shows a warning in the presence of any other exception.
79
98
"""
80
- function file_watcher_task! (fw:: FileWatcher )
99
+ function file_watcher_task! (fw:: FileWatcher ):: Nothing
81
100
try
82
101
while true
83
102
sleep (fw. sleeptime)
84
103
85
104
# only check files if there's a callback to call upon changes
86
105
fw. callback === nothing && continue
87
106
88
- # keep track of any file that may have been deleted
89
- deleted_files = Vector {Int} ()
90
- for (i, wf) ∈ enumerate (fw. watchedfiles)
107
+ for wf ∈ fw. watchedfiles
91
108
state = has_changed (wf)
92
- if state == 0
93
- continue
94
- elseif state == 1
95
- # the file has changed, set it unchanged and trigger callback
109
+ if state == 1
110
+ # file has changed, set it unchanged and trigger callback
96
111
set_unchanged! (wf)
97
112
fw. callback (wf. path)
98
113
elseif state == - 1
99
- # the file does not exist, eventually delete it from list of watched files
100
- push! (deleted_files, i)
101
- if VERBOSE[]
102
- @info " [FileWatcher]: file '$(wf. path) ' does not " *
103
- " exist (anymore); removing it from list of " *
104
- " watched files."
105
- end
114
+ # file has been deleted, set the mtime to -Inf so that
115
+ # if it re-appears then it's immediately marked as changed
116
+ set_deleted! (wf)
106
117
end
107
118
end
108
- # remove deleted files from list of watched files
109
- deleteat! (fw. watchedfiles, deleted_files)
110
119
end
111
120
catch err
112
121
fw. status = :interrupted
113
122
# an InterruptException is the normal way for this task to end
114
123
if ! isa (err, InterruptException) && VERBOSE[]
115
124
@error " fw error" exception= (err, catch_backtrace ())
116
125
end
117
- return nothing
118
126
end
127
+ return nothing
119
128
end
120
129
121
130
125
134
Set or change the callback function being executed upon a file change.
126
135
Can be "hot-swapped", i.e. while the file watcher is running.
127
136
"""
128
- function set_callback! (fw:: FileWatcher , callback:: Function )
137
+ function set_callback! (fw:: FileWatcher , callback:: Function ):: Nothing
129
138
prev_running = stop (fw) # returns true if was running
130
139
fw. callback = callback
131
140
prev_running && start (fw) # restart if it was running before
@@ -149,7 +158,8 @@ Start the file watcher and wait to make sure the task has started.
149
158
"""
150
159
function start (fw:: FileWatcher )
151
160
is_running (fw) || (fw. task = @async file_watcher_task! (fw))
152
- # wait until task runs to ensure reliable start (e.g. if `stop` called right afterwards)
161
+ # wait until task runs to ensure reliable start (e.g. if `stop` called
162
+ # right after start)
153
163
while fw. task. state != :runnable
154
164
sleep (0.01 )
155
165
end
@@ -159,15 +169,16 @@ end
159
169
"""
160
170
stop(fw::FileWatcher)
161
171
162
- Stop the file watcher. The list of files being watched is preserved and new files can still be
163
- added to the file watcher using `watch_file!`. It can be restarted with `start`.
164
- Returns a `Bool` indicating whether the watcher was running before `stop` was called.
172
+ Stop the file watcher. The list of files being watched is preserved and new
173
+ files can still be added to the file watcher using `watch_file!`. It can be
174
+ restarted with `start`. Returns a `Bool` indicating whether the watcher was
175
+ running before `stop` was called.
165
176
"""
166
- function stop (fw:: FileWatcher )
177
+ function stop (fw:: FileWatcher ):: Bool
167
178
was_running = is_running (fw)
168
179
if was_running
169
- # this may fail as the task may get interrupted in between which would lead to
170
- # an error "schedule Task not runnable"
180
+ # this may fail as the task may get interrupted in between which would
181
+ # lead to an error "schedule Task not runnable"
171
182
try
172
183
schedule (fw. task, InterruptException (), error= true )
173
184
catch
186
197
187
198
Checks whether the file specified by `f_path` is being watched.
188
199
"""
189
- is_watched (fw:: FileWatcher , f_path:: AbstractString ) =
190
- any (wf -> wf. path == f_path, fw. watchedfiles)
200
+ function is_watched (fw:: FileWatcher , f_path:: AbstractString )
201
+ return any (wf -> wf. path == f_path, fw. watchedfiles)
202
+ end
191
203
192
204
193
205
"""
@@ -198,6 +210,9 @@ Add a file to be watched for changes.
198
210
function watch_file! (fw:: FileWatcher , f_path:: AbstractString )
199
211
if isfile (f_path) && ! is_watched (fw, f_path)
200
212
push! (fw. watchedfiles, WatchedFile (f_path))
201
- VERBOSE[] && @info (" [FileWatcher]: now watching '$f_path '" )
213
+ if VERBOSE[]
214
+ @info " [FileWatcher]: now watching '$f_path '"
215
+ println ()
216
+ end
202
217
end
203
218
end
0 commit comments