fsevent 0.1

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/README.md ADDED
@@ -0,0 +1,13 @@
1
+ # fsevent
2
+
3
+ fail safe event driven framework
4
+
5
+ # author
6
+
7
+ Tanaka Akira
8
+
9
+ # license
10
+ GPLv3 or later
11
+
12
+ (AIST program administration number: H26PRO-1713)
13
+
data/fsevent.gemspec ADDED
@@ -0,0 +1,43 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'fsevent'
3
+ s.version = '0.1'
4
+ s.date = '2013-06-23'
5
+ s.author = 'Tanaka Akira'
6
+ s.email = 'tanaka-akira@aist.go.jp'
7
+ s.license = 'GPL-3.0+'
8
+ s.required_ruby_version = '>= 1.9.2'
9
+ s.files = %w[
10
+ .gitignore
11
+ LICENSE
12
+ README.md
13
+ fsevent.gemspec
14
+ lib/fsevent.rb
15
+ lib/fsevent/abstractdevice.rb
16
+ lib/fsevent/failsafedevice.rb
17
+ lib/fsevent/framework.rb
18
+ lib/fsevent/periodicschedule.rb
19
+ lib/fsevent/processdevice.rb
20
+ lib/fsevent/processdevicec.rb
21
+ lib/fsevent/schedulemerger.rb
22
+ lib/fsevent/simpledevice.rb
23
+ lib/fsevent/util.rb
24
+ sample/repeat.rb
25
+ sample/repeat2.rb
26
+ ]
27
+ s.test_files = %w[
28
+ test/test_failsafedevice.rb
29
+ test/test_framework.rb
30
+ test/test_processdevice.rb
31
+ test/test_util.rb
32
+ test/test_watch.rb
33
+ ]
34
+ s.has_rdoc = true
35
+ s.homepage = 'https://github.com/fsevent/fsevent'
36
+ s.require_path = 'lib'
37
+ s.summary = 'fail safe event driven framework'
38
+ s.description = <<'End'
39
+ fail safe event driven framework
40
+ End
41
+ end
42
+
43
+
data/lib/fsevent.rb ADDED
@@ -0,0 +1,32 @@
1
+ # fsevent.rb --- library file to be required by users
2
+ #
3
+ # Copyright (C) 2014 National Institute of Advanced Industrial Science and Technology (AIST)
4
+ #
5
+ # This program is free software: you can redistribute it and/or modify
6
+ # it under the terms of the GNU General Public License as published by
7
+ # the Free Software Foundation, either version 3 of the License, or
8
+ # (at your option) any later version.
9
+ #
10
+ # This program is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ # GNU General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU General Public License
16
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
17
+
18
+ require 'rbconfig'
19
+ require 'depq'
20
+
21
+ class FSEvent
22
+ end
23
+
24
+ require 'fsevent/util'
25
+ require 'fsevent/framework'
26
+ require 'fsevent/abstractdevice'
27
+ require 'fsevent/simpledevice'
28
+ require 'fsevent/processdevice'
29
+ require 'fsevent/processdevicec'
30
+ require 'fsevent/failsafedevice.rb'
31
+ require 'fsevent/schedulemerger'
32
+ require 'fsevent/periodicschedule'
@@ -0,0 +1,71 @@
1
+ # abstractdevice.rb --- abstract device definition
2
+ #
3
+ # Copyright (C) 2014 National Institute of Advanced Industrial Science and Technology (AIST)
4
+ #
5
+ # This program is free software: you can redistribute it and/or modify
6
+ # it under the terms of the GNU General Public License as published by
7
+ # the Free Software Foundation, either version 3 of the License, or
8
+ # (at your option) any later version.
9
+ #
10
+ # This program is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ # GNU General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU General Public License
16
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
17
+
18
+ # Abstract class for devices
19
+ #
20
+ class FSEvent::AbstractDevice
21
+ def initialize(device_name)
22
+ @name = device_name
23
+ @current_status = {}
24
+ @schedule = FSEvent::ScheduleMerger.new
25
+ end
26
+ attr_reader :name, :schedule
27
+ attr_writer :framework
28
+
29
+ # Called from the framework when this device is registered.
30
+ def registered
31
+ # child process calls:
32
+ # * @framework.add_watch
33
+ # * @framework.define_status
34
+ # * @framework.status_changed # possible but needless
35
+ # * @framework.set_elapsed_time
36
+ end
37
+
38
+ # Called from the framework when this device is unregistered.
39
+ def unregistered
40
+ end
41
+
42
+ # Called from the framework
43
+ def run(watched_status_change)
44
+ raise NotImplementedError
45
+ # child process calls:
46
+ # * @framework.add_watch # possible but should be rare
47
+ # * @framework.define_status # possible but should be rare
48
+ # * @framework.status_changed
49
+ # * @framework.set_elapsed_time
50
+ end
51
+
52
+ def add_watch(watchee_device_name, status_name, reaction = :immediate)
53
+ @framework.add_watch(watchee_device_name, status_name, reaction)
54
+ end
55
+
56
+ def define_status(status_name, value)
57
+ @framework.define_status(status_name, value)
58
+ end
59
+
60
+ def status_changed(status_name, value)
61
+ @framework.status_changed(status_name, value)
62
+ end
63
+
64
+ def unregister_device(device_name)
65
+ @framework.unregister_device(device_name)
66
+ end
67
+
68
+ def set_elapsed_time(t)
69
+ @framework.set_elapsed_time(t)
70
+ end
71
+ end
@@ -0,0 +1,85 @@
1
+ # failsafedevice.rb --- fail safe device class
2
+ #
3
+ # Copyright (C) 2014 National Institute of Advanced Industrial Science and Technology (AIST)
4
+ #
5
+ # This program is free software: you can redistribute it and/or modify
6
+ # it under the terms of the GNU General Public License as published by
7
+ # the Free Software Foundation, either version 3 of the License, or
8
+ # (at your option) any later version.
9
+ #
10
+ # This program is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ # GNU General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU General Public License
16
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
17
+
18
+ class FSEvent::FailSafeDevice < FSEvent::AbstractDevice
19
+ def initialize(device_name, initial_status, *watchee_device_names)
20
+ super device_name
21
+ raise "One devices required at least" if watchee_device_names.empty?
22
+ @current_status = {} # status_name -> value
23
+ @current_status_list = {} # status_name -> watchee_device_name status_name -> value
24
+ @status_merger = {}
25
+ initial_status.each {|k, v, merger|
26
+ @current_status[k] = v
27
+ @status_merger[k] = merger_callable(merger)
28
+ @current_status_list[k] = {}
29
+ watchee_device_names.each {|watchee_device_name|
30
+ @current_status_list[k][watchee_device_name] = v
31
+ }
32
+ }
33
+ @watchee_device_names = watchee_device_names
34
+ end
35
+
36
+ def merger_callable(merger)
37
+ case merger
38
+ when :max
39
+ method(:merger_max)
40
+ when :min
41
+ method(:merger_min)
42
+ when :lazy
43
+ method(:merger_lazy)
44
+ else
45
+ merger
46
+ end
47
+ end
48
+
49
+ def merger_max(cur, *values) values.max end
50
+ def merger_min(cur, *values) values.min end
51
+ def merger_lazy(cur, *values) values.uniq.length == 1 ? values[0] : cur end
52
+
53
+ def registered
54
+ @watchee_device_names.each {|n|
55
+ @current_status.each {|k, v|
56
+ add_watch n, k
57
+ }
58
+ }
59
+ @current_status.each {|k, v|
60
+ define_status k, v
61
+ }
62
+ end
63
+
64
+ def run(watched_status_change)
65
+ updated = {}
66
+ watched_status_change.each {|watchee_device_name, h|
67
+ h.each {|status_name, value|
68
+ unless updated.has_key? status_name
69
+ updated[status_name] = @current_status_list[status_name]
70
+ end
71
+ updated[status_name][watchee_device_name] = value
72
+ }
73
+ }
74
+ updated.each {|status_name, h|
75
+ merger = @status_merger[status_name]
76
+ cur_val = @current_status[status_name]
77
+ values = @watchee_device_names.map {|d| h[d] }
78
+ new_val = merger.call(cur_val, *values)
79
+ if cur_val != new_val
80
+ @current_status[status_name] = new_val
81
+ status_changed status_name, new_val
82
+ end
83
+ }
84
+ end
85
+ end
@@ -0,0 +1,298 @@
1
+ # framework.rb --- fail safe event driven framework
2
+ #
3
+ # Copyright (C) 2014 National Institute of Advanced Industrial Science and Technology (AIST)
4
+ #
5
+ # This program is free software: you can redistribute it and/or modify
6
+ # it under the terms of the GNU General Public License as published by
7
+ # the Free Software Foundation, either version 3 of the License, or
8
+ # (at your option) any later version.
9
+ #
10
+ # This program is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ # GNU General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU General Public License
16
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
17
+
18
+ class FSEvent
19
+ include FSEvent::Util
20
+
21
+ def initialize(initial_time=Time.now)
22
+ @current_time = initial_time
23
+
24
+ @devices = {} # device_name -> device
25
+
26
+ @status = {} # device_name -> status_name -> value
27
+
28
+ # valid values of reaction: :immediate, :immediate_only_at_beginning, :schedule
29
+ @watches = nested_hash(3) # watchee_device_name -> status_name -> watcher_device_name -> reaction
30
+ @watch_patterns = [] # [watchee_device_name_pat, status_name_pat, watcher_device_name, reaction]
31
+ @watched_status_change = nested_hash(3) # watcher_device_name -> watchee_device_name -> status_name -> value
32
+
33
+ @q = Depq.new
34
+ @schedule_locator = {} # device_name -> locator
35
+ end
36
+ attr_reader :current_time
37
+
38
+ def register_device(device, register_time=@current_time)
39
+ device_name = device.name
40
+ value = [:register, device_name, device]
41
+ @schedule_locator[device_name] = @q.insert value, register_time
42
+ end
43
+
44
+ def start
45
+ until @q.empty?
46
+ loc = @q.delete_min_locator
47
+ event_type, *args = loc.value
48
+ @current_time = loc.priority
49
+ case event_type
50
+ when :register; at_register(loc, *args)
51
+ when :wakeup; at_wakeup(loc, *args)
52
+ when :sleep; at_sleep(loc, *args)
53
+ else
54
+ raise "unexpected event type: #{event_type}"
55
+ end
56
+ end
57
+ end
58
+
59
+ def wrap_device_action(&block)
60
+ Thread.current[:fsevent_device_watch_buffer] = device_watch_buffer = []
61
+ Thread.current[:fsevent_device_define_buffer] = device_define_buffer = []
62
+ Thread.current[:fsevent_device_changed_buffer] = device_changed_buffer = []
63
+ Thread.current[:fsevent_unregister_device_buffer] = unregister_device_buffer = []
64
+ Thread.current[:fsevent_device_elapsed_time] = nil
65
+ t1 = Time.now
66
+ yield
67
+ t2 = Time.now
68
+ elapsed = Thread.current[:fsevent_device_elapsed_time] || t2 - t1
69
+ return device_watch_buffer, device_define_buffer, device_changed_buffer, unregister_device_buffer, elapsed
70
+ ensure
71
+ Thread.current[:fsevent_device_watch_buffer] = nil
72
+ Thread.current[:fsevent_device_define_buffer] = nil
73
+ Thread.current[:fsevent_device_changed_buffer] = nil
74
+ Thread.current[:fsevent_unregister_device_buffer] = nil
75
+ Thread.current[:fsevent_device_elapsed_time] = nil
76
+ end
77
+ private :wrap_device_action
78
+
79
+ # Called from a device. (mainly from registered().)
80
+ def add_watch(watchee_device_name, status_name, reaction = :immediate)
81
+ Thread.current[:fsevent_device_watch_buffer] << [:add, watchee_device_name, status_name, reaction]
82
+ end
83
+
84
+ # Called from a device. (mainly from registered().)
85
+ def del_watch(watchee_device_name, status_name)
86
+ Thread.current[:fsevent_device_watch_buffer] << [:del, watchee_device_name, status_name, nil]
87
+ end
88
+
89
+ # Called from a device to define the status.
90
+ def define_status(status_name, value)
91
+ Thread.current[:fsevent_device_define_buffer] << [status_name, value]
92
+ end
93
+
94
+ # Called from a device to notify the status.
95
+ def status_changed(status_name, value)
96
+ Thread.current[:fsevent_device_changed_buffer] << [status_name, value]
97
+ end
98
+
99
+ # Called from a device.
100
+ def unregister_device(device_name)
101
+ Thread.current[:fsevent_unregister_device_buffer] << device_name
102
+ end
103
+
104
+ # Called from a device to set the elapsed time.
105
+ def set_elapsed_time(t)
106
+ raise "elapsed time must be positive: #{t}" if t <= 0
107
+ Thread.current[:fsevent_device_elapsed_time] = t
108
+ end
109
+
110
+ def at_register(loc, device_name, device)
111
+ if @devices.has_key? device_name
112
+ raise "Device already registered: #{device_name}"
113
+ end
114
+
115
+ device_watch_buffer, device_define_buffer, device_changed_buffer, unregister_device_buffer, elapsed =
116
+ wrap_device_action {
117
+ device.framework = self
118
+ device.registered
119
+ }
120
+
121
+ @devices[device_name] = device
122
+ @status[device_name] = {}
123
+
124
+ value = [:sleep, device_name, device_watch_buffer, device_define_buffer, device_changed_buffer, unregister_device_buffer]
125
+ loc.update value, current_time + elapsed
126
+ @q.insert_locator loc
127
+ end
128
+ private :at_register
129
+
130
+ def at_wakeup(loc, device_name)
131
+ time = loc.priority
132
+ device = @devices[device_name]
133
+
134
+ watched_status_change = @watched_status_change.delete(device_name)
135
+ watched_status_change = nonempty_hash(watched_status_change, 2)
136
+
137
+ device_watch_buffer, device_define_buffer, device_changed_buffer, unregister_device_buffer, elapsed =
138
+ wrap_device_action { device.run(watched_status_change) }
139
+
140
+ value = [:sleep, device_name, device_watch_buffer, device_define_buffer, device_changed_buffer, unregister_device_buffer]
141
+ loc.update value, time + elapsed
142
+ @q.insert_locator loc
143
+ end
144
+ private :at_wakeup
145
+
146
+ def at_sleep(loc, device_name, device_watch_buffer, device_define_buffer, device_changed_buffer, unregister_device_buffer)
147
+ sleep_time = loc.priority
148
+ update_status(device_name, device_define_buffer, device_changed_buffer)
149
+ wakeup_immediate = update_watch(device_name, device_watch_buffer)
150
+ notify_status_change(device_name, sleep_time, device_define_buffer, device_changed_buffer)
151
+ wakeup_immediate ||= immediate_wakeup?(device_name)
152
+ setup_next_schedule(device_name, loc, sleep_time, wakeup_immediate)
153
+ unregister_device_internal(unregister_device_buffer)
154
+ end
155
+ private :at_sleep
156
+
157
+ def update_status(device_name, device_define_buffer, device_changed_buffer)
158
+ device_define_buffer.each {|status_name, value|
159
+ if @status[device_name].has_key? status_name
160
+ raise "device status already defined: #{device_name} #{status_name}"
161
+ end
162
+ @status[device_name][status_name] = value
163
+ }
164
+ device_changed_buffer.each {|status_name, value|
165
+ unless @status[device_name].has_key? status_name
166
+ raise "device status not defined: #{device_name} #{status_name}"
167
+ end
168
+ @status[device_name][status_name] = value
169
+ }
170
+ end
171
+ private :update_status
172
+
173
+ def update_watch(device_name, device_watch_buffer)
174
+ wakeup_immediate = false
175
+ device_watch_buffer.each {|add_or_del, watchee_device_name, status_name, reaction|
176
+ case add_or_del
177
+ when :add
178
+ @watches[watchee_device_name][status_name][device_name] = reaction
179
+ if @status.has_key?(watchee_device_name) &&
180
+ @status[watchee_device_name].has_key?(status_name)
181
+ @watched_status_change[device_name][watchee_device_name][status_name] = @status[watchee_device_name][status_name]
182
+ wakeup_immediate ||= reaction_immediate_at_beginning? reaction
183
+ end
184
+ when :del
185
+ @watches[watchee_device_name][status_name].delete device_name
186
+ @watched_status_change[device_name][watchee_device_name].delete status_name
187
+ else
188
+ raise "unexpected add_or_del: #{add_or_del.inspect}"
189
+ end
190
+ }
191
+ wakeup_immediate
192
+ end
193
+ private :update_watch
194
+
195
+ def notify_status_change(device_name, sleep_time, device_define_buffer, device_changed_buffer)
196
+ device_define_buffer.each {|status_name, _|
197
+ value = @status[device_name][status_name]
198
+ lookup_watchers(device_name, status_name).each {|watcher_device_name, reaction|
199
+ @watched_status_change[watcher_device_name][device_name][status_name] = value
200
+ set_wakeup_if_possible(watcher_device_name, sleep_time) if reaction_immediate_at_beginning? reaction
201
+ }
202
+ }
203
+ device_changed_buffer.each {|status_name, _|
204
+ value = @status[device_name][status_name]
205
+ lookup_watchers(device_name, status_name).each {|watcher_device_name, reaction|
206
+ @watched_status_change[watcher_device_name][device_name][status_name] = value
207
+ set_wakeup_if_possible(watcher_device_name, sleep_time) if reaction_immediate_at_subsequent? reaction
208
+ }
209
+ }
210
+ end
211
+ private :notify_status_change
212
+
213
+ def lookup_watchers(watchee_device_name, status_name)
214
+ result = []
215
+ if @watches.has_key?(watchee_device_name) &&
216
+ @watches[watchee_device_name].has_key?(status_name)
217
+ @watches[watchee_device_name][status_name].each {|watcher_device_name, reaction|
218
+ result << [watcher_device_name, reaction]
219
+ }
220
+ end
221
+ result
222
+ end
223
+ private :lookup_watchers
224
+
225
+ def set_wakeup_if_possible(device_name, time)
226
+ loc = @schedule_locator[device_name]
227
+ if !loc.in_queue?
228
+ loc.update [:wakeup, device_name], time
229
+ @q.insert_locator loc
230
+ return
231
+ end
232
+ case event_type = loc.value.first
233
+ when :wakeup # The device is sleeping now.
234
+ if time < loc.priority
235
+ loc.update_priority time
236
+ end
237
+ when :sleep # The device is working now.
238
+ # Nothing to do. at_sleep itself checks arrived events at last.
239
+ else
240
+ raise "unexpected event type: #{event_type}"
241
+ end
242
+ end
243
+ private :set_wakeup_if_possible
244
+
245
+ def setup_next_schedule(device_name, loc, sleep_time, wakeup_immediate)
246
+ device = @devices[device_name]
247
+ wakeup_time = nil
248
+ if wakeup_immediate
249
+ wakeup_time = sleep_time
250
+ elsif wakeup_time = device.schedule.shift
251
+ if wakeup_time < sleep_time
252
+ wakeup_time = sleep_time
253
+ end
254
+ while device.schedule.first && device.schedule.first < sleep_time
255
+ device.schedule.shift
256
+ end
257
+ end
258
+ if wakeup_time
259
+ value = [:wakeup, device_name]
260
+ loc.update value, wakeup_time
261
+ @q.insert_locator loc
262
+ end
263
+ end
264
+ private :setup_next_schedule
265
+
266
+ def immediate_wakeup?(watcher_device_name)
267
+ return false unless @watched_status_change.has_key?(watcher_device_name)
268
+ @watched_status_change[watcher_device_name].each {|watchee_device_name, h|
269
+ h.each {|status_name, value|
270
+ lookup_watchers(watchee_device_name, status_name).each {|watcher_device_name2, reaction|
271
+ next if watcher_device_name != watcher_device_name2
272
+ return true if reaction_immediate_at_subsequent?(reaction)
273
+ }
274
+ }
275
+ }
276
+ false
277
+ end
278
+ private :immediate_wakeup?
279
+
280
+ def unregister_device_internal(unregister_device_buffer)
281
+ unregister_device_buffer.each {|device_name|
282
+ device = @devices.delete device_name
283
+ @status.delete device_name
284
+ @watches.each {|watchee_device_name, h1|
285
+ h1.each {|status_name, h2|
286
+ h2.delete device_name
287
+ }
288
+ }
289
+ @watched_status_change.delete device_name
290
+ @status.delete device_name
291
+ loc = @schedule_locator.fetch(device_name)
292
+ device.unregistered
293
+ @q.delete_locator loc
294
+ }
295
+ end
296
+ private :unregister_device_internal
297
+
298
+ end