wdm 0.0.3 → 0.1.0
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.
- data/LICENSE +21 -21
- data/README.md +169 -20
- data/ext/wdm/entry.c +10 -5
- data/ext/wdm/extconf.rb +20 -2
- data/ext/wdm/memory.c +2 -2
- data/ext/wdm/memory.h +2 -4
- data/ext/wdm/monitor.c +10 -5
- data/ext/wdm/queue.c +59 -24
- data/ext/wdm/queue.h +1 -2
- data/ext/wdm/rb_change.c +11 -7
- data/ext/wdm/rb_monitor.c +35 -20
- data/ext/wdm/utils.c +10 -5
- data/ext/wdm/wdm.c +2 -1
- data/ext/wdm/wdm.h +6 -0
- data/ext/wdm/wdm.vcxproj +2 -0
- data/lib/wdm.rb +10 -0
- metadata +3 -5
data/LICENSE
CHANGED
@@ -1,22 +1,22 @@
|
|
1
|
-
Copyright (c) 2012 Maher Sallam
|
2
|
-
|
3
|
-
MIT License
|
4
|
-
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
-
a copy of this software and associated documentation files (the
|
7
|
-
"Software"), to deal in the Software without restriction, including
|
8
|
-
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
-
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
-
permit persons to whom the Software is furnished to do so, subject to
|
11
|
-
the following conditions:
|
12
|
-
|
13
|
-
The above copyright notice and this permission notice shall be
|
14
|
-
included in all copies or substantial portions of the Software.
|
15
|
-
|
16
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
-
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
-
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
-
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
-
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
-
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
1
|
+
Copyright (c) 2012 Maher Sallam
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
22
|
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
CHANGED
@@ -1,25 +1,14 @@
|
|
1
1
|
# Windows Directory Monitor (WDM)
|
2
2
|
|
3
|
-
Windows Directory Monitor (WDM) is a library which can be used to monitor directories for changes.
|
4
|
-
It's mostly implemented in C and uses the Win32 API for a better performance.
|
5
|
-
|
6
|
-
**Note:** This is still a work in progress, so it's not advisable to use
|
7
|
-
it yet in anything (unless you are testing it, which is very much appreciated :)).
|
3
|
+
Windows Directory Monitor (WDM) is a thread-safe ruby library which can be used to monitor directories for changes on Windows.
|
8
4
|
|
9
|
-
|
5
|
+
It's mostly implemented in C and uses the Win32 API for a better performance.
|
10
6
|
|
11
|
-
|
12
|
-
- ~~Enable watching subdirectories.~~
|
13
|
-
- ~~Add options to the `watch` method.~~
|
14
|
-
- ~~Provide info about the change in the callback.~~
|
15
|
-
- ~~Convert \ to / in paths.~~
|
16
|
-
- ~~Don't allow directories to be watched while the monitor is running.~~
|
17
|
-
- ~~Check if the passed direcoty exists.~~
|
18
|
-
- ~~Convert passed directories to absolute paths.~~
|
7
|
+
**Important**: WDM only runs on ruby versions >= *1.9.2*!
|
19
8
|
|
20
9
|
## Installation
|
21
10
|
|
22
|
-
|
11
|
+
If you are using Bundler, add the following line to your application's Gemfile:
|
23
12
|
|
24
13
|
gem 'wdm'
|
25
14
|
|
@@ -31,20 +20,180 @@ Or install it yourself as:
|
|
31
20
|
|
32
21
|
$ gem install wdm
|
33
22
|
|
23
|
+
## Usage
|
24
|
+
|
25
|
+
For a simple example on how to use WDM, you can take a look at the `example` directory of the repository.
|
26
|
+
|
27
|
+
## Benchmarks
|
28
|
+
|
29
|
+
You can find a comparison of different ruby libraries for watching directory changes on Windows in the `benchmark` directory of the repository.
|
30
|
+
|
31
|
+
## Reference
|
32
|
+
|
33
|
+
### `WDM::Monitor`
|
34
|
+
|
35
|
+
To start watching directories, you need an instance of `WDM::Monitor`:
|
36
|
+
|
37
|
+
```ruby
|
38
|
+
monitor = WDM::Monitor.new
|
39
|
+
```
|
40
|
+
|
41
|
+
After that, register a callback for each directory you want to watch:
|
42
|
+
|
43
|
+
```ruby
|
44
|
+
# Watch a single directory
|
45
|
+
monitor.watch('C:\Users\Maher\Desktop') { |change| puts change.path }
|
46
|
+
|
47
|
+
# Watch a directory with its subdirectories
|
48
|
+
monitor.watch_recursively('C:\Users\Maher\Projects\my_project') { |change| puts change.path }
|
49
|
+
```
|
50
|
+
|
51
|
+
Both `Monitor#watch` and `Monitor#watch_recursively` can take a series of options after the first parameter to specify the watching options:
|
52
|
+
|
53
|
+
```ruby
|
54
|
+
# Report changes to directories in the watched directory (Ex.: Addition of an empty directory)
|
55
|
+
monitor.watch('C:\Users\Maher\Desktop', :default, :directories)
|
56
|
+
```
|
57
|
+
|
58
|
+
The supported options are:
|
59
|
+
|
60
|
+
<table>
|
61
|
+
<thead>
|
62
|
+
<tr>
|
63
|
+
<th>Value</th>
|
64
|
+
<th>Meaning</th>
|
65
|
+
</tr>
|
66
|
+
</thead>
|
67
|
+
<tbody>
|
68
|
+
<tr>
|
69
|
+
<td>:default</td>
|
70
|
+
|
71
|
+
<td>
|
72
|
+
The default set of options for watching directories. It's a combination of the :files, :directories and the :last_write options.
|
73
|
+
</td>
|
74
|
+
</tr>
|
75
|
+
|
76
|
+
<tr>
|
77
|
+
<td>:files</td>
|
78
|
+
|
79
|
+
<td>
|
80
|
+
Any file name change in the watched directory or subtree causes a change
|
81
|
+
notification wait operation to return. Changes include renaming, creating, or
|
82
|
+
deleting a file.
|
83
|
+
</td>
|
84
|
+
</tr>
|
85
|
+
|
86
|
+
<tr>
|
87
|
+
<td>:directories</td>
|
88
|
+
|
89
|
+
<td>
|
90
|
+
Any directory-name change in the watched directory or subtree causes a
|
91
|
+
change notification wait operation to return. Changes include creating or
|
92
|
+
deleting a directory.
|
93
|
+
</td>
|
94
|
+
</tr>
|
95
|
+
|
96
|
+
<tr>
|
97
|
+
<td>:attributes</td>
|
98
|
+
|
99
|
+
<td>
|
100
|
+
Any attribute change in the watched directory or subtree causes a change
|
101
|
+
notification wait operation to return.
|
102
|
+
</td>
|
103
|
+
</tr>
|
104
|
+
|
105
|
+
<tr>
|
106
|
+
<td>:size</td>
|
107
|
+
|
108
|
+
<td>
|
109
|
+
Any file-size change in the watched directory or subtree causes a change
|
110
|
+
notification wait operation to return. The operating system detects a change in
|
111
|
+
file size only when the file is written to the disk. For operating systems that
|
112
|
+
use extensive caching, detection occurs only when the cache is sufficiently
|
113
|
+
flushed.
|
114
|
+
</td>
|
115
|
+
</tr>
|
116
|
+
|
117
|
+
<tr>
|
118
|
+
<td>:last_write</td>
|
119
|
+
|
120
|
+
<td>
|
121
|
+
Any change to the last write-time of files in the watched directory or
|
122
|
+
subtree causes a change notification wait operation to return. The operating
|
123
|
+
system detects a change to the last write-time only when the file is written to
|
124
|
+
the disk. For operating systems that use extensive caching, detection occurs
|
125
|
+
only when the cache is sufficiently flushed.
|
126
|
+
</td>
|
127
|
+
</tr>
|
128
|
+
|
129
|
+
<tr>
|
130
|
+
<td>:last_access</td>
|
131
|
+
|
132
|
+
<td>
|
133
|
+
Any change to the last access time of files in the watched directory or
|
134
|
+
subtree causes a change notification wait operation to return.
|
135
|
+
</td>
|
136
|
+
</tr>
|
137
|
+
|
138
|
+
<tr>
|
139
|
+
<td>:creation</td>
|
140
|
+
|
141
|
+
<td>
|
142
|
+
Any change to the creation time of files in the watched directory or subtree
|
143
|
+
causes a change notification wait operation to return.
|
144
|
+
</td>
|
145
|
+
</tr>
|
146
|
+
|
147
|
+
<tr>
|
148
|
+
<td>:security</td>
|
149
|
+
|
150
|
+
<td>
|
151
|
+
Any security-descriptor change in the watched directory or subtree causes a
|
152
|
+
change notification wait operation to return.
|
153
|
+
</td>
|
154
|
+
</tr>
|
155
|
+
</tbody>
|
156
|
+
</table>
|
157
|
+
|
158
|
+
These options map to the filters that `ReadDirectoryChangesW` takes in its `dwNotifyFilter` parameter. You can find more info on the [docs page](http://msdn.microsoft.com/en-us/library/windows/desktop/aa365465.aspx) of `ReadDirectoryChangesW`.
|
159
|
+
|
160
|
+
Now all that's left to be done is to run the monitor:
|
161
|
+
|
162
|
+
```ruby
|
163
|
+
monitor.run!
|
164
|
+
```
|
165
|
+
|
166
|
+
The `Monitor#run!` method blocks the process. Since monitors are thread-safe, you can run them in a thread if you don't want to block your main one:
|
167
|
+
|
168
|
+
```ruby
|
169
|
+
worker_thread = Thread.new { monitor.run! }
|
170
|
+
|
171
|
+
# The process won't block; it will continue with the next line of code...
|
172
|
+
```
|
173
|
+
|
174
|
+
When you are done with the monitor, don't forget to stop it. Here is a snippet to always stop the monitor when the ruby process exits:
|
175
|
+
|
176
|
+
```ruby
|
177
|
+
at_exit { monitor.stop }
|
178
|
+
```
|
179
|
+
|
180
|
+
### `WDM::Change`
|
181
|
+
|
182
|
+
The passed argument to the block is an instance of `WDM::Change`. This class has two methods:
|
183
|
+
|
184
|
+
- `Change#path`: The absolute path to the change.
|
185
|
+
- `Change#type`: This can be one of the following values: `:added`, `:modified`, `:removed`, `:renamed_old_file` or `:renamed_new_file`.
|
186
|
+
|
34
187
|
## Compiling the extension for developers
|
35
188
|
|
36
189
|
Download the source, then run the following:
|
37
190
|
|
38
|
-
$ rake compile
|
191
|
+
$ bundle exec rake compile
|
39
192
|
|
40
193
|
To get debug messages, you need to enable them in the `global.h` file:
|
41
194
|
|
42
195
|
#define WDM_DEBUG_ENABLED TRUE // This is disabled by default
|
43
196
|
|
44
|
-
## Usage
|
45
|
-
|
46
|
-
TODO: Write usage instructions here
|
47
|
-
|
48
197
|
## Contributing
|
49
198
|
|
50
199
|
1. Fork it
|
data/ext/wdm/entry.c
CHANGED
@@ -8,7 +8,8 @@
|
|
8
8
|
// ---------------------------------------------------------
|
9
9
|
|
10
10
|
WDM_PEntryUserData
|
11
|
-
wdm_entry_user_data_new()
|
11
|
+
wdm_entry_user_data_new()
|
12
|
+
{
|
12
13
|
WDM_PEntryUserData user_data;
|
13
14
|
|
14
15
|
user_data = WDM_ALLOC(WDM_EntryUserData);
|
@@ -20,7 +21,8 @@ wdm_entry_user_data_new() {
|
|
20
21
|
}
|
21
22
|
|
22
23
|
void
|
23
|
-
wdm_entry_user_data_free(WDM_PEntryUserData user_data)
|
24
|
+
wdm_entry_user_data_free(WDM_PEntryUserData user_data)
|
25
|
+
{
|
24
26
|
if ( user_data->dir != NULL ) free(user_data->dir);
|
25
27
|
free(user_data);
|
26
28
|
}
|
@@ -30,7 +32,8 @@ wdm_entry_user_data_free(WDM_PEntryUserData user_data) {
|
|
30
32
|
// ---------------------------------------------------------
|
31
33
|
|
32
34
|
WDM_PEntry
|
33
|
-
wdm_entry_new()
|
35
|
+
wdm_entry_new()
|
36
|
+
{
|
34
37
|
WDM_PEntry entry;
|
35
38
|
|
36
39
|
entry = WDM_ALLOC(WDM_Entry);
|
@@ -46,7 +49,8 @@ wdm_entry_new() {
|
|
46
49
|
}
|
47
50
|
|
48
51
|
void
|
49
|
-
wdm_entry_free(WDM_PEntry entry)
|
52
|
+
wdm_entry_free(WDM_PEntry entry)
|
53
|
+
{
|
50
54
|
if ( entry->dir_handle != INVALID_HANDLE_VALUE ) {
|
51
55
|
CancelIo(entry->dir_handle); // Stop monitoring changes
|
52
56
|
CloseHandle(entry->dir_handle);
|
@@ -56,7 +60,8 @@ wdm_entry_free(WDM_PEntry entry) {
|
|
56
60
|
}
|
57
61
|
|
58
62
|
void
|
59
|
-
wdm_entry_list_free(WDM_PEntry entry)
|
63
|
+
wdm_entry_list_free(WDM_PEntry entry)
|
64
|
+
{
|
60
65
|
WDM_PEntry tmp;
|
61
66
|
|
62
67
|
while(entry != NULL) {
|
data/ext/wdm/extconf.rb
CHANGED
@@ -1,9 +1,27 @@
|
|
1
1
|
require 'mkmf'
|
2
|
+
require 'rbconfig'
|
2
3
|
|
3
|
-
|
4
|
+
def generate_makefile
|
5
|
+
create_makefile("wdm_ext")
|
6
|
+
end
|
7
|
+
|
8
|
+
def generate_dummy_makefile
|
9
|
+
File.open("Makefile", "w") do |f|
|
10
|
+
f.puts dummy_makefile('wdm_ext').join
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def windows?
|
15
|
+
RbConfig::CONFIG['host_os'] =~ /mswin|mingw|cygwin/
|
16
|
+
end
|
17
|
+
|
18
|
+
if windows? and
|
19
|
+
have_library("kernel32") and
|
4
20
|
have_header("windows.h") and
|
5
21
|
have_header("ruby.h") and
|
6
22
|
have_const('HAVE_RUBY_ENCODING_H')
|
7
23
|
then
|
8
|
-
|
24
|
+
generate_makefile()
|
25
|
+
else
|
26
|
+
generate_dummy_makefile()
|
9
27
|
end
|
data/ext/wdm/memory.c
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
#include "memory.h"
|
4
4
|
|
5
|
-
void
|
5
|
+
void*
|
6
6
|
wdm_memory_malloc (size_t size)
|
7
7
|
{
|
8
8
|
void *memory = malloc(size);
|
@@ -14,7 +14,7 @@ wdm_memory_malloc (size_t size)
|
|
14
14
|
return memory;
|
15
15
|
}
|
16
16
|
|
17
|
-
void
|
17
|
+
void*
|
18
18
|
wdm_memory_realloc (void *ptr, size_t size)
|
19
19
|
{
|
20
20
|
void *memory = realloc(ptr, size);
|
data/ext/wdm/memory.h
CHANGED
@@ -9,11 +9,9 @@ extern "C" {
|
|
9
9
|
// Prototypes
|
10
10
|
// ---------------------------------------------------------
|
11
11
|
|
12
|
-
void
|
13
|
-
wdm_memory_malloc (size_t);
|
12
|
+
void* wdm_memory_malloc (size_t);
|
14
13
|
|
15
|
-
void
|
16
|
-
wdm_memory_realloc (void *, size_t);
|
14
|
+
void* wdm_memory_realloc (void*, size_t);
|
17
15
|
|
18
16
|
// ---------------------------------------------------------
|
19
17
|
// Macros
|
data/ext/wdm/monitor.c
CHANGED
@@ -7,7 +7,8 @@
|
|
7
7
|
#include "monitor.h"
|
8
8
|
|
9
9
|
WDM_PMonitor
|
10
|
-
wdm_monitor_new()
|
10
|
+
wdm_monitor_new()
|
11
|
+
{
|
11
12
|
WDM_PMonitor monitor;
|
12
13
|
|
13
14
|
monitor = WDM_ALLOC(WDM_Monitor);
|
@@ -32,7 +33,8 @@ wdm_monitor_new() {
|
|
32
33
|
}
|
33
34
|
|
34
35
|
void
|
35
|
-
wdm_monitor_free(WDM_PMonitor monitor)
|
36
|
+
wdm_monitor_free(WDM_PMonitor monitor)
|
37
|
+
{
|
36
38
|
if ( monitor->monitoring_thread != INVALID_HANDLE_VALUE ) CloseHandle(monitor->monitoring_thread);
|
37
39
|
|
38
40
|
wdm_entry_list_free(monitor->head);
|
@@ -45,7 +47,8 @@ wdm_monitor_free(WDM_PMonitor monitor) {
|
|
45
47
|
}
|
46
48
|
|
47
49
|
void
|
48
|
-
wdm_monitor_update_head(WDM_PMonitor monitor, WDM_PEntry new_head)
|
50
|
+
wdm_monitor_update_head(WDM_PMonitor monitor, WDM_PEntry new_head)
|
51
|
+
{
|
49
52
|
EnterCriticalSection(&monitor->lock);
|
50
53
|
new_head->next = monitor->head;
|
51
54
|
monitor->head = new_head;
|
@@ -53,7 +56,8 @@ wdm_monitor_update_head(WDM_PMonitor monitor, WDM_PEntry new_head) {
|
|
53
56
|
}
|
54
57
|
|
55
58
|
WDM_PMonitorCallbackParam
|
56
|
-
wdm_monitor_callback_param_new(WDM_PMonitor monitor, WDM_PEntry entry)
|
59
|
+
wdm_monitor_callback_param_new(WDM_PMonitor monitor, WDM_PEntry entry)
|
60
|
+
{
|
57
61
|
WDM_PMonitorCallbackParam param;
|
58
62
|
|
59
63
|
param = WDM_ALLOC(WDM_MonitorCallbackParam);
|
@@ -65,6 +69,7 @@ wdm_monitor_callback_param_new(WDM_PMonitor monitor, WDM_PEntry entry) {
|
|
65
69
|
}
|
66
70
|
|
67
71
|
void
|
68
|
-
wdm_monitor_callback_param_free(WDM_PMonitorCallbackParam param)
|
72
|
+
wdm_monitor_callback_param_free(WDM_PMonitorCallbackParam param)
|
73
|
+
{
|
69
74
|
free(param);
|
70
75
|
}
|
data/ext/wdm/queue.c
CHANGED
@@ -5,11 +5,19 @@
|
|
5
5
|
#include "memory.h"
|
6
6
|
#include "queue.h"
|
7
7
|
|
8
|
+
// ---------------------------------------------------------
|
9
|
+
// Prototypes of static functions
|
10
|
+
// ---------------------------------------------------------
|
11
|
+
|
12
|
+
static WDM_PQueueItem do_queue_dequeue(WDM_PQueue queue);
|
13
|
+
|
8
14
|
// ---------------------------------------------------------
|
9
15
|
// Queue item functions
|
10
16
|
// ---------------------------------------------------------
|
11
17
|
|
12
|
-
WDM_PQueueItemError
|
18
|
+
WDM_PQueueItemError
|
19
|
+
wdm_queue_item_error_new(VALUE exception, LPCSTR format, ...)
|
20
|
+
{
|
13
21
|
WDM_PQueueItemError error;
|
14
22
|
va_list ap;
|
15
23
|
int length;
|
@@ -27,15 +35,19 @@ WDM_PQueueItemError wdm_queue_item_error_new(VALUE exception, LPCSTR format, ...
|
|
27
35
|
return error;
|
28
36
|
}
|
29
37
|
|
30
|
-
void
|
38
|
+
void
|
39
|
+
wdm_queue_item_error_free(WDM_PQueueItemError error)
|
40
|
+
{
|
31
41
|
if ( error->message != NULL ) free(error->message);
|
32
42
|
free(error);
|
33
43
|
}
|
34
44
|
|
35
|
-
WDM_PQueueItemData
|
45
|
+
WDM_PQueueItemData
|
46
|
+
wdm_queue_item_data_new()
|
47
|
+
{
|
36
48
|
WDM_PQueueItemData data;
|
37
49
|
|
38
|
-
data =
|
50
|
+
data = WDM_ALLOC(WDM_QueueItemData);
|
39
51
|
data->user_data = NULL;
|
40
52
|
|
41
53
|
ZeroMemory(&data->buffer, WDM_BUFFER_SIZE);
|
@@ -43,12 +55,15 @@ WDM_PQueueItemData wdm_queue_item_data_new() {
|
|
43
55
|
return data;
|
44
56
|
}
|
45
57
|
|
46
|
-
void
|
58
|
+
void
|
59
|
+
wdm_queue_item_data_free(WDM_PQueueItemData data)
|
60
|
+
{
|
47
61
|
free(data);
|
48
62
|
}
|
49
63
|
|
50
64
|
WDM_PQueueItem
|
51
|
-
wdm_queue_item_new(WDM_QueueItemType type)
|
65
|
+
wdm_queue_item_new(WDM_QueueItemType type)
|
66
|
+
{
|
52
67
|
WDM_PQueueItem item;
|
53
68
|
|
54
69
|
item = WDM_ALLOC(WDM_QueueItem);
|
@@ -61,14 +76,14 @@ wdm_queue_item_new(WDM_QueueItemType type) {
|
|
61
76
|
item->data = NULL;
|
62
77
|
}
|
63
78
|
|
64
|
-
item->previous = NULL;
|
65
79
|
item->next = NULL;
|
66
80
|
|
67
81
|
return item;
|
68
82
|
}
|
69
83
|
|
70
84
|
void
|
71
|
-
wdm_queue_item_free(WDM_PQueueItem item)
|
85
|
+
wdm_queue_item_free(WDM_PQueueItem item)
|
86
|
+
{
|
72
87
|
if ( item->type == WDM_QUEUE_ITEM_TYPE_ERROR ) {
|
73
88
|
if ( item->error != NULL ) wdm_queue_item_error_free(item->error);
|
74
89
|
}
|
@@ -76,8 +91,8 @@ wdm_queue_item_free(WDM_PQueueItem item) {
|
|
76
91
|
if ( item->data != NULL ) wdm_queue_item_data_free(item->data);
|
77
92
|
}
|
78
93
|
|
79
|
-
// We can't really do anything to the
|
80
|
-
//
|
94
|
+
// We can't really do anything to the next pointer because
|
95
|
+
// we might break any linking the user has established.
|
81
96
|
free(item);
|
82
97
|
}
|
83
98
|
|
@@ -86,7 +101,8 @@ wdm_queue_item_free(WDM_PQueueItem item) {
|
|
86
101
|
// ---------------------------------------------------------
|
87
102
|
|
88
103
|
WDM_PQueue
|
89
|
-
wdm_queue_new()
|
104
|
+
wdm_queue_new()
|
105
|
+
{
|
90
106
|
WDM_PQueue queue;
|
91
107
|
|
92
108
|
queue = WDM_ALLOC(WDM_Queue);
|
@@ -103,21 +119,22 @@ wdm_queue_new() {
|
|
103
119
|
}
|
104
120
|
|
105
121
|
void
|
106
|
-
wdm_queue_free(WDM_PQueue queue)
|
122
|
+
wdm_queue_free(WDM_PQueue queue)
|
123
|
+
{
|
107
124
|
wdm_queue_empty(queue);
|
108
125
|
free(queue);
|
109
126
|
}
|
110
127
|
|
111
128
|
void
|
112
|
-
wdm_queue_enqueue(WDM_PQueue queue, WDM_PQueueItem item)
|
129
|
+
wdm_queue_enqueue(WDM_PQueue queue, WDM_PQueueItem item)
|
130
|
+
{
|
113
131
|
EnterCriticalSection(&queue->lock);
|
114
132
|
|
115
|
-
if ( queue
|
133
|
+
if ( wdm_queue_is_empty(queue) ) {
|
116
134
|
queue->front = queue->rear = item;
|
117
135
|
}
|
118
136
|
else {
|
119
137
|
queue->rear->next = item;
|
120
|
-
item->previous = queue->rear;
|
121
138
|
queue->rear = item;
|
122
139
|
}
|
123
140
|
|
@@ -125,12 +142,11 @@ wdm_queue_enqueue(WDM_PQueue queue, WDM_PQueueItem item) {
|
|
125
142
|
}
|
126
143
|
|
127
144
|
WDM_PQueueItem
|
128
|
-
|
145
|
+
do_queue_dequeue(WDM_PQueue queue)
|
146
|
+
{
|
129
147
|
WDM_PQueueItem item;
|
130
148
|
|
131
|
-
|
132
|
-
|
133
|
-
if ( queue->rear == NULL && queue->front == NULL ) {
|
149
|
+
if ( wdm_queue_is_empty(queue) ) {
|
134
150
|
item = NULL;
|
135
151
|
}
|
136
152
|
else {
|
@@ -141,21 +157,40 @@ wdm_queue_dequeue(WDM_PQueue queue) {
|
|
141
157
|
if ( queue->front == NULL ) queue->rear = NULL;
|
142
158
|
|
143
159
|
// Don't allow the user to mess with the queue
|
144
|
-
item->
|
160
|
+
item->next = NULL;
|
145
161
|
}
|
146
162
|
|
163
|
+
return item;
|
164
|
+
}
|
165
|
+
|
166
|
+
WDM_PQueueItem
|
167
|
+
wdm_queue_dequeue(WDM_PQueue queue)
|
168
|
+
{
|
169
|
+
WDM_PQueueItem item;
|
170
|
+
|
171
|
+
EnterCriticalSection(&queue->lock);
|
172
|
+
|
173
|
+
item = do_queue_dequeue(queue);
|
174
|
+
|
147
175
|
LeaveCriticalSection(&queue->lock);
|
148
176
|
|
149
177
|
return item;
|
150
178
|
}
|
151
179
|
|
152
|
-
void
|
180
|
+
void
|
181
|
+
wdm_queue_empty(WDM_PQueue queue)
|
182
|
+
{
|
183
|
+
EnterCriticalSection(&queue->lock);
|
184
|
+
|
153
185
|
while( ! wdm_queue_is_empty(queue) ) {
|
154
|
-
wdm_queue_item_free(
|
186
|
+
wdm_queue_item_free( do_queue_dequeue(queue) );
|
155
187
|
}
|
188
|
+
|
189
|
+
LeaveCriticalSection(&queue->lock);
|
156
190
|
}
|
157
191
|
|
158
|
-
BOOL
|
159
|
-
wdm_queue_is_empty(WDM_PQueue queue)
|
192
|
+
inline BOOL
|
193
|
+
wdm_queue_is_empty(WDM_PQueue queue)
|
194
|
+
{
|
160
195
|
return queue->front == NULL && queue->rear == NULL;
|
161
196
|
}
|
data/ext/wdm/queue.h
CHANGED
@@ -34,11 +34,10 @@ typedef struct WDM_QueueItem {
|
|
34
34
|
WDM_PQueueItemData data;
|
35
35
|
WDM_PQueueItemError error;
|
36
36
|
};
|
37
|
-
struct WDM_QueueItem* previous;
|
38
37
|
struct WDM_QueueItem* next;
|
39
38
|
} WDM_QueueItem, *WDM_PQueueItem;
|
40
39
|
|
41
|
-
typedef struct
|
40
|
+
typedef struct {
|
42
41
|
CRITICAL_SECTION lock;
|
43
42
|
WDM_PQueueItem front;
|
44
43
|
WDM_PQueueItem rear;
|
data/ext/wdm/rb_change.c
CHANGED
@@ -11,7 +11,7 @@
|
|
11
11
|
// Internal constants
|
12
12
|
// ---------------------------------------------------------
|
13
13
|
|
14
|
-
// The _wsplitpat constants account for two NULL chars, so
|
14
|
+
// The _wsplitpat constants account for two NULL chars, so subtract 1 because we only need one!
|
15
15
|
#define WDM_MAX_FILENAME (_MAX_FNAME + _MAX_EXT - 1)
|
16
16
|
|
17
17
|
// ----------------------------------------------------------
|
@@ -38,10 +38,11 @@ static VALUE extract_change_type_from_notification(const PFILE_NOTIFY_INFORMATIO
|
|
38
38
|
// ----------------------------------------------------------
|
39
39
|
|
40
40
|
// TODO:
|
41
|
-
// 1. this function uses a lot of 'alloca' calls, which AFAIK is not
|
41
|
+
// 1. this function uses a lot of 'alloca' calls, which AFAIK is not recommended! Can this be avoided?
|
42
42
|
// 2. all wcscat calls can be done faster with memcpy, but is it worth sacrificing the readability?
|
43
43
|
static VALUE
|
44
|
-
extract_absolute_path_from_notification(const LPWSTR base_dir, const PFILE_NOTIFY_INFORMATION info)
|
44
|
+
extract_absolute_path_from_notification(const LPWSTR base_dir, const PFILE_NOTIFY_INFORMATION info)
|
45
|
+
{
|
45
46
|
LPWSTR buffer, absolute_filepath;
|
46
47
|
WCHAR file[_MAX_FNAME], ext[_MAX_EXT], filename[WDM_MAX_FILENAME];
|
47
48
|
DWORD filename_len, absolute_filepath_len;
|
@@ -115,7 +116,7 @@ extract_absolute_path_from_notification(const LPWSTR base_dir, const PFILE_NOTIF
|
|
115
116
|
}
|
116
117
|
}
|
117
118
|
|
118
|
-
// The convention in Ruby is to use forward-slashes to
|
119
|
+
// The convention in Ruby is to use forward-slashes to separate dirs on all platforms.
|
119
120
|
wdm_utils_convert_back_to_forward_slashes(absolute_filepath, absolute_filepath_len + 1);
|
120
121
|
|
121
122
|
// Convert the path from WCHAR to multibyte CHAR to use it in a ruby string
|
@@ -141,7 +142,8 @@ extract_absolute_path_from_notification(const LPWSTR base_dir, const PFILE_NOTIF
|
|
141
142
|
}
|
142
143
|
|
143
144
|
static VALUE
|
144
|
-
extract_change_type_from_notification(const PFILE_NOTIFY_INFORMATION info)
|
145
|
+
extract_change_type_from_notification(const PFILE_NOTIFY_INFORMATION info)
|
146
|
+
{
|
145
147
|
ID type;
|
146
148
|
|
147
149
|
switch(info->Action) {
|
@@ -162,7 +164,8 @@ extract_change_type_from_notification(const PFILE_NOTIFY_INFORMATION info) {
|
|
162
164
|
}
|
163
165
|
|
164
166
|
VALUE
|
165
|
-
wdm_rb_change_new_from_notification(const LPWSTR base_dir, const PFILE_NOTIFY_INFORMATION info)
|
167
|
+
wdm_rb_change_new_from_notification(const LPWSTR base_dir, const PFILE_NOTIFY_INFORMATION info)
|
168
|
+
{
|
166
169
|
VALUE change;
|
167
170
|
|
168
171
|
change = rb_class_new_instance(0, NULL, cWDM_Change);
|
@@ -177,7 +180,8 @@ wdm_rb_change_new_from_notification(const LPWSTR base_dir, const PFILE_NOTIFY_IN
|
|
177
180
|
}
|
178
181
|
|
179
182
|
void
|
180
|
-
wdm_rb_change_init()
|
183
|
+
wdm_rb_change_init()
|
184
|
+
{
|
181
185
|
WDM_DEBUG("Registering WDM::Event with Ruby!");
|
182
186
|
|
183
187
|
wdm_rb_sym_at_path = rb_intern("@path");
|
data/ext/wdm/rb_monitor.c
CHANGED
@@ -60,7 +60,8 @@ static VALUE rb_monitor_stop(VALUE);
|
|
60
60
|
// ----------------------------------------------------------
|
61
61
|
|
62
62
|
static void
|
63
|
-
monitor_mark(LPVOID param)
|
63
|
+
monitor_mark(LPVOID param)
|
64
|
+
{
|
64
65
|
WDM_PMonitor monitor;
|
65
66
|
WDM_PEntry entry;
|
66
67
|
|
@@ -74,7 +75,8 @@ monitor_mark(LPVOID param) {
|
|
74
75
|
}
|
75
76
|
|
76
77
|
static void
|
77
|
-
monitor_free(LPVOID param)
|
78
|
+
monitor_free(LPVOID param)
|
79
|
+
{
|
78
80
|
WDM_PMonitor monitor;
|
79
81
|
WDM_PEntry entry;
|
80
82
|
|
@@ -98,7 +100,8 @@ monitor_free(LPVOID param) {
|
|
98
100
|
}
|
99
101
|
|
100
102
|
static VALUE
|
101
|
-
rb_monitor_alloc(VALUE self)
|
103
|
+
rb_monitor_alloc(VALUE self)
|
104
|
+
{
|
102
105
|
WDM_DEBUG("--------------------------------");
|
103
106
|
WDM_DEBUG("Allocating a new monitor object!");
|
104
107
|
WDM_DEBUG("--------------------------------");
|
@@ -107,10 +110,11 @@ rb_monitor_alloc(VALUE self) {
|
|
107
110
|
}
|
108
111
|
|
109
112
|
static DWORD
|
110
|
-
id_to_flag(ID id)
|
113
|
+
id_to_flag(ID id)
|
114
|
+
{
|
111
115
|
if ( id == wdm_rb_sym_default ) return WDM_MONITOR_FLAGS_DEFAULT;
|
112
116
|
|
113
|
-
// TODO: Maybe reorder the if's in the
|
117
|
+
// TODO: Maybe reorder the if's in the frequency of use for better performance?
|
114
118
|
if ( id == wdm_rb_sym_files ) return FILE_NOTIFY_CHANGE_FILE_NAME;
|
115
119
|
if ( id == wdm_rb_sym_directories ) return FILE_NOTIFY_CHANGE_DIR_NAME;
|
116
120
|
if ( id == wdm_rb_sym_attributes ) return FILE_NOTIFY_CHANGE_ATTRIBUTES;
|
@@ -124,7 +128,8 @@ id_to_flag(ID id) {
|
|
124
128
|
}
|
125
129
|
|
126
130
|
static DWORD
|
127
|
-
extract_flags_from_rb_array(VALUE flags_array)
|
131
|
+
extract_flags_from_rb_array(VALUE flags_array)
|
132
|
+
{
|
128
133
|
VALUE flag_symbol;
|
129
134
|
DWORD flags;
|
130
135
|
|
@@ -140,7 +145,8 @@ extract_flags_from_rb_array(VALUE flags_array) {
|
|
140
145
|
}
|
141
146
|
|
142
147
|
static VALUE
|
143
|
-
combined_watch(BOOL recursively, int argc, VALUE *argv, VALUE self)
|
148
|
+
combined_watch(BOOL recursively, int argc, VALUE *argv, VALUE self)
|
149
|
+
{
|
144
150
|
WDM_PMonitor monitor;
|
145
151
|
WDM_PEntry entry;
|
146
152
|
int directory_letters_count;
|
@@ -237,12 +243,14 @@ combined_watch(BOOL recursively, int argc, VALUE *argv, VALUE self) {
|
|
237
243
|
}
|
238
244
|
|
239
245
|
static VALUE
|
240
|
-
rb_monitor_watch(int argc, VALUE *argv, VALUE self)
|
246
|
+
rb_monitor_watch(int argc, VALUE *argv, VALUE self)
|
247
|
+
{
|
241
248
|
return combined_watch(FALSE, argc, argv, self);
|
242
249
|
}
|
243
250
|
|
244
251
|
static VALUE
|
245
|
-
rb_monitor_watch_recursively(int argc, VALUE *argv, VALUE self)
|
252
|
+
rb_monitor_watch_recursively(int argc, VALUE *argv, VALUE self)
|
253
|
+
{
|
246
254
|
return combined_watch(TRUE, argc, argv, self);
|
247
255
|
}
|
248
256
|
|
@@ -256,7 +264,7 @@ handle_entry_change(
|
|
256
264
|
WDM_PQueueItem data_to_process;
|
257
265
|
|
258
266
|
if ( err_code == ERROR_OPERATION_ABORTED ) {
|
259
|
-
// Async operation was
|
267
|
+
// Async operation was canceled. This shouldn't happen.
|
260
268
|
// TODO:
|
261
269
|
// 1. Maybe add a union in the queue for errors?
|
262
270
|
// 2. What's the best action when this happens?
|
@@ -293,7 +301,8 @@ handle_entry_change(
|
|
293
301
|
}
|
294
302
|
|
295
303
|
static BOOL
|
296
|
-
register_monitoring_entry(WDM_PEntry entry)
|
304
|
+
register_monitoring_entry(WDM_PEntry entry)
|
305
|
+
{
|
297
306
|
BOOL success;
|
298
307
|
DWORD bytes;
|
299
308
|
bytes = 0; // Not used because the process callback gets passed the written bytes
|
@@ -318,7 +327,8 @@ register_monitoring_entry(WDM_PEntry entry) {
|
|
318
327
|
}
|
319
328
|
|
320
329
|
static DWORD WINAPI
|
321
|
-
start_monitoring(LPVOID param)
|
330
|
+
start_monitoring(LPVOID param)
|
331
|
+
{
|
322
332
|
WDM_PMonitor monitor;
|
323
333
|
WDM_PEntry curr_entry;
|
324
334
|
|
@@ -361,7 +371,8 @@ start_monitoring(LPVOID param) {
|
|
361
371
|
}
|
362
372
|
|
363
373
|
static VALUE
|
364
|
-
wait_for_changes(LPVOID param)
|
374
|
+
wait_for_changes(LPVOID param)
|
375
|
+
{
|
365
376
|
HANDLE process_event;
|
366
377
|
|
367
378
|
process_event = (HANDLE)param;
|
@@ -370,7 +381,8 @@ wait_for_changes(LPVOID param) {
|
|
370
381
|
}
|
371
382
|
|
372
383
|
static void
|
373
|
-
process_changes(WDM_PQueue changes)
|
384
|
+
process_changes(WDM_PQueue changes)
|
385
|
+
{
|
374
386
|
WDM_PQueueItem item;
|
375
387
|
LPBYTE current_info_entry_offset;
|
376
388
|
PFILE_NOTIFY_INFORMATION info;
|
@@ -412,7 +424,8 @@ process_changes(WDM_PQueue changes) {
|
|
412
424
|
}
|
413
425
|
|
414
426
|
static void
|
415
|
-
stop_monitoring(LPVOID param)
|
427
|
+
stop_monitoring(LPVOID param)
|
428
|
+
{
|
416
429
|
BOOL already_stopped;
|
417
430
|
WDM_PMonitor monitor;
|
418
431
|
WDM_PEntry entry;
|
@@ -449,8 +462,8 @@ stop_monitoring(LPVOID param) {
|
|
449
462
|
}
|
450
463
|
|
451
464
|
static VALUE
|
452
|
-
rb_monitor_run_bang(VALUE self)
|
453
|
-
|
465
|
+
rb_monitor_run_bang(VALUE self)
|
466
|
+
{
|
454
467
|
BOOL already_running,
|
455
468
|
waiting_succeeded;
|
456
469
|
WDM_PMonitor monitor;
|
@@ -484,7 +497,7 @@ rb_monitor_run_bang(VALUE self) {
|
|
484
497
|
start_monitoring, // thread function name
|
485
498
|
monitor, // argument to thread function
|
486
499
|
0, // use default creation flags
|
487
|
-
|
500
|
+
NULL // Ignore thread identifier
|
488
501
|
);
|
489
502
|
|
490
503
|
if ( monitor->monitoring_thread == NULL ) {
|
@@ -514,7 +527,8 @@ rb_monitor_run_bang(VALUE self) {
|
|
514
527
|
}
|
515
528
|
|
516
529
|
static VALUE
|
517
|
-
rb_monitor_stop(VALUE self)
|
530
|
+
rb_monitor_stop(VALUE self)
|
531
|
+
{
|
518
532
|
WDM_PMonitor monitor;
|
519
533
|
|
520
534
|
Data_Get_Struct(self, WDM_Monitor, monitor);
|
@@ -527,7 +541,8 @@ rb_monitor_stop(VALUE self) {
|
|
527
541
|
}
|
528
542
|
|
529
543
|
void
|
530
|
-
wdm_rb_monitor_init()
|
544
|
+
wdm_rb_monitor_init()
|
545
|
+
{
|
531
546
|
WDM_DEBUG("Registering WDM::Monitor with Ruby!");
|
532
547
|
|
533
548
|
wdm_rb_sym_call = rb_intern("call");
|
data/ext/wdm/utils.c
CHANGED
@@ -8,7 +8,8 @@
|
|
8
8
|
// ---------------------------------------------------------
|
9
9
|
|
10
10
|
LPWSTR
|
11
|
-
wdm_utils_convert_back_to_forward_slashes(LPWSTR path, DWORD path_len)
|
11
|
+
wdm_utils_convert_back_to_forward_slashes(LPWSTR path, DWORD path_len)
|
12
|
+
{
|
12
13
|
UINT i;
|
13
14
|
|
14
15
|
for(i = 0; i < (path_len - 1); ++i) { // path_len-1 because we don't need to check the NULL-char!
|
@@ -19,7 +20,8 @@ wdm_utils_convert_back_to_forward_slashes(LPWSTR path, DWORD path_len) {
|
|
19
20
|
}
|
20
21
|
|
21
22
|
LPWSTR
|
22
|
-
wdm_utils_full_pathname(const LPWSTR path)
|
23
|
+
wdm_utils_full_pathname(const LPWSTR path)
|
24
|
+
{
|
23
25
|
WCHAR maxed_path[WDM_MAX_WCHAR_LONG_PATH];
|
24
26
|
LPWSTR full_path;
|
25
27
|
size_t full_path_len;
|
@@ -42,7 +44,8 @@ wdm_utils_full_pathname(const LPWSTR path) {
|
|
42
44
|
}
|
43
45
|
|
44
46
|
BOOL
|
45
|
-
wdm_utils_unicode_is_directory(const LPWSTR path)
|
47
|
+
wdm_utils_unicode_is_directory(const LPWSTR path)
|
48
|
+
{
|
46
49
|
WCHAR unicode_path[WDM_MAX_WCHAR_LONG_PATH];
|
47
50
|
|
48
51
|
wcscpy(unicode_path, L"\\\\?\\");
|
@@ -59,7 +62,8 @@ wdm_utils_unicode_is_directory(const LPWSTR path) {
|
|
59
62
|
}
|
60
63
|
|
61
64
|
BOOL
|
62
|
-
wdm_utils_is_directory(const LPWSTR path)
|
65
|
+
wdm_utils_is_directory(const LPWSTR path)
|
66
|
+
{
|
63
67
|
DWORD dwAttrib = GetFileAttributesW(path);
|
64
68
|
|
65
69
|
return (dwAttrib != INVALID_FILE_ATTRIBUTES &&
|
@@ -67,6 +71,7 @@ wdm_utils_is_directory(const LPWSTR path) {
|
|
67
71
|
}
|
68
72
|
|
69
73
|
BOOL
|
70
|
-
wdm_utils_is_unc_path(const LPWSTR path)
|
74
|
+
wdm_utils_is_unc_path(const LPWSTR path)
|
75
|
+
{
|
71
76
|
return path[0] == path[1] && path[0] == L'\\';
|
72
77
|
}
|
data/ext/wdm/wdm.c
CHANGED
data/ext/wdm/wdm.h
CHANGED
@@ -57,6 +57,12 @@ extern VALUE eWDM_Error;
|
|
57
57
|
|
58
58
|
extern rb_encoding *wdm_rb_enc_utf8;
|
59
59
|
|
60
|
+
// ---------------------------------------------------------
|
61
|
+
// Prototypes
|
62
|
+
// ---------------------------------------------------------
|
63
|
+
|
64
|
+
void Init_wdm_ext();
|
65
|
+
|
60
66
|
// ---------------------------------------------------------
|
61
67
|
|
62
68
|
#ifdef __cplusplus
|
data/ext/wdm/wdm.vcxproj
CHANGED
@@ -20,12 +20,14 @@
|
|
20
20
|
<ConfigurationType>Application</ConfigurationType>
|
21
21
|
<UseDebugLibraries>true</UseDebugLibraries>
|
22
22
|
<CharacterSet>Unicode</CharacterSet>
|
23
|
+
<PlatformToolset>v110</PlatformToolset>
|
23
24
|
</PropertyGroup>
|
24
25
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
|
25
26
|
<ConfigurationType>Application</ConfigurationType>
|
26
27
|
<UseDebugLibraries>false</UseDebugLibraries>
|
27
28
|
<WholeProgramOptimization>true</WholeProgramOptimization>
|
28
29
|
<CharacterSet>Unicode</CharacterSet>
|
30
|
+
<PlatformToolset>v110</PlatformToolset>
|
29
31
|
</PropertyGroup>
|
30
32
|
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
31
33
|
<ImportGroup Label="ExtensionSettings">
|
data/lib/wdm.rb
ADDED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: wdm
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2013-02-11 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rake-compiler
|
@@ -153,6 +153,7 @@ files:
|
|
153
153
|
- ext/wdm/wdm.sln
|
154
154
|
- ext/wdm/wdm.vcxproj
|
155
155
|
- ext/wdm/wdm.vcxproj.filters
|
156
|
+
- lib/wdm.rb
|
156
157
|
- LICENSE
|
157
158
|
- README.md
|
158
159
|
homepage: https://github.com/Maher4Ever/wdm
|
@@ -173,9 +174,6 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
173
174
|
- - ! '>='
|
174
175
|
- !ruby/object:Gem::Version
|
175
176
|
version: '0'
|
176
|
-
segments:
|
177
|
-
- 0
|
178
|
-
hash: 341068911
|
179
177
|
requirements: []
|
180
178
|
rubyforge_project:
|
181
179
|
rubygems_version: 1.8.24
|