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.
@@ -3,9 +3,10 @@ module Entangler
3
3
  module Background
4
4
  module Master
5
5
  protected
6
+
6
7
  def start_remote_slave
7
8
  require 'open3'
8
- ignore_opts = @opts[:ignore].map{|regexp| "-i '#{regexp.inspect}'"}.join(' ')
9
+ ignore_opts = @opts[:ignore].map { |regexp| "-i '#{regexp.inspect}'" }.join(' ')
9
10
  entangler_cmd = "entangler slave #{@opts[:remote_base_dir]} #{ignore_opts}"
10
11
  ssh_cmd = generate_ssh_command("source ~/.rvm/environments/default && #{entangler_cmd}")
11
12
  full_cmd = @opts[:remote_mode] ? ssh_cmd : entangler_cmd
@@ -16,11 +17,15 @@ module Entangler
16
17
 
17
18
  def wait_for_threads
18
19
  super
19
- Process.wait @remote_thread[:pid] rescue nil
20
+ begin
21
+ Process.wait @remote_thread[:pid]
22
+ rescue
23
+ nil
24
+ end
20
25
  end
21
26
 
22
27
  def kill_off_threads
23
- Process.kill("INT", @remote_thread[:pid])
28
+ Process.kill('INT', @remote_thread[:pid])
24
29
  super
25
30
  end
26
31
  end
@@ -2,29 +2,28 @@ require 'logger'
2
2
  require 'fileutils'
3
3
  require 'thread'
4
4
  require_relative 'background/base'
5
- require_relative 'processing/base'
6
5
 
7
6
  module Entangler
8
7
  module Executor
9
8
  class Base
10
- include Entangler::Executor::Background::Base, Entangler::Executor::Processing::Base
9
+ include Entangler::Executor::Background::Base
11
10
 
12
11
  attr_reader :base_dir
13
12
 
14
13
  def initialize(base_dir, opts = {})
15
14
  @base_dir = File.realpath(File.expand_path(base_dir))
16
- @notify_sleep = 0
17
- @exported_at = 0
15
+ @recently_received_paths = []
16
+ @listener_pauses = [false, false]
18
17
  @opts = opts
19
- @opts[:ignore] = [/^\/\.git.*/] unless @opts.has_key?(:ignore)
20
- @opts[:ignore] << /^\/\.entangler.*/
18
+ @opts[:ignore] = [/^\.git.*/] unless @opts.key?(:ignore)
19
+ @opts[:ignore] << /^\.entangler.*/
21
20
 
22
21
  validate_opts
23
- logger.info("Starting executor")
22
+ logger.info('Starting executor')
24
23
  end
25
24
 
26
25
  def generate_abs_path(rel_path)
27
- File.join(self.base_dir, rel_path)
26
+ File.join(base_dir, rel_path)
28
27
  end
29
28
 
30
29
  def strip_base_path(path, base_dir = self.base_dir)
@@ -32,18 +31,18 @@ module Entangler
32
31
  end
33
32
 
34
33
  def run
35
- start_notify_daemon
36
- start_local_io
34
+ start_listener
37
35
  start_remote_io
38
- start_local_consumer
39
- logger.debug("NOTIFY PID: #{@notify_daemon_pid}")
40
- Signal.trap("INT") { kill_off_threads }
36
+ Signal.trap('INT') { kill_off_threads }
41
37
  wait_for_threads
38
+ ensure
39
+ stop_listener
42
40
  end
43
41
 
44
42
  protected
43
+
45
44
  def validate_opts
46
- raise "Base directory doesn't exist" unless Dir.exists?(self.base_dir)
45
+ raise "Base directory doesn't exist" unless Dir.exist?(base_dir)
47
46
  end
48
47
 
49
48
  def send_to_remote(msg = {})
@@ -51,8 +50,12 @@ module Entangler
51
50
  end
52
51
 
53
52
  def logger
54
- FileUtils::mkdir_p log_dir
55
- @logger ||= Logger.new(File.join(log_dir, 'entangler.log'))
53
+ FileUtils.mkdir_p log_dir
54
+ @logger ||= begin
55
+ l = Logger.new(File.join(log_dir, 'entangler.log'))
56
+ l.level = @opts[:verbose] ? Logger::DEBUG : Logger::INFO
57
+ l
58
+ end
56
59
  end
57
60
 
58
61
  def log_dir
@@ -15,49 +15,99 @@ module Entangler
15
15
  end
16
16
 
17
17
  private
