rmpd 1.0.5 → 1.1.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.
data/console.rb ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "pry"
4
+ require "rmpd"
5
+ require "ruby-debug"
6
+ require "stringio"
7
+
8
+ conf = StringIO.new <<EOF
9
+ hostname: admin@localhost
10
+ port: 6601
11
+ EOF
12
+
13
+ mpd = Rmpd::Connection.new(conf)
14
+
15
+ binding.pry
data/lib/rmpd.rb CHANGED
@@ -1,13 +1,15 @@
1
1
  require File.join(File.dirname(__FILE__), "rmpd/config")
2
+ require File.join(File.dirname(__FILE__), "rmpd/command")
2
3
  require File.join(File.dirname(__FILE__), "rmpd/commands")
3
4
  require File.join(File.dirname(__FILE__), "rmpd/connection")
4
- require File.join(File.dirname(__FILE__), "rmpd/multi_response")
5
5
  require File.join(File.dirname(__FILE__), "rmpd/response")
6
6
 
7
7
  module Rmpd
8
8
  ACK_RE = /^ACK \[(\d+)@(\d+)\] \{([^}]*)\} (.*)$/
9
9
  OK_RE = /^OK.*$/
10
10
  LIST_OK_RE = /^list_OK.*$/
11
+ PROTOCOL_RE = /^OK MPD (\d+)\.(\d+)\.(\d+)$/
12
+ END_RE = Regexp.union(ACK_RE, OK_RE, PROTOCOL_RE)
11
13
 
12
14
  class MpdError < StandardError ; end
13
15
 
@@ -0,0 +1,155 @@
1
+ require "delegate"
2
+ require "forwardable"
3
+
4
+
5
+ module Rmpd
6
+ class Command
7
+
8
+ def self.new(name)
9
+ obj = super
10
+ obj.extend(choose_strategy(name))
11
+ end
12
+
13
+ def initialize(name)
14
+ @name = name
15
+ @list = initialize_list(&block) if block_given?
16
+ end
17
+
18
+
19
+ private
20
+
21
+ def self.choose_strategy(name)
22
+ if /command_list_ok/ === name
23
+ CommandListOkStrategy
24
+ elsif /command_list/ === name
25
+ CommandListStrategy
26
+ else
27
+ CommandStrategy
28
+ end
29
+ end
30
+
31
+ def initialize_list
32
+ list = List.new
33
+ yield list
34
+ list
35
+ end
36
+
37
+ module CommandStrategy
38
+
39
+ def execute(connection, *args)
40
+ connection.send_command(@name, *args)
41
+ Response.factory(@name).parse(connection.read_response)
42
+ rescue EOFError
43
+ connection.close
44
+ retry
45
+ end
46
+
47
+ end
48
+
49
+ module CommandListStrategy
50
+
51
+ def execute(connection, *args, &block)
52
+ list = List.new
53
+ yield list
54
+
55
+ connection.send_command("command_list_begin")
56
+ list.map do |command_w_args|
57
+ connection.send_command(*command_w_args)
58
+ end
59
+ connection.send_command("command_list_end")
60
+ Response.factory(@name).parse(connection.read_response)
61
+ end
62
+
63
+ end
64
+
65
+ module CommandListOkStrategy
66
+
67
+ def execute(connection, *args, &block)
68
+ @list = List.new
69
+ yield @list
70
+
71
+ connection.send_command("command_list_ok_begin")
72
+ @list.map do |command_w_args|
73
+ connection.send_command(*command_w_args)
74
+ end
75
+ connection.send_command("command_list_end")
76
+ handle_command_list_ok_response(connection.read_response)
77
+ end
78
+
79
+
80
+ private
81
+
82
+ def handle_command_list_ok_response(lines)
83
+ lines.pop while lines.last =~ LIST_OK_RE || lines.last =~ OK_RE
84
+ ResponseArray.new(split_responses(lines))
85
+ end
86
+
87
+ def split_responses(lines)
88
+ commands = @list.map(&:first)
89
+
90
+ lines.reduce([[]]) do |ra, line|
91
+ if LIST_OK_RE === line
92
+ ra << []
93
+ else
94
+ ra.last << line
95
+ end
96
+ ra
97
+ end.map do |response|
98
+ Response.factory(commands.shift).parse(response)
99
+ end
100
+ end
101
+
102
+ end
103
+
104
+ class ResponseArray < DelegateClass(Array)
105
+
106
+ def ok?
107
+ all?(&:ok?)
108
+ end
109
+
110
+ def ack?
111
+ !ok?
112
+ end
113
+
114
+ def error
115
+ find {|e| e.ack?}.error if ack?
116
+ end
117
+
118
+ end
119
+
120
+ class List
121
+ extend Forwardable
122
+
123
+ def_delegators :@cmds, :empty?, :each, :map, :size
124
+
125
+ def initialize
126
+ @cmds = []
127
+ end
128
+
129
+
130
+ protected
131
+
132
+ def method_missing(name, *args, &block)
133
+ @cmds << [name.to_s, *args]
134
+ end
135
+ end
136
+
137
+ class Splitter
138
+ def initialize(regexp)
139
+ @regexp = regexp
140
+ end
141
+
142
+ def split(lines)
143
+ lines.reduce([]) do |c, i|
144
+ if @regexp === i
145
+ c << [i]
146
+ else
147
+ c.last << i
148
+ end
149
+ c
150
+ end
151
+ end
152
+ end
153
+
154
+ end
155
+ end
data/lib/rmpd/commands.rb CHANGED
@@ -10,25 +10,53 @@ module Rmpd
10
10
 
