rmpd 1.0.5 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
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