18
+
18
19
  def validate_opts
19
20
  super
20
21
  if @opts[:remote_mode]
21
- raise 'Missing remote base dir' unless @opts.keys.include?(:remote_base_dir)
22
- raise 'Missing remote user' unless @opts.keys.include?(:remote_user)
23
- raise 'Missing remote host' unless @opts.keys.include?(:remote_host)
24
22
  @opts[:remote_port] ||= '22'
25
- res = `#{generate_ssh_command("[[ -d '#{@opts[:remote_base_dir]}' ]] && echo 'ok' || echo 'missing'")}`
26
- raise 'Cannot connect to remote' if res.empty?
27
- raise 'Remote base dir invalid' unless res.strip == 'ok'
23
+ validate_remote_opts
28
24
  else
29
- @opts[:remote_base_dir] = File.realpath(File.expand_path(@opts[:remote_base_dir]))
30
- raise "Destination directory can't be the same as the base directory" if @opts[:remote_base_dir] == self.base_dir
31
- raise "Destination directory doesn't exist" unless Dir.exists?(@opts[:remote_base_dir])
25
+ validate_local_opts
32
26
  end
33
27
  end
34
28
 
35
- def perform_initial_rsync
36
- logger.info 'Running initial sync'
37
- local_folders = `find #{base_dir} -type d`.split("\n").tap{|a| a.shift(1) }.map{|path| path.sub(base_dir, '') }
29
+ def validate_local_opts
30
+ @opts[:remote_base_dir] = File.realpath(File.expand_path(@opts[:remote_base_dir]))
31
+ raise "Destination directory can't be the same as the base directory" if @opts[:remote_base_dir] == base_dir
32
+ raise "Destination directory doesn't exist" unless Dir.exist?(@opts[:remote_base_dir])
33
+ end
38
34
 
39
- remote_find_cmd = "find #{@opts[:remote_base_dir]} -type d"
40
- raw_remote_folders = `#{@opts[:remote_mode] ? generate_ssh_command(remote_find_cmd) : remote_find_cmd}`
41
- remote_folders = raw_remote_folders.split("\n").tap{|a| a.shift(1) }.map{|path| path.sub(@opts[:remote_base_dir], '') }
35
+ def validate_remote_opts
36
+ keys = @opts.keys
37
+ raise 'Missing remote base dir' unless keys.include?(:remote_base_dir)
38
+ raise 'Missing remote user' unless keys.include?(:remote_user)
39
+ raise 'Missing remote host' unless keys.include?(:remote_host)
40
+ validate_remote_base_dir
41
+ validate_remote_entangler_version
42
+ end
42
43
 
43
- all_folders = remote_folders | local_folders
44
- ignore_matches = all_folders.map{|path| @opts[:ignore].map{|regexp| (regexp.match(path) || [])[0]}.compact.first}.compact.uniq
45
- exclude_folders = ignore_matches.map{|path| path[1..-1]}
46
- exclude_args = exclude_folders.map{|path| "--exclude #{path}"}.join(' ')
44
+ def validate_remote_base_dir
45
+ res = `#{generate_ssh_command("[[ -d '#{@opts[:remote_base_dir]}' ]] && echo 'ok' || echo 'missing'")}`
46
+ raise 'Cannot connect to remote' if res.empty?
47
+ raise 'Remote base dir invalid' unless res.strip == 'ok'
48
+ end
47
49
 
48
- ssh_settings = @opts[:remote_mode] ? "-e \"ssh -p #{@opts[:remote_port]}\"" : ''
49
- remote_path = @opts[:remote_mode] ? "#{@opts[:remote_user]}@#{@opts[:remote_host]}:#{@opts[:remote_base_dir]}/" : "#{@opts[:remote_base_dir]}/"
50
+ def validate_remote_entangler_version
51
+ return unless @opts[:remote_mode]
52
+ res = `#{generate_ssh_command('source ~/.rvm/environments/default && entangler --version')}`
53
+ remote_version = Gem::Version.new(res.strip)
54
+ local_version = Gem::Version.new(Entangler::VERSION)
55
+ return unless major_version_mismatch?(local_version, remote_version)
56
+ raise 'Entangler version too far apart, please update either local or remote Entangler.' \
57
+ " Local version is #{local_version} and remote version is #{remote_version}."
58
+ end
50
59
 
