watchcat 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Cargo.lock +697 -0
- data/Cargo.toml +7 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +70 -0
- data/LICENSE.txt +21 -0
- data/README.md +103 -0
- data/Rakefile +45 -0
- data/ext/watchcat/.gitignore +1 -0
- data/ext/watchcat/Cargo.lock +494 -0
- data/ext/watchcat/Cargo.toml +15 -0
- data/ext/watchcat/extconf.rb +4 -0
- data/ext/watchcat/src/event.rs +135 -0
- data/ext/watchcat/src/lib.rs +228 -0
- data/lib/watchcat/client.rb +26 -0
- data/lib/watchcat/event.rb +63 -0
- data/lib/watchcat/executor.rb +64 -0
- data/lib/watchcat/kind.rb +200 -0
- data/lib/watchcat/server.rb +14 -0
- data/lib/watchcat/version.rb +3 -0
- data/lib/watchcat.rb +37 -0
- data/watchcat.gemspec +38 -0
- metadata +193 -0
@@ -0,0 +1,228 @@
|
|
1
|
+
use crossbeam_channel::{select, unbounded};
|
2
|
+
use magnus::{
|
3
|
+
block::{block_given, yield_value},
|
4
|
+
class::object,
|
5
|
+
define_module, function, method,
|
6
|
+
scan_args::{get_kwargs, scan_args},
|
7
|
+
Error, Module, Object, Value,
|
8
|
+
};
|
9
|
+
use notify::{Config, PollWatcher, RecommendedWatcher, RecursiveMode, Watcher};
|
10
|
+
use notify_debouncer_mini::new_debouncer;
|
11
|
+
use std::{path::Path, time::Duration};
|
12
|
+
|
13
|
+
mod event;
|
14
|
+
use crate::event::WatchatEvent;
|
15
|
+
|
16
|
+
#[magnus::wrap(class = "Watchcat::Watcher")]
|
17
|
+
struct WatchcatWatcher {
|
18
|
+
tx: crossbeam_channel::Sender<bool>,
|
19
|
+
rx: crossbeam_channel::Receiver<bool>,
|
20
|
+
}
|
21
|
+
|
22
|
+
#[derive(Debug)]
|
23
|
+
enum WatcherEnum {
|
24
|
+
#[allow(dead_code)]
|
25
|
+
Poll(PollWatcher),
|
26
|
+
#[allow(dead_code)]
|
27
|
+
Recommended(RecommendedWatcher),
|
28
|
+
}
|
29
|
+
|
30
|
+
impl WatchcatWatcher {
|
31
|
+
fn new() -> Self {
|
32
|
+
let (tx_executor, rx_executor) = unbounded::<bool>();
|
33
|
+
Self {
|
34
|
+
tx: tx_executor,
|
35
|
+
rx: rx_executor,
|
36
|
+
}
|
37
|
+
}
|
38
|
+
|
39
|
+
fn close(&self) {
|
40
|
+
self.tx.send(true).unwrap()
|
41
|
+
}
|
42
|
+
|
43
|
+
fn watch(&self, args: &[Value]) -> Result<bool, Error> {
|
44
|
+
if !block_given() {
|
45
|
+
return Err(Error::new(magnus::exception::arg_error(), "no block given"));
|
46
|
+
}
|
47
|
+
|
48
|
+
let (pathnames, recursive, force_polling, poll_interval, ignore_remove, debounce) = Self::parse_args(args)?;
|
49
|
+
let mode = if recursive {
|
50
|
+
RecursiveMode::Recursive
|
51
|
+
} else {
|
52
|
+
RecursiveMode::NonRecursive
|
53
|
+
};
|
54
|
+
|
55
|
+
if debounce >= 0 {
|
56
|
+
self.watch_with_debounce(pathnames, mode, ignore_remove, debounce)
|
57
|
+
} else {
|
58
|
+
self.watch_without_debounce(pathnames, mode, force_polling, poll_interval, ignore_remove)
|
59
|
+
}
|
60
|
+
}
|
61
|
+
|
62
|
+
fn watch_without_debounce(&self, pathnames: Vec<String>, mode: RecursiveMode, force_polling: bool, poll_interval: u64, ignore_remove: bool) -> Result<bool, Error> {
|
63
|
+
let (tx, rx) = unbounded();
|
64
|
+
// This variable is needed to keep `watcher` active.
|
65
|
+
let _watcher = match force_polling {
|
66
|
+
true => {
|
67
|
+
let delay = Duration::from_millis(poll_interval);
|
68
|
+
let config = notify::Config::default().with_poll_interval(delay);
|
69
|
+
let mut watcher = PollWatcher::new(tx, config)
|
70
|
+
.map_err(|e| Error::new(magnus::exception::arg_error(), e.to_string()))?;
|
71
|
+
for pathname in &pathnames {
|
72
|
+
let path = Path::new(pathname);
|
73
|
+
watcher
|
74
|
+
.watch(path, mode)
|
75
|
+
.map_err(|e| Error::new(magnus::exception::arg_error(), e.to_string()))?;
|
76
|
+
}
|
77
|
+
WatcherEnum::Poll(watcher)
|
78
|
+
}
|
79
|
+
false => {
|
80
|
+
let mut watcher = RecommendedWatcher::new(tx, Config::default())
|
81
|
+
.map_err(|e| Error::new(magnus::exception::arg_error(), e.to_string()))?;
|
82
|
+
for pathname in &pathnames {
|
83
|
+
let path = Path::new(pathname);
|
84
|
+
watcher
|
85
|
+
.watch(path, mode)
|
86
|
+
.map_err(|e| Error::new(magnus::exception::arg_error(), e.to_string()))?;
|
87
|
+
}
|
88
|
+
WatcherEnum::Recommended(watcher)
|
89
|
+
}
|
90
|
+
};
|
91
|
+
|
92
|
+
loop {
|
93
|
+
select! {
|
94
|
+
recv(self.rx) -> _res => {
|
95
|
+
return Ok(true)
|
96
|
+
}
|
97
|
+
recv(rx) -> res => {
|
98
|
+
match res {
|
99
|
+
Ok(event) => {
|
100
|
+
match event {
|
101
|
+
Ok(event) => {
|
102
|
+
let paths = event
|
103
|
+
.paths
|
104
|
+
.iter()
|
105
|
+
.map(|p| p.to_string_lossy().into_owned())
|
106
|
+
.collect::<Vec<_>>();
|
107
|
+
|
108
|
+
if ignore_remove && matches!(event.kind, notify::event::EventKind::Remove(_)) {
|
109
|
+
continue;
|
110
|
+
}
|
111
|
+
|
112
|
+
yield_value::<(Vec<String>, Vec<String>, String), Value>(
|
113
|
+
|
114
|
+
(WatchatEvent::convert_kind(&event.kind), paths, format!("{:?}", event.kind))
|
115
|
+
)?;
|
116
|
+
}
|
117
|
+
Err(e) => {
|
118
|
+
return Err(
|
119
|
+
Error::new(magnus::exception::runtime_error(), e.to_string())
|
120
|
+
)
|
121
|
+
}
|
122
|
+
}
|
123
|
+
}
|
124
|
+
Err(e) => {
|
125
|
+
return Err(
|
126
|
+
Error::new(magnus::exception::runtime_error(), e.to_string())
|
127
|
+
)
|
128
|
+
}
|
129
|
+
}
|
130
|
+
}
|
131
|
+
}
|
132
|
+
}
|
133
|
+
}
|
134
|
+
|
135
|
+
fn watch_with_debounce(&self, pathnames: Vec<String>, mode: RecursiveMode, ignore_remove: bool, debounce: i64) -> Result<bool, Error> {
|
136
|
+
let (tx, rx) = unbounded();
|
137
|
+
let mut debouncer = new_debouncer(Duration::from_millis(debounce.try_into().unwrap()), tx).unwrap();
|
138
|
+
for pathname in &pathnames {
|
139
|
+
let path = Path::new(pathname);
|
140
|
+
debouncer
|
141
|
+
.watcher()
|
142
|
+
.watch(path, mode)
|
143
|
+
.map_err(|e| Error::new(magnus::exception::arg_error(), e.to_string()))?;
|
144
|
+
}
|
145
|
+
|
146
|
+
loop {
|
147
|
+
select! {
|
148
|
+
recv(self.rx) -> _res => {
|
149
|
+
return Ok(true)
|
150
|
+
}
|
151
|
+
recv(rx) -> res => {
|
152
|
+
match res {
|
153
|
+
Ok(events) => {
|
154
|
+
match events {
|
155
|
+
Ok(events) => {
|
156
|
+
events.iter().for_each(|event| {
|
157
|
+
if ignore_remove && !Path::new(&event.path).exists() {
|
158
|
+
return;
|
159
|
+
}
|
160
|
+
|
161
|
+
yield_value::<(Vec<String>, Vec<String>, String), Value>(
|
162
|
+
(vec![], vec![event.path.to_string_lossy().into_owned()], format!("{:?}", event.kind))
|
163
|
+
).unwrap();
|
164
|
+
});
|
165
|
+
}
|
166
|
+
Err(e) => {
|
167
|
+
return Err(
|
168
|
+
Error::new(magnus::exception::runtime_error(), e.to_string())
|
169
|
+
)
|
170
|
+
}
|
171
|
+
}
|
172
|
+
}
|
173
|
+
Err(e) => {
|
174
|
+
return Err(
|
175
|
+
Error::new(magnus::exception::runtime_error(), e.to_string())
|
176
|
+
)
|
177
|
+
}
|
178
|
+
}
|
179
|
+
}
|
180
|
+
}
|
181
|
+
}
|
182
|
+
}
|
183
|
+
|
184
|
+
#[allow(clippy::let_unit_value, clippy::type_complexity)]
|
185
|
+
fn parse_args(args: &[Value]) -> Result<(Vec<String>, bool, bool, u64, bool, i64), Error> {
|
186
|
+
type KwArgBool = Option<Option<bool>>;
|
187
|
+
type KwArgU64 = Option<Option<u64>>;
|
188
|
+
type KwArgi64 = Option<Option<i64>>;
|
189
|
+
|
190
|
+
let args = scan_args(args)?;
|
191
|
+
let (paths,): (Vec<String>,) = args.required;
|
192
|
+
let _: () = args.optional;
|
193
|
+
let _: () = args.splat;
|
194
|
+
let _: () = args.trailing;
|
195
|
+
let _: () = args.block;
|
196
|
+
|
197
|
+
let kwargs = get_kwargs(
|
198
|
+
args.keywords,
|
199
|
+
&[],
|
200
|
+
&["recursive", "force_polling", "poll_interval", "ignore_remove", "debounce"],
|
201
|
+
)?;
|
202
|
+
let (recursive, force_polling, poll_interval, ignore_remove, debounce): (KwArgBool, KwArgBool, KwArgU64, KwArgBool, KwArgi64) =
|
203
|
+
kwargs.optional;
|
204
|
+
let _: () = kwargs.required;
|
205
|
+
let _: () = kwargs.splat;
|
206
|
+
|
207
|
+
Ok((
|
208
|
+
paths,
|
209
|
+
recursive.flatten().unwrap_or(false),
|
210
|
+
force_polling.flatten().unwrap_or(false),
|
211
|
+
poll_interval.flatten().unwrap_or(200),
|
212
|
+
ignore_remove.flatten().unwrap_or(false),
|
213
|
+
debounce.flatten().unwrap_or(-1),
|
214
|
+
))
|
215
|
+
}
|
216
|
+
}
|
217
|
+
|
218
|
+
#[magnus::init]
|
219
|
+
fn init() -> Result<(), Error> {
|
220
|
+
let module = define_module("Watchcat")?;
|
221
|
+
|
222
|
+
let watcher_class = module.define_class("Watcher", object())?;
|
223
|
+
watcher_class.define_singleton_method("new", function!(WatchcatWatcher::new, 0))?;
|
224
|
+
watcher_class.define_method("watch", method!(WatchcatWatcher::watch, -1))?;
|
225
|
+
watcher_class.define_method("close", method!(WatchcatWatcher::close, 0))?;
|
226
|
+
|
227
|
+
Ok(())
|
228
|
+
}
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Watchcat
|
2
|
+
class Client
|
3
|
+
def initialize(uri, paths:, recursive:, force_polling:, poll_interval:, ignore_remove:, debounce:)
|
4
|
+
DRb.start_service
|
5
|
+
@watcher = Watchcat::Watcher.new
|
6
|
+
@server = DRbObject.new_with_uri(uri)
|
7
|
+
@paths = paths
|
8
|
+
@recursive = recursive
|
9
|
+
@force_polling = force_polling
|
10
|
+
@poll_interval = poll_interval
|
11
|
+
@ignore_remove = ignore_remove
|
12
|
+
@debounce = debounce
|
13
|
+
end
|
14
|
+
|
15
|
+
def run
|
16
|
+
@watcher.watch(
|
17
|
+
@paths,
|
18
|
+
recursive: @recursive,
|
19
|
+
force_polling: @force_polling,
|
20
|
+
poll_interval: @poll_interval,
|
21
|
+
ignore_remove: @ignore_remove,
|
22
|
+
debounce: @debounce
|
23
|
+
) { |notification| @server.execute(notification) }
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require "watchcat/kind"
|
2
|
+
|
3
|
+
module Watchcat
|
4
|
+
class Event
|
5
|
+
attr_reader :kind, :paths, :raw_kind, :event
|
6
|
+
|
7
|
+
def initialize(kinds, paths, raw_kind)
|
8
|
+
@paths = paths
|
9
|
+
@raw_kind = raw_kind
|
10
|
+
build_kind(kinds)
|
11
|
+
end
|
12
|
+
|
13
|
+
def deconstruct_keys(_keys)
|
14
|
+
{ paths: @paths, event: @event }
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def build_kind(kinds)
|
20
|
+
@kind = Watchcat::EventKind.new
|
21
|
+
@event = kinds.shift
|
22
|
+
if event
|
23
|
+
@kind.public_send("#{event}=", Object.const_get("Watchcat::#{event.capitalize}Kind").new)
|
24
|
+
send("build_#{event}_kind", kinds)
|
25
|
+
else
|
26
|
+
@kind.any = Watchcat::AnyKind.new
|
27
|
+
if File.directory?(@paths.first)
|
28
|
+
@kind.any.kind = "folder"
|
29
|
+
else
|
30
|
+
@kind.any.kind = "file"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def build_access_kind(kinds)
|
36
|
+
@kind.access.kind = kinds.shift
|
37
|
+
|
38
|
+
if @kind.access.open? || @kind.access.close?
|
39
|
+
@kind.access.access_mode = Watchcat::AccessMode.new(kinds.shift)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def build_create_kind(kinds)
|
44
|
+
@kind.create.kind = kinds.shift
|
45
|
+
end
|
46
|
+
|
47
|
+
def build_modify_kind(kinds)
|
48
|
+
@kind.modify.kind = kinds.shift
|
49
|
+
|
50
|
+
if @kind.modify.data_change?
|
51
|
+
@kind.modify.data_change = Watchcat::DataChange.new(kinds.shift)
|
52
|
+
elsif @kind.modify.metadata?
|
53
|
+
@kind.modify.metadata = Watchcat::MetadataKind.new(kinds.shift)
|
54
|
+
elsif @kind.modify.rename?
|
55
|
+
@kind.modify.rename = Watchcat::RenameMode.new(kinds.shift)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def build_remove_kind(kinds)
|
60
|
+
@kind.remove.kind = kinds.shift
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require "drb"
|
2
|
+
require "drb/unix"
|
3
|
+
require_relative "server"
|
4
|
+
require_relative "client"
|
5
|
+
|
6
|
+
module Watchcat
|
7
|
+
class Executor
|
8
|
+
def initialize(paths, recursive:, force_polling:, poll_interval:, wait_until_startup:, ignore_remove:, debounce:, block:)
|
9
|
+
@service = nil
|
10
|
+
@child_pid = nil
|
11
|
+
@paths = paths
|
12
|
+
@recursive = recursive
|
13
|
+
@force_polling = force_polling
|
14
|
+
@poll_interval = poll_interval
|
15
|
+
@wait_until_startup = wait_until_startup
|
16
|
+
@ignore_remove = ignore_remove
|
17
|
+
@debounce = debounce
|
18
|
+
@block = block
|
19
|
+
end
|
20
|
+
|
21
|
+
def start
|
22
|
+
server = Server.new(@block)
|
23
|
+
@service = DRb.start_service("drbunix:", server)
|
24
|
+
client = nil
|
25
|
+
client = build_client if @wait_until_startup
|
26
|
+
|
27
|
+
@child_pid = fork do
|
28
|
+
client = build_client unless @wait_until_startup
|
29
|
+
Process.setproctitle("watchcat: watcher")
|
30
|
+
client.run
|
31
|
+
end
|
32
|
+
|
33
|
+
main = Process.pid
|
34
|
+
at_exit do
|
35
|
+
@exit_status = $!.status if $!.is_a?(SystemExit)
|
36
|
+
stop if Process.pid == main
|
37
|
+
exit @exit_status if @exit_status
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def stop
|
42
|
+
begin
|
43
|
+
Process.kill(:KILL, @child_pid)
|
44
|
+
rescue Errno::ESRCH
|
45
|
+
# NOTE: We can ignore this error because there process is already dead.
|
46
|
+
end
|
47
|
+
@service.stop_service
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def build_client
|
53
|
+
Client.new(
|
54
|
+
@service.uri,
|
55
|
+
paths: @paths,
|
56
|
+
recursive: @recursive,
|
57
|
+
force_polling: @force_polling,
|
58
|
+
poll_interval: @poll_interval,
|
59
|
+
debounce: @debounce,
|
60
|
+
ignore_remove: @ignore_remove
|
61
|
+
)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,200 @@
|
|
1
|
+
require "forwardable"
|
2
|
+
|
3
|
+
module Watchcat
|
4
|
+
class EventKind
|
5
|
+
attr_accessor :access, :create, :modify, :remove, :any
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@access, @create, @modify, @remove, @any = nil, nil, nil, nil,nil
|
9
|
+
end
|
10
|
+
|
11
|
+
def access?
|
12
|
+
!@access.nil?
|
13
|
+
end
|
14
|
+
|
15
|
+
def create?
|
16
|
+
!@create.nil?
|
17
|
+
end
|
18
|
+
|
19
|
+
def modify?
|
20
|
+
!@modify.nil?
|
21
|
+
end
|
22
|
+
|
23
|
+
def remove?
|
24
|
+
!@remove.nil?
|
25
|
+
end
|
26
|
+
|
27
|
+
def any?
|
28
|
+
!@any.nil?
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
class AccessKind
|
33
|
+
extend Forwardable
|
34
|
+
|
35
|
+
attr_accessor :kind, :access_mode
|
36
|
+
delegate [:excute_mode?, :read_mode?, :write_mode?] => :@access_mode
|
37
|
+
|
38
|
+
def initialize
|
39
|
+
@kind, @access_mode = nil, nil
|
40
|
+
end
|
41
|
+
|
42
|
+
def read?
|
43
|
+
@kind == "read"
|
44
|
+
end
|
45
|
+
|
46
|
+
def open?
|
47
|
+
@kind == "open"
|
48
|
+
end
|
49
|
+
|
50
|
+
def close?
|
51
|
+
@kind == "close"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
class CreateKind
|
56
|
+
attr_accessor :kind
|
57
|
+
|
58
|
+
def file?
|
59
|
+
@kind == "file"
|
60
|
+
end
|
61
|
+
|
62
|
+
def folder?
|
63
|
+
@kind == "folder"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
class ModifyKind
|
68
|
+
extend Forwardable
|
69
|
+
|
70
|
+
attr_accessor :kind, :data_change, :metadata, :rename
|
71
|
+
delegate [:size?, :content?] => :@data_change
|
72
|
+
delegate [:access_time?, :write_time?, :permission?, :ownership?, :extended?] => :@metadata
|
73
|
+
delegate [:from?, :to?, :both?] => :@rename
|
74
|
+
|
75
|
+
def initialize
|
76
|
+
@kind, @data_change, @metadata, @rename = nil, nil, nil, nil
|
77
|
+
end
|
78
|
+
|
79
|
+
def data_change?
|
80
|
+
@kind == "data_change"
|
81
|
+
end
|
82
|
+
|
83
|
+
def metadata?
|
84
|
+
@kind == "metadata"
|
85
|
+
end
|
86
|
+
|
87
|
+
def rename?
|
88
|
+
@kind == "rename"
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
class RemoveKind
|
93
|
+
attr_accessor :kind
|
94
|
+
|
95
|
+
def file?
|
96
|
+
@kind == "file"
|
97
|
+
end
|
98
|
+
|
99
|
+
def folder?
|
100
|
+
@kind == "folder"
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
class AnyKind
|
105
|
+
attr_accessor :kind
|
106
|
+
|
107
|
+
def file?
|
108
|
+
@kind == "file"
|
109
|
+
end
|
110
|
+
|
111
|
+
def folder?
|
112
|
+
@kind == "folder"
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
|
117
|
+
class AccessMode
|
118
|
+
attr_accessor :mode
|
119
|
+
|
120
|
+
def initialize(mode)
|
121
|
+
@mode = mode
|
122
|
+
end
|
123
|
+
|
124
|
+
def execute_mode?
|
125
|
+
@mode == "execute"
|
126
|
+
end
|
127
|
+
|
128
|
+
def read_mode?
|
129
|
+
@mode == "read"
|
130
|
+
end
|
131
|
+
|
132
|
+
def write_mode?
|
133
|
+
@mode == "write"
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
class DataChange
|
138
|
+
attr_accessor :kind
|
139
|
+
|
140
|
+
def initialize(kind)
|
141
|
+
@kind = kind
|
142
|
+
end
|
143
|
+
|
144
|
+
def size?
|
145
|
+
@kind == "size"
|
146
|
+
end
|
147
|
+
|
148
|
+
def content?
|
149
|
+
@kind == "content"
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
class MetadataKind
|
154
|
+
attr_accessor :kind
|
155
|
+
|
156
|
+
def initialize(kind)
|
157
|
+
@kind = kind
|
158
|
+
end
|
159
|
+
|
160
|
+
def access_time?
|
161
|
+
@kind == "access_time"
|
162
|
+
end
|
163
|
+
|
164
|
+
def write_time?
|
165
|
+
@kind == "write_time"
|
166
|
+
end
|
167
|
+
|
168
|
+
def permission?
|
169
|
+
@kind == "permission"
|
170
|
+
end
|
171
|
+
|
172
|
+
def ownership?
|
173
|
+
@kind == "ownership"
|
174
|
+
end
|
175
|
+
|
176
|
+
def extended?
|
177
|
+
@kind == "extended"
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
class RenameMode
|
182
|
+
attr_accessor :mode
|
183
|
+
|
184
|
+
def initialize(mode)
|
185
|
+
@mode = mode
|
186
|
+
end
|
187
|
+
|
188
|
+
def from?
|
189
|
+
@mode == "from"
|
190
|
+
end
|
191
|
+
|
192
|
+
def to?
|
193
|
+
@mode == "to"
|
194
|
+
end
|
195
|
+
|
196
|
+
def both?
|
197
|
+
@mode == "both"
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require "watchcat/event"
|
2
|
+
|
3
|
+
module Watchcat
|
4
|
+
class Server
|
5
|
+
def initialize(block)
|
6
|
+
@block = block
|
7
|
+
end
|
8
|
+
|
9
|
+
def execute(notification)
|
10
|
+
event = Watchcat::Event.new(notification[0], notification[1], notification[2])
|
11
|
+
@block.call(event)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
data/lib/watchcat.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
require_relative "watchcat/version"
|
2
|
+
require_relative "watchcat/executor"
|
3
|
+
|
4
|
+
begin
|
5
|
+
require "watchcat/#{RUBY_VERSION.to_f}/watchcat"
|
6
|
+
rescue LoadError
|
7
|
+
require "watchcat/watchcat"
|
8
|
+
end
|
9
|
+
|
10
|
+
module Watchcat
|
11
|
+
class << self
|
12
|
+
def watch(
|
13
|
+
paths,
|
14
|
+
recursive: true,
|
15
|
+
force_polling: false,
|
16
|
+
poll_interval: nil,
|
17
|
+
wait_until_startup: false,
|
18
|
+
ignore_remove: false,
|
19
|
+
debounce: -1,
|
20
|
+
&block
|
21
|
+
)
|
22
|
+
w =
|
23
|
+
Watchcat::Executor.new(
|
24
|
+
Array(paths),
|
25
|
+
recursive: recursive,
|
26
|
+
force_polling: force_polling,
|
27
|
+
poll_interval: poll_interval,
|
28
|
+
wait_until_startup: wait_until_startup,
|
29
|
+
ignore_remove: ignore_remove,
|
30
|
+
debounce: debounce,
|
31
|
+
block: block
|
32
|
+
)
|
33
|
+
w.start
|
34
|
+
w
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
data/watchcat.gemspec
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "lib/watchcat/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "watchcat"
|
7
|
+
spec.version = Watchcat::VERSION
|
8
|
+
spec.authors = ["Yuji Yaginuma"]
|
9
|
+
spec.email = ["yuuji.yaginuma@gmail.com"]
|
10
|
+
|
11
|
+
spec.summary = "Simple filesystem notification library for Ruby. "
|
12
|
+
spec.homepage = "https://github.com/y-yagi/watchcat"
|
13
|
+
spec.license = "MIT"
|
14
|
+
spec.required_ruby_version = ">= 3.0.0"
|
15
|
+
|
16
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
17
|
+
spec.metadata["rubygems_mfa_required"] = "true"
|
18
|
+
|
19
|
+
# Specify which files should be added to the gem when it is released.
|
20
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
21
|
+
spec.files = Dir.chdir(__dir__) do
|
22
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
23
|
+
(f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features|benchmark)/|\.(?:git|travis|circleci)|appveyor)})
|
24
|
+
end
|
25
|
+
end
|
26
|
+
spec.require_paths = ["lib"]
|
27
|
+
spec.extensions = ["ext/watchcat/extconf.rb"]
|
28
|
+
|
29
|
+
spec.add_dependency "rb_sys"
|
30
|
+
spec.add_dependency "drb"
|
31
|
+
spec.add_development_dependency "debug"
|
32
|
+
spec.add_development_dependency "minitest"
|
33
|
+
spec.add_development_dependency "minitest-retry"
|
34
|
+
spec.add_development_dependency "rake"
|
35
|
+
spec.add_development_dependency "rake-compiler"
|
36
|
+
spec.add_development_dependency "ruby_memcheck"
|
37
|
+
spec.add_development_dependency "listen"
|
38
|
+
end
|