dtas 0.15.0 → 0.16.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: b38ad68d0ac0b303e95ef7a6de996581fd4a5c73
4
- data.tar.gz: 84066e2d52ede911d045b0d9f62a6d0cb741a0a3
2
+ SHA256:
3
+ metadata.gz: d887afd41dc71abd3448e72c3d33d188e4629b8dbc576af3237c3bd47b246581
4
+ data.tar.gz: cf5262f4c45becd00e75dc2f1c3de5ab05b27bd9a029c9fef458faec5cc58303
5
5
  SHA512:
6
- metadata.gz: e02cd6429e8df6fca2d6d0745e5fa1094910c6688ecbbc404d309f044a6abba32dfc0f0b03f60f5d8a487f94fc64fe6cedc0365c5a5cf539fb6d1db80b05bcad
7
- data.tar.gz: 8849acee6e2ec2ec588f686d7d56e741db25e480a8d9661d0feee4f95fe6f8dbd030ae07806654b7aa6020abd46243bfab8a7f33e25e977467aafe1b7a824352
6
+ metadata.gz: 94f470ad51f9723bf5d3161b445d0434e6e5e45349fc6c5227ecb80aa0c9d1409357979cd8f97edc909bed6c65b75494757b731d2f7cd97f4ed345640465aa7a
7
+ data.tar.gz: 631dc0a9b16771662bb6f8215ab9ee71acd078ac7cae26f1b90408106da840909d59f99cdf569425b2be05f2c9018f032ad426405ca1bbe1df28af005d2f020c
@@ -1,11 +1,11 @@
1
1
  #!/usr/bin/env ruby
2
- # Copyright (C) 2013-2016 all contributors <dtas-all@nongnu.org>
2
+ # Copyright (C) 2013-2019 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
  CONSTANT = "DTAS::VERSION"
6
6
  RVF = "lib/dtas/version.rb"
7
7
  GVF = "GIT-VERSION-FILE"
8
- DEF_VER = "v0.15.0"
8
+ DEF_VER = "v0.16.0"
9
9
  vn = DEF_VER
10
10
 
11
11
  # First see if there is a version file (included in release tarballs),
data/INSTALL CHANGED
@@ -19,13 +19,13 @@ Debian 7+ users can install dependencies easily:
19
19
  # installing dtas RubyGem on GNU/Linux (Linux kernel 2.6.32+)
20
20
 
21
21
  Be sure to have Ruby development headers and a working C compiler.
22
- This will pull in the io_splice and sleepy_penguin RubyGems for minor
22
+ This will pull in the sleepy_penguin RubyGems for minor
23
23
  speedups. If you cannot be bothered to have a development
24
24
  environment, just use "gem install dtas".
25
25
 
26
26
  sudo gem install dtas-linux
27
27
 
28
- This should pull in the "io_splice" and "sleepy_penguin" RubyGems
28
+ This should pull in the "sleepy_penguin" RubyGems
29
29
 
30
30
  For future upgrades of dtas (upgrades to dtas-linux will be infrequent)
31
31
 
@@ -39,16 +39,14 @@ For future upgrades of dtas (upgrades to dtas-linux will be infrequent)
39
39
 
40
40
  Grab the latest tarball from our HTTPS site:
41
41
 
42
- https://80x24.org/dtas/2017/dtas-0.15.0.tar.gz
42
+ https://80x24.org/dtas/2019/dtas-0.16.0.tar.gz
43
43
 
44
- $ tar zxvf dtas-0.15.0.tar.gz
45
- $ cd dtas-0.15.0
44
+ $ tar zxvf dtas-0.16.0.tar.gz
45
+ $ cd dtas-0.16.0
46
46
  $ sudo ruby setup.rb
47
47
 
48
- GNU/Linux users may optionally install "io_splice" and
49
- "sleepy_penguin" packages:
48
+ GNU/Linux users may optionally install the "sleepy_penguin" package:
50
49
 
51
- * io_splice - https://bogomips.org/ruby_io_splice/
52
50
  * sleepy_penguin - https://bogomips.org/sleepy_penguin/
53
51
 
54
52
  # CONTACT
@@ -61,5 +59,5 @@ No subscription is necessary to post to the mailing list.
61
59
 
62
60
  # COPYRIGHT
63
61
 