51
- rsync_cmd = "rsync -azv #{exclude_args} #{ssh_settings} --delete #{base_dir}/ #{remote_path}"
60
+ def major_version_mismatch?(version1, version2)
61
+ version1.segments[0] != version2.segments[0] ||
62
+ (version1.segments[0].zero? && version1 != version2) ||
63
+ ((version1.prerelease? || version2.prerelease?) && version1 != version2)
64
+ end
52
65
 
53
- IO.popen(rsync_cmd).each do |line|
66
+ def perform_initial_rsync
67
+ logger.info 'Running initial sync'
68
+ IO.popen(rsync_cmd_string).each do |line|
54
69
  logger.debug line.chomp
55
70
  end
56
71
  logger.debug 'Initial sync complete'
57
72
  end
58
73
 
74
+ def find_all_folders
75
+ local_folders = process_raw_file_list(`find #{base_dir} -type d`, base_dir)
76
+
77
+ remote_find_cmd = "find #{@opts[:remote_base_dir]} -type d"
78
+ raw_remote_folders = `#{@opts[:remote_mode] ? generate_ssh_command(remote_find_cmd) : remote_find_cmd}`
79
+ remote_folders = process_raw_file_list(raw_remote_folders, @opts[:remote_base_dir])
80
+
81
+ remote_folders | local_folders
82
+ end
83
+
84
+ def process_raw_file_list(output, base)
85
+ output.split("\n").tap { |a| a.shift(1) }
86
+ .map { |path| path.sub("#{base}/", '') }
87
+ end
88
+
89
+ def find_rsync_ignore_folders
90
+ find_all_folders.map do |path|
91
+ @opts[:ignore].map { |regexp| (regexp.match(path) || [])[0] }.compact.first
92
+ end.compact.uniq
93
+ end
94
+
59
95
  def generate_ssh_command(cmd)
60
- "ssh -q #{@opts[:remote_user]}@#{@opts[:remote_host]} -p #{@opts[:remote_port]} -C \"#{cmd}\""
96
+ "ssh -q #{remote_hostname} -p #{@opts[:remote_port]} -C \"#{cmd}\""
97
+ end
98
+
99
+ def remote_hostname
100
+ "#{@opts[:remote_user]}@#{@opts[:remote_host]}"
101
+ end
102
+
103
+ def rsync_cmd_string
104
+ exclude_args = find_rsync_ignore_folders.map { |path| "--exclude #{path}" }.join(' ')
105
+ remote_path = @opts[:remote_mode] ? "#{@opts[:remote_user]}@#{@opts[:remote_host]}:" : ''
106
+ remote_path += "#{@opts[:remote_base_dir]}/"
107
+
108
+ cmd = "rsync -azv #{exclude_args}"
109
+ cmd += " -e \"ssh -p #{@opts[:remote_port]}\"" if @opts[:remote_mode]
110
+ cmd + " --delete #{base_dir}/ #{remote_path}"
61
111
  end
62
112
  end
63
113
  end
@@ -10,7 +10,7 @@ module Entangler
10
10
 
11
11
  @remote_reader = STDIN
12
12
  @remote_writer = STDOUT
13
- $stderr.reopen(File.join(log_dir, 'entangler.err'), "w")
13
+ $stderr.reopen(File.join(log_dir, 'entangler.err'), 'w')
14
14
  end
15
15
  end
16
16
  end
@@ -1,3 +1,3 @@
1
1
  module Entangler
2
- VERSION = "0.4.1"
2
+ VERSION = '1.0.0.beta1'.freeze
3
3
  end
data/lib/entangler.rb CHANGED
@@ -6,7 +6,7 @@ module Entangler
6
6
  attr_accessor :executor
7
7
 
8
8
  def run(base_dir, opts = {})
9
- opts = {mode: 'master', remote_mode: true}.merge(opts)
9
+ opts = { mode: 'master', remote_mode: true }.merge(opts)
10
10
 
11
11
  require 'entangler/executor/base'
12
12
  if opts[:mode] == 'master'
@@ -17,7 +17,7 @@ module Entangler
17
17
  self.executor = Entangler::Executor::Slave.new(base_dir, opts)
18
18
  end
19
19
 
20
- self.executor.run
20
+ executor.run
21
21
  end
22
22
  end
23
23
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: entangler
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.1
4
+ version: 1.0.0.beta1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dave Allie
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2016-12-18 00:00:00.000000000 Z
11
+ date: 2016-12-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -53,19 +53,33 @@ dependencies:
53
53
  - !ruby/object:Gem::Version
54
54
  version: '3.0'