11
11
  private
12
12
 
13
- def read_responses(regexp=/(^file: )/i)
14
- read_response(MultiResponse, regexp)
15
- end
16
-
17
- def receive_server_response
18
- @last_response = nil
13
+ def receive_response
19
14
  lines = []
15
+
20
16
  while lines << @socket.readline do
21
- puts "recv: #{lines.last.strip} (#{OK_RE === lines.last})" if $DEBUG
17
+ puts "recv: #{lines.last.strip}" if $DEBUG
22
18
  case lines.last
23
- when ACK_RE, OK_RE, LIST_OK_RE
24
- @last_response = lines.last
19
+ when ACK_RE, OK_RE
25
20
  break
26
21
  end
27
22
  end
28
- return lines.join
23
+
24
+ lines
29
25
  end
30
26
 
31
27
  def send_command(command, *args)
28
+ if in_command_list?
29
+ @command_list << [command, args]
30
+ else
31
+ case command
32
+ when /^command_list_end$/
33
+ # blah
34
+ @command_list = nil
35
+ when /^command_list.*begin$/
36
+ @command_list = [command, args]
37
+ else
38
+ send_command_now(command, *args)
39
+ end
40
+ end
41
+ end
42
+
43
+ def in_command_list?
44
+ !@command_list.nil?
45
+ end
46
+
47
+ def send_command_now(command, *args)
48
+ connect
49
+ @socket.puts("#{command} #{args.join(" ")}".strip)
50
+ rescue Errno::EPIPE, EOFError
51
+ @socket.close
52
+ if (tries += 1) < 5
53
+ retry
54
+ else
55
+ raise MpdError.new("Retry count exceeded")
56
+ end
57
+ end
58
+
59
+ def send_command_old(command, *args)
32
60
  tries = 0
33
61
 
34
62
  if $DEBUG
@@ -47,6 +75,8 @@ module Rmpd
47
75
  raise MpdError.new("Retry count exceeded")
48
76
  end
49
77
  end
78
+
79
+ receive_response unless @in_command_list
50
80
  end
51
81
 
52
82
  end
@@ -1,15 +1,15 @@
1
1
  module Rmpd
2
2
  module Commands
3
3
 
4
- complex_command :outputs, :regexp => /(^outputid: )/i
5
- complex_command :tagtypes, :regexp => /(^tagtype: )/i, :min_version => [0, 13, 0]
6
-
4
+ simple_command :outputs
5
+ simple_command :tagtypes, :min_version => [0, 13, 0]
7
6
  simple_command :disableoutput
8
7
  simple_command :enableoutput
9
8
  simple_command :update
9
+ simple_command :_kill
10
10
 
11
11
  def kill
12
- send_command("kill")
12
+ _kill
13
13
  @socket.close
14
14
  end
15
15
 
@@ -1,13 +1,13 @@
1
1
  module Rmpd
2
2
  module Commands
3
3
 
4
- complex_command :count, :regexp => /(^songs: )/i
5
- complex_command :find, :regexp => /(^file: )/i
6
- complex_command :list, :regexp => /(^[^:]+: )/i
7
- complex_command :listall, :regexp => /(^[^:]+: )/i
8
- complex_command :listallinfo, :regexp => /(^(directory|file): )/i
9
- complex_command :lsinfo, :regexp => /(^(directory|file): )/i
10
- complex_command :search, :regexp => /(^file: )/i
4
+ simple_command :count
5
+ simple_command :find
6
+ simple_command :list
7
+ simple_command :listall
8
+ simple_command :listallinfo
9
+ simple_command :lsinfo
10
+ simple_command :search
11
11
 