64
- Copyright 2013-2016 all contributors <dtas-all@nongnu.org>
62
+ Copyright 2013-2019 all contributors <dtas-all@nongnu.org>
65
63
  License: GPL-3.0+ <https://www.gnu.org/licenses/gpl-3.0.txt>
data/README CHANGED
@@ -59,7 +59,7 @@ Coming:
59
59
 
60
60
  ## Source code
61
61
 
62
- git clone git://80x24.org/dtas
62
+ git clone https://80x24.org/dtas.git
63
63
 
64
64
  Please use git-format-patch(1) and git-send-email(1) distributed with
65
65
  the git(7) suite for generating and sending patches. Please format
@@ -86,7 +86,7 @@ Atom: <https://80x24.org/dtas-all/new.atom>
86
86
 
87
87
  ## Copyright
88
88
 
89
- Copyright 2013-2016 all contributors <dtas-all@nongnu.org>\
89
+ Copyright 2013-2019 all contributors <dtas-all@nongnu.org>\
90
90
  License: GPL-3.0+ <https://www.gnu.org/licenses/gpl-3.0.txt>
91
91
 
92
92
  dtas is copyrighted Free Software by all contributors, see logs
data/Rakefile CHANGED
@@ -1,4 +1,4 @@
1
- # Copyright (C) 2013-2016 all contributors <dtas-all@nongnu.org>.
1
+ # Copyright (C) 2013-2019 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 'tempfile'
@@ -93,7 +93,7 @@ def tags
93
93
  end
94
94
 
95
95
  task rsync_docs: %w(NEWS NEWS.atom) do
96
- dest = ENV["RSYNC_DEST"] || "80x24.org:/srv/dtas/"
96
+ dest = ENV["RSYNC_DEST"] || "80x24.org:/srv/80x24/dtas/"
97
97
  top = %w(INSTALL NEWS README COPYING NEWS.atom)
98
98
  files = []
99
99
 
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
- # Copyright (C) 2013-2016 all contributors <dtas-all@nongnu.org>
2
+ # Copyright (C) 2013-2019 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
  #
@@ -173,11 +173,11 @@ def may_fail(res, events)
173
173
  Curses.setpos(lineno += 1, 0)
174
174
  Curses.clrtoeol
175
175
  Curses.addstr(infile)
176
- total = " [#{Time.at(samples / rate).strftime(tfmt)}]" if samples != 0
176
+ total = " [#{Time.at(samples / rate).utc.strftime(tfmt)}]" if samples != 0
177
177
  Curses.setpos(lineno += 1, 0)
178
178
  Curses.clrtoeol
179
179
  if rate != 0
180
- Curses.addstr("#{Time.at(elapsed).strftime(tfmt)}#{total} #{fmt}")
180
+ Curses.addstr("#{Time.at(elapsed).utc.strftime(tfmt)}#{total} #{fmt}")
181
181
  else
182
182
  Curses.addstr("#{elapsed} samples #{total} #{fmt}")
183
183
  end
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
- # Copyright (C) 2015-2016 all contributors <dtas-all@nongnu.org>
2
+ # Copyright (C) 2015-2019 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
  #
@@ -75,7 +75,7 @@ def seek_to_cur_pos(cur_pid, fp)
75
75
  end
76
76
 
77
77
  def children_of(ppid)
78
- `ps h -o pid --ppid=#{ppid}`.split(/\s+/s).map(&:to_i)
78
+ `ps h -o pid --ppid=#{ppid}`.split(/\s+/).map(&:to_i)
79
79
  end
80
80
 
81
81
  def expand_pid(pid)
@@ -1,4 +1,4 @@
1
- # Copyright (C) 2013-2016 all contributors <dtas-all@nongnu.org>
1
+ # Copyright (C) 2013-2019 all contributors <dtas-all@nongnu.org>
2
2
  # License: GPL-3.0+ <https://www.gnu.org/licenses/gpl-3.0.txt>
3
3
  #
4
4
  # this just declares dependencies to make gem installation a little easier
@@ -13,8 +13,7 @@
13
13
  s.email = %q{e@80x24.org}
14
14
  s.files = []
15
15
  s.homepage = 'https://80x24.org/dtas/'