55
55
  - !ruby/object:Gem::Dependency
56
- name: lib_ruby_diff
56
+ name: rubocop
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
59
  - - "~>"
60
60
  - !ruby/object:Gem::Version
61
- version: '0.1'
61
+ version: '0.46'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '0.46'
69
+ - !ruby/object:Gem::Dependency
70
+ name: listen
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '3.1'
62
76
  type: :runtime
63
77
  prerelease: false
64
78
  version_requirements: !ruby/object:Gem::Requirement
65
79
  requirements:
66
80
  - - "~>"
67
81
  - !ruby/object:Gem::Version
68
- version: '0.1'
82
+ version: '3.1'
69
83
  - !ruby/object:Gem::Dependency
70
84
  name: to_regexp
71
85
  requirement: !ruby/object:Gem::Requirement
@@ -80,7 +94,7 @@ dependencies:
80
94
  - - "~>"
81
95
  - !ruby/object:Gem::Version
82
96
  version: '0.2'
83
- description: Two way file syncer using platform native notify and rdiff syncing.
97
+ description: Two way file syncer using platform native notify.
84
98
  email:
85
99
  - dave@daveallie.com
86
100
  executables:
@@ -90,6 +104,7 @@ extra_rdoc_files: []
90
104
  files:
91
105
  - ".gitignore"
92
106
  - ".rspec"
107
+ - ".rubocop.yml"
93
108
  - ".travis.yml"
94
109
  - CODE_OF_CONDUCT.md
95
110
  - Gemfile
@@ -106,23 +121,8 @@ files:
106
121
  - lib/entangler/executor/background/master.rb
107
122
  - lib/entangler/executor/base.rb
108
123
  - lib/entangler/executor/master.rb
109
- - lib/entangler/executor/processing/base.rb
110
124
  - lib/entangler/executor/slave.rb
111
125
  - lib/entangler/version.rb
112
- - lib/notifier/README.md
113
- - lib/notifier/bin/darwin/notify
114
- - lib/notifier/bin/linux/notify
115
- - lib/notifier/src/darwin/BUILD
116
- - lib/notifier/src/darwin/README
117
- - lib/notifier/src/darwin/notify.c
118
- - lib/notifier/src/linux/BUILD
119
- - lib/notifier/src/linux/README
120
- - lib/notifier/src/linux/notify.c
121
- - lib/notifier/src/linux/old/BUILD
122
- - lib/notifier/src/linux/old/README
123
- - lib/notifier/src/linux/old/kernel-filesystem-monitor-daemon-cat.cpp
124
- - lib/notifier/src/linux/old/kernel-filesystem-monitor-daemon.cpp
125
- - lib/notifier/src/linux/old/kernel-filesystem-monitor-daemon.hh
126
126
  homepage: https://github.com/daveallie/entangler
127
127
  licenses:
128
128
  - MIT
@@ -138,13 +138,13 @@ required_ruby_version: !ruby/object:Gem::Requirement
138
138
  version: '0'
139
139
  required_rubygems_version: !ruby/object:Gem::Requirement
140
140
  requirements:
141
- - - ">="
141
+ - - ">"
142
142
  - !ruby/object:Gem::Version
143
- version: '0'
143
+ version: 1.3.1
144
144
  requirements: []
145
145
  rubyforge_project:
146
- rubygems_version: 2.5.1
146
+ rubygems_version: 2.4.8
147
147
  signing_key:
148
148
  specification_version: 4
149
- summary: Two way file syncer using platform native notify and rdiff syncing.
149
+ summary: Two way file syncer using platform native notify.
150
150
  test_files: []