12
12
  alias_method :list_all, :listall
13
13
  alias_method :list_all_info, :listallinfo
@@ -4,39 +4,13 @@ module Rmpd
4
4
  private
5
5
 
6
6
  def self.simple_command(name, args={})
7
- block = lambda do |*a|
8
- if args.include?(:min_version)
9
- server_version_at_least(*args[:min_version])
10
- end
11
- send_command(name.to_s.gsub(/^_*/, ""), *quote(a))
12
- read_response unless @in_command_list
13
- end
14
- send(:define_method, name, &block)
15
- end
16
-
17
- def self.complex_command(name, args={})
18
- args = {:regexp => /(^file: )/i}.merge(args)
19
- block = lambda do |*a|
20
- if args.include?(:min_version)
21
- server_version_at_least(*args[:min_version])
22
- end
23
- send_command(name.to_s.gsub(/^_*/, ""), *quote(a))
24
- if @in_command_list
25
- append_command_list_regexp(args[:regexp])
26
- else
27
- read_responses(args[:regexp])
28
- end
29
- end
30
- send(:define_method, name, &block)
31
- end
32
-
33
- def append_command_list_regexp(regexp)
34
- if @in_command_list_response_regexp
35
- @in_command_list_response_regexp = \
36
- Regexp.union(@in_command_list_response_regexp, regexp)
37
- else
38
- @in_command_list_response_regexp = regexp
7
+ command = Proc.new do |*a, &block|
8
+ # if args.include?(:min_version)
9
+ # server_version_at_least(*args[:min_version])
10
+ # end
11
+ Command.new(name.to_s.gsub(/^_*/, "")).execute(mpd, *a, &block)
39
12
  end
13
+ send(:define_method, name, &command)
40
14
  end
41
15
 
42
16
  def quote(args)
@@ -1,9 +1,8 @@
1
1
  module Rmpd
2
2
  module Commands
3
3
 
4
- complex_command :commands, :regexp => /(^command: )/i
5
- simple_command :notcommands, :regexp => /(^command: )/i
6
-
4
+ simple_command :commands
5
+ simple_command :notcommands
7
6
  simple_command :clearerror
8
7
  simple_command :idle
9
8
  simple_command :noidle
@@ -11,60 +10,18 @@ module Rmpd
11
10
  simple_command :ping
12
11
  simple_command :stats
13
12
  simple_command :status
13
+ simple_command :_close
14
14
 
15
15
  def close
16
- send_command("close")
16
+ _close
17
17
  @socket.close
18
18
  end
19
19
 
20
+ simple_command :command_list
21
+ simple_command :command_list_ok
22
+
20
23
  alias_method :clear_error, :clearerror
21
24
  alias_method :not_commands, :notcommands
22
25
 
23
- def command_list
24
- send_command("command_list_begin")
25
- @in_command_list = true
26
- yield self
27
- send_command("command_list_end")
28
- handle_command_list_response
29
- ensure
30
- @in_command_list = false
31
- @in_command_list_response_regexp = nil
32
- end
33
-
34
- def command_list_ok
35
- send_command("command_list_ok_begin")
36
- @in_command_list = true
37
- yield self
38
- send_command("command_list_end")
39
- read_command_list_ok_responses do |responses|
40
- handle_command_list_response.tap do |res|
41
- responses << res unless res.empty?
42
- end
43
- end
44
- ensure
45
- @in_command_list = false
46
- @in_command_list_response_regexp = nil
47
- end
48
-
49
-
50
- private
51
-
52
- def handle_command_list_response
53
- if @in_command_list_response_regexp
54
- read_responses(@in_command_list_response_regexp)
55
- else
56
- read_response
57
- end
58
- end
59
-
60
- def read_command_list_ok_responses
61
- responses = []
62
-
63
- begin
64
- yield responses
65
- end while LIST_OK_RE === @last_response
66
-
67
- responses
68
- end
69
26
  end
70
27
  end
@@ -1,14 +1,14 @@
1
1
  module Rmpd
2
2
  module Commands
3
3
 
4
- complex_command :listplaylist, :min_version => [0, 13, 0]
5
- complex_command :listplaylistinfo, :min_version => [0, 12, 0]
6
- complex_command :playlistfind, :min_version => [0, 13, 0]
7
- complex_command :playlistid
8
- complex_command :playlistinfo
9
- complex_command :playlistsearch, :min_version => [0, 13, 0]
10
- complex_command :plchanges
11
- complex_command :plchangesposid, :regexp => /(^Id: )/i
4
+ simple_command :listplaylist, :min_version => [0, 13, 0]
5
+ simple_command :listplaylistinfo, :min_version => [0, 12, 0]
6
+ simple_command :playlistfind, :min_version => [0, 13, 0]
7
+ simple_command :playlistid
8
+ simple_command :playlistinfo
9
+ simple_command :playlistsearch, :min_version => [0, 13, 0]
10
+ simple_command :plchanges
11
+ simple_command :plchangesposid
12
12
 
