entangler 0.4.1 → 1.0.0.beta1

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
  SHA1:
3
- metadata.gz: 116d9b520b874e6c30aaac728a0d341547bfdd4f
4
- data.tar.gz: e3981339b47c28b20c1510b6e97b32086e174a7e
3
+ metadata.gz: 967e19ece113d7aa2359cf932a74e53ceb422ea6
4
+ data.tar.gz: 822f1cbd484e5a775de97f0d6bff6010312a4154
5
5
  SHA512:
6
- metadata.gz: 42aaecf1a355c18f5f44e09e76fc95ad1c4356b1930bee788386e5577f7d5a1f906cbdc5ea1e3c6e7617aa8735a16cb7dd977d17457e5d4573da232e7e0d7df7
7
- data.tar.gz: 35f0c0b7be7c7984bf8d5215f4996b62d28cbf57afa21fc888087b44b768d40e2c594e2a5f21c5284012cf6d1a9b24ebde73ce752f8b5ae3ad28c07a1b9d5d36
6
+ metadata.gz: a5ba1871dc42e6d6aaf7616b8bc9b793b41d7fb150d1aaa501a453894ecb1fa0ad0c92d2e2385cad855a3c2f9ebf52a07100be2bc659ffc098e8717b0fe1e122
7
+ data.tar.gz: 46bcd6e8caa9fa1ae0a999dbe3a7aef0a579b073ef8886db943bda8686f340f297131dc503815b4326fc6bdc5604d00d4eab5f2c1a72df2b3fb501a4f7e28069
data/.rubocop.yml ADDED
@@ -0,0 +1,9 @@
1
+ AllCops:
2
+ DisplayCopNames: true
3
+ ExtraDetails: true
4
+
5
+ Metrics/LineLength:
6
+ Max: 120
7
+
8
+ Style/Documentation:
9
+ Enabled: false
data/.travis.yml CHANGED
@@ -1,5 +1,20 @@
1
1
  sudo: false
2
2
  language: ruby
3
+ os:
4
+ - linux
5
+ - osx
3
6
  rvm:
4
- - 2.3.0
5
- before_install: gem install bundler -v 1.12.5
7
+ - 2.3.3
8
+ - ruby-head
9
+ matrix:
10
+ include:
11
+ - os: osx
12
+ rvm: 2.2.5
13
+ - os: linux
14
+ rvm: 2.2.6
15
+ allow_failures:
16
+ - rvm: ruby-head
17
+ before_install:
18
+ - gem install bundler -v 1.13.6
19
+ install:
20
+ - bundle install --jobs=3 --retry=3
data/README.md CHANGED
@@ -1,9 +1,8 @@
1
1
  # Entangler
2
2
 