@@ -1,122 +0,0 @@
1
- module Entangler
2
- module Executor
3
- module Processing
4
- module Base
5
- protected
6
- def generate_file_list(path)
7
- dirs = []
8
- files = {}
9
-
10
- Dir.entries(path).each do |f|
11
- next if ['.', '..'].include? f
12
- f_path = File.join(path, f)
13
- if File.directory? f_path
14
- dirs << f
15
- else
16
- files[f] = [File.size(f_path), File.mtime(f_path).to_i]
17
- end
18
- end
19
-
20
- {dirs: dirs, files: files}
21
- end
22
-
23
- def process_new_changes(content)
24
- logger.debug("RECIEVING #{content.length} folder/s from remote:\n#{content.map{|c| "#{c[0][1..-1]}/"}.join("\n")}")
25
-
26
- created_dirs = []
27
- dirs_to_remove = []
28
- files_to_remove = []
29
- files_to_update = []
30
-
31
- content.each do |base, changes|
32
- possible_creation_dirs = changes[:dirs].clone
33
- possible_creation_files = changes[:files].keys.clone
34
- full_base_path = generate_abs_path(base)
35
-
36
- unless File.directory?(full_base_path)
37
- FileUtils::mkdir_p(full_base_path)
38
- @notify_sleep = Time.now.to_i + 60
39
- end
40
-
41
- Dir.entries(full_base_path).each do |f|
42
- next if ['.', '..'].include? f
43
- full_path = File.join(generate_abs_path(base), f)
44
- if File.directory?(full_path)
45
- possible_creation_dirs -= [f]
46
- dirs_to_remove << full_path unless changes[:dirs].include?(f)
47
- elsif changes[:files].has_key?(f)
48
- possible_creation_files -= [f]
49
- files_to_update << File.join(base, f) unless changes[:files][f] == [File.size(full_path), File.mtime(full_path).to_i]
50
- else
51
- files_to_remove << full_path
52
- end
53
- end
54
-
55
- dirs_to_create = possible_creation_dirs.map{|d| File.join(generate_abs_path(base), d)}
56
- if dirs_to_create.any?
57
- logger.debug("Creating #{dirs_to_create.length} dirs")
58
- @notify_sleep = Time.now.to_i + 60
59
- FileUtils.mkdir_p dirs_to_create
60
- end
61
- created_dirs += dirs_to_create
62
- files_to_update += possible_creation_files.map{|f| File.join(base, f)}
63
- end
64
-
65
- @notify_sleep = Time.now.to_i + 60 if (files_to_remove + created_dirs + dirs_to_remove + files_to_update).any?
66
-
67
- if files_to_remove.any?
68
- logger.debug("DELETING #{files_to_remove.length} files")
69
- FileUtils.rm files_to_remove
70
- end
71
- if dirs_to_remove.any?
72
- logger.debug("DELETING #{dirs_to_remove.length} dirs")
73
- FileUtils.rm_r dirs_to_remove
74
- end
75
- if files_to_update.any?
76
- logger.debug("CREATING #{files_to_update.length} new entangled file/s")
77
- send_to_remote(type: :entangled_files, content: files_to_update.map{|f| Entangler::EntangledFile.new(f) })
78
- end
79
- @notify_sleep = Time.now.to_f + 0.5 if (files_to_remove + created_dirs + dirs_to_remove + files_to_update).any?
80
- @notify_sleep += 60 if files_to_update.any?
81
- end
82
-
83
- def process_entangled_files(content)
84
- logger.debug("UPDATING #{content.length} entangled file/s from remote")
85
- completed_files, updated_files = content.partition(&:done?)
86
-
87
- if completed_files.any?
88
- @exported_at = Time.now.to_f
89
- @exported_folders = completed_files.map{|ef| "#{File.dirname(generate_abs_path(ef.path))}/" }.uniq
90
- completed_files.each(&:export)
91
- end
92
-
93
- updated_files = updated_files.find_all{|f| f.state != 1 || f.file_exists? }
94
- if updated_files.any?
95
- send_to_remote(type: :entangled_files, content: updated_files)
96
- end
97
- @notify_sleep = Time.now.to_f + 0.5 if completed_files.any?
98
- end
99
-
100
- def process_lines(lines)
101
- paths = lines.map{|line| line[2..-1] }
102
-
103
- if @exported_at < Time.now.to_f && Time.now.to_f < @exported_at + 2
104
- paths -= @exported_folders
105
- end
106
-
107
- to_process = paths.map do |path|
108
- stripped_path = strip_base_path(path)
109
- next unless @opts[:ignore].nil? || @opts[:ignore].none?{|i| stripped_path.match(i) }
110
- next unless File.directory?(path)
111
-
112
- [stripped_path, generate_file_list(path)]
113
- end.compact.sort_by(&:first)
114
-
115
- return unless to_process.any?
116
- logger.debug("PROCESSING #{to_process.count} folder/s:\n#{to_process.map{|c| "#{c[0][1..-1]}/"}.join("\n")}")
117
- send_to_remote(type: :new_changes, content: to_process)
118
- end
119
- end
120
- end
121
- end
122
- end
@@ -1,3 +0,0 @@
1
- The code in the following subdirectories comes from https://github.com/DmitryKoterov/dklab_realsync, unmodified.
2
-
3
- It is being redistrubuted under GPL, which it was originally provided under. For more information on GPL, please see https://www.gnu.org/licenses/gpl-3.0.en.html.
Binary file
Binary file
@@ -1,7 +0,0 @@
1
- To compile notify.c, run
2
-
3
- clang -framework CoreFoundation -framework CoreServices -o notify notify.c
4
-
5
- or (if you don't have clang)
6
-
7
- gcc -framework CoreFoundation -framework CoreServices -o notify notify.c
@@ -1,12 +0,0 @@
1
- The notify utility continiously prints changes in a specified directory in the following format:
2
-
3
- M changed/path/1
4
- M changed/path/2
5
- -
6
-
7
-
8
- Where M stands for "Modifed", and "-" is the indicator of end of changeset
9
-
10
- Utility only returns specific directories that have changed, it does not print
11
- the files/directories that have been modified: you need to determine them
12
- yourself
@@ -1,63 +0,0 @@
1
- #include <CoreServices/CoreServices.h>
2
- #include <CoreFoundation/CoreFoundation.h>
3
- #include <sys/stat.h>
4
-
5
- static void printChangesFunc(ConstFSEventStreamRef streamRef, void *clientCallBackInfo, size_t numEvents, void *eventPaths, const FSEventStreamEventFlags eventFlags[], const FSEventStreamEventId eventIds[]) {
6
- char **paths = eventPaths;
7
- int i;
8
- for (i = 0; i < numEvents; i++) {
9
- printf("M %s\n", paths[i]);
10
- }
11
- printf("-\n");
12
- fflush(stdout);
13
- }
14
-
15
- void initFSEvents(const char *path) {
16
-
17
- /* Define variables and create a CFArray object containing
18
- CFString objects containing paths to watch.
19
- */
20
-
21
- CFStringRef mypath = CFStringCreateWithCString(NULL, path, kCFStringEncodingUTF8);
22
- CFArrayRef pathsToWatch = CFArrayCreate(NULL, (const void **)&mypath, 1, NULL);
23
- void *callbackInfo = NULL; // could put stream-specific data here.
24
- FSEventStreamRef stream;
25
- CFAbsoluteTime latency = 0.1; /* Latency in seconds */
26
-
27
- /* Create the stream, passing in a callback */
28
- stream = FSEventStreamCreate(NULL,
29
- &printChangesFunc,
30
- callbackInfo,
31
- pathsToWatch,
32
- kFSEventStreamEventIdSinceNow, /* Or a previous event ID */
33
- latency,
34
- kFSEventStreamCreateFlagNone /* Flags explained in reference */
35
- );
36
-
37
- CFRelease(pathsToWatch);
38
- CFRelease(mypath);
39
-
40
- /* Create the stream before calling this. */
41
- FSEventStreamScheduleWithRunLoop(stream, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
42
- FSEventStreamStart(stream);
43
- }
44
-
45
- int main (int argc, const char * argv[]) {
46
-
47
- if(argc != 2) {
48
- printf("Usage: %s <path>\n", argv[0]);
49
- return 1;
50
- }
51
-
52
- struct stat tmp;
53
-
54
- if(stat(argv[1], &tmp) != 0) {
55
- perror("Invalid path");
56
- return 2;
57
- }
58
-
59
- initFSEvents(argv[1]);
60
- CFRunLoopRun();
61
-
62
- return 0;
63
- }
@@ -1,4 +0,0 @@
1
- notify.c was built using dietlibc to be as small as possible (using "bin-i386/diet gcc -static -o notify notify.c").
2
- If you want to compile it without dietlibc, you can just do
3
-
4
- gcc -o notify notify.c
@@ -1,18 +0,0 @@
1
- The notify utility continiously prints changes in a specified directory in the following format:
2
-
3
- M changed/path/1
4
- M changed/path/2
5
- -
6
-
7
- Where M stands for "Modifed", and "-" is the indicator of end of changeset
8
-
9
- Utility only returns specific directories that have changed, it does not print
10
- the files/directories that have been modified: you need to determine them
11
- yourself.
12
-
13
- Linux-specific notes:
14
-
15
- 1. You might need to adjust '/proc/sys/fs/inotify/max_user_watches' to allow more directories to be watched
16
- 2. If you have a lot of changes you might also want to increase queue size in /proc/sys/fs/inotify/max_queued_events
17
- 3. Watched queue can overflow and notify utility can run out of watched directories.
18
- When you get exit code 3, you need to restart the daemon and re-process all directories you are interested in.