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