16
- s.add_dependency(%q<dtas>)
17
- s.add_dependency(%q<io_splice>, '~> 4')
18
- s.add_dependency(%q<sleepy_penguin>, '~> 3')
16
+ s.add_dependency(%q<dtas>, '~> 0.16')
17
+ s.add_dependency(%q<sleepy_penguin>, '~> 3.5')
19
18
  s.licenses = 'GPL-3.0+'
20
19
  end
@@ -1,4 +1,4 @@
1
- # Copyright (C) 2013-2016 all contributors <dtas-all@nongnu.org>
1
+ # Copyright (C) 2013-2019 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
 
@@ -42,3 +42,5 @@ def self.dedupe_str(str)
42
42
 
43
43
  require_relative 'dtas/compat_onenine'
44
44
  require_relative 'dtas/spawn_fix'
45
+ require_relative 'dtas/encoding'
46
+ DTAS.extend(DTAS::Encoding)
@@ -1,4 +1,4 @@
1
- # Copyright (C) 2013-2016 all contributors <dtas-all@nongnu.org>
1
+ # Copyright (C) 2013-2019 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/wait'
@@ -8,7 +8,9 @@
8
8
  class DTAS::Buffer # :nodoc:
9
9
  begin
10
10
  raise LoadError, "no splice with _DTAS_POSIX" if ENV["_DTAS_POSIX"]
11
- require 'io/splice' # splice is only in Linux for now...
11
+ require 'sleepy_penguin' # splice is only in Linux for now...
12
+ SleepyPenguin.respond_to?(:splice) or
13
+ raise LoadError, 'sleepy_penguin 3.5+ required for splice', []
12
14
  require_relative 'buffer/splice'
13
15
  include DTAS::Buffer::Splice
14
16
  rescue LoadError
@@ -1,4 +1,4 @@
1
- # Copyright (C) 2013-2016 all contributors <dtas-all@nongnu.org>
1
+ # Copyright (C) 2013-2019 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'
@@ -7,7 +7,7 @@
7
7
  require_relative '../nonblock'
8
8
 
9
9
  # compatibility code for systems lacking "splice" support via the
10
- # "io-splice" RubyGem. Used only by -player
10
+ # "sleepy_penguin" 3.5+ RubyGem. 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
@@ -1,16 +1,18 @@
1
- # Copyright (C) 2013-2016 all contributors <dtas-all@nongnu.org>
1
+ # Copyright (C) 2013-2019 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 'io/splice'
5
+ require 'sleepy_penguin'
6
6
  require_relative '../../dtas'
7
7
  require_relative '../pipe'
8
8
 
9
- # Used by -player on Linux systems with the "io-splice" RubyGem installed
9
+ # Used by -player on Linux systems with the "sleepy_penguin" RubyGem installed
10
10
  module DTAS::Buffer::Splice # :nodoc:
11
11
  MAX_AT_ONCE = 4096 # page size in Linux
12
12
  MAX_AT_ONCE_1 = 65536
13
- F_MOVE = IO::Splice::F_MOVE
13
+ F_MOVE = SleepyPenguin::F_MOVE
14
+ F_NONBLOCK = SleepyPenguin::F_NONBLOCK
15
+ TRY = { exception: false }.freeze
14
16
 
15
17
  def buffer_size
16
18
  @to_io.pipe_size
@@ -24,13 +26,13 @@ def buffer_size=(bytes)
24
26
 
25
27
  # be sure to only call this with nil when all writers to @wr are done
26
28
  def discard(bytes)
27
- IO.splice(@to_io, nil, DTAS.null, nil, bytes)
29
+ SleepyPenguin.splice(@to_io, DTAS.null, bytes)
28
30
  end
29
31
 
30
32
  def broadcast_one(targets, limit = nil)
31
33
  # single output is always non-blocking
32
34
  limit ||= MAX_AT_ONCE_1
33
- s = IO.trysplice(@to_io, nil, targets[0], nil, limit, F_MOVE)
35
+ s = SleepyPenguin.splice(@to_io, targets[0], limit, F_MOVE, TRY)
34
36
  if Symbol === s
35
37
  targets # our one and only target blocked on write
36
38
  else
@@ -46,7 +48,7 @@ def broadcast_one(targets, limit = nil)
46
48
  def __tee_in_full(src, dst, bytes)
47
49
  rv = 0
48
50
  while bytes > 0
49
- s = IO.tee(src, dst, bytes)
51
+ s = SleepyPenguin.tee(src, dst, bytes)
50
52
  bytes -= s
