sinotify 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +8 -0
- data/History.txt +3 -0
- data/README.rdoc +141 -0
- data/README.txt +141 -0
- data/Rakefile +72 -0
- data/TODO +12 -0
- data/examples/watcher.rb +30 -0
- data/ext/extconf.rb +12 -0
- data/ext/src/inotify-syscalls.h +24 -0
- data/ext/src/inotify.h +113 -0
- data/ext/src/sinotify.c +205 -0
- data/lib/sinotify.rb +18 -0
- data/lib/sinotify/event.rb +185 -0
- data/lib/sinotify/notifier.rb +334 -0
- data/lib/sinotify/prim_event.rb +114 -0
- data/lib/sinotify/watch.rb +21 -0
- data/lib/sinotify_info.rb +47 -0
- data/sinotify.gemspec +80 -0
- data/spec/prim_notify_spec.rb +98 -0
- data/spec/sinotify_spec.rb +265 -0
- data/spec/spec_helper.rb +14 -0
- data/tasks/ann.rake +80 -0
- data/tasks/bones.rake +20 -0
- data/tasks/gem.rake +201 -0
- data/tasks/git.rake +40 -0
- data/tasks/notes.rake +27 -0
- data/tasks/post_load.rake +34 -0
- data/tasks/rdoc.rake +51 -0
- data/tasks/rubyforge.rake +55 -0
- data/tasks/setup.rb +292 -0
- data/tasks/spec.rake +54 -0
- data/tasks/svn.rake +47 -0
- data/tasks/test.rake +40 -0
- data/tasks/zentest.rake +36 -0
- metadata +154 -0
@@ -0,0 +1,334 @@
|
|
1
|
+
module Sinotify
|
2
|
+
|
3
|
+
#
|
4
|
+
# Watch a directory or file for events like create, modify, delete, etc.
|
5
|
+
# (See Sinotify::Event for full list).
|
6
|
+
#
|
7
|
+
# See the synopsis section in the README.txt for example usage.
|
8
|
+
#
|
9
|
+
#
|
10
|
+
class Notifier
|
11
|
+
include Cosell
|
12
|
+
|
13
|
+
attr_accessor :file_or_dir_name, :etypes, :recurse, :recurse_throttle, :logger
|
14
|
+
|
15
|
+
# Required Args
|
16
|
+
#
|
17
|
+
# file_or_dir_name: the file/directory to watch
|
18
|
+
#
|
19
|
+
# Options:
|
20
|
+
# :recurse => (true|false)
|
21
|
+
# whether to automatically create watches on sub directories
|
22
|
+
# default: true if file_or_dir_name is a directory, else false
|
23
|
+
# raises if true and file_or_dir_name is not a directory
|
24
|
+
#
|
25
|
+
# :recurse_throttle =>
|
26
|
+
# When recursing, a background thread drills down into all the child directories
|
27
|
+
# creating notifiers on them. The recurse_throttle tells the notifier how far
|
28
|
+
# to recurse before sleeping for 0.1 seconds, so that drilling down does not hog
|
29
|
+
# the system on large directorie hierarchies.
|
30
|
+
# default is 10
|
31
|
+
#
|
32
|
+
# :etypes =>
|
33
|
+
# which inotify file system event types to listen for (eg :create, :delete, etc)
|
34
|
+
# See docs for Sinotify::Event for list of event types.
|
35
|
+
# default is [:create, :modify, :delete]
|
36
|
+
# Use :all_events to trace everything (although this may be more than you bargained for).
|
37
|
+
#
|
38
|
+
# :logger =>
|
39
|
+
# Where to log errors to. Default is Logger.new(STDOUT).
|
40
|
+
#
|
41
|
+
# :announcement_throttle =>
|
42
|
+
# How many events can be announced at a time before the queue goes back to sleep for a cycle.
|
43
|
+
# (ie. Cosell's 'announcements_per_cycle')
|
44
|
+
#
|
45
|
+
# :announcements_sleep_time =>
|
46
|
+
# How long the queue should sleep for before announcing the next batch of queued up
|
47
|
+
# Sinotify::Events (ie. Cosell's 'sleep_time')
|
48
|
+
#
|
49
|
+
def initialize(file_or_dir_name, opts = {})
|
50
|
+
|
51
|
+
initialize_cosell! # init the announcements framework
|
52
|
+
|
53
|
+
raise "Could not find #{file_or_dir_name}" unless File.exist?(file_or_dir_name)
|
54
|
+
self.file_or_dir_name = file_or_dir_name
|
55
|
+
|
56
|
+
# by default, recurse if directory?. If opts[:recurse] was true and passed in,
|
57
|
+
# make sure the watch is on a directory
|
58
|
+
self.recurse = opts[:recurse].nil?? self.on_directory? : opts[:recurse]
|
59
|
+
raise "Cannot recurse, #{file_or_dir_name} is not a directory" if self.recurse? && !self.on_directory?
|
60
|
+
|
61
|
+
# how many directories at a time to register.
|
62
|
+
self.recurse_throttle = opts[:recurse_throttle] || 10
|
63
|
+
|
64
|
+
self.etypes = Array( opts[:etypes] || [:create, :modify, :delete] )
|
65
|
+
validate_etypes!
|
66
|
+
|
67
|
+
self.prim_notifier = Sinotify::PrimNotifier.new
|
68
|
+
|
69
|
+
# setup async announcements queue (part of the Cosell mixin)
|
70
|
+
@logger = opts[:logger] || Logger.new(STDOUT)
|
71
|
+
sleep_time = opts[:announcements_sleep_time] || 0.05
|
72
|
+
announcement_throttle = opts[:announcement_throttle] || 50
|
73
|
+
self.queue_announcements!(:sleep_time => sleep_time,
|
74
|
+
:logger => @logger,
|
75
|
+
:announcements_per_cycle => announcement_throttle)
|
76
|
+
|
77
|
+
self.closed = false
|
78
|
+
|
79
|
+
# initialize a few variables just to shut up the ruby warnings
|
80
|
+
# Apparently the lazy init idiom using ||= is no longer approved of. Shame that.
|
81
|
+
@spy_logger = nil
|
82
|
+
@spy_logger_level = nil
|
83
|
+
@watch_thread = nil
|
84
|
+
end
|
85
|
+
|
86
|
+
# Sugar.
|
87
|
+
#
|
88
|
+
# Equivalent of calling cosell's
|
89
|
+
#
|
90
|
+
# self.when_announcing(Sinotify::Event) do |event|
|
91
|
+
# do_something_with_event(event)
|
92
|
+
# end
|
93
|
+
#
|
94
|
+
# becomes
|
95
|
+
#
|
96
|
+
# self.on_event { |event| do_something_with_event(event) }
|
97
|
+
#
|
98
|
+
# Since this class only announces one kind of event, it made sense to
|
99
|
+
# provide a more terse version of that statement.
|
100
|
+
def on_event &block
|
101
|
+
self.when_announcing(Sinotify::Event, &block)
|
102
|
+
end
|
103
|
+
|
104
|
+
# whether this watch is on a directory
|
105
|
+
def on_directory?
|
106
|
+
File.directory?(self.file_or_dir_name)
|
107
|
+
end
|
108
|
+
|
109
|
+
# Start watching for inotify file system events.
|
110
|
+
def watch!
|
111
|
+
raise "Cannot reopen an inotifier. Create a new one instead" if self.closed?
|
112
|
+
self.add_all_directories_in_background
|
113
|
+
self.start_prim_event_loop_thread
|
114
|
+
return self
|
115
|
+
end
|
116
|
+
|
117
|
+
# Close this notifier. Notifiers cannot be reopened after close!.
|
118
|
+
def close!
|
119
|
+
@closed = true
|
120
|
+
self.remove_all_watches
|
121
|
+
self.kill_queue! # cosell
|
122
|
+
end
|
123
|
+
|
124
|
+
# Log a message every time a prim_event comes in (will be logged even if it is considered 'noise'),
|
125
|
+
# and log a message whenever an event is announced. Overrides Cosell's spy! method (and uses cosell's
|
126
|
+
# spy! to log announced events).
|
127
|
+
#
|
128
|
+
# Options:
|
129
|
+
# :logger => The log to log to. Default is a logger on STDOUT
|
130
|
+
# :level => The log level to log with. Default is :info
|
131
|
+
# :spy_on_prim_events => Spy on PrimEvents (raw inotify events) too
|
132
|
+
#
|
133
|
+
def spy!(opts = {})
|
134
|
+
self.spy_on_prim_events = opts[:spy_on_prim_events].eql?(true)
|
135
|
+
self.spy_logger = opts[:logger] || Logger.new(STDOUT)
|
136
|
+
self.spy_logger_level = opts[:level] || :info
|
137
|
+
opts[:on] = Sinotify::Event
|
138
|
+
opts[:preface_with] = "Sinotify::Notifier Event Spy"
|
139
|
+
super(opts)
|
140
|
+
end
|
141
|
+
|
142
|
+
# Return a list of files/directories currently being watched. Will only contain one entry unless
|
143
|
+
# this notifier was setup on a directory with the option :recurse => true.
|
144
|
+
def all_directories_being_watched
|
145
|
+
self.watches.values.collect{|w| w.path }.sort
|
146
|
+
end
|
147
|
+
|
148
|
+
def watches
|
149
|
+
@watches ||= {}
|
150
|
+
end
|
151
|
+
|
152
|
+
# Whether this notifier watches all the files in all of the subdirectories
|
153
|
+
# of the directory being watched.
|
154
|
+
def recurse?
|
155
|
+
self.recurse
|
156
|
+
end
|
157
|
+
|
158
|
+
def to_s
|
159
|
+
"Sinotify::Notifier[#{self.file_or_dir_name}, :watches => #{self.watches.size}]"
|
160
|
+
end
|
161
|
+
|
162
|
+
protected
|
163
|
+
|
164
|
+
#:stopdoc:
|
165
|
+
|
166
|
+
attr_accessor :spy_on_prim_events, :spy_logger, :spy_logger_level
|
167
|
+
|
168
|
+
def validate_etypes!
|
169
|
+
bad = self.etypes.detect{|etype| PrimEvent.mask_from_etype(etype).nil? }
|
170
|
+
raise "Unrecognized etype '#{bad}'. Please see valid list in docs for Sinotify::Event" if bad
|
171
|
+
end
|
172
|
+
|
173
|
+
# some events we don't want to report (certain events are generated just from creating watches)
|
174
|
+
def event_is_noise? prim_event, watch
|
175
|
+
|
176
|
+
etypes_strings = prim_event.etypes.map{|e|e.to_s}.sort
|
177
|
+
|
178
|
+
# the simple act of creating a watch causes these to fire"
|
179
|
+
return true if ["close_nowrite", "isdir"].eql?(etypes_strings)
|
180
|
+
return true if ["isdir", "open"].eql?(etypes_strings)
|
181
|
+
return true if ["ignored"].eql?(etypes_strings)
|
182
|
+
|
183
|
+
# If the event is on a subdir of the directory specified in watch, don't send it because
|
184
|
+
# there should be another even (on the subdir itself) that comes through, and this one
|
185
|
+
# will be redundant.
|
186
|
+
return true if ["delete", "isdir"].eql?(etypes_strings)
|
187
|
+
|
188
|
+
return false
|
189
|
+
end
|
190
|
+
|
191
|
+
# Open up a background thread that adds all the watches on @file_or_dir_name and,
|
192
|
+
# if @recurse is true, all of its subdirs.
|
193
|
+
def add_all_directories_in_background
|
194
|
+
@child_dir_thread = Thread.new do
|
195
|
+
begin
|
196
|
+
self.add_watches!
|
197
|
+
rescue Exception => x
|
198
|
+
log "Exception: #{x}, trace: \n\t#{x.backtrace.join("\n\t")}", :error
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
def add_watches!(fn = self.file_or_dir_name, throttle = 0)
|
204
|
+
|
205
|
+
return if closed?
|
206
|
+
if throttle.eql?(self.recurse_throttle)
|
207
|
+
sleep 0.1
|
208
|
+
throttle = 0
|
209
|
+
end
|
210
|
+
throttle += 1
|
211
|
+
|
212
|
+
self.add_watch(fn)
|
213
|
+
|
214
|
+
if recurse?
|
215
|
+
Dir[File.join(fn, '/**')].each do |child_fn|
|
216
|
+
next if child_fn.eql?(fn)
|
217
|
+
self.add_watches!(child_fn, throttle) if File.directory?(child_fn)
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
end
|
222
|
+
|
223
|
+
def add_watch(fn)
|
224
|
+
watch_descriptor = self.prim_notifier.add_watch(fn, self.raw_mask)
|
225
|
+
# puts "ADDED WATCH: #{watch_descriptor} for #{fn}"
|
226
|
+
remove_watch(watch_descriptor) # remove the existing, if it exists
|
227
|
+
watch = Watch.new(:path => fn, :watch_descriptor => watch_descriptor)
|
228
|
+
self.watches[watch_descriptor.to_s] = watch
|
229
|
+
end
|
230
|
+
|
231
|
+
# Remove the watch associated with the watch_descriptor passed in
|
232
|
+
def remove_watch(watch_descriptor, prim_remove = false)
|
233
|
+
if watches[watch_descriptor.to_s]
|
234
|
+
#logger.debug "REMOVING WATCH: #{watch_descriptor}"
|
235
|
+
self.watches.delete(watch_descriptor.to_s)
|
236
|
+
|
237
|
+
# the prim_notifier will complain if we remove a watch on a deleted file,
|
238
|
+
# since the watch will have automatically been removed already. Be default we
|
239
|
+
# don't care, but if caller is sure there are some prim watches to clean
|
240
|
+
# up, then they can pass 'true' for prim_remove. Another way to handle
|
241
|
+
# this would be to just default to true and fail silently, but trying this
|
242
|
+
# more conservative approach for now.
|
243
|
+
self.prim_notifier.rm_watch(watch_descriptor.to_i) if prim_remove
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
def remove_all_watches
|
248
|
+
logger.debug "REMOVING ALL WATHCES"
|
249
|
+
self.watches.keys.each{|watch_descriptor| self.remove_watch(watch_descriptor, true) }
|
250
|
+
@watches = nil
|
251
|
+
end
|
252
|
+
|
253
|
+
def log(msg, level = :debug)
|
254
|
+
puts(msg) unless [:debug, :info].include?(level)
|
255
|
+
self.logger.send(level, msg) if self.logger
|
256
|
+
end
|
257
|
+
|
258
|
+
# Listen for linux inotify events, and as they come in
|
259
|
+
# 1. adapt them into Sinotify::Event objects
|
260
|
+
# 2. 'announce' them using Cosell.
|
261
|
+
# By default, Cosell is setup to Queue the announcements in a bg thread.
|
262
|
+
#
|
263
|
+
# The references to two different logs in this method may be a bit confusing. The @spy_logger
|
264
|
+
# exclusively logs (spys on) events and announcements. The "log" method instead uses the @logger
|
265
|
+
# and logs errors and exceptions. The @logger is defined when creating this object (using the :logger
|
266
|
+
# option), and the @spy_logger is defined in the :spy! method.
|
267
|
+
#
|
268
|
+
def start_prim_event_loop_thread
|
269
|
+
|
270
|
+
raise "Already watching!" unless @watch_thread.nil?
|
271
|
+
|
272
|
+
@watch_thread = Thread.new do
|
273
|
+
begin
|
274
|
+
self.prim_notifier.each_event do |prim_event|
|
275
|
+
watch = self.watches[prim_event.watch_descriptor.to_s]
|
276
|
+
if event_is_noise?(prim_event, watch)
|
277
|
+
self.spy_logger.debug("Sinotify::Notifier Spy: Skipping noise[#{prim_event.inspect}]") if self.spy_on_prim_events
|
278
|
+
else
|
279
|
+
spy_on_prim_event(prim_event)
|
280
|
+
if watch.nil?
|
281
|
+
self.log "Could not determine watch from descriptor #{prim_event.watch_descriptor}, something is wrong. Event: #{prim_event.inspect}", :warn
|
282
|
+
else
|
283
|
+
event = Sinotify::Event.from_prim_event_and_watch(prim_event, watch)
|
284
|
+
self.announce event
|
285
|
+
if event.has_etype?(:create) && event.directory?
|
286
|
+
Thread.new do
|
287
|
+
# have to thread this because the :create event comes in _before_ the directory exists,
|
288
|
+
# and inotify will not permit a watch on a file unless it exists
|
289
|
+
sleep 0.1
|
290
|
+
self.add_watch(event.path)
|
291
|
+
end
|
292
|
+
end
|
293
|
+
# puts "REMOVING: #{event.inspect}, WATCH: #{self.watches[event.watch_descriptor.to_s]}" if event.has_etype?(:delete) && event.directory?
|
294
|
+
self.remove_watch(event.watch_descriptor) if event.has_etype?(:delete) && event.directory?
|
295
|
+
break if closed?
|
296
|
+
end
|
297
|
+
end
|
298
|
+
end
|
299
|
+
rescue Exception => x
|
300
|
+
log "Exception: #{x}, trace: \n\t#{x.backtrace.join("\n\t")}", :error
|
301
|
+
end
|
302
|
+
|
303
|
+
log "Exiting prim event loop thread for #{self}"
|
304
|
+
end
|
305
|
+
|
306
|
+
end
|
307
|
+
|
308
|
+
def raw_mask
|
309
|
+
if @raw_mask.nil?
|
310
|
+
(self.etypes << :delete_self) if self.etypes.include?(:delete)
|
311
|
+
@raw_mask = self.etypes.inject(0){|raw, etype| raw | PrimEvent.mask_from_etype(etype) }
|
312
|
+
end
|
313
|
+
@raw_mask
|
314
|
+
end
|
315
|
+
|
316
|
+
def spy_on_prim_event(prim_event)
|
317
|
+
if self.spy_on_prim_events
|
318
|
+
msg = "Sinotify::Notifier Prim Event Spy: #{prim_event.inspect}"
|
319
|
+
self.spy_logger.send(@spy_logger_level, msg)
|
320
|
+
end
|
321
|
+
end
|
322
|
+
|
323
|
+
# ruby gives warnings in verbose mode if you use attr_accessor to set these next few:
|
324
|
+
def prim_notifier; @prim_notifier; end
|
325
|
+
def prim_notifier= x; @prim_notifier = x; end
|
326
|
+
def watch_descriptor; @watch_descriptor; end
|
327
|
+
def watch_descriptor= x; @watch_descriptor = x; end
|
328
|
+
def closed?; @closed.eql?(true); end
|
329
|
+
def closed= x; @closed = x; end
|
330
|
+
|
331
|
+
#:startdoc:
|
332
|
+
end
|
333
|
+
end
|
334
|
+
|
@@ -0,0 +1,114 @@
|
|
1
|
+
module Sinotify
|
2
|
+
|
3
|
+
#
|
4
|
+
# Sinotify::PrimEvent is a ruby wrapper for an inotify event
|
5
|
+
# Use the Sinotify::PrimNotifier to register to listen for these Events.
|
6
|
+
#
|
7
|
+
# Most users of Sinotify will not want to listen for prim events, instead opting
|
8
|
+
# to use a Sinotify::Notifier to listen for Sinotify::Events. See docs for those classes.
|
9
|
+
#
|
10
|
+
# Methods :name, :mask, and :wd defined in sinotify.c
|
11
|
+
#
|
12
|
+
# For convenience, inotify masks are represented in the PrimEvent as an 'etype',
|
13
|
+
# which is just a ruby symbol corresponding to the mask. For instance, a mask
|
14
|
+
# represented as Sinotify::MODIFY has an etype of :modify. You can still get
|
15
|
+
# the mask if you want the 'raw' int mask value. In other words:
|
16
|
+
# <pre>
|
17
|
+
# $ irb
|
18
|
+
# >> require 'sinotify'
|
19
|
+
# => true
|
20
|
+
# >> Sinotify::MODIFY
|
21
|
+
# => 2
|
22
|
+
# >> Sinotify::Event.etype_from_mask(Sinotify::MODIFY)
|
23
|
+
# => :modify
|
24
|
+
# >> Sinotify::Event.mask_from_etype(:modify)
|
25
|
+
# => 2
|
26
|
+
# </pre>
|
27
|
+
#
|
28
|
+
# See docs for Sinotify::Event class for full list of supported event symbol types and
|
29
|
+
# their symbols.
|
30
|
+
#
|
31
|
+
class PrimEvent
|
32
|
+
|
33
|
+
# map the constants defined in the 'c' lib to ruby symbols
|
34
|
+
@@mask_to_etype_map = {
|
35
|
+
Sinotify::PrimEvent::CREATE => :create,
|
36
|
+
Sinotify::PrimEvent::MOVE => :move,
|
37
|
+
Sinotify::PrimEvent::ACCESS => :access,
|
38
|
+
Sinotify::PrimEvent::MODIFY => :modify,
|
39
|
+
Sinotify::PrimEvent::ATTRIB => :attrib,
|
40
|
+
Sinotify::PrimEvent::CLOSE_WRITE => :close_write,
|
41
|
+
Sinotify::PrimEvent::CLOSE_NOWRITE => :close_nowrite,
|
42
|
+
Sinotify::PrimEvent::OPEN => :open,
|
43
|
+
Sinotify::PrimEvent::MOVED_FROM => :moved_from,
|
44
|
+
Sinotify::PrimEvent::MOVED_TO => :moved_to,
|
45
|
+
Sinotify::PrimEvent::DELETE => :delete,
|
46
|
+
Sinotify::PrimEvent::DELETE_SELF => :delete_self,
|
47
|
+
Sinotify::PrimEvent::MOVE_SELF => :move_self,
|
48
|
+
Sinotify::PrimEvent::UNMOUNT => :unmount,
|
49
|
+
Sinotify::PrimEvent::Q_OVERFLOW => :q_overflow,
|
50
|
+
Sinotify::PrimEvent::IGNORED => :ignored,
|
51
|
+
Sinotify::PrimEvent::CLOSE => :close,
|
52
|
+
Sinotify::PrimEvent::MASK_ADD => :mask_add,
|
53
|
+
Sinotify::PrimEvent::ISDIR => :isdir,
|
54
|
+
Sinotify::PrimEvent::ONLYDIR => :onlydir,
|
55
|
+
Sinotify::PrimEvent::DONT_FOLLOW => :dont_follow,
|
56
|
+
Sinotify::PrimEvent::ONESHOT => :oneshot,
|
57
|
+
Sinotify::PrimEvent::ALL_EVENTS => :all_events,
|
58
|
+
}
|
59
|
+
|
60
|
+
@@etype_to_mask_map = {}
|
61
|
+
@@mask_to_etype_map.each{|k,v| @@etype_to_mask_map[v] = k}
|
62
|
+
|
63
|
+
def self.etype_from_mask(mask)
|
64
|
+
@@mask_to_etype_map[mask]
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.mask_from_etype(etype)
|
68
|
+
@@etype_to_mask_map[etype]
|
69
|
+
end
|
70
|
+
|
71
|
+
def self.all_etypes
|
72
|
+
@@mask_to_etype_map.values.sort{|e1,e2| e1.to_s <=> e2.to_s}
|
73
|
+
end
|
74
|
+
|
75
|
+
def name
|
76
|
+
@name ||= self.prim_name
|
77
|
+
end
|
78
|
+
|
79
|
+
def wd
|
80
|
+
@wd ||= self.prim_wd
|
81
|
+
end
|
82
|
+
|
83
|
+
def mask
|
84
|
+
@mask ||= self.prim_mask
|
85
|
+
end
|
86
|
+
|
87
|
+
# When first creating a watch, inotify sends a bunch of events that have masks
|
88
|
+
# don't seem to match up w/ any of the masks defined in inotify.h. Pass on those.
|
89
|
+
def recognized?
|
90
|
+
return !self.etypes.empty?
|
91
|
+
end
|
92
|
+
|
93
|
+
# Return whether this event has etype specified
|
94
|
+
def has_etype?(etype)
|
95
|
+
mask_for_etype = self.class.mask_from_etype(etype)
|
96
|
+
return (self.mask & mask_for_etype).eql?(mask_for_etype)
|
97
|
+
end
|
98
|
+
|
99
|
+
def etypes
|
100
|
+
@etypes ||= self.class.all_etypes.select{|et| self.has_etype?(et) }
|
101
|
+
end
|
102
|
+
|
103
|
+
def watch_descriptor
|
104
|
+
self.wd
|
105
|
+
end
|
106
|
+
|
107
|
+
def inspect
|
108
|
+
"<#{self.class} :name => '#{self.name}', :etypes => #{self.etypes.inspect}, :mask => #{self.mask.to_s(16)}, :watch_descriptor => #{self.watch_descriptor}>"
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
112
|
+
|
113
|
+
end
|
114
|
+
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Sinotify
|
2
|
+
#
|
3
|
+
# Just a little struct to describe a single inotifier watch
|
4
|
+
# Note that the is_dir needs to be saved because we won't be
|
5
|
+
# able to deduce that later if it was a deleted object.
|
6
|
+
#
|
7
|
+
class Watch
|
8
|
+
attr_accessor :is_dir, :path, :watch_descriptor
|
9
|
+
def initialize(args={})
|
10
|
+
args.each{|k,v| self.send("#{k}=",v)}
|
11
|
+
@timestamp ||= Time.now
|
12
|
+
@is_dir = File.directory?(path)
|
13
|
+
end
|
14
|
+
def directory?
|
15
|
+
self.is_dir.eql?(true)
|
16
|
+
end
|
17
|
+
def to_s
|
18
|
+
"Sinotify::Watch[:is_dir => #{is_dir}, :path => #{path}, :watch_descriptor => #{watch_descriptor}]"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|