evesync 1.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +25 -0
- data/Rakefile +83 -0
- data/bin/evedatad +34 -0
- data/bin/evehand +36 -0
- data/bin/evemond +42 -0
- data/bin/evesync +164 -0
- data/bin/evesyncd +44 -0
- data/bin/start +16 -0
- data/config/example.conf +58 -0
- data/lib/evesync/config.rb +63 -0
- data/lib/evesync/constants.rb +17 -0
- data/lib/evesync/database.rb +107 -0
- data/lib/evesync/discover.rb +77 -0
- data/lib/evesync/err.rb +25 -0
- data/lib/evesync/handler/file.rb +28 -0
- data/lib/evesync/handler/package.rb +30 -0
- data/lib/evesync/handler.rb +89 -0
- data/lib/evesync/ipc/client.rb +37 -0
- data/lib/evesync/ipc/data/file.rb +64 -0
- data/lib/evesync/ipc/data/hashable.rb +74 -0
- data/lib/evesync/ipc/data/ignore.rb +22 -0
- data/lib/evesync/ipc/data/package.rb +58 -0
- data/lib/evesync/ipc/data/utils.rb +14 -0
- data/lib/evesync/ipc/data.rb +50 -0
- data/lib/evesync/ipc/ipc.rb +42 -0
- data/lib/evesync/ipc/server.rb +56 -0
- data/lib/evesync/log.rb +66 -0
- data/lib/evesync/ntp.rb +16 -0
- data/lib/evesync/os/linux/arch/package_manager.rb +29 -0
- data/lib/evesync/os/linux/arch/package_watcher.rb +59 -0
- data/lib/evesync/os/linux/arch.rb +2 -0
- data/lib/evesync/os/linux/base_package_manager.rb +80 -0
- data/lib/evesync/os/linux/deb/dpkg.rb +30 -0
- data/lib/evesync/os/linux/deb/package_manager.rb +38 -0
- data/lib/evesync/os/linux/deb/package_watcher.rb +32 -0
- data/lib/evesync/os/linux/deb.rb +2 -0
- data/lib/evesync/os/linux/rhel/package_manager.rb +42 -0
- data/lib/evesync/os/linux/rhel/package_watcher.rb +32 -0
- data/lib/evesync/os/linux/rhel/rpm.rb +41 -0
- data/lib/evesync/os/linux/rhel.rb +3 -0
- data/lib/evesync/os/linux.rb +9 -0
- data/lib/evesync/os.rb +2 -0
- data/lib/evesync/sync.rb +209 -0
- data/lib/evesync/trigger/base.rb +56 -0
- data/lib/evesync/trigger/file.rb +20 -0
- data/lib/evesync/trigger/package.rb +20 -0
- data/lib/evesync/trigger.rb +106 -0
- data/lib/evesync/utils.rb +51 -0
- data/lib/evesync/watcher/file.rb +156 -0
- data/lib/evesync/watcher/interface.rb +21 -0
- data/lib/evesync/watcher/package.rb +8 -0
- data/lib/evesync/watcher.rb +68 -0
- data/lib/evesync.rb +3 -0
- metadata +198 -0
data/lib/evesync/sync.rb
ADDED
@@ -0,0 +1,209 @@
|
|
1
|
+
require 'socket'
|
2
|
+
require 'full_dup'
|
3
|
+
require 'evesync/discover'
|
4
|
+
require 'evesync/log'
|
5
|
+
require 'evesync/config'
|
6
|
+
require 'evesync/ipc/client'
|
7
|
+
require 'evesync/ipc/data/utils'
|
8
|
+
require 'evesync/utils'
|
9
|
+
|
10
|
+
module Evesync
|
11
|
+
class Sync
|
12
|
+
def initialize
|
13
|
+
@discovery = Discover.new
|
14
|
+
@monitor = IPC::Client.new(
|
15
|
+
port: :evemond
|
16
|
+
)
|
17
|
+
@database = IPC::Client.new(
|
18
|
+
port: :evedatad
|
19
|
+
)
|
20
|
+
@handler = IPC::Client.new(
|
21
|
+
port: :evehand
|
22
|
+
)
|
23
|
+
end
|
24
|
+
|
25
|
+
##
|
26
|
+
# Starting Synchronization between nodes that are
|
27
|
+
# found. Checking if all events are synchronized and
|
28
|
+
# synchronizing missing events.
|
29
|
+
#
|
30
|
+
# TODO:
|
31
|
+
# * Catch the time when an event is sent while
|
32
|
+
# synchronizing
|
33
|
+
|
34
|
+
def synchronize
|
35
|
+
Log.debug('Synchronizing starting...')
|
36
|
+
apply_events fetch_events missed_events
|
37
|
+
Log.debug('Synchronizing done!')
|
38
|
+
end
|
39
|
+
|
40
|
+
##
|
41
|
+
# Sending a discovery message to broadcast.
|
42
|
+
# Local UDP socket listens and responses for handling answers.
|
43
|
+
|
44
|
+
def discover
|
45
|
+
@discovery.send_discovery_message
|
46
|
+
end
|
47
|
+
|
48
|
+
def apply_events(events)
|
49
|
+
events.each do |_, message|
|
50
|
+
message.values.each do |json|
|
51
|
+
ipc_message = IPC::Data.from_json(json)
|
52
|
+
@handler.handle(ipc_message)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# Diffs missed of `v1' that `v2' contain
|
58
|
+
def self.diff_missed(params)
|
59
|
+
v1 = params[:v1]
|
60
|
+
v2 = params[:v2]
|
61
|
+
|
62
|
+
# Fully missed objects
|
63
|
+
fully_missed = v2.reject { |k| v1.include?(k) }
|
64
|
+
|
65
|
+
# Included in both, but may be missed in `v1'
|
66
|
+
maybe_missed = v2.select { |k| v1.include?(k) }
|
67
|
+
|
68
|
+
not_relevant = maybe_missed.select do |k, v|
|
69
|
+
v.max > v1[k].max
|
70
|
+
end
|
71
|
+
|
72
|
+
partially_missed = not_relevant.map do |k, v|
|
73
|
+
[k, v.select { |tms| tms > v1[k].max }]
|
74
|
+
end.to_h
|
75
|
+
|
76
|
+
fully_missed.merge(partially_missed)
|
77
|
+
end
|
78
|
+
|
79
|
+
private
|
80
|
+
|
81
|
+
# We only recieve, dont push events to synchronize.
|
82
|
+
# This is because some node may be setted not to
|
83
|
+
# synchronize, so we don't want to make them synching.
|
84
|
+
def missed_events
|
85
|
+
remote_events = {}
|
86
|
+
remote_handlers = @monitor.remote_handlers
|
87
|
+
|
88
|
+
return {} unless remote_handlers.respond_to? :each
|
89
|
+
|
90
|
+
remote_handlers.each do |handler|
|
91
|
+
begin
|
92
|
+
Log.debug('Synchronizing with host (IP):', handler.ip)
|
93
|
+
remote_events[handler.ip] = handler.events || {}
|
94
|
+
rescue
|
95
|
+
next
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
return {} if remote_events.empty?
|
100
|
+
|
101
|
+
local_events = @database.events
|
102
|
+
|
103
|
+
events_diff(
|
104
|
+
local: local_events,
|
105
|
+
remote: remote_events
|
106
|
+
)
|
107
|
+
end
|
108
|
+
|
109
|
+
# Using Longest common subsequence problem solution
|
110
|
+
# we find timestamps that are absent in our database.
|
111
|
+
#
|
112
|
+
# Order doesn't matter because we sort events
|
113
|
+
#
|
114
|
+
# May be consider using any existing solution
|
115
|
+
def events_diff(params)
|
116
|
+
# params:
|
117
|
+
# local = {object [...events]}
|
118
|
+
# remote = {ip => {object => [...events]}
|
119
|
+
# convert to
|
120
|
+
# remote = {object => {event => [..ips]}}
|
121
|
+
# then
|
122
|
+
# use remote part {object => [...events]} and
|
123
|
+
# compare to local, then get object-event that are
|
124
|
+
# going to be fetched and apply ips (the choice may be random)
|
125
|
+
# that can be used to fetch these events
|
126
|
+
local = params[:local]
|
127
|
+
remote = params[:remote]
|
128
|
+
Log.debug('Synchronizing remote objects:', remote)
|
129
|
+
# Transforming data
|
130
|
+
remote_objects = {}
|
131
|
+
remote.each do |ip, objects|
|
132
|
+
objects.each do |object, events|
|
133
|
+
remote_objects[object] ||= {}
|
134
|
+
events.each do |event|
|
135
|
+
remote_objects[object][event] ||= []
|
136
|
+
remote_objects[object][event].push(ip)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
# Applying algorithm
|
142
|
+
diff = self.class.diff_missed(
|
143
|
+
v1: local,
|
144
|
+
v2: remote_objects.map do |k, v|
|
145
|
+
[k, v.keys]
|
146
|
+
end.to_h
|
147
|
+
)
|
148
|
+
|
149
|
+
# Returning duplicate
|
150
|
+
# remote_diff = remote_objects.full_dup
|
151
|
+
# remote_diff
|
152
|
+
diff.map do |obj, tms|
|
153
|
+
[
|
154
|
+
obj, # 1
|
155
|
+
tms.map do |t|
|
156
|
+
[t, remote_objects[obj][t]]
|
157
|
+
end.to_h # 2
|
158
|
+
]
|
159
|
+
end.to_h
|
160
|
+
end
|
161
|
+
|
162
|
+
# Fetch events from given diff.
|
163
|
+
# events_diff: {object => {event => [ip..]}}
|
164
|
+
def fetch_events(events_diff)
|
165
|
+
if events_diff.empty?
|
166
|
+
Log.info('Synchronizing no events')
|
167
|
+
return {}
|
168
|
+
end
|
169
|
+
|
170
|
+
# Getting {ip => handler} map
|
171
|
+
handlers = {}
|
172
|
+
@monitor.remote_handlers.each do |handler|
|
173
|
+
handlers[handler.ip] = handler
|
174
|
+
end
|
175
|
+
|
176
|
+
# Mapping events to nodes: {ip => {object => [events...]}}
|
177
|
+
nodes_events = map_nodes_for_events(events_diff, handlers)
|
178
|
+
|
179
|
+
# Fetching
|
180
|
+
messages = {}
|
181
|
+
nodes_events.each do |ip, events|
|
182
|
+
messages.merge! handlers[ip].messages(events)
|
183
|
+
end
|
184
|
+
Log.debug('Synchronizing events fetched:', messages)
|
185
|
+
messages
|
186
|
+
end
|
187
|
+
|
188
|
+
# Map events to appropriate nodes that can be used to
|
189
|
+
# fetch events.
|
190
|
+
# TODO: intellectual choosing the nodes to fetch msgs from.
|
191
|
+
# Now choosing the firs matched one for most of the events.
|
192
|
+
def map_nodes_for_events(events_diff, handlers)
|
193
|
+
nodes_events = {}
|
194
|
+
events_diff.each do |object, events|
|
195
|
+
events.each do |event, nodes|
|
196
|
+
handlers.keys.each do |ip|
|
197
|
+
if nodes.include?(ip)
|
198
|
+
nodes_events[ip] ||= {}
|
199
|
+
nodes_events[ip][object] ||= []
|
200
|
+
nodes_events[ip][object].push(event)
|
201
|
+
break
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
206
|
+
nodes_events
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'timeout'
|
2
|
+
require 'evesync/log'
|
3
|
+
|
4
|
+
module Evesync
|
5
|
+
class Trigger
|
6
|
+
module Base
|
7
|
+
# db must have a realization of _save_ method
|
8
|
+
def save_to_db(db, message)
|
9
|
+
db.save(message)
|
10
|
+
end
|
11
|
+
|
12
|
+
# Every element in *remotes* must have a realization
|
13
|
+
# of _handle_ method
|
14
|
+
def send_to_remotes(remotes, message)
|
15
|
+
remotes.each do |evehand|
|
16
|
+
begin
|
17
|
+
Timeout.timeout(30) do # FIXME: take from Config
|
18
|
+
evehand.handle(message)
|
19
|
+
end
|
20
|
+
rescue Timeout::Error
|
21
|
+
Log.warn("Trigger remote timeout: server #{evehand.uri} " \
|
22
|
+
'is not accessible')
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def process(message)
|
28
|
+
if ignore?(message)
|
29
|
+
unignore(message)
|
30
|
+
false
|
31
|
+
else
|
32
|
+
if save_to_db(@db, message)
|
33
|
+
send_to_remotes(@remotes, message)
|
34
|
+
true
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def ignore(ipc_data)
|
40
|
+
@ignore << ipc_data if
|
41
|
+
ipc_data.is_a? data_class
|
42
|
+
end
|
43
|
+
|
44
|
+
def unignore(ipc_data)
|
45
|
+
@ignore.delete_if { |d| d == ipc_data }
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def ignore?(ipc_data)
|
51
|
+
Log.debug("File ignore aray: #{@ignore}")
|
52
|
+
@ignore.find { |d| d == ipc_data }
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'evesync/trigger/base'
|
2
|
+
require 'evesync/ipc/data/file'
|
3
|
+
|
4
|
+
module Evesync
|
5
|
+
class Trigger
|
6
|
+
class File
|
7
|
+
include Trigger::Base
|
8
|
+
|
9
|
+
def initialize(params)
|
10
|
+
@ignore = []
|
11
|
+
@db = params[:db]
|
12
|
+
@remotes = params[:remotes]
|
13
|
+
end
|
14
|
+
|
15
|
+
def data_class
|
16
|
+
IPC::Data::File
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'evesync/trigger/base'
|
2
|
+
require 'evesync/ipc/data/package'
|
3
|
+
|
4
|
+
module Evesync
|
5
|
+
class Trigger
|
6
|
+
class Package
|
7
|
+
include Trigger::Base
|
8
|
+
|
9
|
+
def initialize(params)
|
10
|
+
@ignore = []
|
11
|
+
@db = params[:db]
|
12
|
+
@remotes = params[:remotes]
|
13
|
+
end
|
14
|
+
|
15
|
+
def data_class
|
16
|
+
IPC::Data::Package
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
require 'evesync/trigger/file'
|
2
|
+
require 'evesync/trigger/package'
|
3
|
+
require 'evesync/config'
|
4
|
+
require 'evesync/log'
|
5
|
+
require 'evesync/ipc/client'
|
6
|
+
require 'evesync/utils'
|
7
|
+
|
8
|
+
module Evesync
|
9
|
+
class Trigger
|
10
|
+
def initialize(watcher_queue)
|
11
|
+
@watcher_queue = watcher_queue
|
12
|
+
|
13
|
+
# Local Data daemon
|
14
|
+
evedatad = IPC::Client.new(port: :evedatad)
|
15
|
+
|
16
|
+
@remote_handlers = Config[:evemond]['remotes'].map do |ip|
|
17
|
+
new_remote_handler(ip)
|
18
|
+
end.compact # remove nils
|
19
|
+
|
20
|
+
# Helper triggers
|
21
|
+
package_trigger = Trigger::Package.new(
|
22
|
+
db: evedatad,
|
23
|
+
remotes: @remote_handlers
|
24
|
+
)
|
25
|
+
file_trigger = Trigger::File.new(
|
26
|
+
db: evedatad,
|
27
|
+
remotes: @remote_handlers
|
28
|
+
)
|
29
|
+
|
30
|
+
@triggers = [package_trigger, file_trigger]
|
31
|
+
|
32
|
+
Log.debug('Trigger initialization done!')
|
33
|
+
end
|
34
|
+
|
35
|
+
def start
|
36
|
+
@thr = Thread.new do
|
37
|
+
loop { biz }
|
38
|
+
end
|
39
|
+
Log.debug('Trigger started')
|
40
|
+
end
|
41
|
+
|
42
|
+
def stop
|
43
|
+
@thr.exit
|
44
|
+
Log.debug('Trigger stopped')
|
45
|
+
end
|
46
|
+
|
47
|
+
def ignore(change)
|
48
|
+
trigger_method(:ignore, change)
|
49
|
+
end
|
50
|
+
|
51
|
+
def unignore(change)
|
52
|
+
trigger_method(:unignore, change)
|
53
|
+
end
|
54
|
+
|
55
|
+
def add_remote_node(ip)
|
56
|
+
unless @remote_handlers.find { |h| h.ip == ip }
|
57
|
+
remote_handler = new_remote_handler(ip)
|
58
|
+
@remote_handlers << remote_handler if remote_handler
|
59
|
+
end
|
60
|
+
Log.debug 'Trigger actual remote nodes:', @remote_handlers.map(&:ip)
|
61
|
+
end
|
62
|
+
|
63
|
+
attr_reader :remote_handlers
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
# Main thread business logic goes here
|
68
|
+
def biz
|
69
|
+
change = @watcher_queue.pop
|
70
|
+
Log.info "Trigger dequed event: #{change}"
|
71
|
+
trigger = message_trigger(change)
|
72
|
+
trigger.process(change)
|
73
|
+
end
|
74
|
+
|
75
|
+
# Send a method to target (choose by change class name)
|
76
|
+
def trigger_method(method, change)
|
77
|
+
Log.debug("Trigger calling '#{method}' on '#{change.class.name}'")
|
78
|
+
|
79
|
+
trigger = message_trigger(change)
|
80
|
+
|
81
|
+
if trigger
|
82
|
+
trigger.send(method, change) && true
|
83
|
+
else
|
84
|
+
# TODO: forward somewhere
|
85
|
+
Log.error('Trigger: no watchers will be notified on ' \
|
86
|
+
"#{change}")
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def message_trigger(message)
|
91
|
+
class_last = message.class.name.to_s.split('::')[-1]
|
92
|
+
@triggers.find do |trigger|
|
93
|
+
trigger.to_s.include? class_last
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def new_remote_handler(ip)
|
98
|
+
unless Utils.local_ip?(ip)
|
99
|
+
IPC::Client.new(
|
100
|
+
port: :evehand,
|
101
|
+
ip: ip
|
102
|
+
)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module Evesync
|
2
|
+
module Utils
|
3
|
+
class << self
|
4
|
+
def local_ip?(ip)
|
5
|
+
ips = `getent hosts #{ip}`
|
6
|
+
.lines
|
7
|
+
.map(&:split)
|
8
|
+
.map(&:first)
|
9
|
+
loc_ips = local_ips
|
10
|
+
|
11
|
+
!(ips & loc_ips).empty?
|
12
|
+
end
|
13
|
+
|
14
|
+
def local_ip
|
15
|
+
local_ips.first
|
16
|
+
end
|
17
|
+
|
18
|
+
def local_ips
|
19
|
+
`ip a`
|
20
|
+
.lines
|
21
|
+
.grep(/inet/)
|
22
|
+
.map(&:split)
|
23
|
+
.map { |lines| lines[1].split('/')[0] }
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# For ruby < 2.1
|
31
|
+
class Array
|
32
|
+
unless defined? to_h
|
33
|
+
def to_h
|
34
|
+
Hash[*flatten(1)]
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
class Hash
|
40
|
+
unless defined? deep_merge
|
41
|
+
def deep_merge(h)
|
42
|
+
self.merge(h) do |_k, a, b|
|
43
|
+
if a.is_a? Hash
|
44
|
+
a.deep_merge(b)
|
45
|
+
else
|
46
|
+
b
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,156 @@
|
|
1
|
+
require 'date'
|
2
|
+
require 'rb-inotify'
|
3
|
+
require 'evesync/config'
|
4
|
+
require 'evesync/ntp'
|
5
|
+
require 'evesync/ipc/data/file'
|
6
|
+
require 'evesync/watcher/interface'
|
7
|
+
|
8
|
+
module Evesync
|
9
|
+
class Watcher
|
10
|
+
|
11
|
+
##
|
12
|
+
# Watches the files and directories, defined in
|
13
|
+
# configuration attribute _watch_
|
14
|
+
#
|
15
|
+
# TODO:
|
16
|
+
# * Test on various cases, make it work properly
|
17
|
+
# * Find out all possible occasions
|
18
|
+
|
19
|
+
class File < Watcher::Interface
|
20
|
+
def initialize(queue)
|
21
|
+
@queue = queue
|
22
|
+
@watches = Config[:evemond]['watch']
|
23
|
+
@period = Config[:evemond]['watch_interval'].to_i
|
24
|
+
@inotify = INotify::Notifier.new
|
25
|
+
@events = {}
|
26
|
+
@wfiles = []
|
27
|
+
@wdirs = []
|
28
|
+
initialize_watcher
|
29
|
+
end
|
30
|
+
|
31
|
+
def start
|
32
|
+
@inotify_thr = Thread.new { @inotify.run }
|
33
|
+
@main_thr = Thread.new do
|
34
|
+
loop do
|
35
|
+
sleep @period
|
36
|
+
send_events
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def stop
|
42
|
+
@inotify.stop
|
43
|
+
@inotify_thr.exit
|
44
|
+
@main_thr.exit
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
# Send all events from the methods-handlers
|
50
|
+
def send_events
|
51
|
+
@events.each do |file, events|
|
52
|
+
event = guess_event(events)
|
53
|
+
mode = case event
|
54
|
+
when :delete then nil
|
55
|
+
else ::File::Stat.new(file).mode
|
56
|
+
end
|
57
|
+
|
58
|
+
msg = IPC::Data::File.new(
|
59
|
+
name: file,
|
60
|
+
mode: mode,
|
61
|
+
action: event,
|
62
|
+
touched_at: NTP.time.to_s
|
63
|
+
)
|
64
|
+
@queue.push msg
|
65
|
+
Log.debug("Watcher File guessed event #{event} " \
|
66
|
+
"from #{events} on #{file}")
|
67
|
+
end
|
68
|
+
@events = {}
|
69
|
+
end
|
70
|
+
|
71
|
+
def initialize_watcher
|
72
|
+
@watches.each do |filename|
|
73
|
+
unless ::File.exist? filename
|
74
|
+
Log.error("Watcher File: '#{filename}' absent on system")
|
75
|
+
end
|
76
|
+
|
77
|
+
# TODO: ignore /dev and /sys, /proc directories
|
78
|
+
if ::File.file? filename
|
79
|
+
watch_file filename
|
80
|
+
elsif ::File.directory? filename
|
81
|
+
watch_directory filename
|
82
|
+
else
|
83
|
+
# Seems to be a drive or
|
84
|
+
Log.warn("Watcher File: watching '#{filename}' is not implemented yet")
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def watch_file(filename)
|
90
|
+
# add file modify watch
|
91
|
+
Log.debug("Watcher File watching #{filename}")
|
92
|
+
|
93
|
+
@inotify.watch(filename, :modify) do |e|
|
94
|
+
Log.debug("Watcher File: MODIFIED #{e.absolute_name}")
|
95
|
+
h_file(e.absolute_name, [:modify]) # the only flag we need
|
96
|
+
end
|
97
|
+
|
98
|
+
# Waiting for file to disappear and created again
|
99
|
+
@wfiles << filename unless @wfiles.include?(filename)
|
100
|
+
dirname = ::File.dirname(filename)
|
101
|
+
unless @wdirs.include?(dirname)
|
102
|
+
watch_directory_for_file(dirname)
|
103
|
+
@wdirs << dirname
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def watch_directory(dirname)
|
108
|
+
@inotify.watch(dirname, :create, :delete, :moved_to, :moved_from) do |e|
|
109
|
+
Log.debug("Watcher File: DIRECTORY #{e.absolute_name}")
|
110
|
+
h_directory(e.absolute_name, e.flags)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def watch_directory_for_file(dirname)
|
115
|
+
@inotify.watch(dirname, :create, :moved_to) do |e|
|
116
|
+
watch_file(e.absolute_name) if @wfiles.include? e.absolute_name
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
# Handlers of inotify changes
|
121
|
+
|
122
|
+
def h_file(filename, events)
|
123
|
+
@events[filename] = [] unless @events[filename]
|
124
|
+
|
125
|
+
@events[filename] += events
|
126
|
+
Log.debug("Watcher File added events for file #{filename}")
|
127
|
+
end
|
128
|
+
|
129
|
+
def h_directory(filename, events)
|
130
|
+
if ::File.file? filename
|
131
|
+
watch_file(filename)
|
132
|
+
@watches << filename unless @watches.include? filename
|
133
|
+
elsif ::File.directory? filename
|
134
|
+
watch_directory(filename)
|
135
|
+
end
|
136
|
+
|
137
|
+
# Better pass a file
|
138
|
+
@events[filename] = [] unless @events[filename]
|
139
|
+
|
140
|
+
@events[filename] += events
|
141
|
+
Log.debug("Watcher File added events for directory #{filename}")
|
142
|
+
end
|
143
|
+
|
144
|
+
def guess_event(events)
|
145
|
+
return events.first if events.length == 1
|
146
|
+
|
147
|
+
return :modify if events.include?(:create) && events.include?(:modify)
|
148
|
+
|
149
|
+
return :delete if events.include?(:delete) && (!events.include? :create)
|
150
|
+
|
151
|
+
# TODO: find out more logic
|
152
|
+
events.last
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Evesync
|
2
|
+
class Watcher
|
3
|
+
# Base watcher abstract class with methods for other
|
4
|
+
# watchers to implement
|
5
|
+
class Interface
|
6
|
+
# The class must be initialized with the queue object
|
7
|
+
def initialize(_queue)
|
8
|
+
raise NotImplementedError, "must implement 'initialize'"
|
9
|
+
end
|
10
|
+
|
11
|
+
# The watcher must be able to handle start and stop calls
|
12
|
+
def start
|
13
|
+
raise NotImplementedError, "must implement 'start'"
|
14
|
+
end
|
15
|
+
|
16
|
+
def stop
|
17
|
+
raise NotImplementedError, "must implement 'stop'"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|