13
13
  simple_command :_playlist
14
14
  simple_command :add # The docs on the wiki don't line up with empirical
@@ -30,22 +30,7 @@ module Rmpd
30
30
  simple_command :shuffle
31
31
  simple_command :swap
32
32
  simple_command :swapid
33
-
34
-
35
- # must be a file only, cannot use with a directory
36
- def addid(path, pos=nil)
37
- # pos is only for r7153+, but what version is that?
38
- server_version_at_least(0, 14, 0) if pos
39
- args = [path]
40
- args << pos if pos
41
- send_command("addid", *quote(args))
42
- @add_id_response_regex ||= /(^Id: )/i
43
- if @in_command_list
44
- append_command_list_regexp(@add_id_response_regex)
45
- else
46
- read_responses(@add_id_response_regex)
47
- end
48
- end
33
+ simple_command :addid
49
34
 
50
35
  alias_method :add_id, :addid
51
36
  alias_method :current_song, :currentsong
data/lib/rmpd/config.rb CHANGED
@@ -21,7 +21,8 @@ module Rmpd
21
21
  else
22
22
  config = {}
23
23
  end
24
- config = config[Rails.env] if defined?(Rails) && config[Rails.env]
24
+ puts "env: #{detected_env}" if env_detected? && $DEBUG
25
+ config = config[detected_env] if env_detected?
25
26
  puts "config: #{config.inspect}" if $DEBUG
26
27
  init_host_and_password(config)
27
28
  init_port(config)
@@ -33,6 +34,22 @@ module Rmpd
33
34
  HOSTNAME_RE = /(.*)@(.*)/
34
35
 
35
36
 
37
+ def detected_env
38
+ if defined?(Rails)
39
+ Rails.env
40
+ elsif ENV.include?("APP_ENV")
41
+ ENV["APP_ENV"]
42
+ elsif ENV.include?("RACK_ENV")
43
+ ENV["RACK_ENV"]
44
+ elsif ENV.include?("RAILS_ENV")
45
+ ENV["RAILS_ENV"]
46
+ end
47
+ end
48
+
49
+ def env_detected?
50
+ !!detected_env
51
+ end
52
+
36
53
  def init_host_and_password(config)
37
54
  if config["hostname"]
38
55
  @hostname = parse_hostname(config["hostname"])
@@ -3,81 +3,78 @@ require "socket"
3
3
 
4
4
  module Rmpd
5
5
  class Connection
6
-
7
- include Rmpd::Commands
8
6
  include Socket::Constants
7
+ include Rmpd::Commands
9
8
 
10
- attr_reader :error
11
9
 
12
- def initialize(config_file=nil)
13
- @config = Rmpd::Config.new(config_file)
14
- @socket = nil
15
- end
10
+ MAX_RETRIES = 5
16
11
 
17
- def server_version
18
- "#{@server_version_major}.#{@server_version_minor}.#{@server_version_patch}"
19
- end
20
12
 
21
13
  attr_reader :socket
22
14
 
23
- private
24
15
 
25
- def authenticate
26
- raise "Socket no good!" if (@socket.nil? || @socket.closed?)
27
- send_command("password", @config.password)
28
- read_response
29
- true
16
+ def initialize(config_file=nil)
17
+ @config = Rmpd::Config.new(config_file)
18
+ @socket = nil
19
+ end
20
+
21
+ def close
22
+ @socket.close
30
23
  end
31
24
 
32
25
  def connect
33
26
  return unless @socket.nil? || @socket.closed?
34
- error = nil
35
27
 
36
28
  Socket::getaddrinfo(@config.hostname, @config.port, nil, SOCK_STREAM).each do |info|
37
29
  begin
38
- puts "args: #{info.inspect}" if $DEBUG
39
30
  sockaddr = Socket.pack_sockaddr_in(info[1], info[3])
40
31
  @socket = Socket.new(info[4], info[5], 0)
41
32
  @socket.connect(sockaddr)
42
33
  rescue StandardError => error
43
34
  $stderr.puts "Failed to connect to #{info[3]}: #{error}"
44
35
  @socket = nil
