ruby-livesync 1.0.0.beta5 → 1.0.0.rc1
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 +7 -3
- data/lib/{ruby-livesync/live_sync → live_sync}/cmd_watcher.rb +17 -7
- data/lib/{ruby-livesync/live_sync → live_sync}/daemon.rb +4 -0
- data/lib/live_sync/dsl.rb +47 -0
- data/lib/live_sync/py/inotify.py +57 -0
- data/lib/{ruby-livesync/live_sync → live_sync}/py/watchdog.py +5 -0
- data/lib/{ruby-livesync/live_sync → live_sync}/py_inotify_watcher.rb +4 -0
- data/lib/{ruby-livesync/live_sync → live_sync}/rb_watcher.rb +5 -5
- data/lib/live_sync/reverse_rsync.rb +40 -0
- data/lib/{ruby-livesync/live_sync → live_sync}/rsync.rb +18 -8
- data/lib/{ruby-livesync/live_sync → live_sync}/sync.rb +11 -21
- data/lib/live_sync/target.rb +60 -0
- data/lib/live_sync/version.rb +5 -0
- data/lib/live_sync/watcher.rb +31 -0
- data/lib/{ruby-livesync/live_sync.rb → live_sync.rb} +1 -0
- data/lib/ruby-livesync.rb +1 -1
- data/livesync.service +0 -1
- metadata +20 -19
- data/lib/ruby-livesync/live_sync/dsl.rb +0 -44
- data/lib/ruby-livesync/live_sync/py/inotify.py +0 -64
- data/lib/ruby-livesync/live_sync/target.rb +0 -36
- data/lib/ruby-livesync/live_sync/version.rb +0 -5
- data/lib/ruby-livesync/live_sync/watcher.rb +0 -16
- /data/lib/{ruby-livesync/live_sync → live_sync}/log.rb +0 -0
- /data/lib/{ruby-livesync/live_sync → live_sync}/py_watchdog_watcher.rb +0 -0
- /data/lib/{ruby-livesync/live_sync → live_sync}/ssh.rb +0 -0
- /data/lib/{ruby-livesync/live_sync → live_sync}/user.rb +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f621a2ec4f634f6e4e7d2d46e463c99b83f34025c86a49e4047580ef478d3ff5
|
4
|
+
data.tar.gz: a818d9fc69caf291779641cf8fe0020bdff456cd3fe76bf7c09b5715b1c6c334
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: df3f56a0cb1adc2079a7efc8f61d8acd31ba814e319295be5f8cc6b0f0926e05189c1f45eaf18015b1d01aca1459a45f1bf0266f1a99f43ccd4c5a82531182ba
|
7
|
+
data.tar.gz: e45f1394423c70d5922747012c1a8b42811c19f70a979173bb36a0796cc85766c8157763057b692b1badc6f2ba2b8d9e376e4de39dc7161f76c1d6895e64a9c4
|
data/config/sample.rb
CHANGED
@@ -14,15 +14,19 @@ sync '4tb' do
|
|
14
14
|
# fork to user below, usually associated with private keys
|
15
15
|
user = :root
|
16
16
|
|
17
|
+
# interval to collect all watched events and run rsync
|
17
18
|
delay = 5
|
18
19
|
|
19
20
|
# event list from inotify
|
20
|
-
# full list at https://
|
21
|
-
modes = %i[create modify]
|
21
|
+
# full list at https://man.archlinux.org/man/inotifywait.1#EVENTS
|
22
|
+
modes = %i[create modify delete]
|
22
23
|
|
23
24
|
source = '/mnt/4tb/'
|
24
|
-
target rsync: '
|
25
|
+
target rsync: 'user@remote:/mnt/4tb' do
|
25
26
|
opts = '-ax --partial' # default
|
27
|
+
|
28
|
+
# enables bidirectional sync, using rsync's --update and a pyinotify based watcher
|
29
|
+
reverse_sync
|
26
30
|
end
|
27
31
|
|
28
32
|
# possible values are: true, false, :initial, :watched
|
@@ -4,6 +4,8 @@ module LiveSync
|
|
4
4
|
class_attribute :base_cmd
|
5
5
|
self.base_cmd = 'bash -s'
|
6
6
|
|
7
|
+
class_attribute :rsh
|
8
|
+
|
7
9
|
class_attribute :script
|
8
10
|
self.script = <<-HEREDOC
|
9
11
|
tp=$(mktemp -u)
|
@@ -16,25 +18,33 @@ module LiveSync
|
|
16
18
|
done
|
17
19
|
HEREDOC
|
18
20
|
|
19
|
-
def watch path, *modes, **params
|
21
|
+
def watch path, *modes, delay: 1, **params, &block
|
20
22
|
modes = DEFAULT_MODES if modes.blank?
|
21
23
|
script = self.script % parsed_params(path, *modes, **params)
|
24
|
+
log&.debug "#{self.class.name}: running #{base_cmd}"
|
22
25
|
stdin, stdout, stderr, @wait_thr = Open3.popen3 base_cmd
|
23
26
|
stdin.write script
|
24
27
|
stdin.close
|
25
28
|
|
26
29
|
Thread.new do
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
30
|
+
loop do
|
31
|
+
events = []
|
32
|
+
Timeout.timeout(delay){ stdout.each_line.each{ |l| events << l } } # accummulator
|
33
|
+
break if @wait_thr.join(0.1)
|
34
|
+
rescue Timeout::Error
|
35
|
+
ensure
|
36
|
+
notify events.map{ |l| parse l }, &block if events.present?
|
31
37
|
end
|
32
38
|
end
|
33
39
|
Thread.new do
|
34
|
-
stderr.each_line.each{ |line|
|
40
|
+
stderr.each_line.each{ |line| log&.error line }
|
35
41
|
end
|
36
42
|
end
|
37
43
|
|
44
|
+
def watching?
|
45
|
+
@wait_thr
|
46
|
+
end
|
47
|
+
|
38
48
|
def parsed_params path, *modes, recursive: true, delay: 1, excludes: [], **params
|
39
49
|
opts = '-r' if recursive
|
40
50
|
{
|
@@ -49,7 +59,7 @@ module LiveSync
|
|
49
59
|
def parse line
|
50
60
|
events,file = line.split ' ', 2
|
51
61
|
events = events.split(',').map(&:downcase).map(&:to_sym)
|
52
|
-
|
62
|
+
OpenStruct.new absolute_name: file.chomp, flags: events
|
53
63
|
end
|
54
64
|
|
55
65
|
end
|
@@ -1,6 +1,9 @@
|
|
1
1
|
module LiveSync
|
2
2
|
class Daemon
|
3
3
|
|
4
|
+
class_attribute :dry
|
5
|
+
self.dry = !!ENV['DRY']
|
6
|
+
|
4
7
|
def self.start config
|
5
8
|
new(config).start
|
6
9
|
end
|
@@ -9,6 +12,7 @@ module LiveSync
|
|
9
12
|
|
10
13
|
def initialize config
|
11
14
|
@config = config
|
15
|
+
$config = config
|
12
16
|
@pids = []
|
13
17
|
@syncs = Hash.new{ |h, k| h[k] = [] }
|
14
18
|
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module LiveSync
|
2
|
+
module DSL
|
3
|
+
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
|
8
|
+
class_attribute :ctx
|
9
|
+
|
10
|
+
class_attribute :attrs
|
11
|
+
self.attrs = []
|
12
|
+
|
13
|
+
def self.dsl attr, default: nil, enum: nil, type: nil, &block
|
14
|
+
self.attrs << attr
|
15
|
+
|
16
|
+
define_method attr do |sv=nil, &ablock|
|
17
|
+
v = instance_variable_get "@#{attr}"
|
18
|
+
v ||= if block
|
19
|
+
then instance_exec sv || default.dup, ablock, &block
|
20
|
+
else sv || default.dup end
|
21
|
+
instance_variable_set "@#{attr}", v if v
|
22
|
+
return v if sv.nil? # getter
|
23
|
+
|
24
|
+
# setter validation
|
25
|
+
raise "#{ctx}/#{attr}: incorrect type of #{v.inspect}" if type and !v.is_a? type
|
26
|
+
raise "#{ctx}/#{attr}: value not one of following #{enum}" if enum and !v.in? enum
|
27
|
+
|
28
|
+
v
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def dsl_apply &block
|
33
|
+
if b = binding and bs = block.source.match(/do(.+)end$/m)&.captures&.first
|
34
|
+
b.eval bs, $config
|
35
|
+
attrs.each do |a|
|
36
|
+
next unless a.in? b.local_variables
|
37
|
+
send a, b.local_variable_get(a)
|
38
|
+
end
|
39
|
+
else
|
40
|
+
instance_exec &block
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
import pyinotify
|
2
|
+
import os
|
3
|
+
import time
|
4
|
+
import sys
|
5
|
+
|
6
|
+
class EventHandler(pyinotify.ProcessEvent):
|
7
|
+
def process_default(self, event):
|
8
|
+
full_path = event.pathname
|
9
|
+
event_desc = event.maskname
|
10
|
+
print(f"{event_desc} {full_path}", flush=True)
|
11
|
+
|
12
|
+
def watch_path(path, excludes, recursive, mask, base_device):
|
13
|
+
wm = pyinotify.WatchManager()
|
14
|
+
handler = EventHandler()
|
15
|
+
notifier = pyinotify.Notifier(wm, handler)
|
16
|
+
|
17
|
+
def add_watch(path):
|
18
|
+
if os.path.isdir(path) and not any(exclude in path for exclude in excludes):
|
19
|
+
try:
|
20
|
+
if os.stat(path).st_dev == base_device:
|
21
|
+
wm.add_watch(path, mask, auto_add=recursive)
|
22
|
+
else:
|
23
|
+
print(f"Skipping {path} (different device)", file=sys.stderr)
|
24
|
+
except Exception as e:
|
25
|
+
print(f"Error adding watch on {path}: {e}", file=sys.stderr)
|
26
|
+
|
27
|
+
if recursive:
|
28
|
+
for root, dirs, _ in os.walk(path):
|
29
|
+
add_watch(root)
|
30
|
+
for dir in dirs:
|
31
|
+
add_watch(os.path.join(root, dir))
|
32
|
+
else:
|
33
|
+
add_watch(path)
|
34
|
+
|
35
|
+
try:
|
36
|
+
while True:
|
37
|
+
notifier.process_events()
|
38
|
+
if notifier.check_events():
|
39
|
+
notifier.read_events()
|
40
|
+
time.sleep(%{delay})
|
41
|
+
except KeyboardInterrupt:
|
42
|
+
notifier.stop()
|
43
|
+
|
44
|
+
# Configuration
|
45
|
+
path = '%{path}'
|
46
|
+
excludes = [%{excludes}]
|
47
|
+
recursive = %{recursive}
|
48
|
+
event_mask = pyinotify.IN_CREATE | pyinotify.IN_MODIFY | pyinotify.IN_DELETE
|
49
|
+
base_device = os.stat(path).st_dev
|
50
|
+
|
51
|
+
try:
|
52
|
+
import setproctitle
|
53
|
+
setproctitle.setproctitle(f'livesync/py_inotify: {path}')
|
54
|
+
except ImportError: pass
|
55
|
+
|
56
|
+
watch_path(path, excludes, recursive, event_mask, base_device)
|
57
|
+
|
@@ -41,6 +41,11 @@ observer = Observer()
|
|
41
41
|
event_handler = MyEventHandler(observer, excludes)
|
42
42
|
event_handler.schedule_directory(path)
|
43
43
|
|
44
|
+
try:
|
45
|
+
import setproctitle
|
46
|
+
setproctitle.setproctitle('livesync/py_watchdog: %{path}')
|
47
|
+
except ImportError: pass
|
48
|
+
|
44
49
|
observer.start()
|
45
50
|
try:
|
46
51
|
while True:
|
@@ -6,7 +6,7 @@ module LiveSync
|
|
6
6
|
modes = DEFAULT_MODES if modes.blank?
|
7
7
|
modes << :delete if sync&.delete&.in? [true, :watched]
|
8
8
|
|
9
|
-
tracker = Tracker.new path, delay, &block
|
9
|
+
tracker = Tracker.new self, path, delay, &block
|
10
10
|
rtgts = glob path, recursive: recursive, excludes: excludes
|
11
11
|
track(path, rtgts, *modes,
|
12
12
|
delay: 1,
|
@@ -23,12 +23,12 @@ module LiveSync
|
|
23
23
|
protected
|
24
24
|
|
25
25
|
class Tracker
|
26
|
-
def initialize path, delay, &block
|
26
|
+
def initialize watcher, path, delay, &block
|
27
|
+
@watcher = watcher
|
27
28
|
@delay = delay
|
28
29
|
@block = block
|
29
30
|
@to_sync = Set.new
|
30
31
|
@notifier = INotify::Notifier.new
|
31
|
-
@pathname = Pathname.new path
|
32
32
|
end
|
33
33
|
|
34
34
|
def watch *args, &block
|
@@ -36,12 +36,12 @@ module LiveSync
|
|
36
36
|
end
|
37
37
|
|
38
38
|
def track event
|
39
|
-
@to_sync <<
|
39
|
+
@to_sync << OpenStruct.new(absolute_name: event.absolute_name, flags: event.flags)
|
40
40
|
end
|
41
41
|
|
42
42
|
def check
|
43
43
|
@notifier.process # calls track
|
44
|
-
@
|
44
|
+
@watcher.notify @to_sync, &@block
|
45
45
|
@to_sync.clear
|
46
46
|
ensure
|
47
47
|
timeout_check
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module LiveSync
|
2
|
+
class ReverseRsync < Rsync
|
3
|
+
|
4
|
+
dsl :watcher, default: :py_inotify do |name|
|
5
|
+
klass = :"#{name.to_s.camelize}Watcher"
|
6
|
+
klass = LiveSync.const_get klass
|
7
|
+
watcher = klass.new sync
|
8
|
+
raise "reverse_sync: provided #{name}, but it required a cmd watcher" unless watcher.is_a? CmdWatcher
|
9
|
+
watcher
|
10
|
+
end
|
11
|
+
|
12
|
+
attr_reader :parent
|
13
|
+
delegate :ssh, to: :parent
|
14
|
+
|
15
|
+
def initialize parent, &block
|
16
|
+
super parent.sync, parent.sync.dest, parent.sync.source, reverse: true
|
17
|
+
|
18
|
+
@parent = parent
|
19
|
+
opts << ' -u'
|
20
|
+
parent.opts << ' -u'
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
def running?
|
25
|
+
@wait_thr or parent.running?
|
26
|
+
end
|
27
|
+
|
28
|
+
def initial
|
29
|
+
watcher.base_cmd = "#{rsh} #{userhost} setsid #{watcher.base_cmd}"
|
30
|
+
watch
|
31
|
+
sleep 1 while parent.running?
|
32
|
+
super
|
33
|
+
end
|
34
|
+
|
35
|
+
def start
|
36
|
+
# reuse parent's ssh
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
@@ -3,12 +3,17 @@ module LiveSync
|
|
3
3
|
|
4
4
|
dsl :opts, default: '-ax --partial', type: String
|
5
5
|
|
6
|
-
|
6
|
+
dsl :reverse_sync do |v, block|
|
7
|
+
ReverseRsync.new self, &block
|
8
|
+
end
|
9
|
+
|
10
|
+
attr_reader :ssh, :rprefix
|
7
11
|
|
8
|
-
def initialize *args, &block
|
9
|
-
super
|
10
|
-
# add trailing slash in case the dir is the same
|
11
|
-
|
12
|
+
def initialize *args, **params, &block
|
13
|
+
super *args, **params, &block
|
14
|
+
# add trailing slash in case the dir name is the same
|
15
|
+
@source = File.join source, '' if File.basename(source) == File.basename(dest)
|
16
|
+
@rprefix = 'reverse: ' if reverse
|
12
17
|
end
|
13
18
|
|
14
19
|
def start
|
@@ -25,6 +30,8 @@ module LiveSync
|
|
25
30
|
args = []
|
26
31
|
args << '--delete' if sync.delete.in? [true, :initial]
|
27
32
|
run :initial, *args, loglevel: :info
|
33
|
+
|
34
|
+
@reverse_sync&.initial
|
28
35
|
end
|
29
36
|
|
30
37
|
def partial paths
|
@@ -39,20 +46,23 @@ module LiveSync
|
|
39
46
|
protected
|
40
47
|
|
41
48
|
def run type, *args, loglevel: :debug
|
42
|
-
cmd = "rsync -e '#{rsh}' #{opts} #{
|
49
|
+
cmd = "rsync -e '#{rsh}' #{opts} #{source} #{dest} #{args.join ' '}"
|
43
50
|
sync.excludes.each{ |e| cmd << " --exclude='#{e}'" }
|
44
51
|
|
45
|
-
log.send loglevel, "#{type}: starting with cmd: #{cmd}"
|
52
|
+
log.send loglevel, "#{rprefix}#{type}: starting with cmd: #{cmd}"
|
53
|
+
return binding.pry if Daemon.dry
|
46
54
|
stdin, stdout, stderr, @wait_thr = Open3.popen3 cmd
|
47
55
|
yield stdin, stdout, stderr if block_given?
|
56
|
+
|
48
57
|
Thread.new do
|
49
58
|
Process.wait @wait_thr.pid rescue Errno::ECHILD; nil
|
50
59
|
@wait_thr = nil
|
51
|
-
log.send loglevel, "#{type}: finished"
|
60
|
+
log.send loglevel, "#{rprefix}#{type}: finished"
|
52
61
|
end
|
53
62
|
end
|
54
63
|
|
55
64
|
def rsh
|
65
|
+
# -t -t ensure the running process is killed with the client
|
56
66
|
"ssh -o ControlPath=#{ssh.cpath}"
|
57
67
|
end
|
58
68
|
|
@@ -5,12 +5,10 @@ module LiveSync
|
|
5
5
|
|
6
6
|
attr_reader :name
|
7
7
|
attr_reader :log
|
8
|
-
attr_reader :watcher
|
9
|
-
attr_reader :target
|
10
8
|
|
11
9
|
def initialize name = nil, &block
|
12
10
|
fill_name name
|
13
|
-
source name if File.
|
11
|
+
source name if File.directory? name
|
14
12
|
dsl_apply(&block)
|
15
13
|
end
|
16
14
|
|
@@ -22,20 +20,23 @@ module LiveSync
|
|
22
20
|
|
23
21
|
dsl :enabled, default: true
|
24
22
|
|
25
|
-
dsl :watcher,
|
23
|
+
dsl :watcher, default: :rb do |name|
|
26
24
|
klass = :"#{name.to_s.camelize}Watcher"
|
27
|
-
|
25
|
+
klass = LiveSync.const_get klass
|
26
|
+
klass.new self
|
28
27
|
end
|
29
28
|
|
30
29
|
dsl :user, default: :root
|
31
30
|
dsl :source do |source|
|
32
31
|
raise "#{ctx}: source isn't a directory" unless File.directory? source
|
33
|
-
fill_name source
|
34
|
-
|
32
|
+
fill_name source if !name
|
33
|
+
source
|
35
34
|
end
|
36
35
|
|
37
|
-
|
38
|
-
|
36
|
+
attr_reader :dest
|
37
|
+
dsl :target do |opts, block|
|
38
|
+
@dest = opts[:rsync]
|
39
|
+
Rsync.new self, source, @dest, &block
|
39
40
|
end
|
40
41
|
|
41
42
|
dsl :delay, default: 5, type: Integer
|
@@ -55,23 +56,12 @@ module LiveSync
|
|
55
56
|
def guard
|
56
57
|
fork do
|
57
58
|
Process.setproctitle "livesync: sync #{ctx}"
|
58
|
-
|
59
|
-
|
60
|
-
watch source
|
59
|
+
target.watch
|
61
60
|
target.initial
|
62
61
|
sleep 1.day while true
|
63
62
|
end
|
64
63
|
end
|
65
64
|
|
66
|
-
def watch dir
|
67
|
-
@watcher.watch dir, *modes, excludes: excludes, delay: delay, &method(:sync)
|
68
|
-
end
|
69
|
-
|
70
|
-
def sync paths
|
71
|
-
return if target.running?
|
72
|
-
target.partial paths
|
73
|
-
end
|
74
|
-
|
75
65
|
protected
|
76
66
|
|
77
67
|
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module LiveSync
|
2
|
+
class Target
|
3
|
+
|
4
|
+
include DSL
|
5
|
+
|
6
|
+
attr_reader :sync
|
7
|
+
delegate :log, to: :sync
|
8
|
+
|
9
|
+
attr_reader :source, :dest
|
10
|
+
attr_reader :userhost, :localpath, :remotepath, :watchpath
|
11
|
+
attr_reader :reverse
|
12
|
+
|
13
|
+
delegate :watcher, :modes, :excludes, :delay, to: :sync
|
14
|
+
|
15
|
+
def initialize sync, source, dest, reverse: false, &block
|
16
|
+
@sync = sync
|
17
|
+
@source = source
|
18
|
+
@dest = dest
|
19
|
+
@reverse = reverse
|
20
|
+
@to_sync = Set.new
|
21
|
+
|
22
|
+
@localpath = if reverse then dest else source end
|
23
|
+
@userhost, @remotepath = if reverse then source else dest end.split ':'
|
24
|
+
@watchpath = if reverse then @remotepath else @localpath end
|
25
|
+
raise "#{sync.ctx}: missing target path" unless @remotepath
|
26
|
+
|
27
|
+
dsl_apply &block if block
|
28
|
+
end
|
29
|
+
|
30
|
+
def watch
|
31
|
+
watcher.watch watchpath, *modes, excludes: excludes, delay: delay, &method(:on_notify)
|
32
|
+
end
|
33
|
+
|
34
|
+
def on_notify events
|
35
|
+
wpath = Pathname.new watchpath
|
36
|
+
paths = events.map{ |e| Pathname.new(e.absolute_name).relative_path_from(wpath).to_s }
|
37
|
+
@to_sync.merge paths
|
38
|
+
return if running?
|
39
|
+
partial @to_sync
|
40
|
+
@to_sync.clear
|
41
|
+
end
|
42
|
+
|
43
|
+
def start
|
44
|
+
false
|
45
|
+
end
|
46
|
+
|
47
|
+
def running?
|
48
|
+
false
|
49
|
+
end
|
50
|
+
|
51
|
+
def initial
|
52
|
+
raise 'not implemented'
|
53
|
+
end
|
54
|
+
|
55
|
+
def partial paths
|
56
|
+
raise 'not implemented'
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module LiveSync
|
2
|
+
class Watcher
|
3
|
+
|
4
|
+
DEFAULT_MODES = %i[create modify]
|
5
|
+
|
6
|
+
attr_reader :sync
|
7
|
+
delegate :log, to: :sync, allow_nil: true
|
8
|
+
|
9
|
+
def initialize sync=nil
|
10
|
+
@sync = sync
|
11
|
+
end
|
12
|
+
|
13
|
+
def watch path, *modes, delay: 1, excludes: [], &block
|
14
|
+
end
|
15
|
+
|
16
|
+
def notify events, &block
|
17
|
+
log&.debug "NOTIFY: #{events.inspect}"
|
18
|
+
events.each{ |e| e.flags = e.flags.map{ |f| flag_map f } }
|
19
|
+
block.call Set.new events
|
20
|
+
end
|
21
|
+
|
22
|
+
def watching?
|
23
|
+
false
|
24
|
+
end
|
25
|
+
|
26
|
+
def flag_map e
|
27
|
+
e
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
@@ -15,5 +15,6 @@ require_relative 'live_sync/cmd_watcher'
|
|
15
15
|
require_relative 'live_sync/py_inotify_watcher'
|
16
16
|
require_relative 'live_sync/py_watchdog_watcher'
|
17
17
|
require_relative 'live_sync/sync'
|
18
|
+
require_relative 'live_sync/reverse_rsync'
|
18
19
|
require_relative 'live_sync/daemon'
|
19
20
|
|
data/lib/ruby-livesync.rb
CHANGED
data/livesync.service
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.rc1
|
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-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: pry
|
@@ -62,24 +62,25 @@ extra_rdoc_files: []
|
|
62
62
|
files:
|
63
63
|
- bin/livesync
|
64
64
|
- config/sample.rb
|
65
|
+
- lib/live_sync.rb
|
66
|
+
- lib/live_sync/cmd_watcher.rb
|
67
|
+
- lib/live_sync/daemon.rb
|
68
|
+
- lib/live_sync/dsl.rb
|
69
|
+
- lib/live_sync/log.rb
|
70
|
+
- lib/live_sync/py/inotify.py
|
71
|
+
- lib/live_sync/py/watchdog.py
|
72
|
+
- lib/live_sync/py_inotify_watcher.rb
|
73
|
+
- lib/live_sync/py_watchdog_watcher.rb
|
74
|
+
- lib/live_sync/rb_watcher.rb
|
75
|
+
- lib/live_sync/reverse_rsync.rb
|
76
|
+
- lib/live_sync/rsync.rb
|
77
|
+
- lib/live_sync/ssh.rb
|
78
|
+
- lib/live_sync/sync.rb
|
79
|
+
- lib/live_sync/target.rb
|
80
|
+
- lib/live_sync/user.rb
|
81
|
+
- lib/live_sync/version.rb
|
82
|
+
- lib/live_sync/watcher.rb
|
65
83
|
- lib/ruby-livesync.rb
|
66
|
-
- lib/ruby-livesync/live_sync.rb
|
67
|
-
- lib/ruby-livesync/live_sync/cmd_watcher.rb
|
68
|
-
- lib/ruby-livesync/live_sync/daemon.rb
|
69
|
-
- lib/ruby-livesync/live_sync/dsl.rb
|
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
|
76
|
-
- lib/ruby-livesync/live_sync/rsync.rb
|
77
|
-
- lib/ruby-livesync/live_sync/ssh.rb
|
78
|
-
- lib/ruby-livesync/live_sync/sync.rb
|
79
|
-
- lib/ruby-livesync/live_sync/target.rb
|
80
|
-
- lib/ruby-livesync/live_sync/user.rb
|
81
|
-
- lib/ruby-livesync/live_sync/version.rb
|
82
|
-
- lib/ruby-livesync/live_sync/watcher.rb
|
83
84
|
- livesync.service
|
84
85
|
homepage: https://github.com/brauliobo/ruby-livesync
|
85
86
|
licenses:
|
@@ -1,44 +0,0 @@
|
|
1
|
-
module LiveSync
|
2
|
-
module DSL
|
3
|
-
|
4
|
-
extend ActiveSupport::Concern
|
5
|
-
|
6
|
-
included do
|
7
|
-
|
8
|
-
class_attribute :ctx
|
9
|
-
|
10
|
-
class_attribute :attrs
|
11
|
-
self.attrs = []
|
12
|
-
|
13
|
-
def self.dsl attr, default: nil, enum: nil,
|
14
|
-
type: nil, skip_set: false, &block
|
15
|
-
self.attrs << attr
|
16
|
-
|
17
|
-
define_method attr do |sv=nil, opts={}, &ablock|
|
18
|
-
sv = default if opts[:init]
|
19
|
-
(v = instance_variable_get("@#{attr}"); return(if v.nil? then default else v end)) if sv.nil?
|
20
|
-
|
21
|
-
raise "#{ctx}/#{attr}: incorrect type of #{sv.inspect}" if type and !sv.is_a? type
|
22
|
-
raise "#{ctx}/#{attr}: value not one of following #{enum}" if enum and !sv.in? enum
|
23
|
-
|
24
|
-
instance_variable_set "@#{attr}", sv unless skip_set
|
25
|
-
instance_exec sv, ablock, &block if block
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
def dsl_apply &block
|
30
|
-
if b = binding and bs = block.source.match(/do(.+)end$/m)&.captures&.first
|
31
|
-
b.eval bs
|
32
|
-
attrs.each do |a|
|
33
|
-
next send a, nil, {init: true} unless a.in? b.local_variables
|
34
|
-
send a, b.local_variable_get(a)
|
35
|
-
end
|
36
|
-
else
|
37
|
-
instance_exec &block
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
end
|
42
|
-
|
43
|
-
end
|
44
|
-
end
|
@@ -1,64 +0,0 @@
|
|
1
|
-
import pyinotify
|
2
|
-
import os
|
3
|
-
import time
|
4
|
-
import sys
|
5
|
-
|
6
|
-
class EventHandler(pyinotify.ProcessEvent):
|
7
|
-
def my_init(self, excludes, recursive, mask, base_device):
|
8
|
-
self.excludes = excludes
|
9
|
-
self.recursive = recursive
|
10
|
-
self.base_device = base_device
|
11
|
-
self.mask = mask
|
12
|
-
self.events = set()
|
13
|
-
|
14
|
-
def process_default(self, event):
|
15
|
-
full_path = event.pathname
|
16
|
-
if not any(exclude in full_path for exclude in self.excludes):
|
17
|
-
event_desc = event.maskname
|
18
|
-
self.events.add((event_desc, full_path))
|
19
|
-
|
20
|
-
def print_events(self):
|
21
|
-
if self.events:
|
22
|
-
for event_desc, full_path in self.events:
|
23
|
-
print(f"{event_desc} {full_path}", flush=True)
|
24
|
-
self.events.clear()
|
25
|
-
|
26
|
-
def watch_path(self, path):
|
27
|
-
if not any(exclude in path for exclude in self.excludes):
|
28
|
-
try:
|
29
|
-
if os.stat(path).st_dev == self.base_device:
|
30
|
-
wm.add_watch(path, self.mask, auto_add=True)
|
31
|
-
else:
|
32
|
-
print(f"Skipping {path} (different device)", file=sys.stderr)
|
33
|
-
except Exception as e:
|
34
|
-
print(f"Error adding watch on {path}: {e}", file=sys.stderr)
|
35
|
-
|
36
|
-
def add_watch(self, path):
|
37
|
-
if self.recursive:
|
38
|
-
for root, dirs, _ in os.walk(path):
|
39
|
-
self.watch_path(root)
|
40
|
-
for dir in dirs:
|
41
|
-
self.watch_path(os.path.join(root, dir))
|
42
|
-
else:
|
43
|
-
self.watch_path(path)
|
44
|
-
|
45
|
-
excludes = [%{excludes}]
|
46
|
-
recursive = %{recursive}
|
47
|
-
event_mask = pyinotify.IN_CREATE | pyinotify.IN_MODIFY | pyinotify.IN_MOVED_TO | pyinotify.IN_DELETE # Event mask
|
48
|
-
base_device = os.stat('%{path}').st_dev
|
49
|
-
|
50
|
-
wm = pyinotify.WatchManager()
|
51
|
-
handler = EventHandler(excludes=excludes, recursive=recursive, mask=event_mask, base_device=base_device)
|
52
|
-
notifier = pyinotify.Notifier(wm, handler)
|
53
|
-
handler.add_watch('%{path}')
|
54
|
-
|
55
|
-
try:
|
56
|
-
while True:
|
57
|
-
notifier.process_events()
|
58
|
-
if notifier.check_events():
|
59
|
-
notifier.read_events()
|
60
|
-
handler.print_events()
|
61
|
-
time.sleep(%{delay})
|
62
|
-
except KeyboardInterrupt:
|
63
|
-
notifier.stop()
|
64
|
-
|
@@ -1,36 +0,0 @@
|
|
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
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|