watchcat 0.2.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.
- 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
|