51
53
  rv += s
52
54
  end
@@ -56,7 +58,7 @@ def __tee_in_full(src, dst, bytes)
56
58
  def __splice_in_full(src, dst, bytes, flags)
57
59
  rv = 0
58
60
  while bytes > 0
59
- s = IO.splice(src, nil, dst, nil, bytes, flags)
61
+ s = SleepyPenguin.splice(src, dst, bytes, flags)
60
62
  rv += s
61
63
  bytes -= s
62
64
  end
@@ -69,7 +71,7 @@ def __broadcast_tee(blocked, targets, chunk_size)
69
71
  targets.delete_if do |dst|
70
72
  begin
71
73
  t = (dst.nonblock? || most_teed == 0) ?
72
- IO.trytee(@to_io, dst, chunk_size) :
74
+ SleepyPenguin.tee(@to_io, dst, chunk_size, F_NONBLOCK, TRY) :
73
75
  __tee_in_full(@to_io, dst, chunk_size)
74
76
  if Integer === t
75
77
  if t > most_teed
@@ -117,7 +119,7 @@ def broadcast_inf(targets, limit = nil)
117
119
  begin
118
120
  targets << last
119
121
  if last.nonblock? || most_teed == 0
120
- s = IO.trysplice(@to_io, nil, last, nil, bytes, F_MOVE)
122
+ s = SleepyPenguin.splice(@to_io, last, bytes, F_MOVE|F_NONBLOCK, TRY)
121
123
  if Symbol === s
122
124
  blocked << last
123
125
 
@@ -0,0 +1,58 @@
1
+ # Copyright (C) 2018-2019 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
+ case @charlock_holmes
15
+ when nil
16
+ begin
17
+ require 'charlock_holmes'
18
+ @charlock_holmes = CharlockHolmes::EncodingDetector.new
19
+ rescue LoadError
20
+ warn "`charlock_holmes` gem not available for encoding detection"
21
+ @charlock_holmes = false
22
+ end
23
+ when false
24
+ enc_fallback(str, enc, old)
25
+ else
26
+ res = @charlock_holmes.detect(str)
27
+ if det = res[:ruby_encoding]
28
+ str.force_encoding(det)
29
+ warn "charlock_holmes detected #{str.inspect} as #{det}..."
30
+ str.valid_encoding? or enc_fallback(str, det, old)
31
+ else
32
+ enc_fallback(str, enc, old)
33
+ end
34
+ end
35
+ str
36
+ end
37
+
38
+ def enc_fallback(str, enc, old) # :nodoc:
39
+ str.force_encoding(old)
40
+ warn "could not detect encoding for #{str.inspect} (not #{enc})"
41
+ end
42
+
43
+ public
44
+
45
+ def try_enc(str, enc, harder = true) # :nodoc:
46
+ old = str.encoding
47
+ return str if old == enc
48
+ str.force_encoding(enc)
49
+ unless str.valid_encoding?
50
+ if harder
51
+ try_enc_harder(str, enc, old)
52
+ else
53
+ enc_fallback(str, enc, old)
54
+ end
55
+ end
56
+ str
57
+ end
58
+ end
@@ -1,5 +1,5 @@
1
1
  # -*- encoding: utf-8 -*-
2
- # Copyright (C) 2015-2016 all contributors <dtas-all@nongnu.org>
2
+ # Copyright (C) 2015-2019 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
- require 'sequel/no_core_ext'
42
- db = Sequel.connect(db, single_threaded: true)
43
- end
44
- if db.class.to_s.downcase.include?('sqlite')
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]+)/s
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
- @db.transaction do
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.each do |tag, value|
113
- tag_id = @tag_map[tag] or next
114
- value.strip!
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
- @db.transaction do
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
- jobs.times { |i| fork { worker(todo) } }
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
- @db.transaction do
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
- @db.transaction do
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
@@ -1,8 +1,8 @@
1
- # Copyright (C) 2013-2016 all contributors <dtas-all@nongnu.org>
1
+ # Copyright (C) 2013-2019 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
  begin
5
- require 'io/splice'
5
+ require 'sleepy_penguin'
6
6
  rescue LoadError
7
7
  end
8
8
  require_relative '../dtas'
@@ -20,12 +20,15 @@ def self.new
20
20
  rv
21
21
  end