36
+ raise MpdConnRefusedError.new(error)
45
37
  else
46
38
  break
47
39
  end
48
40
  end
49
- raise MpdConnRefusedError.new(error) if @socket.nil?
50
41
 
51
- parse_server_version(@socket.readline)
52
- authenticate if @config.password
42
+ read_response # protocol version, ignore for now
43
+ password(@config.password) if @config.password
53
44
  end
54
45
 
55
- def parse_server_version(version)
56
- /OK MPD (\d+)\.(\d+)\.(\d+)/.match(version.to_s)
57
- @server_version_major = $~[1].to_i
58
- @server_version_minor = $~[2].to_i
59
- @server_version_patch = $~[3].to_i
60
- end
46
+ def send_command(command, *args)
47
+ tries = 0
61
48
 
62
- def read_response(klass=Response, *args)
63
- x = klass.new(receive_server_response, *args)
64
- @error = x.error
65
- x
49
+ begin
50
+ connect
51
+ @socket.puts("#{command} #{quote(args).join(" ")}".strip)
52
+ rescue Errno::EPIPE, EOFError
53
+ @socket.close
54
+ if (tries += 1) < MAX_RETRIES
55
+ retry
56
+ else
57
+ raise MpdError.new("Retry count exceeded")
58
+ end
59
+ end
66
60
  end
67
61
 
68
- def server_version_at_least(major, minor, patch)
69
- connect
70
- e = MpdError.new("Requires server version #{major}.#{minor}.#{patch}")
62
+ def read_response
63
+ response = []
71
64
 
72
- raise e if major > @server_version_major
73
- return true if major < @server_version_major
65
+ while (line = @socket.readline)
66
+ response << line.strip
67
+ break if END_RE === line
68
+ end
69
+ response
70
+ end
74
71
 
75
- raise e if minor > @server_version_minor
76
- return true if minor < @server_version_minor
72
+ def mpd
73
+ self
74
+ end
77
75
 
78
- raise e if patch > @server_version_patch
79
- return true if patch < @server_version_patch
80
- true
76
+ def quote(args)
77
+ args.collect {|arg| "\"#{arg.to_s.gsub(/"/, "\\\"")}\""}
81
78
  end
82
79
 
83
80
  end
data/lib/rmpd/response.rb CHANGED
@@ -1,20 +1,72 @@
1
1
  require "delegate"
2
2
 
3
3
  module Rmpd
4
+ class Response < SimpleDelegator
4
5
 
5
- KEY_VALUE_RE = /^([^:]+):\s*(.*)$/
6
- KNOWN_INT_FIELDS = [:pos, :id, :track, :playlistlength, :playlist,
7
- :xfade, :repeat, :random, :queued, :volume, :song, :songid]
8
- KNOWN_COMPLEX_FIELDS = [:time,]
6
+ KEY_VALUE_RE = /^([^:]+):\s*(.*)$/
7
+ KNOWN_INT_FIELDS = [
8
+ "bitrate",
9
+ "consume",
10
+ "id",
11
+ "nextsong",
12
+ "nextsongid",
13
+ "playlist",
14
+ "playlistlength",
15
+ "playtime",
16
+ "pos",
17
+ "queued",
18
+ "random",
19
+ "repeat",
20
+ "single",
21
+ "song",
22
+ "songid",
23
+ "songs",
24
+ "track",
25
+ "volume",
26
+ "xfade",
27
+ ]
28
+ KNOWN_FLOAT_FIELDS = [
29
+ "elapsed",
30
+ "mixrampdb",
31
+ ]
32
+ KNOWN_COMPLEX_FIELDS = [
33
+ "time",
34
+ "audio"
35
+ ]
36
+ MULTI_RESPONSE_COMMANDS = [
37
+ "commands",
38
+ "find",
39
+ "idle",
40
+ "list",
41
+ "outputs",
42
+ "playlistinfo",
43
+ "search",
44
+ "tagtypes",
45
+ ]
9
46
 
10
- class Response < DelegateClass(Hash)
11
47
 
12
48
  attr_reader :error
13
49
 
14
- def initialize(data)
15
- super({})
50
+
51
+ def self.choose_strategy(name)
52
+ if MULTI_RESPONSE_COMMANDS.include?(name.to_s)
53
+ [Array, ResponseMultiStrategy]
54
+ else
55
+ [Hash, ResponseSingleStrategy]
56
+ end
57
+ end
58
+
59
+ def self.factory(command_name)
60
+ if MULTI_RESPONSE_COMMANDS.include?(command_name.to_s)
61
+ MultiResponse.new
62
+ else
63
+ SingleResponse.new
64
+ end
65
+ end
66
+
67
+ def initialize(parent)
68
+ super
16
69
  @error = nil
