fusuma 2.5.1 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +55 -34
- data/lib/fusuma/config/index.rb +19 -46
- data/lib/fusuma/config/searcher.rb +14 -61
- data/lib/fusuma/custom_process.rb +33 -1
- data/lib/fusuma/device.rb +1 -0
- data/lib/fusuma/multi_logger.rb +16 -0
- data/lib/fusuma/plugin/base.rb +4 -1
- data/lib/fusuma/plugin/buffers/gesture_buffer.rb +63 -6
- data/lib/fusuma/plugin/detectors/detector.rb +10 -9
- data/lib/fusuma/plugin/detectors/hold_detector.rb +24 -14
- data/lib/fusuma/plugin/detectors/pinch_detector.rb +10 -10
- data/lib/fusuma/plugin/detectors/rotate_detector.rb +4 -11
- data/lib/fusuma/plugin/detectors/swipe_detector.rb +7 -15
- data/lib/fusuma/plugin/events/records/gesture_record.rb +5 -2
- data/lib/fusuma/plugin/events/records/index_record.rb +1 -1
- data/lib/fusuma/plugin/executors/command_executor.rb +2 -2
- data/lib/fusuma/plugin/executors/executor.rb +1 -4
- data/lib/fusuma/plugin/filters/libinput_device_filter.rb +2 -1
- data/lib/fusuma/plugin/inputs/input.rb +14 -36
- data/lib/fusuma/plugin/inputs/timer_input.rb +36 -21
- data/lib/fusuma/plugin/manager.rb +11 -6
- data/lib/fusuma/version.rb +1 -1
- data/lib/fusuma.rb +44 -34
- metadata +3 -3
@@ -7,9 +7,25 @@ module Fusuma
|
|
7
7
|
module Buffers
|
8
8
|
# manage events and generate command
|
9
9
|
class GestureBuffer < Buffer
|
10
|
+
CacheEntry = Struct.new(:checked, :value)
|
10
11
|
DEFAULT_SOURCE = "libinput_gesture_parser"
|
11
12
|
DEFAULT_SECONDS_TO_KEEP = 100
|
12
13
|
|
14
|
+
|
15
|
+
def initialize(*args)
|
16
|
+
super(*args)
|
17
|
+
@cache = {}
|
18
|
+
@cache_select_by = {}
|
19
|
+
@cache_sum10 = {}
|
20
|
+
end
|
21
|
+
|
22
|
+
def clear
|
23
|
+
super.clear
|
24
|
+
@cache = {}
|
25
|
+
@cache_select_by = {}
|
26
|
+
@cache_sum10 = {}
|
27
|
+
end
|
28
|
+
|
13
29
|
def config_param_types
|
14
30
|
{
|
15
31
|
source: [String],
|
@@ -40,6 +56,9 @@ module Fusuma
|
|
40
56
|
MultiLogger.debug("#{self.class.name}##{__method__}")
|
41
57
|
|
42
58
|
@events.delete(e)
|
59
|
+
@cache = {}
|
60
|
+
@cache_select_by = {}
|
61
|
+
@cache_sum10 = {}
|
43
62
|
end
|
44
63
|
end
|
45
64
|
|
@@ -59,11 +78,35 @@ module Fusuma
|
|
59
78
|
def sum_attrs(attr)
|
60
79
|
updating_events.map do |gesture_event|
|
61
80
|
gesture_event.record.delta[attr].to_f
|
62
|
-
end.
|
81
|
+
end.reduce(:+)
|
82
|
+
end
|
83
|
+
|
84
|
+
# @param attr [Symbol]
|
85
|
+
# @return [Float]
|
86
|
+
def sum_last10_attrs(attr) # sums last 10 values of attr (or all if length < 10)
|
87
|
+
cache_entry = ( @cache_sum10[attr] ||= CacheEntry.new(0, 0) )
|
88
|
+
upd_ev = updating_events
|
89
|
+
if upd_ev.length > cache_entry.checked + 1 then
|
90
|
+
cache_entry.value = upd_ev.last(10).map do |gesture_event|
|
91
|
+
gesture_event.record.delta[attr].to_f
|
92
|
+
end.reduce(:+)
|
93
|
+
elsif upd_ev.length > cache_entry.checked
|
94
|
+
cache_entry.value = cache_entry.value + upd_ev[-1].record.delta[attr].to_f - \
|
95
|
+
(upd_ev.length > 10 ? upd_ev[-11].record.delta[attr].to_f : 0)
|
96
|
+
else
|
97
|
+
return cache_entry.value
|
98
|
+
end
|
99
|
+
cache_entry.checked = upd_ev.length
|
100
|
+
cache_entry.value
|
63
101
|
end
|
64
102
|
|
65
103
|
def updating_events
|
66
|
-
@
|
104
|
+
cache_entry = ( @cache[:updating_events] ||= CacheEntry.new(0, []) )
|
105
|
+
cache_entry.checked.upto(@events.length - 1).each do |i|
|
106
|
+
(cache_entry.value << @events[i]) if @events[i].record.status == "update"
|
107
|
+
end
|
108
|
+
cache_entry.checked = @events.length
|
109
|
+
cache_entry.value
|
67
110
|
end
|
68
111
|
|
69
112
|
# @param attr [Symbol]
|
@@ -96,14 +139,28 @@ module Fusuma
|
|
96
139
|
self.class.new events
|
97
140
|
end
|
98
141
|
|
142
|
+
def select_by_type(type)
|
143
|
+
cache_entry = ( @cache_select_by[type] ||= CacheEntry.new(0, self.class.new([])) )
|
144
|
+
cache_entry.checked.upto(@events.length - 1).each do |i|
|
145
|
+
(cache_entry.value.events << @events[i]) if @events[i].record.gesture == type
|
146
|
+
end
|
147
|
+
cache_entry.checked = @events.length
|
148
|
+
cache_entry.value
|
149
|
+
end
|
150
|
+
|
99
151
|
def select_from_last_begin
|
100
152
|
return self if empty?
|
153
|
+
cache_entry = ( @cache[:last_begin] ||= CacheEntry.new(0, nil) )
|
154
|
+
|
155
|
+
cache_entry.value = (@events.length - 1).downto(cache_entry.checked).find do |i|
|
156
|
+
@events[i].record.status == "begin"
|
157
|
+
end || cache_entry.value
|
158
|
+
cache_entry.checked = @events.length
|
101
159
|
|
102
|
-
|
103
|
-
return GestureBuffer.new([]) if
|
160
|
+
return self if cache_entry.value == 0
|
161
|
+
return GestureBuffer.new([]) if cache_entry.value.nil?
|
104
162
|
|
105
|
-
|
106
|
-
GestureBuffer.new(@events[index_last_begin..-1])
|
163
|
+
GestureBuffer.new(@events[cache_entry.value..-1])
|
107
164
|
end
|
108
165
|
end
|
109
166
|
end
|
@@ -8,9 +8,18 @@ module Fusuma
|
|
8
8
|
module Detectors
|
9
9
|
# Inherite this base
|
10
10
|
class Detector < Base
|
11
|
+
def initialize(*args)
|
12
|
+
super(*args)
|
13
|
+
@tag = self.class.tag
|
14
|
+
@type = self.class.type
|
15
|
+
end
|
16
|
+
|
17
|
+
attr_reader :tag
|
18
|
+
attr_reader :type
|
19
|
+
|
11
20
|
# @return [Array<String>]
|
12
21
|
def sources
|
13
|
-
@
|
22
|
+
@sources ||= self.class.const_get(:SOURCES)
|
14
23
|
end
|
15
24
|
|
16
25
|
# Always watch buffers and detect them or not
|
@@ -43,14 +52,6 @@ module Fusuma
|
|
43
52
|
@last_time.nil?
|
44
53
|
end
|
45
54
|
|
46
|
-
def tag
|
47
|
-
self.class.tag
|
48
|
-
end
|
49
|
-
|
50
|
-
def type
|
51
|
-
self.class.type
|
52
|
-
end
|
53
|
-
|
54
55
|
class << self
|
55
56
|
def tag
|
56
57
|
name.split("Detectors::").last.underscore
|
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative "./detector"
|
4
|
+
require_relative "../inputs/timer_input"
|
4
5
|
|
5
6
|
module Fusuma
|
6
7
|
module Plugin
|
@@ -10,6 +11,7 @@ module Fusuma
|
|
10
11
|
SOURCES = %w[gesture timer].freeze
|
11
12
|
BUFFER_TYPE = "gesture"
|
12
13
|
GESTURE_RECORD_TYPE = "hold"
|
14
|
+
Timer = Inputs::TimerInput.instance
|
13
15
|
|
14
16
|
BASE_THERESHOLD = 0.7
|
15
17
|
|
@@ -21,16 +23,15 @@ module Fusuma
|
|
21
23
|
hold_buffer = find_hold_buffer(buffers)
|
22
24
|
return if hold_buffer.empty?
|
23
25
|
|
24
|
-
|
26
|
+
last_hold = hold_buffer.events.last
|
25
27
|
|
26
28
|
timer_buffer = buffers.find { |b| b.type == "timer" }
|
27
|
-
|
29
|
+
last_timer = timer_buffer.events.last
|
28
30
|
|
29
31
|
finger = hold_buffer.finger
|
30
|
-
holding_time = calc_holding_time(hold_events:
|
32
|
+
holding_time = calc_holding_time(hold_events: hold_buffer.events, last_timer: last_timer)
|
31
33
|
|
32
|
-
|
33
|
-
status = case hold_events.last.record.status
|
34
|
+
status = case last_hold.record.status
|
34
35
|
when "begin"
|
35
36
|
if holding_time.zero?
|
36
37
|
"begin"
|
@@ -42,16 +43,19 @@ module Fusuma
|
|
42
43
|
when "end"
|
43
44
|
"end"
|
44
45
|
else
|
45
|
-
last_record =
|
46
|
+
last_record = last_hold.record.status
|
46
47
|
raise "Unexpected Status:#{last_record.status} in #{last_record}"
|
47
48
|
end
|
48
49
|
|
49
50
|
repeat_index = create_repeat_index(finger: finger, status: status)
|
50
51
|
oneshot_index = create_oneshot_index(finger: finger)
|
51
52
|
|
52
|
-
|
53
|
-
|
54
|
-
|
53
|
+
if status == "begin" then
|
54
|
+
@timeout = nil
|
55
|
+
if threshold(index: oneshot_index) < Timer.interval then
|
56
|
+
Timer.wake_early(Time.now + threshold(index: oneshot_index))
|
57
|
+
end
|
58
|
+
elsif status == "timer"
|
55
59
|
return if @timeout
|
56
60
|
|
57
61
|
return unless enough?(index: oneshot_index, holding_time: holding_time)
|
@@ -97,12 +101,12 @@ module Fusuma
|
|
97
101
|
def find_hold_buffer(buffers)
|
98
102
|
buffers.find { |b| b.type == BUFFER_TYPE }
|
99
103
|
.select_from_last_begin
|
100
|
-
.
|
104
|
+
.select_by_type(GESTURE_RECORD_TYPE)
|
101
105
|
end
|
102
106
|
|
103
|
-
def calc_holding_time(hold_events:,
|
104
|
-
last_time = if
|
105
|
-
|
107
|
+
def calc_holding_time(hold_events:, last_timer:)
|
108
|
+
last_time = if last_timer && (hold_events.last.time < last_timer.time)
|
109
|
+
last_timer.time
|
106
110
|
else
|
107
111
|
hold_events.last.time
|
108
112
|
end
|
@@ -110,7 +114,13 @@ module Fusuma
|
|
110
114
|
end
|
111
115
|
|
112
116
|
def enough?(index:, holding_time:)
|
113
|
-
|
117
|
+
diff = threshold(index: index) - holding_time
|
118
|
+
if diff < 0 then
|
119
|
+
true
|
120
|
+
elsif diff < Timer.interval
|
121
|
+
Timer.wake_early(Time.now + diff)
|
122
|
+
false
|
123
|
+
end
|
114
124
|
end
|
115
125
|
|
116
126
|
def threshold(index:)
|
@@ -19,7 +19,7 @@ module Fusuma
|
|
19
19
|
def detect(buffers)
|
20
20
|
gesture_buffer = buffers.find { |b| b.type == BUFFER_TYPE }
|
21
21
|
.select_from_last_begin
|
22
|
-
.
|
22
|
+
.select_by_type(GESTURE_RECORD_TYPE)
|
23
23
|
|
24
24
|
updating_events = gesture_buffer.updating_events
|
25
25
|
return if updating_events.empty?
|
@@ -100,10 +100,10 @@ module Fusuma
|
|
100
100
|
def create_repeat_index(gesture:, finger:, direction:, status:)
|
101
101
|
Config::Index.new(
|
102
102
|
[
|
103
|
-
Config::Index::Key.new(gesture),
|
104
|
-
Config::Index::Key.new(finger.to_i),
|
105
|
-
Config::Index::Key.new(direction, skippable: true),
|
106
|
-
Config::Index::Key.new(status)
|
103
|
+
Config::Index::Key.new(gesture), # 'pinch'
|
104
|
+
Config::Index::Key.new(finger.to_i), # 2, 3, 4
|
105
|
+
Config::Index::Key.new(direction, skippable: true), # 'in', 'out'
|
106
|
+
Config::Index::Key.new(status) # 'begin', 'update', 'end'
|
107
107
|
]
|
108
108
|
)
|
109
109
|
end
|
@@ -115,9 +115,9 @@ module Fusuma
|
|
115
115
|
def create_oneshot_index(gesture:, finger:, direction:)
|
116
116
|
Config::Index.new(
|
117
117
|
[
|
118
|
-
Config::Index::Key.new(gesture),
|
119
|
-
Config::Index::Key.new(finger.to_i, skippable: true),
|
120
|
-
Config::Index::Key.new(direction)
|
118
|
+
Config::Index::Key.new(gesture), # 'pinch'
|
119
|
+
Config::Index::Key.new(finger.to_i, skippable: true), # 2, 3, 4
|
120
|
+
Config::Index::Key.new(direction) # 'in', 'out'
|
121
121
|
]
|
122
122
|
)
|
123
123
|
end
|
@@ -161,9 +161,9 @@ module Fusuma
|
|
161
161
|
|
162
162
|
def calc
|
163
163
|
if @target > @base
|
164
|
-
IN
|
165
|
-
else
|
166
164
|
OUT
|
165
|
+
else
|
166
|
+
IN
|
167
167
|
end
|
168
168
|
end
|
169
169
|
end
|
@@ -19,21 +19,14 @@ module Fusuma
|
|
19
19
|
def detect(buffers)
|
20
20
|
gesture_buffer = buffers.find { |b| b.type == BUFFER_TYPE }
|
21
21
|
.select_from_last_begin
|
22
|
-
.
|
22
|
+
.select_by_type(GESTURE_RECORD_TYPE)
|
23
23
|
|
24
24
|
updating_events = gesture_buffer.updating_events
|
25
25
|
return if updating_events.empty?
|
26
26
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
last_10.sum_attrs(:rotate) / updating_time
|
31
|
-
else
|
32
|
-
updating_time = 100 * (updating_events.last.time - updating_events.first.time)
|
33
|
-
gesture_buffer.sum_attrs(:rotate) / updating_time
|
34
|
-
end
|
35
|
-
|
36
|
-
return if updating_events.empty?
|
27
|
+
updating_time = 100 * (updating_events[-1].time -
|
28
|
+
(updating_events[-10] || updating_events.first).time)
|
29
|
+
oneshot_angle = gesture_buffer.sum_last10_attrs(:rotate) / updating_time
|
37
30
|
|
38
31
|
finger = gesture_buffer.finger
|
39
32
|
|
@@ -19,22 +19,15 @@ module Fusuma
|
|
19
19
|
def detect(buffers)
|
20
20
|
gesture_buffer = buffers.find { |b| b.type == BUFFER_TYPE }
|
21
21
|
.select_from_last_begin
|
22
|
-
.
|
22
|
+
.select_by_type(GESTURE_RECORD_TYPE)
|
23
23
|
|
24
24
|
updating_events = gesture_buffer.updating_events
|
25
25
|
return if updating_events.empty?
|
26
26
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
last_10.sum_attrs(:move_y) / updating_time]
|
32
|
-
else
|
33
|
-
updating_time = 100 * (updating_events.last.time - updating_events.first.time)
|
34
|
-
[gesture_buffer.sum_attrs(:move_x) / updating_time,
|
35
|
-
gesture_buffer.sum_attrs(:move_y) / updating_time]
|
36
|
-
end
|
37
|
-
(gesture_buffer.sum_attrs(:move_x) / updating_time)
|
27
|
+
updating_time = 100 * (updating_events.last.time -
|
28
|
+
(updating_events[-10] || updating_events.first).time)
|
29
|
+
oneshot_move_x = gesture_buffer.sum_last10_attrs(:move_x) / updating_time
|
30
|
+
oneshot_move_y = gesture_buffer.sum_last10_attrs(:move_y) / updating_time
|
38
31
|
|
39
32
|
finger = gesture_buffer.finger
|
40
33
|
status = case gesture_buffer.events.last.record.status
|
@@ -67,8 +60,7 @@ module Fusuma
|
|
67
60
|
|
68
61
|
oneshot_direction = Direction.new(move_x: oneshot_move_x, move_y: oneshot_move_y).to_s
|
69
62
|
oneshot_quantity = Quantity.new(move_x: oneshot_move_x, move_y: oneshot_move_y).to_f
|
70
|
-
oneshot_index = create_oneshot_index(gesture: type, finger: finger,
|
71
|
-
direction: oneshot_direction)
|
63
|
+
oneshot_index = create_oneshot_index(gesture: type, finger: finger, direction: oneshot_direction)
|
72
64
|
if enough_oneshot_threshold?(index: oneshot_index, quantity: oneshot_quantity)
|
73
65
|
return [
|
74
66
|
create_event(record: Events::Records::IndexRecord.new(
|
@@ -109,7 +101,7 @@ module Fusuma
|
|
109
101
|
Config::Index.new(
|
110
102
|
[
|
111
103
|
Config::Index::Key.new(gesture),
|
112
|
-
Config::Index::Key.new(finger.to_i
|
104
|
+
Config::Index::Key.new(finger.to_i),
|
113
105
|
Config::Index::Key.new(direction)
|
114
106
|
]
|
115
107
|
)
|
@@ -11,9 +11,12 @@ module Fusuma
|
|
11
11
|
# define gesture format
|
12
12
|
attr_reader :status, :gesture, :finger, :delta
|
13
13
|
|
14
|
-
Delta = Struct.new(
|
14
|
+
Delta = Struct.new(
|
15
|
+
:move_x, :move_y,
|
15
16
|
:unaccelerated_x, :unaccelerated_y,
|
16
|
-
:zoom,
|
17
|
+
:zoom,
|
18
|
+
:rotate
|
19
|
+
)
|
17
20
|
|
18
21
|
# @param status [String]
|
19
22
|
# @param gesture [String]
|
@@ -39,14 +39,14 @@ module Fusuma
|
|
39
39
|
# @return [String]
|
40
40
|
def search_command(event)
|
41
41
|
command_index = Config::Index.new([*event.record.index.keys, :command])
|
42
|
-
Config.search(command_index)
|
42
|
+
Config.instance.search(command_index)
|
43
43
|
end
|
44
44
|
|
45
45
|
# @param event [Event]
|
46
46
|
# @return [Float]
|
47
47
|
def args_accel(event)
|
48
48
|
accel_index = Config::Index.new([*event.record.index.keys, :accel])
|
49
|
-
(Config.search(accel_index) || 1).to_f
|
49
|
+
(Config.instance.search(accel_index) || 1).to_f
|
50
50
|
end
|
51
51
|
end
|
52
52
|
end
|
@@ -26,12 +26,9 @@ module Fusuma
|
|
26
26
|
end
|
27
27
|
|
28
28
|
# @param event [Events::Event]
|
29
|
-
# @param time [Time]
|
30
29
|
# @return [TrueClass, FalseClass]
|
31
30
|
def enough_interval?(event)
|
32
|
-
|
33
|
-
# Otherwise, a wrong index will cause invalid intervals.
|
34
|
-
return true if event.record.index.with_context.keys.any? { |key| key.symbol == :end }
|
31
|
+
return true if event.record.index.keys.any? { |key| key.symbol == :end }
|
35
32
|
|
36
33
|
return false if @wait_until && event.time < @wait_until
|
37
34
|
|
@@ -25,7 +25,8 @@ module Fusuma
|
|
25
25
|
keep_device.reset
|
26
26
|
return false
|
27
27
|
end
|
28
|
-
|
28
|
+
device_id = record.to_s.match(/\S*/, 1).to_s
|
29
|
+
keep_device.all.map(&:id).include?(device_id)
|
29
30
|
end
|
30
31
|
|
31
32
|
def keep_device
|
@@ -9,8 +9,14 @@ module Fusuma
|
|
9
9
|
# Inherite this base
|
10
10
|
# @abstract Subclass and override {#io} to implement
|
11
11
|
class Input < Base
|
12
|
+
def initialize(*args)
|
13
|
+
super(*args)
|
14
|
+
@tag = self.class.name.split("Inputs::").last.underscore
|
15
|
+
end
|
16
|
+
|
17
|
+
attr_reader :tag
|
18
|
+
|
12
19
|
# Wait multiple inputs until it becomes readable
|
13
|
-
# and read lines with nonblock
|
14
20
|
# @param inputs [Array<Input>]
|
15
21
|
# @return [Event]
|
16
22
|
def self.select(inputs)
|
@@ -20,20 +26,17 @@ module Fusuma
|
|
20
26
|
input = inputs.find { |i| i.io == io }
|
21
27
|
|
22
28
|
begin
|
23
|
-
|
29
|
+
# NOTE: io.readline is blocking method
|
30
|
+
# each input plugin must write line to pipe (include `\n`)
|
31
|
+
line = io.readline(chomp: true)
|
24
32
|
rescue EOFError => e
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
warn "stop process: #{i.class.name.underscore}"
|
29
|
-
Process.kill(:SIGKILL, i.pid)
|
30
|
-
end
|
31
|
-
exit 1
|
33
|
+
MultiLogger.error "#{input.class.name}: #{e}"
|
34
|
+
MultiLogger.error "Shutdown fusuma process..."
|
35
|
+
Process.kill("TERM", Process.pid)
|
32
36
|
rescue => e
|
33
|
-
|
37
|
+
MultiLogger.error "#{input.class.name}: #{e}"
|
34
38
|
exit 1
|
35
39
|
end
|
36
|
-
|
37
40
|
input.create_event(record: line)
|
38
41
|
end
|
39
42
|
|
@@ -53,32 +56,7 @@ module Fusuma
|
|
53
56
|
MultiLogger.debug(input_event: e)
|
54
57
|
e
|
55
58
|
end
|
56
|
-
|
57
|
-
def tag
|
58
|
-
self.class.name.split("Inputs::").last.underscore
|
59
|
-
end
|
60
59
|
end
|
61
60
|
end
|
62
61
|
end
|
63
62
|
end
|
64
|
-
|
65
|
-
# ref: https://github.com/Homebrew/brew/blob/6b2dbbc96f7d8aa12f9b8c9c60107c9cc58befc4/Library/Homebrew/extend/io.rb
|
66
|
-
class IO
|
67
|
-
def readline_nonblock(sep = $INPUT_RECORD_SEPARATOR)
|
68
|
-
line = +""
|
69
|
-
buffer = +""
|
70
|
-
|
71
|
-
loop do
|
72
|
-
break if buffer == sep
|
73
|
-
|
74
|
-
read_nonblock(1, buffer)
|
75
|
-
line.concat(buffer)
|
76
|
-
end
|
77
|
-
|
78
|
-
line.freeze
|
79
|
-
rescue IO::WaitReadable, EOFError => e
|
80
|
-
raise e if line.empty?
|
81
|
-
|
82
|
-
line.freeze
|
83
|
-
end
|
84
|
-
end
|
@@ -1,20 +1,29 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative "./input"
|
4
|
+
require "timeout"
|
4
5
|
|
5
6
|
module Fusuma
|
6
7
|
module Plugin
|
7
8
|
module Inputs
|
8
9
|
# libinput commands wrapper
|
9
10
|
class TimerInput < Input
|
10
|
-
|
11
|
+
include Singleton
|
12
|
+
DEFAULT_INTERVAL = 5
|
13
|
+
EPSILON_TIME = 0.02
|
11
14
|
def config_param_types
|
12
15
|
{
|
13
16
|
interval: [Float]
|
14
17
|
}
|
15
18
|
end
|
16
19
|
|
17
|
-
|
20
|
+
def initialize(*args, interval: nil)
|
21
|
+
super(*args)
|
22
|
+
@interval = interval || config_params(:interval) || DEFAULT_INTERVAL
|
23
|
+
@early_wake_queue = Queue.new
|
24
|
+
end
|
25
|
+
|
26
|
+
attr_reader :pid, :interval
|
18
27
|
|
19
28
|
def io
|
20
29
|
@io ||= begin
|
@@ -26,26 +35,36 @@ module Fusuma
|
|
26
35
|
end
|
27
36
|
|
28
37
|
def start(reader, writer)
|
29
|
-
|
30
|
-
timer_loop(
|
38
|
+
Thread.new do
|
39
|
+
timer_loop(writer)
|
31
40
|
end
|
32
|
-
|
33
|
-
writer.close
|
34
|
-
pid
|
41
|
+
nil
|
35
42
|
end
|
36
43
|
|
37
|
-
def timer_loop(
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
44
|
+
def timer_loop(writer)
|
45
|
+
delta_t = @interval
|
46
|
+
next_wake = Time.now + delta_t
|
47
|
+
loop do
|
48
|
+
sleep_time = next_wake - Time.now
|
49
|
+
if sleep_time <= 0
|
50
|
+
raise Timeout::Error
|
51
|
+
end
|
52
|
+
|
53
|
+
Timeout.timeout(sleep_time) do
|
54
|
+
next_wake = [@early_wake_queue.deq, next_wake].min
|
43
55
|
end
|
44
|
-
rescue
|
45
|
-
|
46
|
-
|
47
|
-
MultiLogger.error e
|
56
|
+
rescue Timeout::Error
|
57
|
+
writer.puts "timer"
|
58
|
+
next_wake = Time.now + delta_t
|
48
59
|
end
|
60
|
+
rescue Errno::EPIPE
|
61
|
+
exit 0
|
62
|
+
rescue => e
|
63
|
+
MultiLogger.error e
|
64
|
+
end
|
65
|
+
|
66
|
+
def wake_early(t)
|
67
|
+
@early_wake_queue.push(t + EPSILON_TIME)
|
49
68
|
end
|
50
69
|
|
51
70
|
private
|
@@ -53,10 +72,6 @@ module Fusuma
|
|
53
72
|
def create_io
|
54
73
|
IO.pipe
|
55
74
|
end
|
56
|
-
|
57
|
-
def interval
|
58
|
-
config_params(:interval) || DEFAULT_INTERVAL
|
59
|
-
end
|
60
75
|
end
|
61
76
|
end
|
62
77
|
end
|
@@ -27,29 +27,34 @@ module Fusuma
|
|
27
27
|
@_fusuma_default_plugin_paths ||= Dir.glob(File.expand_path("#{__dir__}/../../#{search_key}")).grep_v(exclude_path_pattern).sort
|
28
28
|
end
|
29
29
|
|
30
|
+
# @return [Array<String>] paths of external plugins (installed by gem)
|
30
31
|
def fusuma_external_plugin_paths
|
31
32
|
@_fusuma_external_plugin_paths ||=
|
32
33
|
Gem.find_latest_files(search_key).map do |siblings_plugin|
|
33
34
|
next unless %r{fusuma-plugin-(.+).*/lib/#{plugin_dir_name}/.+\.rb}.match?(siblings_plugin)
|
34
35
|
|
35
36
|
match_data = siblings_plugin.match(%r{(.*)/(.*)/lib/(.*)})
|
36
|
-
|
37
|
-
raise "Not Found: #{match_data[1]}/#{match_data[2]}/*.gemspec" unless
|
37
|
+
plugin_gemspec_path = Dir.glob("#{match_data[1]}/#{match_data[2]}/*.gemspec").first
|
38
|
+
raise "Not Found: #{match_data[1]}/#{match_data[2]}/*.gemspec" unless plugin_gemspec_path
|
38
39
|
|
39
|
-
|
40
|
+
plugin_gemspec = Gem::Specification.load(plugin_gemspec_path)
|
40
41
|
fusuma_gemspec_path = File.expand_path("../../../fusuma.gemspec", __dir__)
|
41
42
|
fusuma_gemspec = Gem::Specification.load(fusuma_gemspec_path)
|
42
43
|
|
43
|
-
if
|
44
|
+
if plugin_gemspec.dependencies.find { |d| d.name == "fusuma" }&.match?(fusuma_gemspec)
|
44
45
|
siblings_plugin
|
45
46
|
else
|
46
|
-
MultiLogger.warn "#{
|
47
|
-
MultiLogger.warn "gemspec: #{
|
47
|
+
MultiLogger.warn "#{plugin_gemspec.name} #{plugin_gemspec.version} is incompatible with running #{fusuma_gemspec.name} #{fusuma_gemspec.version}"
|
48
|
+
MultiLogger.warn "gemspec: #{plugin_gemspec_path}"
|
48
49
|
next
|
49
50
|
end
|
50
51
|
end.compact.grep_v(exclude_path_pattern).sort
|
51
52
|
end
|
52
53
|
|
54
|
+
# @return [String] search key for plugin
|
55
|
+
# @example
|
56
|
+
# search_key
|
57
|
+
# => "fusuma/plugin/detectors/*rb"
|
53
58
|
def search_key
|
54
59
|
File.join(plugin_dir_name, "*rb")
|
55
60
|
end
|
data/lib/fusuma/version.rb
CHANGED