22
22
 
23
- # create no-op methods for non-Linux
24
- unless method_defined?(:pipe_size=)
25
- def pipe_size=(_)
26
- end
23
+ def pipe_size=(nr)
24
+ defined?(SleepyPenguin::F_SETPIPE_SZ) and
25
+ fcntl(SleepyPenguin::F_SETPIPE_SZ, nr)
27
26
  end
28
27
 
28
+ def pipe_size
29
+ fcntl(SleepyPenguin::F_GETPIPE_SZ)
30
+ end if defined?(SleepyPenguin::F_GETPIPE_SZ)
31
+
29
32
  # avoid syscall, we never change IO#nonblock= directly
30
33
  def nonblock?
31
34
  false
@@ -0,0 +1,75 @@
1
+ # Copyright (C) 2017-2019 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
@@ -1,4 +1,4 @@
1
- # Copyright (C) 2013-2016 all contributors <dtas-all@nongnu.org>
1
+ # Copyright (C) 2013-2019 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
  # encoding: binary
@@ -50,17 +50,19 @@ def mcache_lookup(infile)
50
50
  out =~ /^Sample Rate\s*:\s*(\d+)/n and dst['rate'] = $1.to_i
51
51
  out =~ /^Precision\s*:\s*(\d+)-bit/n and dst['bits'] = $1.to_i
52
52
 
53
+ enc = Encoding.default_external # typically Encoding::UTF_8
53
54
  if out =~ /\nComments\s*:[ \t]*\n?(.*)\z/mn
54
55
  comments = dst['comments'] = {}
55
56
  key = nil
56
57
  $1.split(/\n/n).each do |line|
57
58
  if line.sub!(/^([^=]+)=/ni, '')
58
- key = DTAS.dedupe_str($1.upcase)
59
+ key = DTAS.dedupe_str(DTAS.try_enc($1.upcase, enc))
59
60
  end
60
61
  (comments[key] ||= ''.b) << "#{line}\n" unless line.empty?
61
62
  end
62
63
  comments.each do |k,v|
63
64
  v.chomp!
65
+ DTAS.try_enc(v, enc)
64
66
  comments[k] = DTAS.dedupe_str(v)
65
67
  end
66
68
  end
@@ -1,4 +1,4 @@
1
- # Copyright (C) 2013-2016 all contributors <dtas-all@nongnu.org>
1
+ # Copyright (C) 2013-2019 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
  begin
@@ -37,9 +37,13 @@ def watch_files(paths, blk)
37
37
  @dir2wd = {}
38
38
  Array(paths).each do |path|
39
39
  watchdir, watchbase = File.split(File.expand_path(path))
40
- wd = @dir2wd[watchdir] ||= add_watch(watchdir, FLAGS)
41
- m = @watches[wd] ||= {}
42
- m[watchbase] = true
40
+ begin
41
+ wd = @dir2wd[watchdir] ||= add_watch(watchdir, FLAGS)
42
+ m = @watches[wd] ||= {}
43
+ m[watchbase] = true
44
+ rescue SystemCallError => e
45
+ warn "#{watchdir.dump}: #{e.message} (#{e.class})"
46
+ end
43
47
  end
44
48
  end
45
49
  end
@@ -1,4 +1,4 @@
1
- # Copyright (C) 2013-2016 all contributors <dtas-all@nongnu.org>
1
+ # Copyright (C) 2013-2019 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 './test/helper'
@@ -49,14 +49,14 @@ def test_set_buffer_size
49
49
  buf = new_buffer
50
50
  buf.buffer_size = @@max_size
51
51
  assert_equal @@max_size, buf.buffer_size
52
- end if @@max_size
52
+ end if defined?(SleepyPenguin::F_GETPIPE_SZ)
53
53
 
54
54
  def test_buffer_size
55
55
  buf = new_buffer
56
56
  assert_operator buf.buffer_size, :>, 128
57
57
  buf.buffer_size = @@max_size
58
58
  assert_equal @@max_size, buf.buffer_size
59
- end if @@max_size
59
+ end if defined?(SleepyPenguin::F_GETPIPE_SZ)
60
60
 
61
61
  def test_broadcast_1
62
62
  buf = new_buffer
@@ -87,9 +87,9 @@ def test_broadcast_tee
87
87
  assert_empty blocked
