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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 182e21dbd5e045ed2de4492cdefcd295dd92f9af62154ce4cfb7569610cd55ee
4
- data.tar.gz: c3d0fe0760f40b408b9e3b72d0349dcf5a838f8aff1224ae2de424af508947fb
3
+ metadata.gz: f621a2ec4f634f6e4e7d2d46e463c99b83f34025c86a49e4047580ef478d3ff5
4
+ data.tar.gz: a818d9fc69caf291779641cf8fe0020bdff456cd3fe76bf7c09b5715b1c6c334
5
5
  SHA512:
6
- metadata.gz: 76aa54c57ad5ee3b64fcee946432021d5e5e948125ffaf72c524bb81bd2abdfefbd53138a67772d05e557690b6ffb89cf608b6267ac124fcb3f0ecc84f59d9e1
7
- data.tar.gz: b38edffeb1ae8e63d20346cebf2510da50950bd621d2bf8cf864d0975915b8ee91d823299449cc294225b9c657b966df9e2b934c5a22fe21a988c03e82b41e61
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://manpages.ubuntu.com/manpages/latest/en/man1/inotifywait.1.html#events
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: 'root@bhavapower:/mnt/extensor/4tb' do
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
- stdout.sync = true
28
- stdout.each_line.each do |line|
29
- file,events = parse line
30
- yield [OpenStruct.new(absolute_name: file, flags: events)]
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| STDERR.puts 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
- [file.chomp, events]
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:
@@ -15,5 +15,9 @@ module LiveSync
15
15
  }
16
16
  end
17
17
 
18
+ def flag_map e
19
+ e.to_s.gsub(/^in_/, '').to_sym
20
+ end
21
+
18
22
  end
19
23
  end
@@ -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 << Pathname.new(event.absolute_name).relative_path_from(@pathname).to_s
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
- @block.call @to_sync
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
- attr_reader :ssh
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
- sync.source File.join(sync.source, '') if File.basename(sync.source) == File.basename(@path)
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} #{sync.source} #{dest} #{args.join ' '}"
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.exist? name
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, skip_set: true, default: :rb do |name|
23
+ dsl :watcher, default: :rb do |name|
26
24
  klass = :"#{name.to_s.camelize}Watcher"
27
- @watcher_class = LiveSync.const_get klass
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
- @pathname = Pathname.new source
32
+ fill_name source if !name
33
+ source
35
34
  end
36
35
 
37
- dsl :target, skip_set: true do |opts, &block|
38
- @target = Rsync.new self, opts[:rsync], &block if opts[:rsync]
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
- @watcher = @watcher_class.new self
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,5 @@
1
+ module LiveSync
2
+
3
+ VERSION = '1.0.0.rc1'
4
+
5
+ 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
@@ -3,5 +3,5 @@ require 'active_support/all'
3
3
 
4
4
  require 'rb-inotify'
5
5
 
6
- require_relative 'ruby-livesync/live_sync'
6
+ require_relative 'live_sync'
7
7
 
data/livesync.service CHANGED
@@ -4,7 +4,6 @@ After=network-online.target
4
4
  Wants=network-online.target
5
5
 
6
6
  [Service]
7
- Type=Simple
8
7
  ExecStart=/usr/bin/livesync /etc/livesync/config.rb
9
8
  Restart=always
10
9
 
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.beta5
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-18 00:00:00.000000000 Z
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
@@ -1,5 +0,0 @@
1
- module LiveSync
2
-
3
- VERSION = '1.0.0.beta5'
4
-
5
- end
@@ -1,16 +0,0 @@
1
- module LiveSync
2
- class Watcher
3
-
4
- DEFAULT_MODES = %i[create modify]
5
-
6
- attr_reader :sync
7
-
8
- def initialize sync=nil
9
- @sync = sync
10
- end
11
-
12
- def watch path, *modes, delay: 1, excludes: [], &block
13
- end
14
-
15
- end
16
- end
File without changes
File without changes
File without changes