mpd_client 0.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.
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in mpd_client.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Anton Maminov
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,63 @@
1
+ # MPDClient
2
+
3
+ Yet another Music Player Daemon (MPD) client library written entirely in Ruby.
4
+ `mpd_client` is a Ruby port of the [python-mpd](https://github.com/Mic92/python-mpd2) library.
5
+
6
+ ## Installation
7
+
8
+ Add this line to your application `Gemfile`:
9
+
10
+ ```ruby
11
+ gem 'mpd_client'
12
+ ```
13
+
14
+ And then execute:
15
+
16
+ ```
17
+ $ bundle
18
+ ```
19
+
20
+ Or install it yourself as:
21
+
22
+ ```
23
+ $ gem install mpd_client
24
+ ```
25
+
26
+ ## Usage
27
+ All functionality is contained in the `MPDClient` class. Creating an instance of this class is as simple as:
28
+
29
+ ```ruby
30
+ client = MPDClient.new
31
+ ```
32
+
33
+ Once you have an instance of the `MPDClient` class, start by connecting to the server:
34
+
35
+ ```ruby
36
+ client.connect('localhost', 6600)
37
+ ```
38
+
39
+ The client library can be used as follows:
40
+
41
+ ```ruby
42
+ puts client.mpd_version # print the mpd version
43
+ puts client.search('title', 'ruby') # print the result of the command 'search title ruby'
44
+ client.close # send the close command
45
+ client.disconect # disconnect from the server
46
+ ```
47
+
48
+ Command lists are also supported using `command_list_ok_begin` and `command_list_end`:
49
+
50
+ ```ruby
51
+ client.command_list_ok_begin # start a command list
52
+ client.update # insert the update command into the list
53
+ client.status # insert the status command into the list
54
+ client.command_list_end # result will be a Array with the results
55
+ ```
56
+
57
+ ## Contributing
58
+
59
+ 1. Fork it
60
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
61
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
62
+ 4. Push to the branch (`git push origin my-new-feature`)
63
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
@@ -0,0 +1,5 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ class MPDClient
4
+ VERSION = "0.0.1"
5
+ end
data/lib/mpd_client.rb ADDED
@@ -0,0 +1,329 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'socket'
4
+ require "mpd_client/version"
5
+
6
+ HELLO_PREFIX = "OK MPD "
7
+ ERROR_PREFIX = "ACK "
8
+ SUCCESS = "OK"
9
+ NEXT = "list_OK"
10
+
11
+ # http://mpd.wikia.com/wiki/MusicPlayerDaemonCommands
12
+ COMMANDS = {
13
+ # Status Commands
14
+ "clearerror" => "fetch_nothing",
15
+ "currentsong" => "fetch_object",
16
+ "idle" => "fetch_list",
17
+ "noidle" => "",
18
+ "status" => "fetch_object",
19
+ "stats" => "fetch_object",
20
+ # Playback Option Commands
21
+ "consume" => "fetch_nothing",
22
+ "crossfade" => "fetch_nothing",
23
+ "mixrampdb" => "fetch_nothing",
24
+ "mixrampdelay" => "fetch_nothing",
25
+ "random" => "fetch_nothing",
26
+ "repeat" => "fetch_nothing",
27
+ "setvol" => "fetch_nothing",
28
+ "single" => "fetch_nothing",
29
+ "replay_gain_mode" => "fetch_nothing",
30
+ "replay_gain_status" => "fetch_item",
31
+ # Playback Control Commands
32
+ "next" => "fetch_nothing",
33
+ "pause" => "fetch_nothing",
34
+ "play" => "fetch_nothing",
35
+ "playid" => "fetch_nothing",
36
+ "previous" => "fetch_nothing",
37
+ "seek" => "fetch_nothing",
38
+ "seekid" => "fetch_nothing",
39
+ "seekcur" => "fetch_nothing",
40
+ "stop" => "fetch_nothing",
41
+ # Playlist Commands
42
+ "add" => "fetch_nothing",
43
+ "addid" => "fetch_item",
44
+ "clear" => "fetch_nothing",
45
+ "delete" => "fetch_nothing",
46
+ "deleteid" => "fetch_nothing",
47
+ "move" => "fetch_nothing",
48
+ "moveid" => "fetch_nothing",
49
+ "playlist" => "fetch_playlist",
50
+ "playlistfind" => "fetch_songs",
51
+ "playlistid" => "fetch_songs",
52
+ "playlistinfo" => "fetch_songs",
53
+ "playlistsearch" => "fetch_songs",
54
+ "plchanges" => "fetch_songs",
55
+ "plchangesposid" => "fetch_changes",
56
+ "prio" => "fetch_nothing",
57
+ "prioid" => "fetch_nothing",
58
+ "shuffle" => "fetch_nothing",
59
+ "swap" => "fetch_nothing",
60
+ "swapid" => "fetch_nothing",
61
+ # Stored Playlist Commands
62
+ "listplaylist" => "fetch_list",
63
+ "listplaylistinfo" => "fetch_songs",
64
+ "listplaylists" => "fetch_playlists",
65
+ "load" => "fetch_nothing",
66
+ "playlistadd" => "fetch_nothing",
67
+ "playlistclear" => "fetch_nothing",
68
+ "playlistdelete" => "fetch_nothing",
69
+ "playlistmove" => "fetch_nothing",
70
+ "rename" => "fetch_nothing",
71
+ "rm" => "fetch_nothing",
72
+ "save" => "fetch_nothing",
73
+ # Database Commands
74
+ "count" => "fetch_object",
75
+ "find" => "fetch_songs",
76
+ "findadd" => "fetch_nothing",
77
+ "list" => "fetch_list",
78
+ "listall" => "fetch_database",
79
+ "listallinfo" => "fetch_database",
80
+ "lsinfo" => "fetch_database",
81
+ "search" => "fetch_songs",
82
+ "update" => "fetch_item",
83
+ "rescan" => "fetch_item",
84
+ # Sticker Commands
85
+ "sticker get" => "fetch_item",
86
+ "sticker set" => "fetch_nothing",
87
+ "sticker delete" => "fetch_nothing",
88
+ "sticker list" => "fetch_list",
89
+ "sticker find" => "fetch_songs",
90
+ # Connection Commands
91
+ "close" => "",
92
+ "kill" => "",
93
+ "password" => "fetch_nothing",
94
+ "ping" => "fetch_nothing",
95
+ # Audio Output Commands
96
+ "disableoutput" => "fetch_nothing",
97
+ "enableoutput" => "fetch_nothing",
98
+ "outputs" => "fetch_outputs",
99
+ # Reflection Commands
100
+ "config" => "fetch_item",
101
+ "commands" => "fetch_list",
102
+ "notcommands" => "fetch_list",
103
+ "tagtypes" => "fetch_list",
104
+ "urlhandlers" => "fetch_list",
105
+ "decoders" => "fetch_plugins",
106
+ # Client To Client
107
+ "subscribe" => "fetch_nothing",
108
+ "unsubscribe" => "fetch_nothing",
109
+ "channels" => "fetch_list",
110
+ "readmessages" => "fetch_messages",
111
+ "sendmessage" => "fetch_nothing"
112
+ }
113
+
114
+ class MPDClient
115
+ attr_reader :mpd_version
116
+
117
+ def initialize
118
+ reset
119
+ end
120
+
121
+ def connect(host = 'localhost', port = 6600)
122
+ @socket = TCPSocket.new(host, port)
123
+ hello
124
+ end
125
+
126
+ def disconnect
127
+ @socket.close
128
+ reset
129
+ end
130
+
131
+ # http://www.musicpd.org/doc/protocol/ch01s04.html
132
+ def command_list_ok_begin
133
+ raise "Already in command list" unless @command_list.nil?
134
+ write_command('command_list_ok_begin')
135
+ @command_list = []
136
+ end
137
+
138
+ def command_list_end
139
+ raise "Not in command list" if @command_list.nil?
140
+ write_command('command_list_end')
141
+
142
+ return fetch_command_list
143
+ end
144
+
145
+ class << self
146
+ def add_command(name, retval)
147
+ define_method name.to_sym do |*args|
148
+ execute(name, *args, retval)
149
+ end
150
+ end
151
+
152
+ def remove_command(name)
153
+ raise "Can't remove not existent '#{name}' command" unless method_defined? name.to_sym
154
+ remove_method name.to_sym
155
+ end
156
+ end
157
+
158
+ private
159
+
160
+ def execute(command, *args, retval)
161
+ if !@command_list.nil?
162
+ write_command(command, *args)
163
+ @command_list << retval
164
+ else
165
+ write_command(command, *args)
166
+ eval retval
167
+ end
168
+ end
169
+
170
+ def write_line(line)
171
+ @socket.puts line
172
+ @socket.flush
173
+ end
174
+
175
+ def write_command(command, *args)
176
+ parts = [command]
177
+ args.each{|arg| parts << "\"#{escape(arg)}\""}
178
+ write_line(parts.join(' '))
179
+ end
180
+
181
+ def read_line
182
+ line = @socket.gets.force_encoding('utf-8')
183
+ raise "Connection lost while reading line" unless line.end_with?("\n")
184
+ line.chomp!
185
+ if line.start_with?(ERROR_PREFIX)
186
+ error = line[/#{ERROR_PREFIX}(.*)/, 1].strip
187
+ raise error
188
+ end
189
+
190
+ if !@command_list.nil?
191
+ return if line == NEXT
192
+ raise "Got unexpected '#{SUCCESS}'" if line == SUCCESS
193
+ elsif line == SUCCESS
194
+ return
195
+ end
196
+
197
+ return line
198
+ end
199
+
200
+ def read_pair(separator)
201
+ line = read_line
202
+ return if line.nil?
203
+ pair = line.split(separator)
204
+ raise "Could now parse pair: '#{line}'" if pair.size < 2
205
+
206
+ return pair #Array
207
+ end
208
+
209
+ def read_pairs(separator = ': ')
210
+ result = []
211
+ pair = read_pair(separator)
212
+ while pair
213
+ result << pair
214
+ pair = read_pair(separator)
215
+ end
216
+
217
+ return result
218
+ end
219
+
220
+ def fetch_item
221
+ pairs = read_pairs
222
+ return nil if pairs.size != 1
223
+ return pairs[0][1]
224
+ end
225
+
226
+ def fetch_nothing
227
+ line = read_line
228
+ raise "Got unexpected return value: #{line}" unless line.nil?
229
+ end
230
+
231
+ def fetch_list
232
+ result = []
233
+ seen = nil
234
+ read_pairs.each do |key, value|
235
+ if key != seen
236
+ if seen != nil
237
+ raise "Expected key '#{seen}', got '#{key}'"
238
+ end
239
+ seen = key
240
+ end
241
+ result << value
242
+ end
243
+ end
244
+
245
+ def fetch_objects(delimeters = [])
246
+ result = []
247
+ obj = {}
248
+ read_pairs.each do |key, value|
249
+ key = key.downcase
250
+ if delimeters.include?(key)
251
+ result << obj unless obj.empty?
252
+ obj = {}
253
+ elsif obj.include?(key)
254
+ obj[key] << value
255
+ end
256
+ obj[key] = value
257
+ end
258
+
259
+ result << obj unless obj.empty?
260
+
261
+ return result
262
+ end
263
+
264
+ def fetch_object
265
+ objs = fetch_objects
266
+ return objs ? objs[0] : {}
267
+ end
268
+
269
+ def fetch_changes; fetch_objects(['cpos']); end
270
+
271
+ def fetch_songs; fetch_objects(['file']); end
272
+
273
+ def fetch_messages; fetch_objects('channel'); end
274
+
275
+ def fetch_outputs; fetch_objects(['outputid']); end
276
+
277
+ def fetch_plugins; fetch_objects(['plugin']); end
278
+
279
+ def fetch_database; fetch_objects(['file', 'directory', 'playlist']); end
280
+
281
+ def fetch_playlists; fetch_objects(['playlist']); end
282
+
283
+ def fetch_playlist
284
+ result = []
285
+ read_pairs(':').each do |key, value|
286
+ result << value
287
+ end
288
+
289
+ return result
290
+ end
291
+
292
+ def fetch_command_list
293
+ result = []
294
+ begin
295
+ @command_list.each do |retval|
296
+ result << (eval retval)
297
+ end
298
+ ensure
299
+ @command_list = nil
300
+ end
301
+
302
+ return result
303
+ end
304
+
305
+
306
+ def hello
307
+ line = @socket.gets
308
+ raise "Connection lost while reading MPD hello" unless line.end_with?("\n")
309
+ line.chomp!
310
+ raise "Got invalid MPD hello: #{line}" unless line.start_with?(HELLO_PREFIX)
311
+ @mpd_version = line[/#{HELLO_PREFIX}(.*)/, 1]
312
+ end
313
+
314
+ def reset
315
+ @mpd_version = nil
316
+ @command_list = nil
317
+ @socket = nil
318
+ end
319
+
320
+ def escape(text)
321
+ text.gsub("\\", "\\\\").gsub('"', '\\"')
322
+ end
323
+
324
+ end
325
+
326
+ COMMANDS.each_pair do |name, callback|
327
+ MPDClient.add_command(name, callback)
328
+ end
329
+
@@ -0,0 +1,17 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/mpd_client/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Anton Maminov"]
6
+ gem.email = ["anton.linux@gmail.com"]
7
+ gem.description = %q{Yet another Ruby MPD client library}
8
+ gem.summary = %q{Simple Music Player Daemon library written entirely in Ruby}
9
+ gem.homepage = "https://github.com/mamantoha/mpd_client"
10
+
11
+ gem.files = `git ls-files`.split($\)
12
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
13
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
14
+ gem.name = "mpd_client"
15
+ gem.require_paths = ["lib"]
16
+ gem.version = MPDClient::VERSION
17
+ end
metadata ADDED
@@ -0,0 +1,53 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mpd_client
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Anton Maminov
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-03-30 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description: Yet another Ruby MPD client library
15
+ email:
16
+ - anton.linux@gmail.com
17
+ executables: []
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - .gitignore
22
+ - Gemfile
23
+ - LICENSE
24
+ - README.md
25
+ - Rakefile
26
+ - lib/mpd_client.rb
27
+ - lib/mpd_client/version.rb
28
+ - mpd_client.gemspec
29
+ homepage: https://github.com/mamantoha/mpd_client
30
+ licenses: []
31
+ post_install_message:
32
+ rdoc_options: []
33
+ require_paths:
34
+ - lib
35
+ required_ruby_version: !ruby/object:Gem::Requirement
36
+ none: false
37
+ requirements:
38
+ - - ! '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ required_rubygems_version: !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ! '>='
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ requirements: []
48
+ rubyforge_project:
49
+ rubygems_version: 1.8.21
50
+ signing_key:
51
+ specification_version: 3
52
+ summary: Simple Music Player Daemon library written entirely in Ruby
53
+ test_files: []