88
88
  assert_equal "HELLO", a[0].read(5)
89
89
  assert_equal "HELLO", b[0].read(5)
90
- max = '*' * a[0].pipe_size
90
+ max = '*' * pipe_size(a[0])
91
91
  assert_equal max.size, a[1].write(max)
92
- assert_equal a[0].nread, a[0].pipe_size
92
+ assert_equal a[0].nread, pipe_size(a[0])
93
93
  a[1].nonblock = true
94
94
  assert_equal 5, buf.__broadcast_tee(blocked, [a[1], b[1]], 5)
95
95
  assert_equal [a[1]], blocked
@@ -108,10 +108,10 @@ def test_broadcast
108
108
  assert_equal "HELLO", a[0].read(5)
109
109
  assert_equal "HELLO", b[0].read(5)
110
110
 
111
- return unless b[1].respond_to?(:pipe_size)
111
+ return unless defined?(SleepyPenguin::F_GETPIPE_SZ)
112
112
 
113
113
  b[1].nonblock = true
114
- b[1].write('*' * b[1].pipe_size)
114
+ b[1].write('*' * pipe_size(b[1]))
115
115
  buf.wr.write "BYE"
116
116
  assert_equal :wait_readable, buf.broadcast([a[1], b[1]])
117
117
  assert_equal 8, buf.bytes_xfer
@@ -157,8 +157,8 @@ def test_broadcast_all_full
157
157
  a = pipe
158
158
  b = pipe
159
159
  buf = new_buffer
160
- a[1].write('*' * a[1].pipe_size)
161
- b[1].write('*' * b[1].pipe_size)
160
+ a[1].write('*' * pipe_size(a[1]))
161
+ b[1].write('*' * pipe_size(b[1]))
162
162
 
163
163
  a[1].nonblock = true
164
164
  b[1].nonblock = true
@@ -167,7 +167,7 @@ def test_broadcast_all_full
167
167
  buf.wr.write "HELLO"
168
168
  assert_equal tmp, buf.broadcast(tmp)
169
169
  assert_equal [a[1], b[1]], tmp
170
- end if IO.method_defined?(:pipe_size)
170
+ end if defined?(SleepyPenguin::F_GETPIPE_SZ)
171
171
 
172
172
  def test_serialize
173
173
  buf = new_buffer
@@ -204,4 +204,8 @@ def test_load_size
204
204
  assert_equal 4096, buf.buffer_size
205
205
  buf.close!
206
206
  end
207
+
208
+ def pipe_size(io)
209
+ io.fcntl(SleepyPenguin::F_GETPIPE_SZ)
210
+ end
207
211
  end
@@ -0,0 +1,20 @@
1
+ # Copyright (C) 2018-2019 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 './test/helper'
5
+ require 'dtas'
6
+ require 'yaml'
7
+
8
+ class TestEncoding < Testcase
9
+ def test_encoding
10
+ data = <<EOD # <20180111114546.77906b35@cumparsita.ch>
11
+ ---
12
+ comments:
13
+ ARTIST: !binary |-
14
+ RW5yaXF1ZSBSb2Ryw61ndWV6
15
+ EOD
16
+ hash = YAML.load(data)
17
+ artist = DTAS.try_enc(hash['comments']['ARTIST'], Encoding::UTF_8)
18
+ assert_equal 'Enrique Rodríguez', artist
19
+ end
20
+ end
@@ -1,10 +1,10 @@
1
- # Copyright (C) 2013-2016 all contributors <dtas-all@nongnu.org>
1
+ # Copyright (C) 2013-2019 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_relative 'helper'
5
5
  begin
6
6
  require 'dtas/mlib'
7
- require 'sequel/no_core_ext'
7
+ require 'sequel'
8
8
  require 'sqlite3'
9
9
  rescue LoadError => err
10
10
  warn "skipping mlib test: #{err.message}"
@@ -13,7 +13,7 @@
13
13
 
14
14
  class TestMlib < Testcase
15
15
  def setup
16
- @db = Sequel.sqlite(':memory:')
16
+ @db = Sequel.sqlite(':memory:', case_sensitive_like: false)
17
17
  end
18
18
 
19
19
  def test_migrate
