rb-fchange 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,31 @@
1
+ # rb-fchange
2
+
3
+ Code not ready. It's just proof of concept
4
+ This is a simple wrapper over the Windows Kernel functions for monitoring the specified directory or subtree.
5
+
6
+ Example
7
+
8
+ ```ruby
9
+ require 'rb-fchange'
10
+
11
+ notifier = FChange::Notifier.new
12
+ notifier.watch("test", :all_events, :recursive) do |event|
13
+ p Time.now.utc
14
+ end
15
+
16
+ Signal.trap('INT') do
17
+ p "Bye bye...",
18
+ notifier.stop
19
+ abort("\n")
20
+ end
21
+
22
+ notifier.run
23
+ ```
24
+
25
+ ## TODO
26
+
27
+ - add latency setting with 0.5 default
28
+ - default flag for events :all_events
29
+ - rework interface (should more look like rb-fsevent)
30
+ - add specs (can use specs from rb-fsevent)
31
+ - publish on rubygems
@@ -0,0 +1,2 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
@@ -0,0 +1,14 @@
1
+ require "rb-fchange/native"
2
+ require "rb-fchange/native/flags"
3
+ require "rb-fchange/notifier"
4
+ require "rb-fchange/watcher"
5
+ require "rb-fchange/event"
6
+
7
+ # The root module of the library, which is laid out as so:
8
+ #
9
+ # * {Notifier} -- The main class, where the notifications are set up
10
+ # * {Watcher} -- A watcher for a single file or directory
11
+ # * {Event} -- An filesystem event notification
12
+ module FChange
13
+
14
+ end
@@ -0,0 +1,29 @@
1
+ module FChange
2
+ # An event caused by a change on the filesystem.
3
+ # Each {Watcher} can fire many events,
4
+ # which are passed to that watcher's callback.
5
+ class Event
6
+ # The {Watcher} that fired this event.
7
+ #
8
+ # @return [Watcher]
9
+ attr_reader :watcher
10
+
11
+ # Creates an event from a string of binary data.
12
+ # Differs from {Event.consume} in that it doesn't modify the string.
13
+ #
14
+ # @private
15
+ # @param watcher [Watcher] The {Watcher} that fired the event
16
+ def initialize(watcher)
17
+ @watcher = watcher
18
+ end
19
+
20
+ # Calls the callback of the watcher that fired this event,
21
+ # passing in the event itself.
22
+ #
23
+ # @private
24
+ def callback!
25
+ @watcher.callback!(self)
26
+ end
27
+
28
+ end
29
+ end
@@ -0,0 +1,70 @@
1
+ require 'win32/api'
2
+
3
+ module FChange
4
+ # This module contains the low-level foreign-function.
5
+ # It's an implementation detail, and not meant for users to deal with.
6
+ #
7
+ # @private
8
+ module Native
9
+
10
+ #
11
+ INFINITE = 0xFFFFFFFF
12
+
13
+ #
14
+ WAIT_OBJECT_0 = 0x00000000
15
+
16
+ # HANDLE FindFirstChangeNotification(
17
+ # LPCTSTR lpPathName, // directory name
18
+ # BOOL bWatchSubtree, // monitoring option
19
+ # DWORD dwNotifyFilter // filter conditions
20
+ #);
21
+ @_k32FindFirstChangeNotification =
22
+ Win32::API.new('FindFirstChangeNotification', ['P', 'I', 'L'], 'L')
23
+
24
+ # @return handle
25
+ def k32FindFirstChangeNotification(path, recursive, flag)
26
+ @_k32FindFirstChangeNotification.call(path, recursive ? 1 : 0, flag)
27
+ end
28
+
29
+ module_function "k32FindFirstChangeNotification"
30
+
31
+ # BOOL FindNextChangeNotification(
32
+ # HANDLE hChangeHandle // handle to change notification
33
+ # );
34
+ @_k32FindNextChangeNotification =
35
+ Win32::API.new('FindNextChangeNotification', ['L'], 'I')
36
+
37
+ def k32FindNextChangeNotification(handle)
38
+ @_k32FindNextChangeNotification.call(handle)
39
+ end
40
+
41
+ module_function "k32FindNextChangeNotification"
42
+
43
+ # DWORD WaitForMultipleObjects(
44
+ # DWORD nCount, // number of handles in array
45
+ # CONST HANDLE *lpHandles, // object-handle array
46
+ # BOOL bWaitAll, // wait option
47
+ # DWORD dwMilliseconds // time-out interval
48
+ # );
49
+ @_k32WaitForMultipleObjects =
50
+ Win32::API.new('WaitForMultipleObjects', ['L', 'P', 'I', 'L'], 'L')
51
+
52
+ def k32WaitForMultipleObjects(count, handles, wait_all, time)
53
+ @_k32WaitForMultipleObjects.call(count, handles, wait_all, time)
54
+ end
55
+
56
+ module_function "k32WaitForMultipleObjects"
57
+
58
+ # BOOL FindCloseChangeNotification(
59
+ # HANDLE hChangeHandle
60
+ # );
61
+ @_k32FindCloseChangeNotification =
62
+ Win32::API.new('FindCloseChangeNotification', ['L'], 'I')
63
+
64
+ def k32FindCloseChangeNotification(handle)
65
+ @_k32FindCloseChangeNotification.call(handle)
66
+ end
67
+
68
+ module_function "k32FindCloseChangeNotification"
69
+ end
70
+ end
@@ -0,0 +1,83 @@
1
+ module FChange
2
+ module Native
3
+ # A module containing all the flags to be passed to {Notifier#watch}.
4
+ # @see http://msdn.microsoft.com/en-us/library/aa364417(v=VS.85).aspx
5
+ #
6
+ # @private
7
+ module Flags
8
+
9
+ # Any file name change in the watched directory or subtree causes a change
10
+ # notification wait operation to return. Changes include renaming,
11
+ # creating, or deleting a file name.
12
+ FILE_NOTIFY_CHANGE_FILE_NAME = 0x00000001
13
+
14
+ # Any directory-name change in the watched directory or subtree causes a
15
+ # change notification wait operation to return. Changes include creating
16
+ # or deleting a directory.
17
+ FILE_NOTIFY_CHANGE_DIR_NAME = 0x00000002
18
+
19
+ # Any attribute change in the watched directory or subtree causes a change
20
+ # notification wait operation to return.
21
+ FILE_NOTIFY_CHANGE_ATTRIBUTES = 0x00000004
22
+
23
+ # Any file-size change in the watched directory or subtree causes a change
24
+ # notification wait operation to return. The operating system detects a
25
+ # change in file size only when the file is written to the disk.
26
+ # For operating systems that use extensive caching, detection occurs only
27
+ # when the cache is sufficiently flushed.
28
+ FILE_NOTIFY_CHANGE_SIZE = 0x00000008
29
+
30
+ # Any change to the last write-time of files in the watched directory or
31
+ # subtree causes a change notification wait operation to return.
32
+ # The operating system detects a change to the last write-time only when
33
+ # the file is written to the disk. For operating systems that use
34
+ # extensive caching, detection occurs only when the cache is sufficiently
35
+ # flushed
36
+ FILE_NOTIFY_CHANGE_LAST_WRITE = 0x00000010
37
+
38
+ # Any change to the last access time of files in the watched directory or
39
+ # subtree causes a change notification wait operation to return.
40
+ FILE_NOTIFY_CHANGE_LAST_ACCESS = 0x00000020
41
+
42
+ # Any change to the creation time of files in the watched directory or
43
+ # subtree causes a change notification wait operation to return.
44
+ FILE_NOTIFY_CHANGE_CREATION = 0x00000040
45
+
46
+ # Any security-descriptor change in the watched directory or subtree
47
+ # causes a change notification wait operation to return.
48
+ FILE_NOTIFY_CHANGE_SECURITY = 0x00000100
49
+
50
+ FILE_NOTIFY_CHANGE_ALL_EVENTS = (
51
+ FILE_NOTIFY_CHANGE_ATTRIBUTES |
52
+ FILE_NOTIFY_CHANGE_CREATION |
53
+ FILE_NOTIFY_CHANGE_DIR_NAME |
54
+ FILE_NOTIFY_CHANGE_FILE_NAME |
55
+ FILE_NOTIFY_CHANGE_LAST_ACCESS |
56
+ FILE_NOTIFY_CHANGE_LAST_WRITE |
57
+ FILE_NOTIFY_CHANGE_SECURITY |
58
+ FILE_NOTIFY_CHANGE_SIZE
59
+ )
60
+
61
+ # Converts a list of flags to the bitmask that the C API expects.
62
+ #
63
+ # @param flags [Array<Symbol>]
64
+ # @return [Fixnum]
65
+ def self.to_mask(flags)
66
+ flags.map {|flag| const_get("FILE_NOTIFY_CHANGE_#{flag.to_s.upcase}")}.
67
+ inject(0) {|mask, flag| mask | flag}
68
+ end
69
+
70
+ # Converts a bitmask from the C API into a list of flags.
71
+ #
72
+ # @param mask [Fixnum]
73
+ # @return [Array<Symbol>]
74
+ def self.from_mask(mask)
75
+ constants.map {|c| c.to_s}.select do |c|
76
+ next false unless c =~ /^FILE_NOTIFY_CHANGE_/
77
+ const_get(c) & mask != 0
78
+ end.map {|c| c.sub("FILE_NOTIFY_CHANGE_", "").downcase.to_sym} - [:all_events]
79
+ end
80
+
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,139 @@
1
+ module FChange
2
+ # Notifier wraps a single instance of FChange.
3
+ # It's possible to have more than one instance,
4
+ # but usually unnecessary.
5
+ #
6
+ # @example
7
+ # # Create the notifier
8
+ # notifier = FChange::Notifier.new
9
+ #
10
+ # # Run this callback whenever the file path/to/foo.txt is read
11
+ # notifier.watch("path/to/foo/", :all_events) do
12
+ # puts "foo was accessed!"
13
+ # end
14
+ #
15
+ # # Nothing happens until you run the notifier!
16
+ # notifier.run
17
+ class Notifier
18
+ # A hash from {Watcher} ids to the instances themselves.
19
+ #
20
+ # @private
21
+ # @return [{Fixnum => Watcher}]
22
+ attr_reader :watchers
23
+
24
+ attr_reader :dwChangeHandles
25
+ attr_reader :lp_dwChangeHandles
26
+
27
+ # Creates a new {Notifier}.
28
+ #
29
+ # @return [Notifier]
30
+ def initialize
31
+ @watchers = {}
32
+ @dwChangeHandles = []
33
+ @lp_dwChangeHandles = 0
34
+ end
35
+
36
+ # Adds a new {Watcher} to the queue.
37
+ def add_watcher(watcher)
38
+
39
+ @watchers[watcher.id] = watcher
40
+
41
+ @dwChangeHandles.push watcher.id
42
+
43
+ # Pack event handles into newly created storage area
44
+ # to be used for Win32 call
45
+ @lp_dwChangeHandles = dwChangeHandles.pack("L" * dwChangeHandles.count)
46
+
47
+ end
48
+
49
+ # Watches a file or directory for changes,
50
+ # calling the callback when there are.
51
+ # This is only activated once \{#process} or \{#run} is called.
52
+ #
53
+ # **Note that by default, this does not recursively watch subdirectories
54
+ # of the watched directory**.
55
+ # To do so, use the `:recursive` flag.
56
+ #
57
+ # `:recursive`
58
+ # : Recursively watch any subdirectories that are created.
59
+ #
60
+ # @param path [String] The path to the file or directory
61
+ # @param flags [Array<Symbol>] Which events to watch for
62
+ # @yield [event] A block that will be called
63
+ # whenever one of the specified events occur
64
+ # @yieldparam event [Event] The Event object containing information
65
+ # about the event that occured
66
+ # @return [Watcher] A Watcher set up to watch this path for these events
67
+ # @raise [SystemCallError] if the file or directory can't be watched,
68
+ # e.g. if the file isn't found, read access is denied,
69
+ # or the flags don't contain any events
70
+ def watch(path, *flags, &callback)
71
+ recursive = flags.include?(:recursive)
72
+ flags.include?(:recursive)
73
+ #:latency = 0.5
74
+ flags = flags - [:recursive]
75
+ @flags = flags.freeze
76
+ Watcher.new(self, path, recursive, *flags, &callback)
77
+ end
78
+
79
+ # Starts the notifier watching for filesystem events.
80
+ # Blocks until \{#stop} is called.
81
+ #
82
+ # @see #process
83
+ def run
84
+ @stop = false
85
+ process until @stop
86
+ end
87
+
88
+ # Stop watching for filesystem events.
89
+ # That is, if we're in a \{#run} loop,
90
+ # exit out as soon as we finish handling the events.
91
+ def stop
92
+ @stop = true
93
+ end
94
+
95
+ # Blocks until there are one or more filesystem events
96
+ # that this notifier has watchers registered for.
97
+ # Once there are events, the appropriate callbacks are called
98
+ # and this function returns.
99
+ #
100
+ # @see #run
101
+ def process
102
+ read_events.each {|event| event.callback!}
103
+ end
104
+
105
+ # Blocks until there are one or more filesystem events
106
+ # that this notifier has watchers registered for.
107
+ # Once there are events, returns their {Event} objects.
108
+ #
109
+ # @private
110
+ def read_events
111
+
112
+ # can return WAIT_TIMEOUT = 0x00000102
113
+ dwWaitStatus = Native.k32WaitForMultipleObjects(@dwChangeHandles.count,
114
+ @lp_dwChangeHandles, 0, 500)
115
+
116
+ events = []
117
+
118
+ # this call blocks all threads completely.
119
+ @dwChangeHandles.each_index do |index|
120
+ if dwWaitStatus == Native::WAIT_OBJECT_0 + index
121
+
122
+ ev = Event.new(@watchers[@dwChangeHandles[index]])
123
+ events << ev
124
+
125
+ r = Native.k32FindNextChangeNotification(@dwChangeHandles[index])
126
+ if r == 0
127
+ raise SystemCallError.new("Failed to watch", r)
128
+ end
129
+ end
130
+ end
131
+ events
132
+ end
133
+
134
+ def close
135
+
136
+ end
137
+
138
+ end
139
+ end
@@ -0,0 +1,80 @@
1
+ module FChange
2
+ # Watchers monitor a single path for changes,
3
+ # specified by {FChange::Notifier#watch event flags}.
4
+ # A watcher is usually created via \{Notifier#watch}.
5
+ #
6
+ # One {Notifier} may have many {Watcher}s.
7
+ # The Notifier actually takes care of the checking for events,
8
+ # via \{Notifier#run #run} or \{Notifier#process #process}.
9
+ # The main purpose of having Watcher objects
10
+ # is to be able to disable them using \{#close}.
11
+ class Watcher
12
+ # The {Notifier} that this Watcher belongs to.
13
+ #
14
+ # @return [Notifier]
15
+ attr_reader :notifier
16
+
17
+ # The path that this Watcher is watching.
18
+ #
19
+ # @return [String]
20
+ attr_reader :path
21
+
22
+ # The {FChange::Notifier#watch flags}
23
+ # specifying the events that this Watcher is watching for,
24
+ # and potentially some options as well.
25
+ #
26
+ # @return [Array<Symbol>]
27
+ attr_reader :flags
28
+
29
+ # The id for this Watcher.
30
+ # Used to retrieve this Watcher from {Notifier#watchers}.
31
+ #
32
+ # @private
33
+ # @return [Fixnum]
34
+ attr_reader :id
35
+
36
+ #
37
+ # @private
38
+ # @return [Boolean]
39
+ attr_reader :recursive
40
+
41
+ # Calls this Watcher's callback with the given {Event}.
42
+ #
43
+ # @private
44
+ # @param event [Event]
45
+ def callback!(event)
46
+ @callback[event]
47
+ end
48
+
49
+ # Disables this Watcher, so that it doesn't fire any more events.
50
+ #
51
+ # @raise [SystemCallError] if the watch fails to be disabled for some reason
52
+ def close
53
+ r = Native.k32FindCloseChangeNotification(@id)
54
+ #@notifier.remove_watcher(self)
55
+ return if r == 0
56
+ raise SystemCallError.new("Failed to stop watching #{@path.inspect}", r)
57
+ end
58
+
59
+ # Creates a new {Watcher}.
60
+ #
61
+ # @private
62
+ # @see Notifier#watch
63
+ def initialize(notifier, path, recursive, *flags, &callback)
64
+ @notifier = notifier
65
+ @callback = callback || proc {}
66
+ @path = path
67
+ @flags = flags
68
+ @recursive = recursive
69
+ @id = Native.k32FindFirstChangeNotification(path, recursive,
70
+ Native::Flags.to_mask(flags));
71
+
72
+ unless @id < 0
73
+ @notifier.add_watcher(self)
74
+ return
75
+ end
76
+
77
+ raise SystemCallError.new("Failed to watch #{path.inspect}", @id)
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,32 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "rb-fchange/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = %q{rb-fchange}
7
+ s.version = FChange::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["stereobooster"]
10
+ s.date = %q{2011-04-28}
11
+ s.description = %q{A Ruby wrapper for Windows Kernel functions for monitoring the specified directory or subtree}
12
+ s.email = %q{stereobooster@gmail.com}
13
+ s.extra_rdoc_files = [
14
+ "README.md"
15
+ ]
16
+ s.files = [
17
+ "README.md",
18
+ "Rakefile",
19
+ "lib/rb-fchange.rb",
20
+ "lib/rb-fchange/event.rb",
21
+ "lib/rb-fchange/native.rb",
22
+ "lib/rb-fchange/native/flags.rb",
23
+ "lib/rb-fchange/notifier.rb",
24
+ "lib/rb-fchange/watcher.rb",
25
+ "rb-fchange.gemspec"
26
+ ]
27
+ s.homepage = %q{http://github.com/stereobooster/rb-fchange}
28
+ s.require_paths = ["lib"]
29
+ s.rubygems_version = %q{1.3.7}
30
+ s.summary = %q{A Ruby wrapper for Windows Kernel functions for monitoring the specified directory or subtree}
31
+ s.add_dependency('win32-api', '>= 1.4.8')
32
+ end
metadata ADDED
@@ -0,0 +1,90 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rb-fchange
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 2
10
+ version: 0.0.2
11
+ platform: ruby
12
+ authors:
13
+ - stereobooster
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-04-28 00:00:00 +03:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: win32-api
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 23
30
+ segments:
31
+ - 1
32
+ - 4
33
+ - 8
34
+ version: 1.4.8
35
+ type: :runtime
36
+ version_requirements: *id001
37
+ description: A Ruby wrapper for Windows Kernel functions for monitoring the specified directory or subtree
38
+ email: stereobooster@gmail.com
39
+ executables: []
40
+
41
+ extensions: []
42
+
43
+ extra_rdoc_files:
44
+ - README.md
45
+ files:
46
+ - README.md
47
+ - Rakefile
48
+ - lib/rb-fchange.rb
49
+ - lib/rb-fchange/event.rb
50
+ - lib/rb-fchange/native.rb
51
+ - lib/rb-fchange/native/flags.rb
52
+ - lib/rb-fchange/notifier.rb
53
+ - lib/rb-fchange/watcher.rb
54
+ - rb-fchange.gemspec
55
+ has_rdoc: true
56
+ homepage: http://github.com/stereobooster/rb-fchange
57
+ licenses: []
58
+
59
+ post_install_message:
60
+ rdoc_options: []
61
+
62
+ require_paths:
63
+ - lib
64
+ required_ruby_version: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ hash: 3
70
+ segments:
71
+ - 0
72
+ version: "0"
73
+ required_rubygems_version: !ruby/object:Gem::Requirement
74
+ none: false
75
+ requirements:
76
+ - - ">="
77
+ - !ruby/object:Gem::Version
78
+ hash: 3
79
+ segments:
80
+ - 0
81
+ version: "0"
82
+ requirements: []
83
+
84
+ rubyforge_project:
85
+ rubygems_version: 1.5.2
86
+ signing_key:
87
+ specification_version: 3
88
+ summary: A Ruby wrapper for Windows Kernel functions for monitoring the specified directory or subtree
89
+ test_files: []
90
+