ruby-livesync 1.0.0.beta5 → 1.0.0.rc1
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 +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
|