o-mpd_client 0.2.0.1
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/.gitignore +17 -0
- data/.hound.yml +2 -0
- data/.rubocop.yml +30 -0
- data/.travis.yml +10 -0
- data/CHANGELOG.md +44 -0
- data/Gemfile +16 -0
- data/LICENSE +22 -0
- data/MPD_COMMANDS.md +653 -0
- data/README.md +148 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/examples/Gemfile +5 -0
- data/examples/albumart.rb +18 -0
- data/examples/client.rb +17 -0
- data/examples/idle.rb +27 -0
- data/examples/range.rb +22 -0
- data/examples/rangeid.rb +26 -0
- data/examples/search_and_replace_playlist.rb +44 -0
- data/examples/stickers.rb +53 -0
- data/lib/mpd_client/version.rb +7 -0
- data/lib/mpd_client.rb +549 -0
- data/mpd_client.gemspec +25 -0
- data/spec/mpd_client/mpd_client_spec.rb +17 -0
- data/spec/spec_helper.rb +8 -0
- metadata +85 -0
data/lib/mpd_client.rb
ADDED
|
@@ -0,0 +1,549 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'socket'
|
|
4
|
+
require 'stringio'
|
|
5
|
+
require 'timeout'
|
|
6
|
+
require 'mpd_client/version'
|
|
7
|
+
|
|
8
|
+
module MPD
|
|
9
|
+
HELLO_PREFIX = 'OK MPD '
|
|
10
|
+
ERROR_PREFIX = 'ACK '
|
|
11
|
+
SUCCESS = "OK\n"
|
|
12
|
+
NEXT = "list_OK\n"
|
|
13
|
+
|
|
14
|
+
# MPD changelog: https://github.com/MusicPlayerDaemon/MPD/blob/master/NEWS
|
|
15
|
+
# Protocol: https://mpd.readthedocs.io/en/latest/protocol.html
|
|
16
|
+
COMMANDS = {
|
|
17
|
+
# Status Commands
|
|
18
|
+
'clearerror' => 'fetch_nothing',
|
|
19
|
+
'currentsong' => 'fetch_object',
|
|
20
|
+
'idle' => 'fetch_list',
|
|
21
|
+
'noidle' => '',
|
|
22
|
+
'status' => 'fetch_object',
|
|
23
|
+
'stats' => 'fetch_object',
|
|
24
|
+
# Playback Option Commands
|
|
25
|
+
'consume' => 'fetch_nothing',
|
|
26
|
+
'crossfade' => 'fetch_nothing',
|
|
27
|
+
'mixrampdb' => 'fetch_nothing',
|
|
28
|
+
'mixrampdelay' => 'fetch_nothing',
|
|
29
|
+
'random' => 'fetch_nothing',
|
|
30
|
+
'repeat' => 'fetch_nothing',
|
|
31
|
+
# TODO parse the volume out
|
|
32
|
+
'getvol' => 'fetch_item',
|
|
33
|
+
'setvol' => 'fetch_nothing',
|
|
34
|
+
'single' => 'fetch_nothing',
|
|
35
|
+
'replay_gain_mode' => 'fetch_nothing',
|
|
36
|
+
'replay_gain_status' => 'fetch_item',
|
|
37
|
+
'volume' => 'fetch_nothing',
|
|
38
|
+
# Playback Control Commands
|
|
39
|
+
'next' => 'fetch_nothing',
|
|
40
|
+
'pause' => 'fetch_nothing',
|
|
41
|
+
'play' => 'fetch_nothing',
|
|
42
|
+
'playid' => 'fetch_nothing',
|
|
43
|
+
'previous' => 'fetch_nothing',
|
|
44
|
+
'seek' => 'fetch_nothing',
|
|
45
|
+
'seekid' => 'fetch_nothing',
|
|
46
|
+
'seekcur' => 'fetch_nothing',
|
|
47
|
+
'stop' => 'fetch_nothing',
|
|
48
|
+
# Playlist Commands
|
|
49
|
+
'add' => 'fetch_nothing',
|
|
50
|
+
'addid' => 'fetch_item',
|
|
51
|
+
'addtagid' => 'fetch_nothing',
|
|
52
|
+
'cleartagid' => 'fetch_nothing',
|
|
53
|
+
'clear' => 'fetch_nothing',
|
|
54
|
+
'delete' => 'fetch_nothing',
|
|
55
|
+
'deleteid' => 'fetch_nothing',
|
|
56
|
+
'move' => 'fetch_nothing',
|
|
57
|
+
'moveid' => 'fetch_nothing',
|
|
58
|
+
'playlistfind' => 'fetch_songs',
|
|
59
|
+
'playlistid' => 'fetch_songs',
|
|
60
|
+
'playlistinfo' => 'fetch_songs',
|
|
61
|
+
'playlistsearch' => 'fetch_songs',
|
|
62
|
+
'plchanges' => 'fetch_songs',
|
|
63
|
+
'plchangesposid' => 'fetch_changes',
|
|
64
|
+
'prio' => 'fetch_nothing',
|
|
65
|
+
'prioid' => 'fetch_nothing',
|
|
66
|
+
'rangeid' => 'fetch_nothing',
|
|
67
|
+
'shuffle' => 'fetch_nothing',
|
|
68
|
+
'swap' => 'fetch_nothing',
|
|
69
|
+
'swapid' => 'fetch_nothing',
|
|
70
|
+
# Stored Playlist Commands
|
|
71
|
+
'listplaylist' => 'fetch_list',
|
|
72
|
+
'listplaylistinfo' => 'fetch_songs',
|
|
73
|
+
'listplaylists' => 'fetch_playlists',
|
|
74
|
+
'load' => 'fetch_nothing',
|
|
75
|
+
'playlistadd' => 'fetch_nothing',
|
|
76
|
+
'playlistclear' => 'fetch_nothing',
|
|
77
|
+
'playlistdelete' => 'fetch_nothing',
|
|
78
|
+
'playlistmove' => 'fetch_nothing',
|
|
79
|
+
'rename' => 'fetch_nothing',
|
|
80
|
+
'rm' => 'fetch_nothing',
|
|
81
|
+
'save' => 'fetch_nothing',
|
|
82
|
+
# Database Commands
|
|
83
|
+
'count' => 'fetch_object',
|
|
84
|
+
'find' => 'fetch_songs',
|
|
85
|
+
'findadd' => 'fetch_nothing',
|
|
86
|
+
'list' => 'fetch_list',
|
|
87
|
+
'listall' => 'fetch_database',
|
|
88
|
+
'listallinfo' => 'fetch_database',
|
|
89
|
+
'listfiles' => 'fetch_database',
|
|
90
|
+
'lsinfo' => 'fetch_database',
|
|
91
|
+
'search' => 'fetch_songs',
|
|
92
|
+
'searchadd' => 'fetch_nothing',
|
|
93
|
+
'searchaddp1' => 'fetch_nothing',
|
|
94
|
+
'update' => 'fetch_item',
|
|
95
|
+
'rescan' => 'fetch_item',
|
|
96
|
+
'readcomments' => 'fetch_object',
|
|
97
|
+
# Mounts and neighbors
|
|
98
|
+
'mount' => 'fetch_nothing',
|
|
99
|
+
'unmount' => 'fetch_nothing',
|
|
100
|
+
'listmounts' => 'fetch_mounts',
|
|
101
|
+
'listneighbors' => 'fetch_neighbors',
|
|
102
|
+
# Sticker Commands
|
|
103
|
+
'sticker get' => 'fetch_sticker',
|
|
104
|
+
'sticker set' => 'fetch_nothing',
|
|
105
|
+
'sticker delete' => 'fetch_nothing',
|
|
106
|
+
'sticker list' => 'fetch_stickers',
|
|
107
|
+
'sticker find' => 'fetch_songs',
|
|
108
|
+
# Connection Commands
|
|
109
|
+
'close' => '',
|
|
110
|
+
'kill' => '',
|
|
111
|
+
'password' => 'fetch_nothing',
|
|
112
|
+
'ping' => 'fetch_nothing',
|
|
113
|
+
# Audio Output Commands
|
|
114
|
+
'disableoutput' => 'fetch_nothing',
|
|
115
|
+
'enableoutput' => 'fetch_nothing',
|
|
116
|
+
'outputs' => 'fetch_outputs',
|
|
117
|
+
'toggleoutput' => 'fetch_nothing',
|
|
118
|
+
# Reflection Commands
|
|
119
|
+
'config' => 'fetch_item',
|
|
120
|
+
'commands' => 'fetch_list',
|
|
121
|
+
'notcommands' => 'fetch_list',
|
|
122
|
+
'tagtypes' => 'fetch_list',
|
|
123
|
+
'urlhandlers' => 'fetch_list',
|
|
124
|
+
'decoders' => 'fetch_plugins',
|
|
125
|
+
# Client To Client
|
|
126
|
+
'subscribe' => 'fetch_nothing',
|
|
127
|
+
'unsubscribe' => 'fetch_nothing',
|
|
128
|
+
'channels' => 'fetch_list',
|
|
129
|
+
'readmessages' => 'fetch_messages',
|
|
130
|
+
'sendmessage' => 'fetch_nothing'
|
|
131
|
+
}.freeze
|
|
132
|
+
|
|
133
|
+
# The `MPD::Client` is used for interactions with a MPD server.
|
|
134
|
+
#
|
|
135
|
+
# Example:
|
|
136
|
+
#
|
|
137
|
+
# ```ruby
|
|
138
|
+
# require 'mpd_client'
|
|
139
|
+
# require 'logger'
|
|
140
|
+
#
|
|
141
|
+
# client = MPD::Client.new
|
|
142
|
+
# client.log = Logger.new($stderr)
|
|
143
|
+
# client.connect('/var/run/mpd/socket')
|
|
144
|
+
# ```
|
|
145
|
+
class Client
|
|
146
|
+
attr_reader :mpd_version
|
|
147
|
+
|
|
148
|
+
class << self
|
|
149
|
+
# Default logger for all `MPD::Client`` instances
|
|
150
|
+
#
|
|
151
|
+
# ```ruby
|
|
152
|
+
# MPD::Client.log = Logger.new($stderr)
|
|
153
|
+
# ```
|
|
154
|
+
attr_accessor :log
|
|
155
|
+
|
|
156
|
+
def connect(host = 'localhost', port = 6600, timeout: nil)
|
|
157
|
+
client = MPD::Client.new
|
|
158
|
+
client.connect(host, port, timeout: timeout)
|
|
159
|
+
|
|
160
|
+
client
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def add_command(name, retval)
|
|
164
|
+
escaped_name = name.tr(' ', '_')
|
|
165
|
+
|
|
166
|
+
define_method escaped_name.to_sym do |*args|
|
|
167
|
+
ensure_connected
|
|
168
|
+
|
|
169
|
+
execute(name, *args, retval)
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def remove_command(name)
|
|
174
|
+
raise "Can't remove not existent '#{name}' command" unless method_defined? name.to_sym
|
|
175
|
+
|
|
176
|
+
remove_method name.to_sym
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def initialize
|
|
181
|
+
@mutex = Mutex.new
|
|
182
|
+
reset
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def connect(host = 'localhost', port = 6600, timeout: nil)
|
|
186
|
+
@host = host
|
|
187
|
+
@port = port
|
|
188
|
+
@timeout = timeout
|
|
189
|
+
|
|
190
|
+
reconnect
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def reconnect
|
|
194
|
+
log&.info("MPD (re)connect #{@host}, #{@port}")
|
|
195
|
+
|
|
196
|
+
@socket =
|
|
197
|
+
Timeout.timeout(@timeout) do
|
|
198
|
+
if @host.start_with?('/')
|
|
199
|
+
UNIXSocket.new(@host)
|
|
200
|
+
else
|
|
201
|
+
TCPSocket.new(@host, @port)
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
hello
|
|
206
|
+
@connected = true
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def disconnect
|
|
210
|
+
log&.info('MPD disconnect')
|
|
211
|
+
begin
|
|
212
|
+
Timeout.timeout(@timeout) do
|
|
213
|
+
@socket.close
|
|
214
|
+
end
|
|
215
|
+
rescue Timeout::Error
|
|
216
|
+
@socket = nil
|
|
217
|
+
end
|
|
218
|
+
reset
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def reset
|
|
222
|
+
@mpd_version = nil
|
|
223
|
+
@command_list = nil
|
|
224
|
+
@socket = nil
|
|
225
|
+
@log = nil
|
|
226
|
+
@connected = false
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def connected?
|
|
230
|
+
@connected
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
# https://www.musicpd.org/doc/protocol/command_lists.html
|
|
234
|
+
def command_list_ok_begin
|
|
235
|
+
raise 'Already in command list' unless @command_list.nil?
|
|
236
|
+
|
|
237
|
+
write_command('command_list_ok_begin')
|
|
238
|
+
|
|
239
|
+
@command_list = []
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
def command_list_end
|
|
243
|
+
raise 'Not in command list' if @command_list.nil?
|
|
244
|
+
|
|
245
|
+
write_command('command_list_end')
|
|
246
|
+
|
|
247
|
+
fetch_command_list
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
# The current logger. If no logger has been set MPD::Client.log is used
|
|
251
|
+
def log
|
|
252
|
+
@log || MPD::Client.log
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
# Sets the +logger+ used by this instance of MPD::Client
|
|
256
|
+
attr_writer :log
|
|
257
|
+
|
|
258
|
+
def albumart(uri)
|
|
259
|
+
fetch_binary(StringIO.new, 0, 'albumart', uri)
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
def readpicture(uri)
|
|
263
|
+
fetch_binary(StringIO.new, 0, 'readpicture', uri)
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
private
|
|
267
|
+
|
|
268
|
+
def ensure_connected
|
|
269
|
+
raise 'Please connect to MPD server' unless connected?
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
def execute(command, *args, retval)
|
|
273
|
+
@mutex.synchronize do
|
|
274
|
+
write_command(command, *args)
|
|
275
|
+
|
|
276
|
+
if @command_list.nil?
|
|
277
|
+
eval retval
|
|
278
|
+
else
|
|
279
|
+
@command_list << retval
|
|
280
|
+
end
|
|
281
|
+
end
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
def write_line(line)
|
|
285
|
+
begin
|
|
286
|
+
Timeout.timeout(@timeout) do
|
|
287
|
+
@socket.puts line
|
|
288
|
+
end
|
|
289
|
+
rescue Errno::EPIPE, Timeout::Error
|
|
290
|
+
reconnect
|
|
291
|
+
Timeout.timeout(@timeout) do
|
|
292
|
+
@socket.puts line
|
|
293
|
+
end
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
Timeout.timeout(@timeout) do
|
|
297
|
+
@socket.flush
|
|
298
|
+
end
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
def write_command(command, *args)
|
|
302
|
+
parts = [command]
|
|
303
|
+
|
|
304
|
+
args.each do |arg|
|
|
305
|
+
line =
|
|
306
|
+
if arg.is_a?(Array)
|
|
307
|
+
arg.size == 1 ? "\"#{arg[0].to_i}:\"" : "\"#{arg[0].to_i}:#{arg[1].to_i}\""
|
|
308
|
+
else
|
|
309
|
+
"\"#{escape(arg)}\""
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
parts << line
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
# log.debug("Calling MPD: #{command}#{args}") if log
|
|
316
|
+
log&.debug("Calling MPD: #{parts.join(' ')}")
|
|
317
|
+
write_line(parts.join(' '))
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
def read_line
|
|
321
|
+
line = Timeout.timeout(@timeout) do
|
|
322
|
+
@socket.gets
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
raise 'Connection lost while reading line' unless line.end_with?("\n")
|
|
326
|
+
|
|
327
|
+
if line.start_with?(ERROR_PREFIX)
|
|
328
|
+
error = line[/#{ERROR_PREFIX}(.*)/, 1].strip
|
|
329
|
+
raise error
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
if !@command_list.nil?
|
|
333
|
+
return if line == NEXT
|
|
334
|
+
raise "Got unexpected '#{SUCCESS}'" if line == SUCCESS
|
|
335
|
+
elsif line == SUCCESS
|
|
336
|
+
return
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
line
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
def read_pair
|
|
343
|
+
line = read_line
|
|
344
|
+
|
|
345
|
+
return if line.nil?
|
|
346
|
+
|
|
347
|
+
line.split(': ', 2)
|
|
348
|
+
end
|
|
349
|
+
|
|
350
|
+
def read_pairs
|
|
351
|
+
result = []
|
|
352
|
+
|
|
353
|
+
pair = read_pair
|
|
354
|
+
|
|
355
|
+
while pair
|
|
356
|
+
result << pair
|
|
357
|
+
pair = read_pair
|
|
358
|
+
end
|
|
359
|
+
|
|
360
|
+
result
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
def fetch_item
|
|
364
|
+
pairs = read_pairs
|
|
365
|
+
|
|
366
|
+
return nil if pairs.size != 1
|
|
367
|
+
|
|
368
|
+
pairs[0][1]
|
|
369
|
+
end
|
|
370
|
+
|
|
371
|
+
def fetch_nothing
|
|
372
|
+
line = read_line
|
|
373
|
+
|
|
374
|
+
raise "Got unexpected value: #{line}" unless line.nil?
|
|
375
|
+
end
|
|
376
|
+
|
|
377
|
+
def fetch_list
|
|
378
|
+
result = []
|
|
379
|
+
seen = nil
|
|
380
|
+
|
|
381
|
+
read_pairs.each do |key, value|
|
|
382
|
+
value = value.chomp.force_encoding('utf-8')
|
|
383
|
+
|
|
384
|
+
if key != seen
|
|
385
|
+
raise "Expected key '#{seen}', got '#{key}'" unless seen.nil?
|
|
386
|
+
|
|
387
|
+
seen = key
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
result << value
|
|
391
|
+
end
|
|
392
|
+
|
|
393
|
+
result
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
def fetch_objects(delimeters = [])
|
|
397
|
+
result = []
|
|
398
|
+
obj = {}
|
|
399
|
+
|
|
400
|
+
read_pairs.each do |key, value|
|
|
401
|
+
key = key.downcase
|
|
402
|
+
value = value.chomp.force_encoding('utf-8')
|
|
403
|
+
|
|
404
|
+
if delimeters.include?(key)
|
|
405
|
+
result << obj unless obj.empty?
|
|
406
|
+
obj = {}
|
|
407
|
+
elsif obj.include?(key)
|
|
408
|
+
obj[key] << value
|
|
409
|
+
end
|
|
410
|
+
|
|
411
|
+
obj[key] = value
|
|
412
|
+
end
|
|
413
|
+
|
|
414
|
+
result << obj unless obj.empty?
|
|
415
|
+
|
|
416
|
+
result
|
|
417
|
+
end
|
|
418
|
+
|
|
419
|
+
def fetch_object
|
|
420
|
+
objs = fetch_objects
|
|
421
|
+
|
|
422
|
+
objs ? objs[0] : {}
|
|
423
|
+
end
|
|
424
|
+
|
|
425
|
+
def fetch_binary(io = StringIO.new, offset = 0, *args)
|
|
426
|
+
data = {}
|
|
427
|
+
|
|
428
|
+
@mutex.synchronize do
|
|
429
|
+
write_command(*args, offset)
|
|
430
|
+
|
|
431
|
+
binary = false
|
|
432
|
+
|
|
433
|
+
read_pairs.each do |item|
|
|
434
|
+
if binary
|
|
435
|
+
io << item.join(': ')
|
|
436
|
+
next
|
|
437
|
+
end
|
|
438
|
+
|
|
439
|
+
key = item[0]
|
|
440
|
+
value = item[1].chomp
|
|
441
|
+
|
|
442
|
+
binary = (key == 'binary')
|
|
443
|
+
|
|
444
|
+
data[key] = value
|
|
445
|
+
end
|
|
446
|
+
end
|
|
447
|
+
|
|
448
|
+
size = data['size'].to_i
|
|
449
|
+
binary = data['binary'].to_i
|
|
450
|
+
|
|
451
|
+
next_offset = offset + binary
|
|
452
|
+
|
|
453
|
+
return [data, io] if next_offset >= size
|
|
454
|
+
|
|
455
|
+
io.seek(-1, IO::SEEK_CUR)
|
|
456
|
+
|
|
457
|
+
fetch_binary(io, next_offset, *args)
|
|
458
|
+
end
|
|
459
|
+
|
|
460
|
+
def fetch_changes
|
|
461
|
+
fetch_objects(['cpos'])
|
|
462
|
+
end
|
|
463
|
+
|
|
464
|
+
def fetch_songs
|
|
465
|
+
fetch_objects(['file'])
|
|
466
|
+
end
|
|
467
|
+
|
|
468
|
+
def fetch_mounts
|
|
469
|
+
fetch_objects(['mount'])
|
|
470
|
+
end
|
|
471
|
+
|
|
472
|
+
def fetch_neighbors
|
|
473
|
+
fetch_objects(['neighbor'])
|
|
474
|
+
end
|
|
475
|
+
|
|
476
|
+
def fetch_messages
|
|
477
|
+
fetch_objects('channel')
|
|
478
|
+
end
|
|
479
|
+
|
|
480
|
+
def fetch_outputs
|
|
481
|
+
fetch_objects(['outputid'])
|
|
482
|
+
end
|
|
483
|
+
|
|
484
|
+
def fetch_plugins
|
|
485
|
+
fetch_objects(['plugin'])
|
|
486
|
+
end
|
|
487
|
+
|
|
488
|
+
def fetch_database
|
|
489
|
+
fetch_objects(%w[file directory playlist])
|
|
490
|
+
end
|
|
491
|
+
|
|
492
|
+
def fetch_playlists
|
|
493
|
+
fetch_objects(['playlist'])
|
|
494
|
+
end
|
|
495
|
+
|
|
496
|
+
def fetch_stickers
|
|
497
|
+
result = []
|
|
498
|
+
|
|
499
|
+
read_pairs.each do |_key, sticker|
|
|
500
|
+
value = sticker.split('=', 2)
|
|
501
|
+
raise "Could now parse sticker: #{sticker}" if value.size < 2
|
|
502
|
+
|
|
503
|
+
result << Hash[*value]
|
|
504
|
+
end
|
|
505
|
+
|
|
506
|
+
result
|
|
507
|
+
end
|
|
508
|
+
|
|
509
|
+
def fetch_sticker
|
|
510
|
+
fetch_stickers[0]
|
|
511
|
+
end
|
|
512
|
+
|
|
513
|
+
def fetch_command_list
|
|
514
|
+
result = []
|
|
515
|
+
|
|
516
|
+
begin
|
|
517
|
+
@command_list.each do |retval|
|
|
518
|
+
result << (eval retval)
|
|
519
|
+
end
|
|
520
|
+
ensure
|
|
521
|
+
@command_list = nil
|
|
522
|
+
end
|
|
523
|
+
|
|
524
|
+
result
|
|
525
|
+
end
|
|
526
|
+
|
|
527
|
+
def hello
|
|
528
|
+
line = Timeout.timeout(@timeout) do
|
|
529
|
+
@socket.gets
|
|
530
|
+
end
|
|
531
|
+
|
|
532
|
+
raise 'Connection lost while reading MPD hello' unless line.end_with?("\n")
|
|
533
|
+
|
|
534
|
+
line.chomp!
|
|
535
|
+
|
|
536
|
+
raise "Got invalid MPD hello: #{line}" unless line.start_with?(HELLO_PREFIX)
|
|
537
|
+
|
|
538
|
+
@mpd_version = line[/#{HELLO_PREFIX}(.*)/, 1]
|
|
539
|
+
end
|
|
540
|
+
|
|
541
|
+
def escape(text)
|
|
542
|
+
text.to_s.gsub('\\', '\\\\').gsub('"', '\\"')
|
|
543
|
+
end
|
|
544
|
+
end
|
|
545
|
+
end
|
|
546
|
+
|
|
547
|
+
MPD::COMMANDS.each_pair do |name, callback|
|
|
548
|
+
MPD::Client.add_command(name, callback)
|
|
549
|
+
end
|
data/mpd_client.gemspec
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
lib = File.expand_path('lib', __dir__)
|
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
5
|
+
require 'mpd_client/version'
|
|
6
|
+
|
|
7
|
+
Gem::Specification.new do |gem|
|
|
8
|
+
gem.authors = ['Anton Maminov']
|
|
9
|
+
gem.email = ['anton.linux@gmail.com']
|
|
10
|
+
gem.description = 'Yet another Ruby MPD client library'
|
|
11
|
+
gem.summary = 'Simple Music Player Daemon library written entirely in Ruby'
|
|
12
|
+
gem.homepage = 'https://github.com/mamantoha/mpd_client'
|
|
13
|
+
|
|
14
|
+
gem.files = `git ls-files`.split($OUTPUT_RECORD_SEPARATOR)
|
|
15
|
+
gem.executables = gem.files.grep(%r{^bin/}).map { |f| File.basename(f) }
|
|
16
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
|
17
|
+
gem.required_ruby_version = '>= 2.6.6'
|
|
18
|
+
gem.name = 'o-mpd_client'
|
|
19
|
+
gem.require_paths = ['lib']
|
|
20
|
+
gem.version = MPD::Client::VERSION
|
|
21
|
+
gem.license = 'MIT'
|
|
22
|
+
|
|
23
|
+
gem.add_development_dependency 'bundler'
|
|
24
|
+
#gem.metadata['rubygems_mfa_required'] = 'true'
|
|
25
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
describe MPD::Client do
|
|
6
|
+
it 'should have version' do
|
|
7
|
+
expect(MPD::Client::VERSION).to_not be_nil
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
describe '#initialize' do
|
|
11
|
+
it 'should create new client' do
|
|
12
|
+
client = MPD::Client.new
|
|
13
|
+
|
|
14
|
+
expect(client.connected?).to eq(false)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: o-mpd_client
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.2.0.1
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Anton Maminov
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2023-10-11 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: bundler
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - ">="
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '0'
|
|
20
|
+
type: :development
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - ">="
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '0'
|
|
27
|
+
description: Yet another Ruby MPD client library
|
|
28
|
+
email:
|
|
29
|
+
- anton.linux@gmail.com
|
|
30
|
+
executables:
|
|
31
|
+
- console
|
|
32
|
+
- setup
|
|
33
|
+
extensions: []
|
|
34
|
+
extra_rdoc_files: []
|
|
35
|
+
files:
|
|
36
|
+
- ".gitignore"
|
|
37
|
+
- ".hound.yml"
|
|
38
|
+
- ".rubocop.yml"
|
|
39
|
+
- ".travis.yml"
|
|
40
|
+
- CHANGELOG.md
|
|
41
|
+
- Gemfile
|
|
42
|
+
- LICENSE
|
|
43
|
+
- MPD_COMMANDS.md
|
|
44
|
+
- README.md
|
|
45
|
+
- bin/console
|
|
46
|
+
- bin/setup
|
|
47
|
+
- examples/Gemfile
|
|
48
|
+
- examples/albumart.rb
|
|
49
|
+
- examples/client.rb
|
|
50
|
+
- examples/idle.rb
|
|
51
|
+
- examples/range.rb
|
|
52
|
+
- examples/rangeid.rb
|
|
53
|
+
- examples/search_and_replace_playlist.rb
|
|
54
|
+
- examples/stickers.rb
|
|
55
|
+
- lib/mpd_client.rb
|
|
56
|
+
- lib/mpd_client/version.rb
|
|
57
|
+
- mpd_client.gemspec
|
|
58
|
+
- spec/mpd_client/mpd_client_spec.rb
|
|
59
|
+
- spec/spec_helper.rb
|
|
60
|
+
homepage: https://github.com/mamantoha/mpd_client
|
|
61
|
+
licenses:
|
|
62
|
+
- MIT
|
|
63
|
+
metadata: {}
|
|
64
|
+
post_install_message:
|
|
65
|
+
rdoc_options: []
|
|
66
|
+
require_paths:
|
|
67
|
+
- lib
|
|
68
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
69
|
+
requirements:
|
|
70
|
+
- - ">="
|
|
71
|
+
- !ruby/object:Gem::Version
|
|
72
|
+
version: 2.6.6
|
|
73
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
74
|
+
requirements:
|
|
75
|
+
- - ">="
|
|
76
|
+
- !ruby/object:Gem::Version
|
|
77
|
+
version: '0'
|
|
78
|
+
requirements: []
|
|
79
|
+
rubygems_version: 3.3.15
|
|
80
|
+
signing_key:
|
|
81
|
+
specification_version: 4
|
|
82
|
+
summary: Simple Music Player Daemon library written entirely in Ruby
|
|
83
|
+
test_files:
|
|
84
|
+
- spec/mpd_client/mpd_client_spec.rb
|
|
85
|
+
- spec/spec_helper.rb
|