entangler 0.4.1 → 1.0.0.beta1

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