@@ -0,0 +1,47 @@
1
+ # Copyright (C) 2017-2019 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 './test/helper'
5
+ require 'dtas/pipeline'
6
+
7
+ class TestPipeline < Testcase
8
+ include DTAS::Pipeline
9
+ def setup
10
+ @env = ENV.to_hash
11
+ end
12
+
13
+ def pipeline_result
14
+ IO.pipe do |rd, wr|
15
+ begin
16
+ pid = fork do
17
+ rd.close
18
+ $stdout.reopen(wr)
19
+ yield
20
+ exit!(0)
21
+ end
22
+ wr.close
23
+ return rd.read
24
+ ensure
25
+ _, status = Process.waitpid2(pid)
26
+ assert_predicate status, :success?
27
+ end
28
+ end
29
+ nil
30
+ end
31
+
32
+ def test_pipeline
33
+ assert_equal("BYYRU\n", pipeline_result do
34
+ run_pipeline(@env, [
35
+ %w(echo hello), # anything which generates something to stdout
36
+ %w(tr [a-z] [A-Z]), # upcase
37
+ # this lambda runs inside its own process
38
+ lambda do
39
+ $stdin.each_line { |l| $stdout.write("#{l.chomp.reverse}\n") }
40
+ exit!(0)
41
+ end,
42
+ # rot13
43
+ %w(tr [a-m][n-z][A-M][N-Z] [n-z][a-m][N-Z][A-M])
44
+ ])
45
+ end)
46
+ end
47
+ end
@@ -1,8 +1,8 @@
1
- # Copyright (C) 2013-2016 all contributors <dtas-all@nongnu.org>
1
+ # Copyright (C) 2013-2019 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
  begin
5
- require 'io/splice'
5
+ require 'sleepy_penguin'
6
6
  require './test/player_integration'
7
7
  class TestSinkPipeSizeIntegration < Testcase
8
8
  include PlayerIntegration
@@ -14,7 +14,7 @@ def test_sink_pipe_size_integration
14
14
  s.req_ok("sink ed default pipe_size=0x10000")
15
15
  s.req_ok("sink ed default pipe_size=")
16
16
  s.req_ok("sink ed default pipe_size=4096")
17
- end if IO.method_defined?(:pipe_size=)
17
+ end if SleepyPenguin.const_defined?(:F_SETPIPE_SZ)
18
18
  end
19
19
  rescue LoadError
20
20
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dtas
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.15.0
4
+ version: 0.16.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - dtas hackers
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-04-07 00:00:00.000000000 Z
11
+ date: 2019-01-02 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: |-
14
14
  Free Software command-line tools for audio playback, mastering, and
@@ -98,6 +98,7 @@ files:
98
98
  - lib/dtas/cue_index.rb
99
99
  - lib/dtas/disclaimer.rb
100
100
  - lib/dtas/edit_client.rb
101
+ - lib/dtas/encoding.rb
101
102
  - lib/dtas/fadefx.rb
102
103
  - lib/dtas/format.rb
103
104
  - lib/dtas/mcache.rb
@@ -108,6 +109,7 @@ files:
108
109
  - lib/dtas/parse_time.rb
109
110
  - lib/dtas/partstats.rb
110
111
  - lib/dtas/pipe.rb
112
+ - lib/dtas/pipeline.rb
111
113
  - lib/dtas/player.rb
112
114
  - lib/dtas/player/client_handler.rb
113
115
  - lib/dtas/process.rb
@@ -164,6 +166,7 @@ files:
164
166
  - test/helper.rb
165
167
  - test/player_integration.rb
166
168
  - test/test_buffer.rb
169
+ - test/test_encoding.rb
167
170
  - test/test_env.rb
168
171
  - test/test_fadefx.rb
169
172
  - test/test_format.rb
@@ -171,6 +174,7 @@ files:
171
174
  - test/test_mcache.rb
172
175
  - test/test_mlib.rb
173
176
  - test/test_parse_freq.rb
177
+ - test/test_pipeline.rb
174
178
  - test/test_player.rb
175
179
  - test/test_player_client_handler.rb
176
180
  - test/test_player_integration.rb
@@ -206,8 +210,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
206
210
  - !ruby/object:Gem::Version
207
211
  version: '0'
208
212
  requirements: []
209
- rubyforge_project:
210
- rubygems_version: 2.6.10
213
+ rubygems_version: 3.0.1
211
214
  signing_key:
212
215
  specification_version: 4
213
216
  summary: duct tape audio suite for *nix