3
- Syncing tool used to keep a local and remote (over SSH) folder in sync.
3
+ [![Build Status](https://travis-ci.org/daveallie/entangler.svg?branch=master)](https://travis-ci.org/daveallie/entangler)
4
4
 
5
- ## Prerequisites
6
- - librsync 2.x
5
+ Syncing tool used to keep a local and remote (over SSH) folder in sync.
7
6
 
8
7
  ## Installation
9
8
 
@@ -19,27 +18,29 @@ $ entangler master /some/base/path user@remote:/some/remote/path
19
18
 
20
19
  ```
21
20
  $ entangler -h
22
- Entangler v0.4.1
21
+ Entangler v1.0.0.beta1
23
22
 
24
23
  Usage:
25
24
  entangler master <base_dir> <remote_user>@<remote_host>:<remote_base_dir> [options]
26
25
  entangler master <base_dir> <other_synced_base_dir> [options]
27
26
 
28
27
  Options:
29
- -i, --ignore '/.git' Ignore folder when syncing, string is regex if surrounded by '/'
30
- All folders should be relative to the base sync directory, but should start with a '/'.
28
+ -i, --ignore '.git' Ignore path when syncing, string is regex if surrounded by '/'
29
+ All paths should be relative to the base sync directory.
31
30
  -p, --port PORT Overwrite the SSH port (usually 22)
32
31
  (doesn't do anything in slave mode)
33
- -v, --version Show version number
32
+ -v, --verbose Log Debug lines
33
+ --version Show version number
34
34
  -h, --help Show this message
35
35
  ```
36
36
 
37
- ### Ignoring folders
37
+ ### Ignoring files and folders
38
38
 
39
- If you specify string, instead of regex, it will match any folder path starting with that string, i.e. `-i '/.git'` will ignore the `.git` folder and all its sub-directories.
40
- If you want to just ignore the the `.git` sub-directories but not the content in the git folder, you can use `-i '/^\/\.git\/[^\/]*/'` which will match any path starting with `/.git/`, but not `/.git` itself.
39
+ If you specify a string, instead of a regex, it will match any path starting with that string, i.e. `-i '.git'` will ignore the `.git`
40
+ folder and all its sub-directories. If you want to just ignore the the `.git` sub-directories but not the content in the git folder, you'll
41
+ have to use regex. `-i '/^\.git\/[^\/]+\/.*/'` will match all sub-directories of `.git/`, but not the files in `.git`.
41
42
 
42
- You can specify multiple `-i` or `--ignore` flags to ignore multiple directories.
43
+ You can specify multiple `-i` or `--ignore` flags to ignore multiple paths.
43
44
 
44
45
  ## Development
45
46
 
@@ -51,7 +52,6 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
51
52
 
52
53
  Bug reports and pull requests are welcome on GitHub at https://github.com/daveallie/entangler. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
53
54
 
54
-
55
55
  ## License
56
56
 
57
57
  The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
data/Rakefile CHANGED
@@ -1,6 +1,8 @@
1
- require "bundler/gem_tasks"
2
- require "rspec/core/rake_task"
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+ require 'rubocop/rake_task'
3
4
 
5
+ RuboCop::RakeTask.new(:rubocop)
4
6
  RSpec::Core::RakeTask.new(:spec)
5
7
 
6
- task :default => :spec
8
+ task default: [:rubocop, :spec]
data/bin/console CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require "bundler/setup"
4
- require "entangler"
3
+ require 'bundler/setup'
4
+ require 'entangler'
5
5
 
6
6
  # You can add fixtures and/or initialization code here to make experimenting
7
7
  # with your gem easier. You can also use a different console, if you like.
@@ -10,5 +10,5 @@ require "entangler"
10
10
  # require "pry"
11
11
  # Pry.start
12
12
 
13
- require "irb"
13
+ require 'irb'
14
14
  IRB.start
data/entangler.gemspec CHANGED
@@ -4,24 +4,25 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
4
  require 'entangler/version'
5
5
 
6
6
  Gem::Specification.new do |spec|
7
- spec.name = "entangler"
7
+ spec.name = 'entangler'
8
8
  spec.version = Entangler::VERSION
9
- spec.authors = ["Dave Allie"]
10
- spec.email = ["dave@daveallie.com"]
9
+ spec.authors = ['Dave Allie']
10
+ spec.email = ['dave@daveallie.com']
11
11
 
12
- spec.summary = %q{Two way file syncer using platform native notify and rdiff syncing.}
13
- spec.description = %q{Two way file syncer using platform native notify and rdiff syncing.}
14
- spec.homepage = "https://github.com/daveallie/entangler"
15
- spec.license = "MIT"
12
+ spec.summary = 'Two way file syncer using platform native notify.'
13
+ spec.description = 'Two way file syncer using platform native notify.'
14
+ spec.homepage = 'https://github.com/daveallie/entangler'
15
+ spec.license = 'MIT'
16
16
 
17
17
  spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
18
- spec.bindir = "exe"
18
+ spec.bindir = 'exe'
19
19
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20
- spec.require_paths = ["lib"]
20
+ spec.require_paths = ['lib']
21
21
 
22
- spec.add_development_dependency "bundler", "~> 1.12"
23
- spec.add_development_dependency "rake", "~> 10.0"
24
- spec.add_development_dependency "rspec", "~> 3.0"
25
- spec.add_dependency "lib_ruby_diff", "~> 0.1"
26
- spec.add_dependency "to_regexp", "~> 0.2"
22
+ spec.add_development_dependency 'bundler', '~> 1.12'
23
+ spec.add_development_dependency 'rake', '~> 10.0'
24
+ spec.add_development_dependency 'rspec', '~> 3.0'
25
+ spec.add_development_dependency 'rubocop', '~> 0.46'
26
+ spec.add_dependency 'listen', '~> 3.1'
27
+ spec.add_dependency 'to_regexp', '~> 0.2'
27
28
  end
data/exe/entangler CHANGED
@@ -5,59 +5,60 @@ require 'to_regexp'
5
5
 
6
6
  options = {}
7
7
  OptionParser.new do |opts|
8
- opts.banner = %Q(Entangler v#{Entangler::VERSION}
8
+ opts.banner = %(Entangler v#{Entangler::VERSION}
9
9
 
10
10
  Usage:
11
11
  entangler master <base_dir> <remote_user>@<remote_host>:<remote_base_dir> [options]
12
12
  entangler master <base_dir> <other_synced_base_dir> [options])
13
13
 
14
- opts.separator ""
15
- opts.separator "Options:"
14
+ opts.separator ''
15
+ opts.separator 'Options:'
16
16
 
17
- opts.on("-i", "--ignore '/.git'", "Ignore folder when syncing, string is regex if surrounded by '/'",
18
- "All folders should be relative to the base sync directory, but should start with a '/'.") do |ignore|
17
+ opts.on('-i', "--ignore '.git'", "Ignore path when syncing, string is regex if surrounded by '/'",
18
+ 'All paths should be relative to the base sync directory.') do |ignore|
19
19
  options[:ignore] ||= []
20
20
  options[:ignore] << ignore
21
21
  end
22
22
 
23
- opts.on("-p", "--port PORT", "Overwrite the SSH port (usually 22)", "(doesn't do anything in slave mode)") do |port|
23
+ opts.on('-p', '--port PORT', 'Overwrite the SSH port (usually 22)', "(doesn't do anything in slave mode)") do |port|
24
24
  options[:port] = port
25
25
  end
26
26
 
27
- opts.on_tail("-v", "--version", "Show version number") do |v|
27
+ opts.on('-v', '--verbose', 'Log Debug lines') do
28
+ options[:verbose] = true
29
+ end
30
+
31
+ opts.on_tail('--version', 'Show version number') do
28
32
  puts Entangler::VERSION
29
33
  exit
30
34
  end
31
35
 
32
- opts.on_tail("-h", "--help", "Show this message") do
36
+ opts.on_tail('-h', '--help', 'Show this message') do
33
37
  puts opts
34
38
  exit
35
39
  end
36
40
  end.parse!
37
41
 
38
42
  mode = ARGV.shift
39
- unless mode && ['master', 'slave'].include?(mode)
43
+ unless mode && %w(master slave).include?(mode)
40
44
  puts "Mode unknown, please read help:\nentangler -h"
41
45
  exit 1
42
46
  end
43
47
 
44
48
  base_dir = ARGV.shift
45
49
  unless base_dir
46
- puts "Missing base directory"
50
+ puts 'Missing base directory'
47
51
  exit 1
48
52
  end
49
53
 
50
54
  if mode == 'master'
51
55
  remote_information = ARGV.shift
52
56
 
53
- unless remote_information
54
- raise 'Missing destination information'
55
- exit 1
56
- end
57
+ raise 'Missing destination information' unless remote_information
57
58
 
58
59
  user = host = path = error = nil
59
60
  remote_mode = false
60
- if remote_information.match(/[^@]+@[^:]+:.+/)
61
+ if remote_information =~ /[^@]+@[^:]+:.+/
61
62
  remote_mode = true
62
63
  user, rest = remote_information.split('@', 2)
63
64
  host, path = (rest || '').split(':', 2)
@@ -74,17 +75,27 @@ if mode == 'master'
74
75
  exit 1
75
76
  end
76
77
 
77
- opts = {remote_base_dir: path, remote_mode: remote_mode}
78
- opts.merge!(remote_user: user, remote_host: host) if remote_mode
78
+ opts = { remote_base_dir: path, remote_mode: remote_mode }
79
+ if remote_mode
80
+ opts[:remote_user] = user
81
+ opts[:remote_host] = host
82
+ end
79
83
  opts[:remote_port] = options[:port] if options[:port]
80
84
  else
81
- opts = {mode: 'slave'}
85
+ opts = { mode: 'slave' }
82
86
  end
83
87
 
84
88
  if options[:ignore]
85
89
  opts[:ignore] = options[:ignore].map do |opt|
86
- opt.to_regexp(detect: true)
90
+ if ToRegexp::String.literal? opt
91
+ source, *rest = opt.as_regexp(detect: true)
92
+ ::Regexp.new "^#{source}", *rest
93
+ else
94
+ opt.to_regexp(detect: true)
95
+ end
87
96
  end
88
97
  end
89
98
 
99
+ opts[:verbose] = options[:verbose]
100
+
90
101
  Entangler.run(base_dir, opts)
@@ -1,23 +1,15 @@
1
- require 'lib_ruby_diff'
2
- require 'tempfile'
1
+ require 'fileutils'
3
2
 
4
3
  module Entangler
5
4
  class EntangledFile
6
- # 0: file initialized
7
- # 1: sig loaded
8
- # 2: delta loaded
9
- attr_accessor :state
10
- attr_accessor :desired_modtime
11
- attr_reader :path
5
+ attr_accessor :desired_modtime, :action
6
+ attr_reader :path, :contents
12
7
 
13
- def initialize(rel_path)
8
+ def initialize(action, rel_path)
9
+ @action = action
14
10
  @path = rel_path
15
- @state = 0
16
11
  @desired_modtime = Time.now.to_i
17
- end
18
-
19
- def done?
20
- @state == 2
12
+ @contents = nil
21
13
  end
22
14
 
23
15
  def full_path
@@ -25,116 +17,53 @@ module Entangler
25
17
  end
26
18
 
27
19
  def file_exists?
28
- File.exists?(full_path)
20
+ File.exist?(full_path)
29
21
  end
30
22
 
31
- def export
32
- raise "Delta file doesn't exist when creaing patched file" unless delta_exists?
33
- tempfile = Tempfile.new('final_file')
34
- if File.exists?(full_path)
35
- LibRubyDiff.patch(full_path, delta_file.path, tempfile.path)
36
- else
37
- temp_empty_file = Tempfile.new('empty_file')
38
- LibRubyDiff.patch(temp_empty_file.path, delta_file.path, tempfile.path)
23
+ def process
24
+ if action == :create || action == :update
25
+ create_parent_directory
26
+ write_contents
27
+ elsif action == :delete
28
+ delete_file
39
29
  end
40
- tempfile.rewind
41
- File.open(full_path, 'w'){|f| f.write(tempfile.read)}
42
- tempfile.close
43
- tempfile.unlink
44
- File.utime(File.atime(full_path), @desired_modtime, full_path)
45
30
  end
46
31
 
47
32
  private
48
- def signature_exists?
49
- defined?(@signature_tempfile)
50
- end
51
33
 
52
- def signature_file
53
- return @signature_tempfile if signature_exists?
54
- @signature_tempfile = Tempfile.new('sig_file')
55
- if File.exists?(full_path)
56
- LibRubyDiff.signature(full_path, @signature_tempfile.path)
34
+ def create_parent_directory
35
+ dirname = File.dirname(full_path)
36
+ if File.exist?(dirname)
37
+ unless File.directory?(dirname)
38
+ FileUtils.rm dirname
39
+ FileUtils.mkdir_p dirname
40
+ end
57
41
  else
58
- temp_empty_file = Tempfile.new('empty_file')
59
- LibRubyDiff.signature(temp_empty_file.path, @signature_tempfile.path)
42
+ FileUtils.mkdir_p dirname
60
43
  end
61
- @signature_tempfile.rewind
62
- @signature_tempfile
63
- end
64
-
65
- def write_signature(contents)
66
- @signature_tempfile = Tempfile.new('sig_file')
67
- @signature_tempfile.write(contents)
68
- @signature_tempfile.rewind
69
- end
70
-
71
- def signature
72
- signature_file.read
73
- end
74
-
75
- def delta_exists?
76
- defined?(@delta_tempfile)
77
- end
78
-
79
- def delta_file
80
- return @delta_tempfile if delta_exists?
81
- raise "Signature file doesn't exist when creaing delta" unless signature_exists?
82
-
83
- @delta_tempfile = Tempfile.new('delta_file')
84
- LibRubyDiff.delta(full_path, signature_file.path, @delta_tempfile.path)
85
- @delta_tempfile.rewind
86
- @delta_tempfile
87
44
  end
88
45
 
89
- def write_delta(contents)
90
- @delta_tempfile = Tempfile.new('delta_file')
91
- @delta_tempfile.write(contents)
92
- @delta_tempfile.rewind
46
+ def write_contents
47
+ delete_file if file_exists? && File.directory?(full_path)
48
+ File.open(full_path, 'w') { |f| f.write(contents) }
49
+ File.utime(File.atime(full_path), desired_modtime, full_path)
93
50
  end
94
51
 
95
- def delta
96
- delta_file.read
97
- end
98
-
99
- def close_and_unlink_files
100
- if signature_exists?
101
- @signature_tempfile.close
102
- @signature_tempfile.unlink
103
- @signature_tempfile = nil
104
- end
105
-
106
- if delta_exists?
107
- @delta_tempfile.close
108
- @delta_tempfile.unlink
109
- @delta_tempfile = nil
110
- end
52
+ def delete_file
53
+ FileUtils.rm_rf(full_path) if file_exists?
111
54
  end
112
55
 
113
56
  def marshal_dump
114
- last_arg = nil
115
-
116
- if @state == 0
117
- last_arg = signature_file.read
118
- @state = 1
119
- elsif @state == 1
57
+ if file_exists? && (action == :create || action == :update)
120
58
  @desired_modtime = File.mtime(full_path).to_i
121
- last_arg = delta_file.read
122
- @state = 2
59
+ @contents = File.read(full_path)
123
60
  end
124
61
 
125
- close_and_unlink_files
126
-
127
- [@path, @state, @desired_modtime, last_arg]
62
+ [action, path, desired_modtime, contents]
128
63
  end
129
64
 
130
65
  def marshal_load(array)
131
- @path, @state, @desired_modtime, last_arg = *array
132
-
133
- if @state == 1
134
- write_signature(last_arg)
135
- elsif @state == 2
136
- write_delta(last_arg)
137
- end
66
+ @action, @path, @desired_modtime, @contents = *array
138
67
  end
139
68
  end
140
69
  end
@@ -1,106 +1,111 @@
1
+ require 'listen'
2
+ require 'entangler/entangled_file'
3
+
1
4
  module Entangler
2
5
  module Executor
3
6
  module Background
4
7
  module Base
5
8
  protected
6
- def wait_for_threads
7
- @consumer_thread.join
8
- @remote_io_thread.join
9
- @local_io_thread.join
10
- Process.wait @notify_daemon_pid
11
- end
12
9
 
13
- def kill_off_threads
14
- Process.kill("TERM", @notify_daemon_pid) rescue nil
15
- @consumer_thread.terminate
16
- @remote_io_thread.terminate
17
- @local_io_thread.terminate
10
+ def start_listener
11
+ logger.info('starting listener')
12
+ listener.start
18
13
  end
19
14
 
20
- def start_notify_daemon
21
- logger.info('starting notify daemon')
22
- r,w = IO.pipe
23
- @notify_daemon_pid = spawn(start_notify_daemon_cmd, out: w)
24
- w.close
25
- @notify_reader = r
15
+ def stop_listener
16
+ listener.stop
26
17
  end
27
18
 
28
19
  def start_remote_io
29
20
  logger.info('starting remote IO')
30
21
  @remote_io_thread = Thread.new do
31
- begin
22
+ with_kill_threads_rescue do
32
23
  loop do
33
24
  msg = Marshal.load(@remote_reader)
34
- next if msg.nil?
35
-
36
- case msg[:type]
37
- when :new_changes
38
- process_new_changes(msg[:content])
39
- when :entangled_files
40
- process_entangled_files(msg[:content])
41
- end
25
+ process_remote_changes(msg)
42
26
  end
43
- rescue => e
44
- $stderr.puts e.message
45
- $stderr.puts e.backtrace.join("\n")
46
- kill_off_threads
47
27
  end
48
28
  end
49
29
  end
50
30
 
51
- def start_local_io
52
- logger.info('starting local IO')
53
- @local_action_queue = Queue.new
54
- @local_io_thread = Thread.new do
55
- begin
56
- msg = []
57
- loop do
58
- ready = IO.select([@notify_reader]).first
59
- next unless ready && ready.any?
60
- break if ready.first.eof?
61
- line = ready.first.gets
62
- next if line.nil? || line.empty?
63
- line = line.strip
64
- next if line == '-'
65
- @local_action_queue.push line
66
- end
67
- rescue => e
68
- $stderr.puts e.message
69
- $stderr.puts e.backtrace.join("\n")
70
- kill_off_threads
31
+ def wait_for_threads
32
+ @remote_io_thread.join
33
+ end
34
+
35
+ def kill_off_threads
36
+ @remote_io_thread.terminate
37
+ end
38
+
39
+ private
40
+
41
+ def listener
42
+ @listener ||= begin
43
+ l = Listen::Listener.new(base_dir) do |modified, added, removed|
44
+ process_local_changes(generate_entangled_files(added, :create) +
45
+ generate_entangled_files(modified, :update) +
46
+ generate_entangled_files(removed, :delete))
71
47
  end
48
+ l.ignore!(@opts[:ignore])
49
+ l
72
50
  end
73
51
  end
74
52
 
75
- def start_local_consumer
76
- @consumer_thread = Thread.new do
77
- loop do
78
- msg = [@local_action_queue.pop]
79
- loop do
80
- sleep 0.2
81
- break if @local_action_queue.empty?
82
- while !@local_action_queue.empty?
83
- msg << @local_action_queue.pop
84
- end
85
- end
86
- while Time.now.to_f <= @notify_sleep
87
- sleep 0.5
88
- while !@local_action_queue.empty?
89
- msg << @local_action_queue.pop
90
- end
91
- end
92
- process_lines(msg.uniq)
93
- msg = []
94
- sleep 0.5
53
+ def generate_entangled_files(paths, action)
54
+ paths.map { |path| Entangler::EntangledFile.new(action, strip_base_path(path)) }
55
+ end
56
+
57
+ def remove_recently_changed_files(entangled_files)
58
+ @recently_received_paths.select! { |_, time| Time.now.to_f < time + 0.5 }
59
+ paths = @recently_received_paths.map(&:first)
60
+ entangled_files.reject { |ef| paths.include?(ef.path) }
61
+ end
62
+
63
+ def process_local_changes(changes)
64
+ with_listener_pause(0) do
65
+ changes = remove_recently_changed_files(changes)
66
+ if changes.any?
67
+ logger.info("PROCESSING #{changes.length} local changes")
68
+ logger.debug(changes.map(&:path).join("\n"))
69
+ send_to_remote(changes)
95
70
  end
96
71
  end
97
72
  end
98
73
 
99
- def start_notify_daemon_cmd
100
- uname = `uname`.strip.downcase
101
- raise 'Unsupported OS' unless ['darwin', 'linux'].include?(uname)
74
+ def process_remote_changes(changes)
75
+ with_listener_pause(1) do
76
+ return if changes.nil?
77
+ logger.info("PROCESSING #{changes.length} remote changes")
78
+ logger.debug(changes.map(&:path).join("\n"))
79
+ changes.each(&:process)
80
+ update_recently_received_paths(changes)
81
+ end
82
+ end
83
+
84
+ def update_recently_received_paths(changes)
85
+ changes.each do |change|
86
+ index = @recently_received_paths.index { |path, _| path == change.path }
87
+ if index.nil?
88
+ @recently_received_paths << [change.path, Time.now.to_f]
89
+ else
90
+ @recently_received_paths[index][1] = Time.now.to_f
91
+ end
92
+ end
93
+ end
94
+
95
+ def with_kill_threads_rescue
96
+ yield
97
+ rescue => e
98
+ $stderr.puts e.message
99
+ $stderr.puts e.backtrace.join("\n")
100
+ kill_off_threads
101
+ end
102
102
 
103
- "#{File.join(File.dirname(File.dirname(File.dirname(File.dirname(__FILE__)))), 'notifier', 'bin', uname, 'notify')} #{self.base_dir}"
103
+ def with_listener_pause(idx)
104
+ @listener_pauses[idx] = true
105
+ listener.pause
106
+ yield
107
+ @listener_pauses[idx] = false
108
+ listener.start if @listener_pauses.none?
104
109
  end
105
110
  end
106
111
  end