17
- parse(data)
18
70
  end
19
71
 
20
72
  def ok?
@@ -25,24 +77,32 @@ module Rmpd
25
77
  !ok?
26
78
  end
27
79
 
28
- private
29
-
30
- def register_key_val_pair(r)
31
- key, val = r[1].downcase.to_sym, r[2]
32
- val = val.to_i if KNOWN_INT_FIELDS.include?(key)
33
- val = send("parse_complex_#{key}", val) if KNOWN_COMPLEX_FIELDS.include?(key)
34
- self[key] = include?(key) ? ([self[key]] << val).flatten : val
35
- self.class.send(:define_method, key) {self[key]}
36
- end
37
-
38
- def parse(data)
39
- data.split("\n").each do |line|
80
+ def parse(lines)
81
+ lines.each do |line|
40
82
  case line
41
- when KEY_VALUE_RE; register_key_val_pair($~)
42
- when LIST_OK_RE, OK_RE; @error = nil
43
- when ACK_RE; @error = MpdAckError.new($~.values_at(0..-1))
83
+ when KEY_VALUE_RE
84
+ register_key_val_pair($~)
85
+ when ACK_RE
86
+ @error = $~[0]
87
+ break
88
+ when OK_RE
89
+ break
90
+ else
91
+ $stderr.puts "Don't know how to parse: #{line}"
44
92
  end
45
93
  end
94
+
95
+ self
96
+ end
97
+
98
+
99
+ protected
100
+
101
+ def transform_value(key, val)
102
+ val = val.to_i if KNOWN_INT_FIELDS.include?(key)
103
+ val = val.to_f if KNOWN_FLOAT_FIELDS.include?(key)
104
+ val = send("parse_complex_#{key}", val) if KNOWN_COMPLEX_FIELDS.include?(key.to_s)
105
+ val
46
106
  end
47
107
 
48
108
  # time can be either an integer (playlistinfo) or elapsed:total (status)
@@ -54,5 +114,56 @@ module Rmpd
54
114
  end
55
115
  end
56
116
 
117
+ def parse_complex_audio(value)
118
+ value.split(":", 3).map(&:to_i)
119
+ end
120
+
121
+ end
122
+
123
+ class MultiResponse < Response
124
+
125
+ def initialize
126
+ super([])
127
+ end
128
+
129
+ def parse(lines)
130
+ @first_key = nil
131
+ @temp = {}
132
+
133
+ super(lines)
134
+ self << @temp unless @temp.empty?
135
+ self
136
+ end
137
+
138
+ def register_key_val_pair(match_data)
139
+ key = match_data[1].downcase
140
+ val = transform_value(key, match_data[2])
141
+
142
+ if @first_key == key
143
+ self << @temp
144
+ @temp = {key => val}
145
+ else
146
+ @first_key ||= key
147
+ @temp[key] = val
148
+ @temp.class.send(:define_method, key.to_s.gsub(/-/, "_")) {self[key]}
149
+ end
150
+ end
151
+
152
+ end
153
+
154
+ class SingleResponse < Response
155
+
156
+ def initialize
157
+ super({})
158
+ end
159
+
160
+ def register_key_val_pair(match_data)
161
+ key = match_data[1].downcase
162
+ val = transform_value(key, match_data[2])
163
+
164
+ self[key] = include?(key) ? ([self[key]] << val).flatten : val
165
+ self.class.send(:define_method, key.to_s.gsub(/-/,"_")) {self[key]}
166
+ end
167
+
57
168
  end
58
169
  end
@@ -0,0 +1,26 @@
1
+ module Rmpd
2
+ class ResponseSplitter
3
+
4
+ def self.split(lines, responses=[])
5
+ known_keys = []
6
+ response_lines = []
7
+
8
+ lines.each do |line|
9
+ if KEY_VALUE_RE === line
10
+ key, value = $~.values_at(1..2)
11
+ if known_keys.include?(key)
12
+ yield responses, response_lines
13
+ response_lines.clear
14
+ known_keys.clear
15
+ end
16
+
17
+ response_lines << line
18
+ known_keys << key
19
+ end
20
+ end
21
+
22
+ yield responses, response_lines
23
+ end
24
+
25
+ end
26
+ end
data/lib/rmpd/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Rmpd
2
- VERSION = "1.0.5"
2
+ VERSION = "1.1.0"
3
3
  end
