em-dir-watcher 0.1.0 → 0.9.1

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE CHANGED
@@ -1,4 +1,5 @@
1
- Copyright (c) 2009 Andrey Tarantsov
1
+ Copyright (c) 2010 Andrey Tarantsov
2
+ Copyright (c) 2010 Mikhail Gusarov
2
3
 
3
4
  Permission is hereby granted, free of charge, to any person obtaining
4
5
  a copy of this software and associated documentation files (the
data/README.md CHANGED
@@ -1,7 +1,46 @@
1
- em-dir-watcher: real directory monitoring for EventMachine
2
- ==========================================================
1
+ em-dir-watcher: sane cross-platform file system change monitoring for Event Machine
2
+ ===================================================================================
3
+
4
+ Compatible with Ruby 1.8+
5
+
6
+ Supported platforms:
7
+
8
+ * Mac OS X: employs FSEvents API via Ruby Cocoa. To support Ruby 1.8, forks a separate watcher process and communicates with it via pipes. (Ruby 1.9 should use threads instead, but this hasn't been implemented yet.)
9
+
10
+ * Linux: employs inotify via rb-inotify gem. This backend is nicest of all because inotify uses file descriptors and thus can be monitored from within Event Machine reactor.
11
+
12
+ * Windows: employs Directory Change Notifications API via win32-changenotify. To support Ruby 1.8, forks a separate watcher process and communicates with it via sockets. (We'd love to communicate via pipes, but couldn't figure a way to do it in a Event Machine-friendly way. Also we'd love to use threads on Ruby 1.9, but not there yet.)
13
+
14
+ * There is no fallback polling backend at the moment. Please contribute it if you need one.
15
+
16
+
17
+ Why not FSSM or Directory Watcher?
18
+ --------------------------------------
19
+
20
+ FSSM rescans the entire directory tree on each change, so it has about 0.5 sec lag on average-sized projects. Em-dir-watcher only rescans the changed subdirectories (on all systems), and avoids rescanning subtrees on the systems that support non-subtree notifications (Mac, Linux). We'd love to see our `Tree` class used in fssm — this should be an easy change.
21
+
22
+ Also, fssm does not know anything about Event Machine, so has to be run in a separate process/thread even on the systems that are reactor-friendly (i.e. Linux). Em-dir-watcher uses `EM.watch` to listen to inotify events on Linux.
23
+
24
+ DirectoryWatcher's Event Machine edition uses `EM.watchFile`, which runs out of max open file limit pretty quickly. Also it employs polling to catch added or removed files, and has to walk the entire directory tree every time. Em-dir-watcher uses native backends to watch for file system changes (just like FSSM), and finds added or removed files just as quickly as modified ones.
25
+
26
+ Besides, both fssm and directory_watcher do not support exclusions, and thus will walk, update and keep the entire tree in memory including the subfolders you don't need. Em-dir-watcher never walks excluded subfolders, so you can exclude the stuff you don't need to watch to further improve the performance.
27
+
28
+
29
+ Installation
30
+ ------------
31
+
32
+ Mac:
33
+
34
+ sudo gem install em-dir-watcher
35
+
36
+ Linux:
37
+
38
+ sudo gem install rb-inotify em-dir-watcher
39
+
40
+ Windows:
41
+
42
+ gem install win32-changenotify em-dir-watcher
3
43
 
4
- Employs FSEvents, inotify or Win32 Directory Change Notifications APIs under EventMachine. (Forks a subprocess for blocking watchers.)
5
44
 
6
45
  Usage
7
46
  -----
@@ -10,11 +49,13 @@ Usage
10
49
  require 'em-dir-watcher'
11
50
 
12
51
  EM.run {
13
- dw = EMDirWatcher.watch '.', ['**/*.css', 'lib/**/*.rb'] do |path|
14
- if File.exists? path
15
- puts "Modified: #{path}"
16
- else
17
- puts "Deleted: #{path}"
52
+ dw = EMDirWatcher.watch '.' do |paths|
53
+ paths.each do |path|
54
+ if File.exists? path
55
+ puts "Modified: #{path}"
56
+ else
57
+ puts "Deleted: #{path}"
58
+ end
18
59
  end
19
60
  end
20
61
  puts "EventMachine running..."
@@ -22,7 +63,83 @@ Usage
22
63
 
23
64
  Run `examples/monitor.rb` to see it in action.
24
65
 
66
+
67
+ EMDirWatcher.watch
68
+ ------------------
69
+
70
+ `EMDirWatcher.watch` accepts a path and an options hash:
71
+
72
+ EMDirWatcher.watch File.expand_path('~/my_project'),
73
+ :include_only => ['*.html', '*.css', '*.js'],
74
+ :exclude => ['~*', 'vendor/plugins'],
75
+ :grace_period => 0.2
76
+
77
+ It returns an object that has a single `stop` method:
78
+
79
+ dw = EMDirWatcher.watch File.expand_path('~/my_project')
80
+ ...
81
+ dw.stop
82
+
83
+
84
+ Inclusions and exclusions
85
+ -------------------------
86
+
87
+ If the list of inclusions is `nil` (the default), all files are included. Otherwise it has to be an array, and specifies a list of patterns to monitor. Each pattern is either a name glob, a path glob or a path regexp (more on this later).
88
+
89
+ The list of exclusions defaults to an empty array, and specifies the list of patterns to exclude. Each pattern is either a name glob, a path glob or a path regexp. If a path matches both an inclusion and an exclusion filter, it is excluded.
90
+
91
+ Patterns are inspired by Git's `.gitignore` conventions. Each pattern can be one of the following:
92
+
93
+ * A string that does not contain a slash is treated as a shell-style glob and is matched against file **base names.** For example, you can use `*.html` to match HTML files in any directory, or `~*` to match temporary files in any directory.
94
+
95
+ * A string that contains a slash is treated as a shell-style glob and is matched against file **paths** (relative to the monitored directory). For example, you can use `lib/rake` to match all files in `lib/rake` directory, or `/vendor` to match a `vendor` directory.
96
+
97
+ * A regexp is matched against file relative paths. For example, you can use `/[A-Z]/` to match all files and directories that contain upper-case characters in their name.
98
+
99
+
100
+ Grace period
101
+ ------------
102
+
103
+ Use `:grace_period => 1.0` to combine the changes together if they occur in quick succession. This, for example, may be used to avoid starting a build while some files are still being updated from a repository.
104
+
105
+ The default grace period is `0`, which means that changes are reported immediately when they occur.
106
+
107
+
108
+ Hacking
109
+ -------
110
+
111
+ Required software:
112
+
113
+ sudo gem install jeweler shoulda
114
+
115
+ To run the tests, use:
116
+
117
+ rake test
118
+
119
+ This is expected to work on all platforms and shouldn't give any failures, EXCEPT that on a Mac spurious test failures occur in about 1–3 of 20 test runs, and we are unable to get rid of them.
120
+
121
+ You can use `./testloop` script to run the tests multiple times in a row to check for unreliable behaviors.
122
+
123
+ To give a more context on Mac test failures, two constants that have an effect on them are `STARTUP_DELAY` in `lib/em-dir-watcher/platform/mac.rb` and `UNIT_DELAY` in `tests/test_monitor.rb`. We've settled on a sweet spot of `0.5`/`0.5`, which gives a 5%–15% failure rate. Increasing them to `1.0`/`1.0` still results in the same failure rate. Decreasing them to `0.2`/`0.2` results in 30% failed test runs.
124
+
125
+ You can use `rake rcov` to check code coverage info. Currently `tree.rb` and `monitor.rb` have 100% test coverage, `mac.rb` has 80% coverage, `linux.rb` should have about 100% coverage and `windows.rb` should have about 80% coverage (the last two were not measured).
126
+
127
+
128
+ Help Wanted aka TODO
129
+ --------------------
130
+
131
+ If you find yourself using this gem, consider implementing one of the following functions:
132
+
133
+ * Ruby 1.9-friendly thread-based Mac backend
134
+ * Ruby 1.9-friendly thread-based Windows backend
135
+ * FFI-based Mac backend — there is no need to employ Ruby Cocoa monster just to invoke a handful of functions; a nearly-working version is in `lib/em-dir-watcher/platform/mac/ffi_fsevents_watcher.rb`, just needs some final touches
136
+ * polling-based fallback backend for the folks on funny operating systems (from FreeBSD to Solaris to HP UX :)
137
+ * use Mac OS X's FSEvents native grace period implementation
138
+
139
+
25
140
  License
26
141
  -------
27
142
 
28
- Copyright (c) 2010 Andrey Tarantsov. Distributed under the MIT license. See LICENSE for details.
143
+ Copyright © 2010 Andrey Tarantsov, Mikhail Gusarov.
144
+
145
+ Distributed under the MIT license. See LICENSE for details.
data/Rakefile CHANGED
@@ -9,7 +9,7 @@ begin
9
9
  gem.description = gem.summary
10
10
  gem.email = "andreyvit@gmail.com"
11
11
  gem.homepage = "http://github.com/mockko/em-dir-watcher"
12
- gem.authors = ["Andrey Tarantsov"]
12
+ gem.authors = ["Andrey Tarantsov", "Mikhail Gusarov"]
13
13
  # gem.add_development_dependency "thoughtbot-shoulda", ">= 0"
14
14
  # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
15
15
  end
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.0
1
+ 0.9.1
data/examples/monitor.rb CHANGED
@@ -4,11 +4,19 @@ require 'rubygems'
4
4
  require 'em-dir-watcher'
5
5
 
6
6
  dir = (ARGV.empty? ? '.' : ARGV.shift)
7
- globs = (ARGV.empty? ? ['**/*'] : ARGV)
7
+ inclusions = ARGV.reject { |arg| arg =~ /^!/ }
8
+ inclusions = nil if inclusions == []
9
+ exclusions = ARGV.select { |arg| arg =~ /^!/ }.collect { |arg| arg[1..-1] }
10
+
11
+ EM.error_handler{ |e|
12
+ puts "Error raised during event loop: #{e.class.name} #{e.message}"
13
+ puts e.backtrace
14
+ }
8
15
 
9
16
  EM.run {
10
- dw = EMDirWatcher.watch dir, globs do |path|
11
- if File.exists? path
17
+ dw = EMDirWatcher.watch dir, inclusions, exclusions do |path|
18
+ full_path = File.join(dir, path)
19
+ if File.exists? full_path
12
20
  puts "Modified: #{path}"
13
21
  else
14
22
  puts "Deleted: #{path}"
@@ -0,0 +1,100 @@
1
+
2
+ require 'io/nonblock'
3
+
4
+ module EMDirWatcher
5
+ module Invokers
6
+
7
+ class SubprocessInvoker
8
+
9
+ attr_reader :active
10
+ attr_reader :input_handler
11
+ attr_accessor :additional_delay
12
+
13
+ def initialize subprocess, &input_handler
14
+ @subprocess = subprocess
15
+ @input_handler = input_handler
16
+ @active = true
17
+ @ready_to_use = false
18
+ @ready_to_use_handlers = []
19
+ @additional_delay = 0.1
20
+ start_subprocess
21
+ end
22
+
23
+ def when_ready_to_use &ready_to_use_handler
24
+ if @ready_to_use_handlers.nil?
25
+ ready_to_use_handler.call()
26
+ else
27
+ @ready_to_use_handlers << ready_to_use_handler
28
+ end
29
+ end
30
+
31
+ def stop
32
+ @active = false
33
+ kill
34
+ end
35
+
36
+ # private methods
37
+
38
+ def ready_to_use!
39
+ return if @ready_to_use
40
+ @ready_to_use = true
41
+ EM.add_timer @additional_delay do
42
+ @ready_to_use_handlers.each { |handler| handler.call() }
43
+ @ready_to_use_handlers = nil
44
+ end
45
+ end
46
+
47
+ def kill
48
+ if @io
49
+ Process.kill 'TERM', @io.pid
50
+ Process.waitpid @io.pid
51
+ @io = nil
52
+ end
53
+ end
54
+
55
+ def start_subprocess
56
+ io = open('|-', 'r')
57
+ if io.nil?
58
+ $stdout.sync = true
59
+ ready = lambda { puts }
60
+ output = lambda { |single_line_string| puts single_line_string.strip }
61
+ @subprocess.call ready, output
62
+ exit
63
+ end
64
+ @io = io
65
+ @io.nonblock = true
66
+
67
+ @connection = EM.watch io do |conn|
68
+ class << conn
69
+ attr_accessor :invoker
70
+
71
+ def notify_readable
72
+ @invoker.ready_to_use!
73
+ @data_received ||= ""
74
+ @data_received << @io.read
75
+ while line = @data_received.slice!(/^[^\n]*[\n]/m)
76
+ @invoker.input_handler.call line.strip
77
+ end
78
+ rescue EOFError
79
+ detach
80
+ @invoker.kill # waitpid to cleanup zombie
81
+ if @invoker.active
82
+ EM.next_tick do
83
+ @invoker.start_subprocess
84
+ end
85
+ end
86
+ end
87
+
88
+ def unbind
89
+ @invoker.kill
90
+ end
91
+ end
92
+ conn.invoker = self
93
+ conn.notify_readable = true
94
+ end
95
+ end
96
+
97
+ end
98
+
99
+ end
100
+ end
@@ -0,0 +1,47 @@
1
+ require 'set'
2
+
3
+ module EMDirWatcher
4
+ Watcher = Platform.const_get(PLATFORM)::Watcher
5
+
6
+ DEFAULT_OPTIONS = {
7
+ :exclude => [],
8
+ :include_only => nil,
9
+ :grace_period => 0.0,
10
+ }.freeze
11
+
12
+ INFINITELY_SMALL_PERIOD = 0.001
13
+
14
+ def self.watch path, options={}
15
+ unless (invalid_keys = options.keys - DEFAULT_OPTIONS.keys).empty?
16
+ raise StandardError, "Unsupported options given to EMDirWatcher.watch: " + invalid_keys.join(", ")
17
+ end
18
+ options = DEFAULT_OPTIONS.merge(options)
19
+ grace_period = options[:grace_period]
20
+
21
+ tree = Tree.new path, options[:include_only], options[:exclude]
22
+
23
+ pending_refresh_requests = Set.new
24
+ process_pending_refresh_requests_scheduled = false
25
+
26
+ process_pending_refresh_requests = lambda do
27
+ process_pending_refresh_requests_scheduled = false
28
+ changed_paths = Set.new
29
+ pending_refresh_requests.each do |change_scope, refresh_subtree|
30
+ changed_paths += tree.refresh! change_scope, refresh_subtree
31
+ end
32
+ yield changed_paths.to_a unless changed_paths.empty?
33
+ end
34
+
35
+ Watcher.new path, options[:include_only], options[:exclude] do |change_scope, refresh_subtree|
36
+ pending_refresh_requests << [change_scope, refresh_subtree]
37
+ if grace_period <= INFINITELY_SMALL_PERIOD
38
+ process_pending_refresh_requests.call
39
+ else
40
+ unless process_pending_refresh_requests_scheduled
41
+ EM.add_timer grace_period, &process_pending_refresh_requests
42
+ process_pending_refresh_requests_scheduled = true
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,51 @@
1
+ require 'rb-inotify'
2
+ require 'io/nonblock'
3
+
4
+ module EMDirWatcher
5
+ module Platform
6
+ module Linux
7
+
8
+ module Native
9
+ extend FFI::Library
10
+ ffi_lib "c"
11
+ attach_function :close, [:int], :int
12
+ end
13
+
14
+ class Watcher
15
+
16
+ def initialize path, inclusions, exclusions
17
+ @notifier = INotify::Notifier.new
18
+
19
+ @notifier.watch(path, :recursive, :attrib, :modify, :create,
20
+ :delete, :delete_self, :moved_from, :moved_to,
21
+ :move_self) do |event|
22
+ yield event.absolute_name
23
+ end
24
+
25
+ @conn = EM.watch @notifier.to_io do |conn|
26
+ class << conn
27
+ attr_accessor :notifier
28
+
29
+ def notify_readable
30
+ @notifier.process
31
+ end
32
+ end
33
+ conn.notifier = @notifier
34
+ conn.notify_readable = true
35
+ end
36
+ end
37
+
38
+ def when_ready_to_use
39
+ yield
40
+ end
41
+
42
+ def ready_to_use?; true; end
43
+
44
+ def stop
45
+ Native.close @notifier.fd
46
+ end
47
+ end
48
+
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,80 @@
1
+
2
+ require 'ffi'
3
+
4
+ module EMDirWatcher
5
+ module Platform
6
+ module Mac
7
+
8
+ module CarbonCore
9
+ extend FFI::Library
10
+ ffi_lib '/System/Library/Frameworks/CoreServices.framework/Frameworks/CarbonCore.framework/Versions/Current/CarbonCore'
11
+
12
+ attach_function :CFStringCreateWithCString, [:pointer, :string, :int], :pointer
13
+ KCFStringEncodingUTF8 = 0x08000100
14
+ attach_function :CFStringGetLength, [:pointer, :pointer, :int, :pointer, :pointer, :pointer], :int
15
+
16
+ attach_function :CFArrayCreate, [:pointer, :pointer, :int, :pointer], :pointer
17
+
18
+ attach_function :CFRunLoopRun, :CFRunLoopRun, [], :void
19
+ attach_function :CFRunLoopGetCurrent, [], :pointer
20
+ attach_variable :kCFRunLoopDefaultMode, :pointer
21
+
22
+ callback :FSEventStreamCallback, [:int], :void
23
+
24
+ KFSEventStreamEventIdSinceNow = -1
25
+ attach_function :FSEventStreamCreate, [:pointer, :FSEventStreamCallback, :pointer, :pointer, :long, :double, :int], :pointer
26
+ attach_function :FSEventStreamScheduleWithRunLoop, [:pointer, :pointer, :pointer], :void
27
+ attach_function :FSEventStreamStart, [:pointer], :void
28
+ attach_function :FSEventStreamStop, [:pointer], :void
29
+ end
30
+
31
+ class FSEventStream
32
+
33
+ KFSEventStreamEventFlagMustScanSubDirs = 0x1
34
+
35
+ class StreamError < StandardError;
36
+ end
37
+
38
+ def initialize(paths, &block)
39
+ raise ArgumentError, 'No callback block was specified.' unless block_given?
40
+ paths.each { |path| raise ArgumentError, "The specified path (#{path}) does not exist." unless File.exist?(path) }
41
+
42
+ handler = lambda do |stream, client_callback_info, number_of_events, paths_pointer, event_flags, event_ids|
43
+ $stderr.puts "CHANGED!"
44
+ block.call(['/'])
45
+ end
46
+ latency = 0.0
47
+ flags = 0
48
+
49
+ path_cfstring = CarbonCore.CFStringCreateWithCString nil, paths[0], CarbonCore::KCFStringEncodingUTF8
50
+ # puts "path_cfstring = #{path_cfstring}"
51
+ # puts "len = #{CarbonCore.CFStringGetLength(path_cfstring)}"
52
+
53
+ paths_ptr = FFI::MemoryPointer.new(:pointer)
54
+ paths_ptr.write_pointer path_cfstring
55
+ paths_cfarray = CarbonCore.CFArrayCreate nil, paths_ptr, 1, nil
56
+ # puts "paths_cfarray = #{paths_cfarray}"
57
+
58
+ fsevent_stream = CarbonCore.FSEventStreamCreate nil, handler, nil, paths_cfarray, CarbonCore::KFSEventStreamEventIdSinceNow, 0.0, 0
59
+ # puts "fsevent_stream = #{fsevent_stream}"
60
+
61
+ # puts "CarbonCore.kCFRunLoopDefaultMode = #{CarbonCore.kCFRunLoopDefaultMode}"
62
+ # puts "len = #{CarbonCore.CFStringGetLength(CarbonCore.kCFRunLoopDefaultMode)}"
63
+
64
+ CarbonCore.FSEventStreamScheduleWithRunLoop fsevent_stream, CarbonCore.CFRunLoopGetCurrent, CarbonCore.kCFRunLoopDefaultMode
65
+
66
+ CarbonCore.FSEventStreamStart fsevent_stream
67
+ end
68
+
69
+ def run_loop
70
+ CarbonCore.CFRunLoopRun
71
+ end
72
+
73
+ def stop
74
+ CarbonCore.FSEventStreamStop(@stream)
75
+ end
76
+ end
77
+
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,51 @@
1
+
2
+ require 'osx/foundation'
3
+ OSX.require_framework '/System/Library/Frameworks/CoreServices.framework/Frameworks/CarbonCore.framework'
4
+
5
+ module EMDirWatcher
6
+ module Platform
7
+ module Mac
8
+
9
+ class FSEventStream
10
+
11
+ KFSEventStreamEventFlagMustScanSubDirs = 0x1
12
+
13
+ class StreamError < StandardError;
14
+ end
15
+
16
+ def initialize(paths, &block)
17
+ raise ArgumentError, 'No callback block was specified.' unless block_given?
18
+ paths.each { |path| raise ArgumentError, "The specified path (#{path}) does not exist." unless File.exist?(path) }
19
+
20
+ callback = Proc.new do |stream, client_callback_info, number_of_events, paths_pointer, event_flags, event_ids|
21
+ paths_pointer.regard_as('*')
22
+ # event_flags.regard_as('*')
23
+ events = []
24
+ number_of_events.times {|i|
25
+ flags = event_flags[i]
26
+ code = if (flags & KFSEventStreamEventFlagMustScanSubDirs) == KFSEventStreamEventFlagMustScanSubDirs then '>' else '-' end
27
+ events << code + paths_pointer[i].to_s
28
+ }
29
+ block.call(events)
30
+ end
31
+ latency = 0.0
32
+ flags = 0
33
+ @stream = OSX.FSEventStreamCreate(OSX::KCFAllocatorDefault, callback, nil, paths, OSX::KFSEventStreamEventIdSinceNow, latency, flags)
34
+ raise(StreamError, 'Unable to create FSEvents stream.') unless @stream
35
+ OSX.FSEventStreamScheduleWithRunLoop(@stream, OSX.CFRunLoopGetCurrent, OSX::KCFRunLoopDefaultMode)
36
+ ok = OSX.FSEventStreamStart(@stream)
37
+ raise(StreamError, 'Unable to start FSEvents stream.') unless ok
38
+ end
39
+
40
+ def run_loop
41
+ OSX.CFRunLoopRun
42
+ end
43
+
44
+ def stop
45
+ OSX.FSEventStreamStop(@stream)
46
+ end
47
+ end
48
+
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,50 @@
1
+
2
+ require "em-dir-watcher/invokers/subprocess_invoker"
3
+
4
+ module EMDirWatcher
5
+ module Platform
6
+ module Mac
7
+
8
+ class Watcher
9
+
10
+ STARTUP_DELAY = 0.5
11
+
12
+ attr_accessor :handler, :active
13
+
14
+ def initialize path, inclusions, exclusions
15
+ subprocess = lambda do |ready, output|
16
+ require "em-dir-watcher/platform/mac/rubycocoa_watcher"
17
+ # require "em-dir-watcher/platform/mac/ffi_fsevents_watcher"
18
+ stream = FSEventStream.new [path] do |changed_paths|
19
+ changed_paths.each { |path| output.call path }
20
+ end
21
+ ready.call()
22
+ stream.run_loop
23
+ end
24
+
25
+ @invoker = EMDirWatcher::Invokers::SubprocessInvoker.new subprocess do |path|
26
+ code, path = path[0], path[1..-1]
27
+ if code == ?> || code == ?-
28
+ refresh_subtree = (code == ?>)
29
+ yield path, refresh_subtree
30
+ end
31
+ end
32
+ # Mac OS X seems to require this delay till it really starts listening for file system changes.
33
+ # See README for explaination of the effect.
34
+ @invoker.additional_delay = STARTUP_DELAY
35
+ end
36
+
37
+ def when_ready_to_use &ready_to_use_handler
38
+ @invoker.when_ready_to_use &ready_to_use_handler
39
+ end
40
+
41
+ def ready_to_use?; true; end
42
+
43
+ def stop
44
+ @invoker.stop
45
+ end
46
+
47
+ end
48
+ end
49
+ end
50
+ end
@@ -1,5 +1,4 @@
1
1
  $stdout.sync = true
2
-
3
2
  require 'rubygems'
4
3
  require 'socket'
5
4
  require 'win32/changenotify'
@@ -14,7 +13,6 @@ socket = TCPSocket.open('127.0.0.1', port)
14
13
  begin
15
14
  cn.wait do |events|
16
15
  events.each do |event|
17
- # puts event.file_name
18
16
  socket.puts event.file_name
19
17
  end
20
18
  end
@@ -13,10 +13,7 @@ public
13
13
 
14
14
  def self.path_to_ruby_exe
15
15
  buf = 0.chr * 260
16
- len = [buf.length].pack('L')
17
-
18
16
  GetModuleFileName.call(0, buf, buf.length)
19
-
20
17
  buf.strip
21
18
  end
22
19
 
@@ -1,5 +1,6 @@
1
1
 
2
2
  require File.join(File.dirname(__FILE__), 'windows', 'path_to_ruby_exe')
3
+ require 'socket'
3
4
 
4
5
  module EMDirWatcher
5
6
  module Platform
@@ -21,23 +22,44 @@ module TcpHandler
21
22
  end
22
23
 
23
24
  class Watcher
24
-
25
- def initialize path, globs, &handler
25
+
26
+ def initialize path, inclusions, exclusions, &handler
26
27
  @path = path
27
- @globs = globs
28
28
  @handler = handler
29
29
  @active = true
30
+ @ready_to_use = false
31
+ @ready_to_use_handlers = []
30
32
 
31
33
  start_server
32
34
  setup_listener
33
35
  end
34
36
 
37
+ def when_ready_to_use &ready_to_use_handler
38
+ if @ready_to_use_handlers.nil?
39
+ ready_to_use_handler.call()
40
+ else
41
+ @ready_to_use_handlers << ready_to_use_handler
42
+ end
43
+ end
44
+
45
+ def ready_to_use!
46
+ return if @ready_to_use
47
+ @ready_to_use = true
48
+ # give the child process additional 100ms to start watching loop
49
+ EM.add_timer 0.1 do
50
+ @ready_to_use_handlers.each { |handler| handler.call() }
51
+ @ready_to_use_handlers = nil
52
+ end
53
+ end
54
+
55
+ def ready_to_use?; @ready_to_use; end
56
+
35
57
  def start_server
36
58
  @server = EM.start_server '127.0.0.1', 0, TcpHandler do |server|
37
59
  server.watcher = self
60
+ ready_to_use!
38
61
  end
39
- @server_port, _ = Socket.unpack_sockaddr_in(EM::get_sockname @server)
40
- puts "Server running on port #{@server_port}"
62
+ @server_port, _ = Socket.unpack_sockaddr_in(EM::get_sockname(@server))
41
63
  end
42
64
 
43
65
  def stop_server
@@ -54,7 +76,7 @@ class Watcher
54
76
 
55
77
  def kill
56
78
  if @io
57
- Process.kill 'TERM', @io.pid
79
+ Process.kill 9, @io.pid
58
80
  Process.waitpid @io.pid
59
81
  @io = nil
60
82
  end
@@ -67,7 +89,7 @@ class Watcher
67
89
  end
68
90
 
69
91
  def path_changed path
70
- @handler.call path
92
+ @handler.call path, true
71
93
  end
72
94
 
73
95
  def listener_died