dtas 0.15.0 → 0.18.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.gitignore +1 -1
- data/Documentation/.gitignore +1 -1
- data/Documentation/GNUmakefile +2 -2
- data/Documentation/dtas-archive.pod +2 -2
- data/Documentation/dtas-console.pod +2 -2
- data/Documentation/dtas-ctl.pod +2 -2
- data/Documentation/dtas-cueedit.pod +2 -2
- data/Documentation/dtas-enq.pod +2 -2
- data/Documentation/dtas-env.pod +3 -3
- data/Documentation/dtas-msinkctl.pod +2 -2
- data/Documentation/dtas-player.pod +2 -2
- data/Documentation/dtas-player_effects.pod +2 -2
- data/Documentation/dtas-player_protocol.pod +4 -4
- data/Documentation/dtas-player_sink_examples.pod +2 -2
- data/Documentation/dtas-sinkedit.pod +4 -4
- data/Documentation/dtas-sourceedit.pod +4 -4
- data/Documentation/dtas-splitfx.pod +7 -1
- data/Documentation/dtas-tl.pod +4 -4
- data/Documentation/dtas-xdelay.pod +2 -2
- data/Documentation/update-footer.rb +2 -2
- data/GIT-VERSION-GEN +2 -2
- data/GNUmakefile +1 -1
- data/HACKING +2 -2
- data/INSTALL +13 -24
- data/README +14 -8
- data/Rakefile +2 -2
- data/TODO +3 -1
- data/bin/dtas-archive +1 -1
- data/bin/dtas-console +19 -23
- data/bin/dtas-ctl +1 -1
- data/bin/dtas-cueedit +1 -1
- data/bin/dtas-enq +1 -1
- data/bin/dtas-mlib +1 -1
- data/bin/dtas-msinkctl +1 -1
- data/bin/dtas-partstats +1 -1
- data/bin/dtas-player +1 -1
- data/bin/dtas-readahead +4 -4
- data/bin/dtas-sinkedit +1 -1
- data/bin/dtas-sourceedit +1 -1
- data/bin/dtas-splitfx +8 -1
- data/bin/dtas-tl +1 -1
- data/bin/dtas-xdelay +1 -1
- data/dtas-linux.gemspec +5 -6
- data/dtas-mpris.gemspec +2 -2
- data/dtas.gemspec +2 -2
- data/examples/zsh-completion/README +3 -0
- data/examples/zsh-completion/_dtas-archive +16 -0
- data/examples/zsh-completion/_dtas-ctl +114 -0
- data/examples/zsh-completion/_dtas-cueedit +7 -0
- data/examples/zsh-completion/_dtas-enq +7 -0
- data/examples/zsh-completion/_dtas-mlib +29 -0
- data/examples/zsh-completion/_dtas-msinkctl +8 -0
- data/examples/zsh-completion/_dtas-partstats +7 -0
- data/examples/zsh-completion/_dtas-sinkedit +11 -0
- data/examples/zsh-completion/_dtas-sourceedit +11 -0
- data/examples/zsh-completion/_dtas-splitfx +17 -0
- data/examples/zsh-completion/_dtas-tl +52 -0
- data/examples/zsh-completion/_dtas-xdelay +17 -0
- data/lib/dtas.rb +12 -2
- data/lib/dtas/buffer.rb +10 -5
- data/lib/dtas/buffer/fiddle_splice.rb +216 -0
- data/lib/dtas/buffer/read_write.rb +3 -3
- data/lib/dtas/buffer/splice.rb +16 -10
- data/lib/dtas/command.rb +1 -1
- data/lib/dtas/compat_onenine.rb +1 -1
- data/lib/dtas/cue_index.rb +1 -1
- data/lib/dtas/disclaimer.rb +1 -1
- data/lib/dtas/edit_client.rb +1 -1
- data/lib/dtas/encoding.rb +57 -0
- data/lib/dtas/fadefx.rb +1 -1
- data/lib/dtas/format.rb +1 -1
- data/lib/dtas/mcache.rb +1 -1
- data/lib/dtas/mlib.rb +38 -31
- data/lib/dtas/mlib/migrations/0001_initial.rb +1 -1
- data/lib/dtas/nonblock.rb +1 -1
- data/lib/dtas/parse_freq.rb +1 -1
- data/lib/dtas/parse_time.rb +1 -1
- data/lib/dtas/partstats.rb +1 -1
- data/lib/dtas/pipe.rb +16 -9
- data/lib/dtas/pipeline.rb +75 -0
- data/lib/dtas/player.rb +3 -3
- data/lib/dtas/player/client_handler.rb +23 -15
- data/lib/dtas/process.rb +1 -1
- data/lib/dtas/replaygain.rb +1 -1
- data/lib/dtas/rg_state.rb +1 -1
- data/lib/dtas/serialize.rb +1 -1
- data/lib/dtas/sigevent.rb +6 -3
- data/lib/dtas/sigevent/efd.rb +3 -1
- data/lib/dtas/sigevent/fiddle_efd.rb +38 -0
- data/lib/dtas/sigevent/pipe.rb +2 -2
- data/lib/dtas/sink.rb +1 -1
- data/lib/dtas/source.rb +1 -1
- data/lib/dtas/source/av.rb +1 -1
- data/lib/dtas/source/av_ff_common.rb +1 -1
- data/lib/dtas/source/cmd.rb +1 -1
- data/lib/dtas/source/common.rb +1 -1
- data/lib/dtas/source/ff.rb +1 -1
- data/lib/dtas/source/file.rb +1 -1
- data/lib/dtas/source/mp3gain.rb +1 -1
- data/lib/dtas/source/sox.rb +4 -2
- data/lib/dtas/source/splitfx.rb +1 -1
- data/lib/dtas/spawn_fix.rb +1 -1
- data/lib/dtas/splitfx.rb +6 -3
- data/lib/dtas/state_file.rb +1 -1
- data/lib/dtas/tfx.rb +1 -1
- data/lib/dtas/track.rb +1 -1
- data/lib/dtas/tracklist.rb +1 -1
- data/lib/dtas/unix_accepted.rb +1 -1
- data/lib/dtas/unix_client.rb +1 -1
- data/lib/dtas/unix_server.rb +1 -1
- data/lib/dtas/util.rb +1 -1
- data/lib/dtas/watchable.rb +60 -55
- data/lib/dtas/watchable/fiddle_ino.rb +78 -0
- data/lib/dtas/watchable/inotify.rb +13 -0
- data/lib/dtas/writable_iter.rb +1 -1
- data/lib/dtas/xs.rb +1 -1
- data/perl/dtas-graph +7 -2
- data/setup.rb +1 -2
- data/test/covshow.rb +1 -1
- data/test/helper.rb +1 -1
- data/test/player_integration.rb +1 -1
- data/test/test_buffer.rb +14 -10
- data/test/test_encoding.rb +20 -0
- data/test/test_env.rb +1 -1
- data/test/test_fadefx.rb +1 -1
- data/test/test_format.rb +1 -1
- data/test/test_format_change.rb +1 -1
- data/test/test_mcache.rb +1 -1
- data/test/test_mlib.rb +3 -3
- data/test/test_parse_freq.rb +1 -1
- data/test/test_pipeline.rb +47 -0
- data/test/test_player.rb +1 -1
- data/test/test_player_client_handler.rb +1 -1
- data/test/test_player_integration.rb +1 -1
- data/test/test_process.rb +1 -1
- data/test/test_rg_integration.rb +1 -1
- data/test/test_rg_state.rb +1 -1
- data/test/test_sigevent.rb +20 -0
- data/test/test_sink.rb +1 -1
- data/test/test_sink_pipe_size.rb +13 -16
- data/test/test_sink_tee_integration.rb +1 -1
- data/test/test_source_av.rb +1 -1
- data/test/test_source_sox.rb +1 -1
- data/test/test_splitfx.rb +1 -1
- data/test/test_tfx.rb +1 -1
- data/test/test_tracklist.rb +1 -1
- data/test/test_unixserver.rb +1 -1
- data/test/test_util.rb +1 -1
- metadata +26 -5
@@ -1,4 +1,4 @@
|
|
1
|
-
# Copyright (C) 2013-
|
1
|
+
# Copyright (C) 2013-2020 all contributors <dtas-all@nongnu.org>
|
2
2
|
# License: GPL-3.0+ <https://www.gnu.org/licenses/gpl-3.0.txt>
|
3
3
|
# frozen_string_literal: true
|
4
4
|
require 'io/nonblock'
|
@@ -6,8 +6,8 @@
|
|
6
6
|
require_relative '../pipe'
|
7
7
|
require_relative '../nonblock'
|
8
8
|
|
9
|
-
# compatibility code for systems lacking "splice" support
|
10
|
-
#
|
9
|
+
# compatibility code for non-Linux systems lacking "splice" support.
|
10
|
+
# Used only by -player
|
11
11
|
module DTAS::Buffer::ReadWrite # :nodoc:
|
12
12
|
MAX_AT_ONCE = 512 # min PIPE_BUF value in POSIX
|
13
13
|
attr_accessor :buffer_size
|
data/lib/dtas/buffer/splice.rb
CHANGED
@@ -1,16 +1,19 @@
|
|
1
|
-
# Copyright (C) 2013-
|
1
|
+
# Copyright (C) 2013-2020 all contributors <dtas-all@nongnu.org>
|
2
2
|
# License: GPL-3.0+ <https://www.gnu.org/licenses/gpl-3.0.txt>
|
3
3
|
# frozen_string_literal: true
|
4
4
|
require 'io/nonblock'
|
5
|
-
require '
|
5
|
+
require 'sleepy_penguin'
|
6
6
|
require_relative '../../dtas'
|
7
7
|
require_relative '../pipe'
|
8
|
+
SleepyPenguin.respond_to?(:splice) or
|
9
|
+
raise LoadError, 'sleepy_penguin 3.5+ required for splice', []
|
8
10
|
|
9
|
-
# Used by -player on Linux systems with the "
|
11
|
+
# Used by -player on Linux systems with the "sleepy_penguin" RubyGem installed
|
10
12
|
module DTAS::Buffer::Splice # :nodoc:
|
11
13
|
MAX_AT_ONCE = 4096 # page size in Linux
|
12
14
|
MAX_AT_ONCE_1 = 65536
|
13
|
-
F_MOVE =
|
15
|
+
F_MOVE = SleepyPenguin::F_MOVE
|
16
|
+
F_NONBLOCK = SleepyPenguin::F_NONBLOCK
|
14
17
|
|
15
18
|
def buffer_size
|
16
19
|
@to_io.pipe_size
|
@@ -24,13 +27,14 @@ def buffer_size=(bytes)
|
|
24
27
|
|
25
28
|
# be sure to only call this with nil when all writers to @wr are done
|
26
29
|
def discard(bytes)
|
27
|
-
|
30
|
+
SleepyPenguin.splice(@to_io, DTAS.null, bytes)
|
28
31
|
end
|
29
32
|
|
30
33
|
def broadcast_one(targets, limit = nil)
|
31
34
|
# single output is always non-blocking
|
32
35
|
limit ||= MAX_AT_ONCE_1
|
33
|
-
s =
|
36
|
+
s = SleepyPenguin.splice(@to_io, targets[0], limit, F_MOVE|F_NONBLOCK,
|
37
|
+
exception: false)
|
34
38
|
if Symbol === s
|
35
39
|
targets # our one and only target blocked on write
|
36
40
|
else
|
@@ -46,7 +50,7 @@ def broadcast_one(targets, limit = nil)
|
|
46
50
|
def __tee_in_full(src, dst, bytes)
|
47
51
|
rv = 0
|
48
52
|
while bytes > 0
|
49
|
-
s =
|
53
|
+
s = SleepyPenguin.tee(src, dst, bytes)
|
50
54
|
bytes -= s
|
51
55
|
rv += s
|
52
56
|
end
|
@@ -56,7 +60,7 @@ def __tee_in_full(src, dst, bytes)
|
|
56
60
|
def __splice_in_full(src, dst, bytes, flags)
|
57
61
|
rv = 0
|
58
62
|
while bytes > 0
|
59
|
-
s =
|
63
|
+
s = SleepyPenguin.splice(src, dst, bytes, flags)
|
60
64
|
rv += s
|
61
65
|
bytes -= s
|
62
66
|
end
|
@@ -69,7 +73,8 @@ def __broadcast_tee(blocked, targets, chunk_size)
|
|
69
73
|
targets.delete_if do |dst|
|
70
74
|
begin
|
71
75
|
t = (dst.nonblock? || most_teed == 0) ?
|
72
|
-
|
76
|
+
SleepyPenguin.tee(@to_io, dst, chunk_size, F_NONBLOCK,
|
77
|
+
exception: false) :
|
73
78
|
__tee_in_full(@to_io, dst, chunk_size)
|
74
79
|
if Integer === t
|
75
80
|
if t > most_teed
|
@@ -117,7 +122,8 @@ def broadcast_inf(targets, limit = nil)
|
|
117
122
|
begin
|
118
123
|
targets << last
|
119
124
|
if last.nonblock? || most_teed == 0
|
120
|
-
s =
|
125
|
+
s = SleepyPenguin.splice(@to_io, last, bytes, F_MOVE|F_NONBLOCK,
|
126
|
+
exception: false)
|
121
127
|
if Symbol === s
|
122
128
|
blocked << last
|
123
129
|
|
data/lib/dtas/command.rb
CHANGED
data/lib/dtas/compat_onenine.rb
CHANGED
data/lib/dtas/cue_index.rb
CHANGED
data/lib/dtas/disclaimer.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
# :enddoc:
|
2
|
-
# Copyright (C) 2013-
|
2
|
+
# Copyright (C) 2013-2020 all contributors <dtas-all@nongnu.org>
|
3
3
|
# License: GPL-3.0+ <https://www.gnu.org/licenses/gpl-3.0.txt>
|
4
4
|
# frozen_string_literal: true
|
5
5
|
DTAS_PROGNAME = File.basename($0)
|
data/lib/dtas/edit_client.rb
CHANGED
@@ -0,0 +1,57 @@
|
|
1
|
+
# Copyright (C) 2018-2020 all contributors <dtas-all@nongnu.org>
|
2
|
+
# License: GPL-3.0+ <https://www.gnu.org/licenses/gpl-3.0.txt>
|
3
|
+
# frozen_string_literal: true
|
4
|
+
|
5
|
+
# This module gets included in DTAS
|
6
|
+
module DTAS::Encoding # :nodoc:
|
7
|
+
def self.extended(mod)
|
8
|
+
mod.instance_eval { @charlock_holmes = nil}
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def try_enc_harder(str, enc, old) # :nodoc:
|
14
|
+
begin
|
15
|
+
require 'charlock_holmes'
|
16
|
+
@charlock_holmes = CharlockHolmes::EncodingDetector.new
|
17
|
+
rescue LoadError
|
18
|
+
@charlock_holmes = false
|
19
|
+
end if @charlock_holmes.nil?
|
20
|
+
|
21
|
+
case @charlock_holmes
|
22
|
+
when false
|
23
|
+
enc_fallback(str, enc, old)
|
24
|
+
else
|
25
|
+
res = @charlock_holmes.detect(str)
|
26
|
+
if det = res[:ruby_encoding]
|
27
|
+
str.force_encoding(det)
|
28
|
+
warn "charlock_holmes detected #{str.inspect} as #{det}..."
|
29
|
+
str.valid_encoding? or enc_fallback(str, det, old)
|
30
|
+
else
|
31
|
+
enc_fallback(str, enc, old)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
str
|
35
|
+
end
|
36
|
+
|
37
|
+
def enc_fallback(str, enc, old) # :nodoc:
|
38
|
+
str.force_encoding(old)
|
39
|
+
warn "could not detect encoding for #{str.inspect} (not #{enc})"
|
40
|
+
end
|
41
|
+
|
42
|
+
public
|
43
|
+
|
44
|
+
def try_enc(str, enc, harder = true) # :nodoc:
|
45
|
+
old = str.encoding
|
46
|
+
return str if old == enc
|
47
|
+
str.force_encoding(enc)
|
48
|
+
unless str.valid_encoding?
|
49
|
+
if harder
|
50
|
+
try_enc_harder(str, enc, old)
|
51
|
+
else
|
52
|
+
enc_fallback(str, enc, old)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
str
|
56
|
+
end
|
57
|
+
end
|
data/lib/dtas/fadefx.rb
CHANGED
data/lib/dtas/format.rb
CHANGED
data/lib/dtas/mcache.rb
CHANGED
data/lib/dtas/mlib.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
2
|
-
# Copyright (C) 2015-
|
2
|
+
# Copyright (C) 2015-2020 all contributors <dtas-all@nongnu.org>
|
3
3
|
# License: GPL-3.0+ <https://www.gnu.org/licenses/gpl-3.0.txt>
|
4
4
|
# frozen_string_literal: true
|
5
5
|
#
|
@@ -10,6 +10,7 @@
|
|
10
10
|
require_relative 'source/ff'
|
11
11
|
require_relative 'source/splitfx'
|
12
12
|
require 'socket'
|
13
|
+
require 'tempfile'
|
13
14
|
|
14
15
|
# For the DTAS Music Library, based on what MPD uses.
|
15
16
|
class DTAS::Mlib # :nodoc:
|
@@ -37,16 +38,13 @@ class DTAS::Mlib # :nodoc:
|
|
37
38
|
|
38
39
|
def initialize(db)
|
39
40
|
if String === db
|
41
|
+
require 'sequel'
|
42
|
+
opts = { single_threaded: true }
|
40
43
|
db = "sqlite://#{db}" unless db.include?('://')
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
db.transaction_mode = :immediate
|
46
|
-
db.synchronous = :off
|
47
|
-
db.case_sensitive_like = false
|
48
|
-
else
|
49
|
-
warn 'non-SQLite databases may not work in the future'
|
44
|
+
opts[:transaction_mode] = :immediate
|
45
|
+
opts[:synchronous] = :off
|
46
|
+
opts[:case_sensitive_like] = false # only for 'search'
|
47
|
+
db = Sequel.connect(db, opts)
|
50
48
|
end
|
51
49
|
@db = db
|
52
50
|
@pwd = nil
|
@@ -65,15 +63,23 @@ def initialize(db)
|
|
65
63
|
]
|
66
64
|
end
|
67
65
|
|
66
|
+
def synchronize
|
67
|
+
@lock.flock(File::LOCK_EX)
|
68
|
+
@db.transaction { yield }
|
69
|
+
ensure
|
70
|
+
@lock.flock(File::LOCK_UN)
|
71
|
+
end
|
72
|
+
|
68
73
|
def init_suffixes
|
69
|
-
`sox --help 2>/dev/null` =~ /\nAUDIO FILE FORMATS:\s*([^\n]+)/
|
74
|
+
`sox --help 2>/dev/null` =~ /\nAUDIO FILE FORMATS:\s*([^\n]+)/
|
70
75
|
re = $1.split(/\s+/).map! { |x| Regexp.quote(x) }.join('|')
|
71
76
|
@suffixes = Regexp.new("\\.(?:#{re}|yml)\\z", Regexp::IGNORECASE)
|
72
77
|
end
|
73
78
|
|
74
|
-
def worker(todo)
|
79
|
+
def worker(todo, lock)
|
80
|
+
@lock = lock
|
75
81
|
@work.close
|
76
|
-
@db.tables # reconnect before chdir
|
82
|
+
synchronize { @db.tables } # reconnect before chdir
|
77
83
|
@pwd = Dir.pwd.b
|
78
84
|
begin
|
79
85
|
buf = todo.recv(16384) # 4x bigger than PATH_MAX ought to be enough
|
@@ -87,7 +93,7 @@ def worker(todo)
|
|
87
93
|
end
|
88
94
|
|
89
95
|
def ignore(job)
|
90
|
-
|
96
|
+
synchronize do
|
91
97
|
node_ensure(job.parent_id, job.path, DM_IGN, job.ctime)
|
92
98
|
end
|
93
99
|
end
|
@@ -106,24 +112,16 @@ def worker_work(job)
|
|
106
112
|
end
|
107
113
|
return ignore(job) unless found
|
108
114
|
tlen = found.duration
|
109
|
-
return ignore(job) if tlen < 0
|
115
|
+
return ignore(job) if tlen.nil? || tlen < 0
|
110
116
|
tlen = tlen.round
|
111
117
|
tmp = {}
|
112
|
-
found.comments
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
# FIXME: this fallback needs testing
|
117
|
-
[ Encoding::UTF_8, Encoding::ISO_8859_1 ].each do |enc|
|
118
|
-
value.force_encoding(enc)
|
119
|
-
if value.valid_encoding?
|
120
|
-
value.encode!(Encoding::UTF_8) if enc != Encoding::UTF_8
|
121
|
-
tmp[tag_id] = value
|
122
|
-
break
|
123
|
-
end
|
118
|
+
if comments = found.comments
|
119
|
+
comments.each do |tag, value|
|
120
|
+
tag_id = @tag_map[tag] or next
|
121
|
+
tmp[tag_id] = value if value.valid_encoding?
|
124
122
|
end
|
125
123
|
end
|
126
|
-
|
124
|
+
synchronize do
|
127
125
|
node_id = node_ensure(job.parent_id, path, tlen, job.ctime)[:id]
|
128
126
|
vals = @db[:vals]
|
129
127
|
comments = @db[:comments]
|
@@ -154,11 +152,20 @@ def update(path, opts = nil)
|
|
154
152
|
@work and raise 'update already running'
|
155
153
|
todo, @work = UNIXSocket.pair(:SOCK_SEQPACKET)
|
156
154
|
@db.disconnect
|
157
|
-
|
155
|
+
|
156
|
+
# like a Mutex between processes
|
157
|
+
@lock = Tempfile.new('dtas.mlib.lock')
|
158
|
+
jobs.times do |i|
|
159
|
+
lock = File.open(@lock.path, 'w')
|
160
|
+
fork { worker(todo, lock) }
|
161
|
+
lock.close
|
162
|
+
end
|
163
|
+
@lock.unlink
|
158
164
|
todo.close
|
159
165
|
scan_dir(path, st)
|
160
166
|
@work.close
|
161
167
|
Process.waitall
|
168
|
+
@lock.close
|
162
169
|
ensure
|
163
170
|
@work = nil
|
164
171
|
end
|
@@ -246,7 +253,7 @@ def root_node
|
|
246
253
|
end
|
247
254
|
|
248
255
|
def dir_vivify(parts, ctime)
|
249
|
-
|
256
|
+
synchronize do
|
250
257
|
dir = root_node
|
251
258
|
last = parts.pop
|
252
259
|
parts.each do |name|
|
@@ -300,7 +307,7 @@ def scan_dir(path, st, parent_id = nil)
|
|
300
307
|
dir = dir_vivify(@pwd.split(%r{/+}n), st.ctime.to_i)
|
301
308
|
dir_id = dir[:id]
|
302
309
|
|
303
|
-
|
310
|
+
synchronize do
|
304
311
|
@db[:nodes].where(parent_id: dir_id).each do |node|
|
305
312
|
File.exist?(node[:name]) or remove_entry(node)
|
306
313
|
end
|
data/lib/dtas/nonblock.rb
CHANGED
data/lib/dtas/parse_freq.rb
CHANGED
data/lib/dtas/parse_time.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
# Copyright (C) 2013-
|
2
|
+
# Copyright (C) 2013-2020 all contributors <dtas-all@nongnu.org>
|
3
3
|
# License: GPL-3.0+ <https://www.gnu.org/licenses/gpl-3.0.txt>
|
4
4
|
# frozen_string_literal: true
|
5
5
|
require_relative '../dtas'
|
data/lib/dtas/partstats.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
# -*- encoding: binary -*-
|
2
|
-
# Copyright (C) 2013-
|
2
|
+
# Copyright (C) 2013-2020 all contributors <dtas-all@nongnu.org>
|
3
3
|
# License: GPL-3.0+ <https://www.gnu.org/licenses/gpl-3.0.txt>
|
4
4
|
# frozen_string_literal: true
|
5
5
|
require_relative '../dtas'
|
data/lib/dtas/pipe.rb
CHANGED
@@ -1,10 +1,6 @@
|
|
1
|
-
# Copyright (C) 2013-
|
1
|
+
# Copyright (C) 2013-2020 all contributors <dtas-all@nongnu.org>
|
2
2
|
# License: GPL-3.0+ <https://www.gnu.org/licenses/gpl-3.0.txt>
|
3
3
|
# frozen_string_literal: true
|
4
|
-
begin
|
5
|
-
require 'io/splice'
|
6
|
-
rescue LoadError
|
7
|
-
end
|
8
4
|
require_relative '../dtas'
|
9
5
|
require_relative 'writable_iter'
|
10
6
|
require_relative 'nonblock'
|
@@ -14,18 +10,29 @@ class DTAS::Pipe < DTAS::Nonblock # :nodoc:
|
|
14
10
|
include DTAS::WritableIter
|
15
11
|
attr_accessor :sink
|
16
12
|
|
13
|
+
if RUBY_PLATFORM =~ /linux/i && File.readable?('/proc/sys/fs/pipe-max-size')
|
14
|
+
F_SETPIPE_SZ = 1031
|
15
|
+
F_GETPIPE_SZ = 1032
|
16
|
+
end
|
17
|
+
|
17
18
|
def self.new
|
18
19
|
_, w = rv = pipe
|
19
20
|
w.writable_iter_init
|
20
21
|
rv
|
21
22
|
end
|
22
23
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
24
|
+
def pipe_size=(nr)
|
25
|
+
fcntl(F_SETPIPE_SZ, nr) if defined?(F_SETPIPE_SZ)
|
26
|
+
rescue Errno::EINVAL # old kernel
|
27
|
+
rescue Errno::EPERM
|
28
|
+
# resizes fail if Linux is close to the pipe limit for the user
|
29
|
+
# or if the user does not have permissions to resize
|
27
30
|
end
|
28
31
|
|
32
|
+
def pipe_size
|
33
|
+
fcntl(F_GETPIPE_SZ)
|
34
|
+
end if defined?(F_GETPIPE_SZ)
|
35
|
+
|
29
36
|
# avoid syscall, we never change IO#nonblock= directly
|
30
37
|
def nonblock?
|
31
38
|
false
|
@@ -0,0 +1,75 @@
|
|
1
|
+
# Copyright (C) 2017-2020 all contributors <dtas-all@nongnu.org>
|
2
|
+
# License: GPL-3.0+ <https://www.gnu.org/licenses/gpl-3.0.txt>
|
3
|
+
# frozen_string_literal: true
|
4
|
+
require_relative '../dtas'
|
5
|
+
require_relative 'spawn_fix'
|
6
|
+
|
7
|
+
module DTAS::Pipeline # :nodoc:
|
8
|
+
include DTAS::SpawnFix
|
9
|
+
|
10
|
+
# Process.spawn wrapper which supports running Proc-like objects in
|
11
|
+
# a separate process, not just external commands.
|
12
|
+
# Returns the pid of the spawned process
|
13
|
+
def pspawn(env, cmd, rdr = {})
|
14
|
+
case cmd
|
15
|
+
when Array
|
16
|
+
spawn(env, *cmd, rdr)
|
17
|
+
else # support running Proc-like objects, too:
|
18
|
+
fork do
|
19
|
+
ENV.update(env) if env
|
20
|
+
|
21
|
+
# setup redirects
|
22
|
+
[ $stdin, $stdout, $stderr ].each_with_index do |io, fd|
|
23
|
+
dst = rdr[fd] and io.reopen(dst)
|
24
|
+
end
|
25
|
+
|
26
|
+
# close all other pipes, since we can't rely on FD_CLOEXEC
|
27
|
+
# (as we do not exec, here)
|
28
|
+
rdr.each do |k, v|
|
29
|
+
k.close if v == :close
|
30
|
+
end
|
31
|
+
cmd.call
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# +pipeline+ is an Array of (Arrays or Procs)
|
37
|
+
def run_pipeline(env, pipeline)
|
38
|
+
pids = {} # pid => pipeline index
|
39
|
+
work = pipeline.dup
|
40
|
+
last = work.pop
|
41
|
+
nr = work.size
|
42
|
+
rdr = {} # redirect mapping for Process.spawn
|
43
|
+
|
44
|
+
# we need to make sure pipes are closed in any forked processes
|
45
|
+
# (they are redirected to stdin or stdout, first)
|
46
|
+
pipes = nr.times.map { IO.pipe.each { |io| rdr[io] = :close } }
|
47
|
+
|
48
|
+
# start the first and last commands first, they only have one pipe, each
|
49
|
+
last_pid = pspawn(env, last, rdr.merge(0 => pipes[-1][0]))
|
50
|
+
pids[last_pid] = nr
|
51
|
+
first = work.shift
|
52
|
+
first_pid = pspawn(env, first, rdr.merge(1 => pipes[0][1]))
|
53
|
+
pids[first_pid] = 0
|
54
|
+
|
55
|
+
# start the middle commands, they both have two pipes:
|
56
|
+
work.each_with_index do |cmd, i|
|
57
|
+
pid = pspawn(env, cmd, rdr.merge(0 => pipes[i][0], 1 => pipes[i+1][1]))
|
58
|
+
pids[pid] = i + 1
|
59
|
+
end
|
60
|
+
|
61
|
+
# all pipes handed off to children, close so they see EOF
|
62
|
+
pipes.flatten!.each(&:close).clear
|
63
|
+
|
64
|
+
# wait for children to finish
|
65
|
+
fails = []
|
66
|
+
until pids.empty?
|
67
|
+
pid, status = Process.waitpid2(-1)
|
68
|
+
nr = pids.delete(pid)
|
69
|
+
status.success? or
|
70
|
+
fails << "reaped #{nr} #{pipeline[nr].inspect} #{status.inspect}"
|
71
|
+
end
|
72
|
+
# behave like "set -o pipefail" in bash
|
73
|
+
raise fails.join("\n") if fails[0]
|
74
|
+
end
|
75
|
+
end
|