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