1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
| | # Copyright (C) 2013-2019 all contributors <dtas-all@nongnu.org>
# License: GPL-3.0+ <https://www.gnu.org/licenses/gpl-3.0.txt>
# frozen_string_literal: true
require_relative '../dtas'
require_relative 'nonblock'
begin
require 'fiddle'
# used to restart DTAS::Source::SplitFX processing in dtas-player
# if the YAML file is edited
module DTAS::Watchable # :nodoc:
class InotifyReadableIter # :nodoc:
Inotify_init = Fiddle::Function.new(DTAS.libc['inotify_init1'],
[ Fiddle::TYPE_INT ],
Fiddle::TYPE_INT)
Inotify_add_watch = Fiddle::Function.new(DTAS.libc['inotify_add_watch'],
[ Fiddle::TYPE_INT, Fiddle::TYPE_VOIDP, Fiddle::TYPE_INT ],
Fiddle::TYPE_INT)
# IO.select compatibility
attr_reader :to_io #:nodoc:
def initialize # :nodoc:
fd = Inotify_init.call(02000000 | 04000) # CLOEXEC | NONBLOCK
raise "inotify_init failed: #{Fiddle.last_error}" if fd < 0
@to_io = DTAS::Nonblock.for_fd(fd)
@buf = ''.b
@q = []
end
# struct inotify_event {
# int wd; /* Watch descriptor */
# uint32_t mask; /* Mask describing event */
# uint32_t cookie; /* Unique cookie associating related
# events (for rename(2)) */
# uint32_t len; /* Size of name field */
# char name[]; /* Optional null-terminated name */
InotifyEvent = Struct.new(:wd, :mask, :cookie, :len, :name) # :nodoc:
def take # :nodoc:
event = @q.pop and return event
case rv = @to_io.read_nonblock(16384, @buf, exception: false)
when :wait_readable, nil
return
else
until rv.empty?
hdr = rv.slice!(0,16)
name = nil
wd, mask, cookie, len = res = hdr.unpack('iIII')
wd && mask && cookie && len or
raise "bogus inotify_event #{res.inspect} hdr=#{hdr.inspect}"
if len > 0
name = rv.slice!(0, len)
name.size == len or raise "short name #{name.inspect} != #{len}"
name.sub!(/\0+\z/, '') or
raise "missing: `\\0', inotify_event.name=#{name.inspect}"
name = DTAS.dedupe_str(name)
end
ie = InotifyEvent.new(wd, mask, cookie, len, name)
if event
@q << ie
else
event = ie
end
end # /until rv.empty?
return event
end while true
end
FLAGS = 8 | 128 # CLOSE_WRITE | MOVED_TO
def readable_iter
or_call = false
while event = take # drain the buffer
w = @watches[event.wd] or next
if (event.mask & FLAGS) != 0 && w[event.name]
or_call = true
end
end
if or_call
@on_readable.call
:delete
else
:wait_readable
end
end
def add_watch(watchdir)
wd = Inotify_add_watch.call(@to_io.fileno, watchdir, FLAGS)
raise "inotify_add_watch failed: #{Fiddle.last_error}" if wd < 0
wd
end
def close
@to_io = @to_io.close if @to_io
end
# we must watch the directory, since
def watch_files(paths, blk)
@watches = {} # wd -> { basename -> true }
@on_readable = blk
@dir2wd = {}
Array(paths).each do |path|
watchdir, watchbase = File.split(File.expand_path(path))
begin
wd = @dir2wd[watchdir] ||= add_watch(watchdir)
m = @watches[wd] ||= {}
m[watchbase] = true
rescue SystemCallError => e
warn "#{watchdir.dump}: #{e.message} (#{e.class})"
end
end
end
end
def watch_begin(blk)
@ino = InotifyReadableIter.new
@ino.watch_files(@watch_extra << @infile, blk)
@ino
end
def watch_extra(paths)
@ino.watch_extra(paths)
end
# Closing the inotify descriptor (instead of using inotify_rm_watch)
# is cleaner because it avoids EINVAL on race conditions in case
# a directory is deleted: https://lkml.org/lkml/2007/7/9/3
def watch_end(srv)
srv.wait_ctl(@ino, :delete)
@ino = @ino.close
end
end
rescue LoadError, Fiddle::DLError => e
warn "#{e.message} (#{e.class})"
end
|