ruby-livesync 1.0.0.beta3 → 1.0.0.beta4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/config/sample.rb +15 -3
- data/lib/ruby-livesync/live_sync/cmd_watcher.rb +56 -0
- data/lib/ruby-livesync/live_sync/daemon.rb +1 -2
- data/lib/ruby-livesync/live_sync/dsl.rb +10 -7
- data/lib/ruby-livesync/live_sync/py/inotify.py +45 -0
- data/lib/ruby-livesync/live_sync/py/watchdog.py +52 -0
- data/lib/ruby-livesync/live_sync/py_inotify_watcher.rb +19 -0
- data/lib/ruby-livesync/live_sync/py_watchdog_watcher.rb +19 -0
- data/lib/ruby-livesync/live_sync/rb_watcher.rb +88 -0
- data/lib/ruby-livesync/live_sync/rsync.rb +18 -10
- data/lib/ruby-livesync/live_sync/sync.rb +19 -36
- data/lib/ruby-livesync/live_sync/target.rb +36 -0
- data/lib/ruby-livesync/live_sync/version.rb +1 -1
- data/lib/ruby-livesync/live_sync/watcher.rb +3 -38
- data/lib/ruby-livesync/live_sync.rb +5 -0
- data/lib/ruby-livesync.rb +0 -1
- metadata +12 -19
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1e77356b7dfd5070935875d4842a16129c5d1eeaaec1e049d2b4e4824032a2c0
|
4
|
+
data.tar.gz: f8350e318d1f3e31dc5925981c1526b74d404795c4300419a2db489ff38e0f2a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9989027e61b51d31e4248e71c7d24e3d1a8c9cfaf74ab532cd99fcc6f25ae7d9e23c12790cc31a57e306395213c846e132e3cdc24c6d14d548f74d525860515d
|
7
|
+
data.tar.gz: 43ac4cf5d5ff1cc2926bb13ba60f4efbafa07fd54d940d8bdb55cbd183b6a608c6d644a4a7173c2ebdf1d490534eedd37e1940ef9854b98b16201d3461be5009
|
data/config/sample.rb
CHANGED
@@ -3,15 +3,27 @@
|
|
3
3
|
sync '4tb' do
|
4
4
|
enabled = false
|
5
5
|
|
6
|
+
# watchers available:
|
7
|
+
# - :rb (default)
|
8
|
+
# - :py_inotify
|
9
|
+
# - :cmd (inotifywait)
|
10
|
+
# - :py_watchdog
|
11
|
+
#
|
12
|
+
watcher = :rb
|
13
|
+
|
6
14
|
# fork to user below, usually associated with private keys
|
7
15
|
user = :root
|
8
16
|
|
9
17
|
delay = 5
|
10
18
|
|
11
|
-
|
12
|
-
|
19
|
+
# event list from inotify
|
20
|
+
# full list at https://manpages.ubuntu.com/manpages/latest/en/man1/inotifywait.1.html#events
|
21
|
+
modes = %i[create modify]
|
13
22
|
|
14
|
-
|
23
|
+
source = '/mnt/4tb/'
|
24
|
+
target rsync: 'root@bhavapower:/mnt/extensor/4tb' do
|
25
|
+
opts = '-ax --partial' # default
|
26
|
+
end
|
15
27
|
|
16
28
|
# possible values are: true, false, :initial, :watched
|
17
29
|
delete = true
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module LiveSync
|
2
|
+
class CmdWatcher < Watcher
|
3
|
+
|
4
|
+
class_attribute :base_cmd
|
5
|
+
self.base_cmd = 'bash -s'
|
6
|
+
|
7
|
+
class_attribute :script
|
8
|
+
self.script = <<-HEREDOC
|
9
|
+
tp=$(mktemp -u)
|
10
|
+
mkfifo $tp
|
11
|
+
inotifywait %{opts} -m -e %{events} --format "%%e %%w%%f" %{path} %{excludes} > $tp 2>/dev/null &
|
12
|
+
ipid=$!
|
13
|
+
trap "kill $ipid; rm -f $tp; exit" INT TERM EXIT
|
14
|
+
while true; do
|
15
|
+
timeout %{delay} cat < $tp | sort | uniq
|
16
|
+
done
|
17
|
+
HEREDOC
|
18
|
+
|
19
|
+
def watch path, *modes, **params
|
20
|
+
modes = DEFAULT_MODES if modes.blank?
|
21
|
+
script = self.script % parsed_params(path, *modes, **params)
|
22
|
+
stdin, stdout, stderr, @wait_thr = Open3.popen3 base_cmd
|
23
|
+
stdin.write script
|
24
|
+
stdin.close
|
25
|
+
|
26
|
+
Thread.new do
|
27
|
+
stdout.sync = true
|
28
|
+
lines = stdout.each_line.each do |line|
|
29
|
+
file,events = parse line
|
30
|
+
yield [OpenStruct.new(absolute_name: file, flags: events)]
|
31
|
+
end
|
32
|
+
end
|
33
|
+
Thread.new do
|
34
|
+
STDERR.puts stderr.read
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def parsed_params path, *modes, recursive: true, delay: 1, excludes: [], **params
|
39
|
+
opts = '-r' if recursive
|
40
|
+
{
|
41
|
+
path: path,
|
42
|
+
opts: opts,
|
43
|
+
events: modes.join(','),
|
44
|
+
delay: delay,
|
45
|
+
excludes: excludes.map{ |e| "'@#{e}'" }.join(' '),
|
46
|
+
}
|
47
|
+
end
|
48
|
+
|
49
|
+
def parse line
|
50
|
+
events,file = line.split ' ', 2
|
51
|
+
events = events.split(',').map(&:downcase).map(&:to_sym)
|
52
|
+
[file.chomp, events]
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
end
|
@@ -10,24 +10,27 @@ module LiveSync
|
|
10
10
|
class_attribute :attrs
|
11
11
|
self.attrs = []
|
12
12
|
|
13
|
-
def self.dsl attr, default: nil, enum: nil,
|
13
|
+
def self.dsl attr, default: nil, enum: nil,
|
14
|
+
type: nil, skip_set: false, &block
|
14
15
|
self.attrs << attr
|
15
|
-
|
16
|
+
|
17
|
+
define_method attr do |sv=nil, opts={}, &ablock|
|
18
|
+
sv = default if opts[:init]
|
16
19
|
(v = instance_variable_get("@#{attr}"); return(if v.nil? then default else v end)) if sv.nil?
|
17
20
|
|
18
|
-
raise "#{ctx}/#{attr}: incorrect type" if type and !sv.is_a? type
|
21
|
+
raise "#{ctx}/#{attr}: incorrect type of #{sv.inspect}" if type and !sv.is_a? type
|
19
22
|
raise "#{ctx}/#{attr}: value not one of following #{enum}" if enum and !sv.in? enum
|
20
23
|
|
21
|
-
instance_variable_set "@#{attr}", sv
|
22
|
-
instance_exec sv, &block if block
|
24
|
+
instance_variable_set "@#{attr}", sv unless skip_set
|
25
|
+
instance_exec sv, ablock, &block if block
|
23
26
|
end
|
24
27
|
end
|
25
28
|
|
26
29
|
def dsl_apply &block
|
27
30
|
if b = binding and bs = block.source.match(/do(.+)end$/m)&.captures&.first
|
28
31
|
b.eval bs
|
29
|
-
attrs.each do |a|
|
30
|
-
next unless a.in? b.local_variables
|
32
|
+
attrs.each do |a|
|
33
|
+
next send a, nil, {init: true} unless a.in? b.local_variables
|
31
34
|
send a, b.local_variable_get(a)
|
32
35
|
end
|
33
36
|
else
|
@@ -0,0 +1,45 @@
|
|
1
|
+
import pyinotify
|
2
|
+
import os
|
3
|
+
import time
|
4
|
+
|
5
|
+
class EventHandler(pyinotify.ProcessEvent):
|
6
|
+
def my_init(self, excludes, recursive, mask):
|
7
|
+
self.excludes = excludes
|
8
|
+
self.recursive = recursive
|
9
|
+
self.mask = mask
|
10
|
+
self.events = set()
|
11
|
+
|
12
|
+
def process_default(self, event):
|
13
|
+
full_path = event.pathname
|
14
|
+
if not any(exclude in full_path for exclude in self.excludes):
|
15
|
+
event_desc = event.maskname
|
16
|
+
self.events.add((event_desc, full_path))
|
17
|
+
|
18
|
+
def print_events(self):
|
19
|
+
if self.events:
|
20
|
+
for event_desc, full_path in self.events:
|
21
|
+
print(f"{event_desc} {full_path}", flush=True)
|
22
|
+
self.events.clear()
|
23
|
+
|
24
|
+
def add_watch(self, path):
|
25
|
+
wm.add_watch(path, self.mask, rec=self.recursive, auto_add=True)
|
26
|
+
|
27
|
+
excludes = [%{excludes}]
|
28
|
+
recursive = %{recursive}
|
29
|
+
event_mask = pyinotify.IN_CREATE | pyinotify.IN_MODIFY | pyinotify.IN_MOVED_TO | pyinotify.IN_DELETE # Event mask
|
30
|
+
|
31
|
+
wm = pyinotify.WatchManager()
|
32
|
+
handler = EventHandler(excludes=excludes, recursive=recursive, mask=event_mask)
|
33
|
+
notifier = pyinotify.Notifier(wm, handler)
|
34
|
+
handler.add_watch('%{path}')
|
35
|
+
|
36
|
+
try:
|
37
|
+
while True:
|
38
|
+
notifier.process_events()
|
39
|
+
if notifier.check_events():
|
40
|
+
notifier.read_events()
|
41
|
+
handler.print_events()
|
42
|
+
time.sleep(%{delay})
|
43
|
+
except KeyboardInterrupt:
|
44
|
+
notifier.stop()
|
45
|
+
|
@@ -0,0 +1,52 @@
|
|
1
|
+
from watchdog.observers import Observer
|
2
|
+
from watchdog.events import FileSystemEventHandler
|
3
|
+
import time
|
4
|
+
|
5
|
+
class MyEventHandler(FileSystemEventHandler):
|
6
|
+
def __init__(self, observer, excludes):
|
7
|
+
self.observer = observer
|
8
|
+
self.excludes = excludes
|
9
|
+
self.events = set()
|
10
|
+
|
11
|
+
def handle_event(self, event):
|
12
|
+
if not any(exclusion in event.src_path for exclusion in self.excludes):
|
13
|
+
self.events.add((event.event_type, event.src_path))
|
14
|
+
if event.event_type == 'created' and os.path.isdir(event.src_path):
|
15
|
+
self.schedule_directory(event.src_path)
|
16
|
+
|
17
|
+
def on_modified(self, event):
|
18
|
+
self.handle_event(event)
|
19
|
+
|
20
|
+
def on_created(self, event):
|
21
|
+
self.handle_event(event)
|
22
|
+
|
23
|
+
def on_deleted(self, event):
|
24
|
+
self.handle_event(event)
|
25
|
+
|
26
|
+
def on_moved(self, event):
|
27
|
+
self.handle_event(event)
|
28
|
+
|
29
|
+
def print_events(self):
|
30
|
+
if self.events:
|
31
|
+
for event_type, src_path in self.events:
|
32
|
+
print(f"{event_type} {src_path}", flush=True)
|
33
|
+
self.events.clear()
|
34
|
+
|
35
|
+
def schedule_directory(self, path):
|
36
|
+
self.observer.schedule(self, path, recursive=%{recursive})
|
37
|
+
|
38
|
+
path = "%{path}"
|
39
|
+
excludes = [%{excludes}]
|
40
|
+
observer = Observer()
|
41
|
+
event_handler = MyEventHandler(observer, excludes)
|
42
|
+
event_handler.schedule_directory(path)
|
43
|
+
|
44
|
+
observer.start()
|
45
|
+
try:
|
46
|
+
while True:
|
47
|
+
time.sleep(%{delay})
|
48
|
+
event_handler.print_events()
|
49
|
+
except KeyboardInterrupt:
|
50
|
+
observer.stop()
|
51
|
+
observer.join()
|
52
|
+
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module LiveSync
|
2
|
+
class PyInotifyWatcher < CmdWatcher
|
3
|
+
|
4
|
+
self.base_cmd = 'python'
|
5
|
+
|
6
|
+
self.script = File.read "#{File.dirname __FILE__}/py/inotify.py"
|
7
|
+
|
8
|
+
def parsed_params path, *modes, recursive: true, delay: 1, excludes: [], **params
|
9
|
+
{
|
10
|
+
path: path,
|
11
|
+
recursive: if recursive then 'True' else 'False' end,
|
12
|
+
events: modes.join(','),
|
13
|
+
delay: delay,
|
14
|
+
excludes: excludes.map{ |e| "'#{e}'" }.join(','),
|
15
|
+
}
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module LiveSync
|
2
|
+
class PyWatchdogWatcher < CmdWatcher
|
3
|
+
|
4
|
+
self.base_cmd = 'python'
|
5
|
+
|
6
|
+
self.script = File.read "#{File.dirname __FILE__}/py/watchdog.py"
|
7
|
+
|
8
|
+
def parsed_params path, *modes, recursive: true, delay: 1, excludes: [], **params
|
9
|
+
{
|
10
|
+
path: path,
|
11
|
+
recursive: if recursive then 'True' else 'False' end,
|
12
|
+
events: modes.join(','),
|
13
|
+
delay: delay,
|
14
|
+
excludes: excludes.map{ |e| "'#{e}'" }.join(','),
|
15
|
+
}
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
module LiveSync
|
2
|
+
class RbWatcher < Watcher
|
3
|
+
|
4
|
+
def watch path, *modes, excludes: [], samefs: true, delay: 1, recursive: true, **params, &block
|
5
|
+
raise "#{path}: not a directory" unless File.directory? path
|
6
|
+
modes = DEFAULT_MODES if modes.blank?
|
7
|
+
modes << :delete if sync&.delete&.in? [true, :watched]
|
8
|
+
|
9
|
+
tracker = Tracker.new path, delay, &block
|
10
|
+
rtgts = glob path, recursive: recursive, excludes: excludes
|
11
|
+
track(path, rtgts, *modes,
|
12
|
+
delay: 1,
|
13
|
+
tracker: tracker,
|
14
|
+
recursive: recursive,
|
15
|
+
excludes: excludes,
|
16
|
+
samefs: samefs,
|
17
|
+
)
|
18
|
+
tracker.timeout_check
|
19
|
+
|
20
|
+
self
|
21
|
+
end
|
22
|
+
|
23
|
+
protected
|
24
|
+
|
25
|
+
class Tracker
|
26
|
+
def initialize path, delay, &block
|
27
|
+
@delay = delay
|
28
|
+
@block = block
|
29
|
+
@to_sync = Set.new
|
30
|
+
@notifier = INotify::Notifier.new
|
31
|
+
@pathname = Pathname.new path
|
32
|
+
end
|
33
|
+
|
34
|
+
def watch *args, &block
|
35
|
+
@notifier.watch *args, &block
|
36
|
+
end
|
37
|
+
|
38
|
+
def track event
|
39
|
+
@to_sync << Pathname.new(event.absolute_name).relative_path_from(@pathname).to_s
|
40
|
+
end
|
41
|
+
|
42
|
+
def check
|
43
|
+
@notifier.process # calls track
|
44
|
+
@block.call @to_sync
|
45
|
+
@to_sync.clear
|
46
|
+
ensure
|
47
|
+
timeout_check
|
48
|
+
end
|
49
|
+
|
50
|
+
def timeout_check
|
51
|
+
Thread.new{ sleep @delay; check }
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def track path, rtgts, *modes, tracker:, **opts
|
56
|
+
rtgts.each do |rt|
|
57
|
+
t = "#{path}/#{rt}"
|
58
|
+
tracker.watch t, *modes do |event|
|
59
|
+
tracker.track event
|
60
|
+
et = event.absolute_name
|
61
|
+
next unless opts[:recursive] and File.directory?(et) and :create.in? event.flags
|
62
|
+
track et, glob(et, **opts), *modes, tracker: tracker, **opts
|
63
|
+
end
|
64
|
+
rescue => e
|
65
|
+
Log.warning "watcher: #{t}: skipping due to #{e.class}: #{e.message}"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def glob path, recursive: true, samefs: true, excludes: [], **params
|
70
|
+
wcard = if recursive then '{.**,**}/' else '' end
|
71
|
+
tgts = [path] + Dir.glob("#{path}/#{wcard}")
|
72
|
+
|
73
|
+
dev = File.stat(path).dev
|
74
|
+
tgts.select!{ |t| dev == File.stat(t).dev } if samefs
|
75
|
+
|
76
|
+
rtgts = tgts.map{ |t| Pathname.new(t).relative_path_from(path).to_s }
|
77
|
+
excs = excludes.map{ |e| Regexp.new e } || []
|
78
|
+
excs.each do |e|
|
79
|
+
next unless mt = rtgts.find{ |rt| e.match rt }
|
80
|
+
Log.debug "watcher: skipping #{path}/#{mt} with subdirs"
|
81
|
+
rtgts.delete mt
|
82
|
+
rtgts.delete_if{ |rt| rt.start_with? mt } if File.directory? "#{path}/#{mt}"
|
83
|
+
end
|
84
|
+
rtgts
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
end
|
@@ -1,12 +1,20 @@
|
|
1
1
|
module LiveSync
|
2
|
-
class Rsync
|
2
|
+
class Rsync < Target
|
3
3
|
|
4
|
-
|
5
|
-
attr_accessor :opts
|
4
|
+
dsl :opts, type: String
|
6
5
|
|
7
|
-
|
8
|
-
|
9
|
-
|
6
|
+
attr_reader :ssh
|
7
|
+
|
8
|
+
def initialize *args, &block
|
9
|
+
super
|
10
|
+
# add trailing slash in case the dir is the same
|
11
|
+
sync.source File.join(sync.source, '') if File.basename(sync.source) == File.basename(@path)
|
12
|
+
end
|
13
|
+
|
14
|
+
def start
|
15
|
+
@ssh = Ssh.connect userhost
|
16
|
+
sleep 1 and log.warning 'waiting for ssh' while !@ssh.available?
|
17
|
+
true
|
10
18
|
end
|
11
19
|
|
12
20
|
def running?
|
@@ -31,21 +39,21 @@ module LiveSync
|
|
31
39
|
protected
|
32
40
|
|
33
41
|
def run type, *args, loglevel: :debug
|
34
|
-
cmd = "rsync -e '#{rsh}' #{opts} #{sync.source} #{
|
42
|
+
cmd = "rsync -e '#{rsh}' #{opts} #{sync.source} #{dest} #{args.join ' '}"
|
35
43
|
sync.excludes.each{ |e| cmd << " --exclude='#{e}'" }
|
36
44
|
|
37
|
-
|
45
|
+
log.send loglevel, "#{type}: starting with cmd: #{cmd}"
|
38
46
|
stdin, stdout, stderr, @wait_thr = Open3.popen3 cmd
|
39
47
|
yield stdin, stdout, stderr if block_given?
|
40
48
|
Thread.new do
|
41
49
|
Process.wait @wait_thr.pid rescue Errno::ECHILD; nil
|
42
50
|
@wait_thr = nil
|
43
|
-
|
51
|
+
log.send loglevel, "#{type}: finished"
|
44
52
|
end
|
45
53
|
end
|
46
54
|
|
47
55
|
def rsh
|
48
|
-
"ssh -o ControlPath=#{
|
56
|
+
"ssh -o ControlPath=#{ssh.cpath}"
|
49
57
|
end
|
50
58
|
|
51
59
|
end
|
@@ -4,17 +4,14 @@ module LiveSync
|
|
4
4
|
include DSL
|
5
5
|
|
6
6
|
attr_reader :name
|
7
|
-
attr_reader :scheduler
|
8
7
|
attr_reader :log
|
9
8
|
attr_reader :watcher
|
10
|
-
attr_reader :
|
11
|
-
attr_reader :rsync
|
9
|
+
attr_reader :target
|
12
10
|
|
13
|
-
def initialize name = nil
|
11
|
+
def initialize name = nil, &block
|
14
12
|
fill_name name
|
15
13
|
source name if File.exist? name
|
16
|
-
|
17
|
-
@rsync = Rsync.new self
|
14
|
+
dsl_apply(&block)
|
18
15
|
end
|
19
16
|
|
20
17
|
def fill_name name
|
@@ -25,6 +22,11 @@ module LiveSync
|
|
25
22
|
|
26
23
|
dsl :enabled, default: true
|
27
24
|
|
25
|
+
dsl :watcher, skip_set: true, default: :rb do |name|
|
26
|
+
klass = :"#{name.to_s.camelize}Watcher"
|
27
|
+
@watcher_class = LiveSync.const_get klass
|
28
|
+
end
|
29
|
+
|
28
30
|
dsl :user, default: :root
|
29
31
|
dsl :source do |source|
|
30
32
|
raise "#{ctx}: source isn't a directory" unless File.directory? source
|
@@ -32,14 +34,14 @@ module LiveSync
|
|
32
34
|
@pathname = Pathname.new source
|
33
35
|
end
|
34
36
|
|
35
|
-
dsl :target do |
|
36
|
-
@
|
37
|
-
raise "#{ctx}: missing target path" unless @target_path
|
38
|
-
source File.join(@source, '') if File.basename(@source) == File.basename(@target_path)
|
37
|
+
dsl :target, skip_set: true do |opts, &block|
|
38
|
+
@target = Rsync.new self, opts[:rsync], &block if opts[:rsync]
|
39
39
|
end
|
40
40
|
|
41
41
|
dsl :delay, default: 5, type: Integer
|
42
42
|
|
43
|
+
dsl :modes, default: %i[create modify], type: Array
|
44
|
+
|
43
45
|
dsl :delete, default: false, enum: [true,false] + %i[initial watched]
|
44
46
|
|
45
47
|
dsl :excludes, default: []
|
@@ -47,46 +49,27 @@ module LiveSync
|
|
47
49
|
def start
|
48
50
|
return log.warning('skipping disabled sync') && false unless enabled
|
49
51
|
raise "#{ctx}: missing target" unless @target
|
50
|
-
|
51
|
-
sleep 1 and log.warning 'waiting for ssh' while !@ssh.available?
|
52
|
-
true
|
52
|
+
target.start
|
53
53
|
end
|
54
54
|
|
55
55
|
def guard
|
56
56
|
fork do
|
57
57
|
Process.setproctitle "livesync: sync #{ctx}"
|
58
|
-
@watcher
|
59
|
-
@scheduler = Rufus::Scheduler.new
|
58
|
+
@watcher = @watcher_class.new self
|
60
59
|
|
61
60
|
watch source
|
62
|
-
|
63
|
-
schedule
|
61
|
+
target.initial
|
64
62
|
sleep 1.day while true
|
65
63
|
end
|
66
64
|
end
|
67
65
|
|
68
|
-
def track event
|
69
|
-
path = event.absolute_name
|
70
|
-
watch path if File.directory?(path) and :create.in? event.flags
|
71
|
-
@to_sync << Pathname.new(path).relative_path_from(@pathname).to_s
|
72
|
-
end
|
73
|
-
|
74
66
|
def watch dir
|
75
|
-
@watcher.
|
76
|
-
end
|
77
|
-
|
78
|
-
def schedule
|
79
|
-
@scheduler.in "#{delay}s", &method(:check)
|
67
|
+
@watcher.watch dir, *modes, excludes: excludes, delay: delay, &method(:sync)
|
80
68
|
end
|
81
69
|
|
82
|
-
def
|
83
|
-
return if
|
84
|
-
|
85
|
-
return if @to_sync.blank?
|
86
|
-
@rsync.partial @to_sync
|
87
|
-
@to_sync.clear
|
88
|
-
ensure
|
89
|
-
schedule
|
70
|
+
def sync paths
|
71
|
+
return if target.running?
|
72
|
+
target.partial paths
|
90
73
|
end
|
91
74
|
|
92
75
|
protected
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module LiveSync
|
2
|
+
class Target
|
3
|
+
|
4
|
+
include DSL
|
5
|
+
|
6
|
+
attr_reader :sync
|
7
|
+
delegate :log, to: :sync
|
8
|
+
|
9
|
+
attr_reader :dest, :path, :userhost
|
10
|
+
|
11
|
+
def initialize sync, dest, &block
|
12
|
+
@sync = sync
|
13
|
+
@dest = dest
|
14
|
+
@userhost, @path = dest.split ':'
|
15
|
+
raise "#{sync.ctx}: missing target path" unless @path
|
16
|
+
dsl_apply &block if block
|
17
|
+
end
|
18
|
+
|
19
|
+
def start
|
20
|
+
false
|
21
|
+
end
|
22
|
+
|
23
|
+
def running?
|
24
|
+
false
|
25
|
+
end
|
26
|
+
|
27
|
+
def initial
|
28
|
+
raise 'not implemented'
|
29
|
+
end
|
30
|
+
|
31
|
+
def partial paths
|
32
|
+
raise 'not implemented'
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
@@ -1,50 +1,15 @@
|
|
1
1
|
module LiveSync
|
2
2
|
class Watcher
|
3
3
|
|
4
|
-
|
5
|
-
delegate_missing_to :notifier
|
4
|
+
DEFAULT_MODES = %i[create modify]
|
6
5
|
|
7
6
|
attr_reader :sync
|
8
7
|
|
9
8
|
def initialize sync=nil
|
10
|
-
@sync
|
11
|
-
@notifier = INotify::Notifier.new
|
9
|
+
@sync = sync
|
12
10
|
end
|
13
11
|
|
14
|
-
def watch path, *modes
|
15
|
-
Log.debug "#{path}: watching for #{modes.join ','}"
|
16
|
-
modes = %i[all_events] if modes.blank?
|
17
|
-
|
18
|
-
notifier.watch path, *modes do |event|
|
19
|
-
yield event
|
20
|
-
end
|
21
|
-
self
|
22
|
-
end
|
23
|
-
|
24
|
-
def dir_rwatch path, *modes
|
25
|
-
raise "#{path}: not a directory" unless File.directory? path
|
26
|
-
modes = %i[create modify] if modes.blank?
|
27
|
-
modes << :delete if sync&.delete&.in? [true, :watched]
|
28
|
-
|
29
|
-
excs = sync&.excludes.map{ |e| Regexp.new e } || []
|
30
|
-
tgts = [path] + Dir.glob("#{path}/{.**,**}/")
|
31
|
-
rtgts = tgts.map{ |t| Pathname.new(t).relative_path_from(path).to_s }
|
32
|
-
excs.each do |e|
|
33
|
-
next unless mt = rtgts.find{ |rt| e.match rt }
|
34
|
-
Log.debug "watcher: skipping #{path}/#{mt} with subdirs"
|
35
|
-
rtgts.delete mt
|
36
|
-
rtgts.delete_if{ |rt| rt.start_with? mt } if File.directory? "#{path}/#{mt}"
|
37
|
-
end
|
38
|
-
|
39
|
-
rtgts.each do |rt|
|
40
|
-
t = "#{path}/#{rt}"
|
41
|
-
notifier.watch t, *modes do |event|
|
42
|
-
yield event
|
43
|
-
end
|
44
|
-
rescue => e
|
45
|
-
Log.warning "watcher: #{t}: skipping due to #{e.class}: #{e.message}"
|
46
|
-
end
|
47
|
-
self
|
12
|
+
def watch path, *modes, delay: 1, excludes: [], &block
|
48
13
|
end
|
49
14
|
|
50
15
|
end
|
@@ -7,8 +7,13 @@ require_relative 'live_sync/dsl'
|
|
7
7
|
require_relative 'live_sync/log'
|
8
8
|
require_relative 'live_sync/user'
|
9
9
|
require_relative 'live_sync/ssh'
|
10
|
+
require_relative 'live_sync/target'
|
10
11
|
require_relative 'live_sync/rsync'
|
11
12
|
require_relative 'live_sync/watcher'
|
13
|
+
require_relative 'live_sync/rb_watcher'
|
14
|
+
require_relative 'live_sync/cmd_watcher'
|
15
|
+
require_relative 'live_sync/py_inotify_watcher'
|
16
|
+
require_relative 'live_sync/py_watchdog_watcher'
|
12
17
|
require_relative 'live_sync/sync'
|
13
18
|
require_relative 'live_sync/daemon'
|
14
19
|
|
data/lib/ruby-livesync.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ruby-livesync
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.0.
|
4
|
+
version: 1.0.0.beta4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Braulio Oliveira
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-05-
|
11
|
+
date: 2024-05-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: pry
|
@@ -52,20 +52,6 @@ dependencies:
|
|
52
52
|
- - ">="
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '0'
|
55
|
-
- !ruby/object:Gem::Dependency
|
56
|
-
name: rufus-scheduler
|
57
|
-
requirement: !ruby/object:Gem::Requirement
|
58
|
-
requirements:
|
59
|
-
- - ">="
|
60
|
-
- !ruby/object:Gem::Version
|
61
|
-
version: '0'
|
62
|
-
type: :runtime
|
63
|
-
prerelease: false
|
64
|
-
version_requirements: !ruby/object:Gem::Requirement
|
65
|
-
requirements:
|
66
|
-
- - ">="
|
67
|
-
- !ruby/object:Gem::Version
|
68
|
-
version: '0'
|
69
55
|
description:
|
70
56
|
email:
|
71
57
|
- brauliobo@gmail.com
|
@@ -78,12 +64,19 @@ files:
|
|
78
64
|
- config/sample.rb
|
79
65
|
- lib/ruby-livesync.rb
|
80
66
|
- lib/ruby-livesync/live_sync.rb
|
67
|
+
- lib/ruby-livesync/live_sync/cmd_watcher.rb
|
81
68
|
- lib/ruby-livesync/live_sync/daemon.rb
|
82
69
|
- lib/ruby-livesync/live_sync/dsl.rb
|
83
70
|
- lib/ruby-livesync/live_sync/log.rb
|
71
|
+
- lib/ruby-livesync/live_sync/py/inotify.py
|
72
|
+
- lib/ruby-livesync/live_sync/py/watchdog.py
|
73
|
+
- lib/ruby-livesync/live_sync/py_inotify_watcher.rb
|
74
|
+
- lib/ruby-livesync/live_sync/py_watchdog_watcher.rb
|
75
|
+
- lib/ruby-livesync/live_sync/rb_watcher.rb
|
84
76
|
- lib/ruby-livesync/live_sync/rsync.rb
|
85
77
|
- lib/ruby-livesync/live_sync/ssh.rb
|
86
78
|
- lib/ruby-livesync/live_sync/sync.rb
|
79
|
+
- lib/ruby-livesync/live_sync/target.rb
|
87
80
|
- lib/ruby-livesync/live_sync/user.rb
|
88
81
|
- lib/ruby-livesync/live_sync/version.rb
|
89
82
|
- lib/ruby-livesync/live_sync/watcher.rb
|
@@ -103,11 +96,11 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
103
96
|
version: '0'
|
104
97
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
105
98
|
requirements:
|
106
|
-
- - "
|
99
|
+
- - ">="
|
107
100
|
- !ruby/object:Gem::Version
|
108
|
-
version:
|
101
|
+
version: '0'
|
109
102
|
requirements: []
|
110
|
-
rubygems_version: 3.3
|
103
|
+
rubygems_version: 3.5.3
|
111
104
|
signing_key:
|
112
105
|
specification_version: 4
|
113
106
|
summary: Lightweight and fast live sync daemon
|