data/rmpd.gemspec CHANGED
@@ -22,4 +22,5 @@ Gem::Specification.new do |s|
22
22
  s.add_development_dependency("rspec", "~> 2.6.0")
23
23
  s.add_development_dependency("ruby-debug")
24
24
  s.add_development_dependency("rake")
25
+ s.add_development_dependency("pry")
25
26
  end
@@ -33,16 +33,6 @@ describe Rmpd::Commands do
33
33
  end.should raise_error(Rmpd::MpdError, "Retry count exceeded")
34
34
  end
35
35
 
36
- it "should hide password output when in debug mode" do
37
- set_password
38
- @socket.stub!(:readline).and_return(*(connect_and_auth_responses + ok))
39
- Kernel.should_receive(:puts).with("send: password #{"*"*8}")
40
- Kernel.should_receive(:puts).with("send: ping")
41
- $DEBUG = 1
42
- @conn.ping
43
- $DEBUG = nil
44
- end
45
-
46
36
  it "should abort after a limited number of tries against a closed connection" do
47
37
  @socket.stub!(:puts).and_raise(EOFError)
48
38
  @socket.stub!(:readline).and_return(*(connect_response + ok))
@@ -25,6 +25,10 @@ describe Rmpd::Config do
25
25
  @filename = File.join(File.dirname(__FILE__), "../../spec/fixtures/config_rails.yml")
26
26
  end
27
27
 
28
+ after(:each) do
29
+ Object.send(:remove_const, :Rails) if defined?(Rails)
30
+ end
31
+
28
32
  it "should load the development environment" do
29
33
  lambda do
30
34
  Rmpd::Config.new(@filename)
@@ -29,6 +29,7 @@ describe Connection do
29
29
  end
30
30
 
31
31
  it "should generate a server version" do
32
+ pending "Do I care anymore?"
32
33
  version = "0.1.2"
33
34
  responses = connect_response(version) + ok
34
35
  @socket.should_receive(:readline).and_return(*responses)
@@ -44,6 +45,7 @@ describe Connection do
44
45
  end
45
46
 
46
47
  it "should restrict access based on server version" do
48
+ pending "Do I care anymore?"
47
49
  responses = connect_response("0.1.0")
48
50
  @socket.stub!(:readline).and_return(*responses)
49
51
  @conn.instance_eval do
@@ -4,6 +4,7 @@ module Rmpd
4
4
  describe Commands, "Generators" do
5
5
 
6
6
  before(:each) do
7
+ pending "Do I care anymore?"
7
8
  @config = mock_config
8
9
  @socket = mock_socket
9
10
  @conn = Connection.new
@@ -19,21 +20,13 @@ module Rmpd
19
20
 
20
21
  describe "simple_command" do
21
22
  it "should allow based on server version" do
23
+ pending "Do I care anymore?"
22
24
  Commands::simple_command(:test, :min_version => [0, 1, 1])
23
25
  lambda do
24
26
  @conn.test
25
27
  end.should_not raise_error(MpdError)
26
28
  end
27
29
  end
28
-
29
- describe "complex_command" do
30
- it "should allow based on server version" do
31
- Rmpd::Commands::complex_command(:test, :min_version => [0, 1, 1])
32
- lambda do
33
- @conn.test
34
- end.should_not raise_error(MpdError)
35
- end
36
- end
37
30
  end
38
31
 
39
32
  describe "when version is NOT ok" do
@@ -5,11 +5,8 @@ include Rmpd
5
5
 
6
6
  describe Rmpd::Response do
7
7
  before(:each) do
8
- data = <<-EOF
9
- foo: bar
10
- OK
11
- EOF
12
- @response = Rmpd::Response.new(data)
8
+ data = ["foo: bar", "OK"]
9
+ @response = Rmpd::Response.factory("status").parse(data)
13
10
  end
14
11
 
15
12
  it "should have a foo method" do
@@ -18,8 +15,8 @@ EOF
18
15
  end
19
16
 
20
17
  it "should have a foo key" do
21
- @response.should include(:foo)
22
- @response[:foo].should == "bar"
18
+ @response.should include("foo")
19
+ @response["foo"].should == "bar"
23
20
  end
24
21
 
25
22
  it "should be ok" do
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rmpd
3
3
  version: !ruby/object:Gem::Version
4
- hash: 29
4
+ hash: 19
5
5
  prerelease:
6
6
  segments:
7
7
  - 1
8
+ - 1
8
9
  - 0
9
- - 5
10
- version: 1.0.5
10
+ version: 1.1.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Eric Wollesen
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2012-08-17 00:00:00 Z
18
+ date: 2012-09-04 00:00:00 Z
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
21
21
  name: rspec
