wdm 0.0.3 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|