ruby-livesync 1.0.0.beta2 → 1.0.0.beta4
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.
- 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 +22 -33
- 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,21 +22,26 @@ 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
|
-
raise "#{ctx}: source
|
32
|
+
raise "#{ctx}: source isn't a directory" unless File.directory? source
|
31
33
|
fill_name source
|
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,40 +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
|
-
|
62
|
-
|
63
|
-
schedule
|
60
|
+
watch source
|
61
|
+
target.initial
|
64
62
|
sleep 1.day while true
|
65
63
|
end
|
66
64
|
end
|
67
65
|
|
68
|
-
def
|
69
|
-
@
|
70
|
-
end
|
71
|
-
|
72
|
-
def schedule
|
73
|
-
@scheduler.in "#{delay}s", &method(:check)
|
66
|
+
def watch dir
|
67
|
+
@watcher.watch dir, *modes, excludes: excludes, delay: delay, &method(:sync)
|
74
68
|
end
|
75
69
|
|
76
|
-
def
|
77
|
-
return if
|
78
|
-
|
79
|
-
return if @to_sync.blank?
|
80
|
-
@rsync.partial @to_sync
|
81
|
-
@to_sync.clear
|
82
|
-
ensure
|
83
|
-
schedule
|
70
|
+
def sync paths
|
71
|
+
return if target.running?
|
72
|
+
target.partial paths
|
84
73
|
end
|
85
74
|
|
86
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
|