@@ -61,6 +61,20 @@ dependencies:
61
61
  type: :development
62
62
  requirement: *id003
63
63
  prerelease: false
64
+ - !ruby/object:Gem::Dependency
65
+ name: pry
66
+ version_requirements: &id004 !ruby/object:Gem::Requirement
67
+ none: false
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ hash: 3
72
+ segments:
73
+ - 0
74
+ version: "0"
75
+ type: :development
76
+ requirement: *id004
77
+ prerelease: false
64
78
  description: Music Player Daemon client in Ruby
65
79
  email:
66
80
  - ericw@xmtp.net
@@ -76,7 +90,9 @@ files:
76
90
  - .rvmrc
77
91
  - Gemfile
78
92
  - Rakefile
93
+ - console.rb
79
94
  - lib/rmpd.rb
95
+ - lib/rmpd/command.rb
80
96
  - lib/rmpd/commands.rb
81
97
  - lib/rmpd/commands/admin.rb
82
98
  - lib/rmpd/commands/database.rb
@@ -86,8 +102,8 @@ files:
86
102
  - lib/rmpd/commands/playlist.rb
87
103
  - lib/rmpd/config.rb
88
104
  - lib/rmpd/connection.rb
89
- - lib/rmpd/multi_response.rb
90
105
  - lib/rmpd/response.rb
106
+ - lib/rmpd/response_splitter.rb
91
107
  - lib/rmpd/version.rb
92
108
  - rmpd.gemspec
93
109
  - spec/fixtures/config.yml
@@ -100,7 +116,6 @@ files:
100
116
  - spec/models/connection_spec.rb
101
117
  - spec/models/generators_spec.rb
102
118
  - spec/models/mildred_mpd_spec.rb
103
- - spec/models/multi_response_spec.rb
104
119
  - spec/models/response_spec.rb
105
120
  - spec/spec.opts
106
121
  - spec/spec_helper.rb
@@ -1,43 +0,0 @@
1
- require "delegate"
2
-
3
- module Rmpd
4
-
5
- class MultiResponse < DelegateClass(Array)
6
-
7
- END_OF_RECORD = "\0x1E" # from ASCII
8
-
9
- def initialize(data, sep)
10
- super([])
11
- @sep = sep
12
- parse(data)
13
- end
14
-
15
- def ok?
16
- self.all?(&:ok?)
17
- end
18
-
19
- def ack?
20
- !ok?
21
- end
22
-
23
- def error
24
- self.map(&:error).compact.first
25
- end
26
-
27
-
28
- private
29
-
30
- def parse(data)
31
- data.gsub!(@sep, "#{END_OF_RECORD}\\1")
32
- data.split(END_OF_RECORD).each do |datum|
33
- if datum.empty?
34
- next
35
- else
36
- r = Response.new(datum)
37
- end
38
- self << r unless r.keys.size.zero? && r.ok?
39
- end
40
- end
41
- end
42
-
43
- end
@@ -1,54 +0,0 @@
1
- require "spec_helper"
2
-
3
- include Rmpd
4
-
5
-
6
- describe Rmpd::MultiResponse do
7
- describe "on success" do
8
- before(:each) do
9
- data = <<-EOF
10
- foo: cat
11
- bar: dog
12
- foo: horse
13
- bar: giraffe
14
- OK
15
- EOF
16
- @response = Rmpd::MultiResponse.new(data, /(^foo:)/)
17
- end
18
-
19
- it "should have a size of 2" do
20
- @response.should have(2).items, @response.inspect
21
- end
22
-
23
- it "should be OK" do
24
- # @response.should be_ok
25
- # doesn't work correctly, see rspec bug #11526
26
- @response.ok?.should be_true
27
- end
28
-
29
- it "should not be ACK" do
30
- # @response.should_not be_ack
31
- # doesn't work correctly, see rspec bug #11526
32
- @response.ack?.should be_false
33
- end
34
- end
35
-
36
- describe "on error" do
37
- before(:each) do
38
- @err_msg = "ACK [2@0] {search} too few arguments for \"search\"\n"
39
- @response = Rmpd::MultiResponse.new(@err_msg, /(^foo:)/)
40
- end
41
-
42
- it "should not be OK" do
43
- # @response.should_not be_ok
44
- # doesn't work correctly, see rspec bug #11526
45
- @response.ok?.should be_false
46
- end
47
-
48
- it "should be ACK" do
49
- # @response.should be_ack
50
- # doesn't work correctly, see rspec bug #11526
51
- @response.ack?.should be_true
52
- end
53
- end
54
- end