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 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