rmpd 1.0.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/.gitignore +4 -0
- data/.rspec +1 -0
- data/Gemfile +4 -0
- data/Rakefile +14 -0
- data/lib/rmpd.rb +25 -0
- data/lib/rmpd/commands.rb +50 -0
- data/lib/rmpd/commands/admin.rb +21 -0
- data/lib/rmpd/commands/database.rb +17 -0
- data/lib/rmpd/commands/generators.rb +34 -0
- data/lib/rmpd/commands/miscellaneous.rb +33 -0
- data/lib/rmpd/commands/playback.rb +30 -0
- data/lib/rmpd/commands/playlist.rb +62 -0
- data/lib/rmpd/config.rb +77 -0
- data/lib/rmpd/connection.rb +84 -0
- data/lib/rmpd/multi_response.rb +43 -0
- data/lib/rmpd/response.rb +47 -0
- data/lib/rmpd/version.rb +3 -0
- data/rmpd.gemspec +24 -0
- data/spec/fixtures/config.yml +2 -0
- data/spec/fixtures/config_no_hostname.yml +1 -0
- data/spec/fixtures/config_no_password.yml +2 -0
- data/spec/fixtures/config_no_port.yml +1 -0
- data/spec/fixtures/config_rails.yml +3 -0
- data/spec/models/commands_spec.rb +135 -0
- data/spec/models/config_spec.rb +128 -0
- data/spec/models/connection_spec.rb +61 -0
- data/spec/models/generators_spec.rb +66 -0
- data/spec/models/mildred_mpd_spec.rb +15 -0
- data/spec/models/multi_response_spec.rb +54 -0
- data/spec/models/response_spec.rb +36 -0
- data/spec/spec.opts +6 -0
- data/spec/spec_helper.rb +43 -0
- data/spec/spec_rcov.opts +5 -0
- metadata +129 -0
    
        data/.gitignore
    ADDED
    
    
    
        data/.rspec
    ADDED
    
    | @@ -0,0 +1 @@ | |
| 1 | 
            +
            --colour
         | 
    
        data/Gemfile
    ADDED
    
    
    
        data/Rakefile
    ADDED
    
    | @@ -0,0 +1,14 @@ | |
| 1 | 
            +
            require 'bundler'
         | 
| 2 | 
            +
            Bundler::GemHelper.install_tasks
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            require "rspec/core/rake_task"
         | 
| 5 | 
            +
            #require "cucumber"
         | 
| 6 | 
            +
            #require "cucumber/rake/task"
         | 
| 7 | 
            +
             | 
| 8 | 
            +
             | 
| 9 | 
            +
            RSpec::Core::RakeTask.new
         | 
| 10 | 
            +
            # Cucumber::Rake::Task.new("cucumber") do
         | 
| 11 | 
            +
            #   puts "No cucumber features defined yet."
         | 
| 12 | 
            +
            # end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
            task :default => [:spec] #, :cucumber]
         | 
    
        data/lib/rmpd.rb
    ADDED
    
    | @@ -0,0 +1,25 @@ | |
| 1 | 
            +
            require File.join(File.dirname(__FILE__), "rmpd/config")
         | 
| 2 | 
            +
            require File.join(File.dirname(__FILE__), "rmpd/commands")
         | 
| 3 | 
            +
            require File.join(File.dirname(__FILE__), "rmpd/connection")
         | 
| 4 | 
            +
            require File.join(File.dirname(__FILE__), "rmpd/multi_response")
         | 
| 5 | 
            +
            require File.join(File.dirname(__FILE__), "rmpd/response")
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            module Rmpd
         | 
| 8 | 
            +
              ACK_RE = /^ACK \[(\d+)@(\d+)\] \{([^}]*)\} (.*)$/
         | 
| 9 | 
            +
              OK_RE = /^OK.*$/
         | 
| 10 | 
            +
             | 
| 11 | 
            +
              class MpdError < Exception ; end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
              class MpdConnRefusedError < MpdError ; end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
              class MpdAckError < MpdError
         | 
| 16 | 
            +
                def initialize(regex_match)
         | 
| 17 | 
            +
                  @error, @command_list_num, @current_command, @message = regex_match.values_at(1..-1)
         | 
| 18 | 
            +
                end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                def to_s
         | 
| 21 | 
            +
                  "ACK [#{@error}@#{@command_list_num}] {#{@current_command}} #{@message}"
         | 
| 22 | 
            +
                end
         | 
| 23 | 
            +
              end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
            end
         | 
| @@ -0,0 +1,50 @@ | |
| 1 | 
            +
            require File.join(File.dirname(__FILE__), "commands/generators")
         | 
| 2 | 
            +
            require File.join(File.dirname(__FILE__), "commands/admin")
         | 
| 3 | 
            +
            require File.join(File.dirname(__FILE__), "commands/database")
         | 
| 4 | 
            +
            require File.join(File.dirname(__FILE__), "commands/miscellaneous")
         | 
| 5 | 
            +
            require File.join(File.dirname(__FILE__), "commands/playback")
         | 
| 6 | 
            +
            require File.join(File.dirname(__FILE__), "commands/playlist")
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            module Rmpd
         | 
| 9 | 
            +
              module Commands
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                private
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                def read_responses(regexp=/(^file: )/i)
         | 
| 14 | 
            +
                  read_response(MultiResponse, regexp)
         | 
| 15 | 
            +
                end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                def receive_server_response
         | 
| 18 | 
            +
                  lines = []
         | 
| 19 | 
            +
                  while lines << @socket.readline do
         | 
| 20 | 
            +
                    puts "recv: #{lines.last.strip} (#{OK_RE === lines.last})" if $DEBUG
         | 
| 21 | 
            +
                    case lines.last
         | 
| 22 | 
            +
                    when ACK_RE, OK_RE: break
         | 
| 23 | 
            +
                    end
         | 
| 24 | 
            +
                  end
         | 
| 25 | 
            +
                  return lines.join
         | 
| 26 | 
            +
                end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                def send_command(command, *args)
         | 
| 29 | 
            +
                  tries = 0
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                  if $DEBUG
         | 
| 32 | 
            +
                    a = command == "password" ? args.map{|x| "*" * 8} : args
         | 
| 33 | 
            +
                    Kernel.puts "send: #{command.strip} #{a.join(" ")}".strip
         | 
| 34 | 
            +
                  end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                  begin
         | 
| 37 | 
            +
                    connect
         | 
| 38 | 
            +
                    @socket.puts("#{command} #{args.join(" ")}".strip)
         | 
| 39 | 
            +
                  rescue Errno::EPIPE, EOFError
         | 
| 40 | 
            +
                    @socket.close
         | 
| 41 | 
            +
                    if (tries += 1) < 5
         | 
| 42 | 
            +
                      retry
         | 
| 43 | 
            +
                    else
         | 
| 44 | 
            +
                      raise MpdError.new("Retry count exceeded")
         | 
| 45 | 
            +
                    end
         | 
| 46 | 
            +
                  end
         | 
| 47 | 
            +
                end
         | 
| 48 | 
            +
             | 
| 49 | 
            +
              end
         | 
| 50 | 
            +
            end
         | 
| @@ -0,0 +1,21 @@ | |
| 1 | 
            +
            module Rmpd
         | 
| 2 | 
            +
              module Commands
         | 
| 3 | 
            +
             | 
| 4 | 
            +
                complex_command :outputs, :regexp => /(^outputid: )/i
         | 
| 5 | 
            +
                complex_command :tagtypes, :regexp => /(^tagtype: )/i, :min_version => [0, 13, 0]
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                simple_command :disableoutput
         | 
| 8 | 
            +
                simple_command :enableoutput
         | 
| 9 | 
            +
                simple_command :update
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                def kill
         | 
| 12 | 
            +
                  send_command("kill")
         | 
| 13 | 
            +
                  @socket.close
         | 
| 14 | 
            +
                end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                alias_method :disable_output, :disableoutput
         | 
| 17 | 
            +
                alias_method :tag_types, :tagtypes
         | 
| 18 | 
            +
                alias_method :enable_output, :enableoutput
         | 
| 19 | 
            +
             | 
| 20 | 
            +
              end
         | 
| 21 | 
            +
            end
         | 
| @@ -0,0 +1,17 @@ | |
| 1 | 
            +
            module Rmpd
         | 
| 2 | 
            +
              module Commands
         | 
| 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
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                alias_method :list_all, :listall
         | 
| 13 | 
            +
                alias_method :list_all_info, :listallinfo
         | 
| 14 | 
            +
                alias_method :ls_info, :lsinfo
         | 
| 15 | 
            +
             | 
| 16 | 
            +
              end
         | 
| 17 | 
            +
            end
         | 
| @@ -0,0 +1,34 @@ | |
| 1 | 
            +
            module Rmpd
         | 
| 2 | 
            +
              module Commands
         | 
| 3 | 
            +
             | 
| 4 | 
            +
                private
         | 
| 5 | 
            +
             | 
| 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 | 
            +
                    read_responses(args[:regexp]) unless @in_command_list
         | 
| 25 | 
            +
                  end
         | 
| 26 | 
            +
                  send(:define_method, name, &block)
         | 
| 27 | 
            +
                end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                def quote(args)
         | 
| 30 | 
            +
                  args.collect {|arg| "\"#{arg.to_s.gsub(/"/, "\\\"")}\""}
         | 
| 31 | 
            +
                end
         | 
| 32 | 
            +
              end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
            end
         | 
| @@ -0,0 +1,33 @@ | |
| 1 | 
            +
            module Rmpd
         | 
| 2 | 
            +
              module Commands
         | 
| 3 | 
            +
             | 
| 4 | 
            +
                complex_command :commands, :regexp => /(^command: )/i
         | 
| 5 | 
            +
                simple_command :notcommands, :regexp => /(^command: )/i
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                simple_command :clearerror
         | 
| 8 | 
            +
                simple_command :idle
         | 
| 9 | 
            +
                simple_command :noidle
         | 
| 10 | 
            +
                simple_command :password
         | 
| 11 | 
            +
                simple_command :ping
         | 
| 12 | 
            +
                simple_command :stats
         | 
| 13 | 
            +
                simple_command :status
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                def close
         | 
| 16 | 
            +
                  send_command("close")
         | 
| 17 | 
            +
                  @socket.close
         | 
| 18 | 
            +
                end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                alias_method :clear_error, :clearerror
         | 
| 21 | 
            +
                alias_method :not_commands, :notcommands
         | 
| 22 | 
            +
             | 
| 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 | 
            +
                  read_response
         | 
| 29 | 
            +
                ensure
         | 
| 30 | 
            +
                  @in_command_list = false
         | 
| 31 | 
            +
                end
         | 
| 32 | 
            +
              end
         | 
| 33 | 
            +
            end
         | 
| @@ -0,0 +1,30 @@ | |
| 1 | 
            +
            module Rmpd
         | 
| 2 | 
            +
              module Commands
         | 
| 3 | 
            +
             | 
| 4 | 
            +
                simple_command :_volume
         | 
| 5 | 
            +
                simple_command :crossfade
         | 
| 6 | 
            +
                simple_command :next
         | 
| 7 | 
            +
                simple_command :pause
         | 
| 8 | 
            +
                simple_command :play
         | 
| 9 | 
            +
                simple_command :playid
         | 
| 10 | 
            +
                simple_command :previous
         | 
| 11 | 
            +
                simple_command :random
         | 
| 12 | 
            +
                simple_command :repeat
         | 
| 13 | 
            +
                simple_command :seek
         | 
| 14 | 
            +
                simple_command :seekid
         | 
| 15 | 
            +
                simple_command :setvol
         | 
| 16 | 
            +
                simple_command :stop
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                def volume(*args)
         | 
| 19 | 
            +
                  $stderr.puts "volume is deprecated, use setvol instead"
         | 
| 20 | 
            +
                  _volume(*args)
         | 
| 21 | 
            +
                end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                alias_method :cross_fade, :crossfade
         | 
| 24 | 
            +
                alias_method :play_id, :playid
         | 
| 25 | 
            +
                alias_method :prev, :previous
         | 
| 26 | 
            +
                alias_method :seek_id, :seekid
         | 
| 27 | 
            +
                alias_method :set_vol, :setvol
         | 
| 28 | 
            +
             | 
| 29 | 
            +
              end
         | 
| 30 | 
            +
            end
         | 
| @@ -0,0 +1,62 @@ | |
| 1 | 
            +
            module Rmpd
         | 
| 2 | 
            +
              module Commands
         | 
| 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
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                simple_command :_playlist
         | 
| 14 | 
            +
                simple_command :add # The docs on the wiki don't line up with empirical
         | 
| 15 | 
            +
                                    # results using 0.13.0
         | 
| 16 | 
            +
                simple_command :clear
         | 
| 17 | 
            +
                simple_command :currentsong
         | 
| 18 | 
            +
                simple_command :delete
         | 
| 19 | 
            +
                simple_command :deleteid
         | 
| 20 | 
            +
                simple_command :load
         | 
| 21 | 
            +
                simple_command :move
         | 
| 22 | 
            +
                simple_command :moveid
         | 
| 23 | 
            +
                simple_command :playlistadd, :min_version => [0, 13, 0]
         | 
| 24 | 
            +
                simple_command :playlistclear, :min_version => [0, 13, 0]
         | 
| 25 | 
            +
                simple_command :playlistdelete, :min_version => [0, 13, 0]
         | 
| 26 | 
            +
                simple_command :playlistmove, :min_version => [0, 13, 0]
         | 
| 27 | 
            +
                simple_command :rename
         | 
| 28 | 
            +
                simple_command :rm
         | 
| 29 | 
            +
                simple_command :save
         | 
| 30 | 
            +
                simple_command :shuffle
         | 
| 31 | 
            +
                simple_command :swap
         | 
| 32 | 
            +
                simple_command :swapid
         | 
| 33 | 
            +
             | 
| 34 | 
            +
             | 
| 35 | 
            +
                # must be a file only, cannot use with a director
         | 
| 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 | 
            +
                  send_command("addid #{quote(path)} #{pos.to_s}")
         | 
| 40 | 
            +
                  read_response
         | 
| 41 | 
            +
                end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                alias_method :add_id, :addid
         | 
| 44 | 
            +
                alias_method :current_song, :currentsong
         | 
| 45 | 
            +
                alias_method :delete_id, :deleteid
         | 
| 46 | 
            +
                alias_method :list_playlist, :listplaylist
         | 
| 47 | 
            +
                alias_method :list_playlist_info, :listplaylistinfo
         | 
| 48 | 
            +
                alias_method :move_id, :moveid
         | 
| 49 | 
            +
                alias_method :pl_changes, :plchanges
         | 
| 50 | 
            +
                alias_method :pl_changes_pos_id, :plchangesposid
         | 
| 51 | 
            +
                alias_method :playlist_add, :playlistadd
         | 
| 52 | 
            +
                alias_method :playlist_clear, :playlistclear
         | 
| 53 | 
            +
                alias_method :playlist_delete, :playlistdelete
         | 
| 54 | 
            +
                alias_method :playlist_find, :playlistfind
         | 
| 55 | 
            +
                alias_method :playlist_id, :playlistid
         | 
| 56 | 
            +
                alias_method :playlist_info, :playlistinfo
         | 
| 57 | 
            +
                alias_method :playlist_move, :playlistmove
         | 
| 58 | 
            +
                alias_method :playlist_search, :playlistsearch
         | 
| 59 | 
            +
                alias_method :swap_id, :swapid
         | 
| 60 | 
            +
             | 
| 61 | 
            +
              end
         | 
| 62 | 
            +
            end
         | 
    
        data/lib/rmpd/config.rb
    ADDED
    
    | @@ -0,0 +1,77 @@ | |
| 1 | 
            +
            require "pathname"
         | 
| 2 | 
            +
            require "yaml"
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            module Rmpd
         | 
| 5 | 
            +
             | 
| 6 | 
            +
              class Config
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                DEFAULT_HOSTNAME = "localhost"
         | 
| 9 | 
            +
                DEFAULT_PORT = 6600
         | 
| 10 | 
            +
                DEFAULT_PASSWORD = nil
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                attr_accessor :hostname, :password, :port
         | 
| 13 | 
            +
                alias_method :host, :hostname
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                def initialize(config_file=nil)
         | 
| 16 | 
            +
                  case config_file
         | 
| 17 | 
            +
                  when String, Pathname
         | 
| 18 | 
            +
                    config = YAML::load_file(config_file)
         | 
| 19 | 
            +
                  when File
         | 
| 20 | 
            +
                    config = YAML::load(config_file)
         | 
| 21 | 
            +
                  else
         | 
| 22 | 
            +
                    config = {}
         | 
| 23 | 
            +
                  end
         | 
| 24 | 
            +
                  config = config[Rails.env] if defined?(Rails) && config[Rails.env]
         | 
| 25 | 
            +
                  puts "config: #{config.inspect}" if $DEBUG
         | 
| 26 | 
            +
                  init_host_and_password(config)
         | 
| 27 | 
            +
                  init_port(config)
         | 
| 28 | 
            +
                end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
             | 
| 31 | 
            +
                private
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                HOSTNAME_RE = /(.*)@(.*)/
         | 
| 34 | 
            +
             | 
| 35 | 
            +
             | 
| 36 | 
            +
                def init_host_and_password(config)
         | 
| 37 | 
            +
                  if config["hostname"]
         | 
| 38 | 
            +
                    @hostname = parse_hostname(config["hostname"])
         | 
| 39 | 
            +
                    @password = parse_password(config["hostname"])
         | 
| 40 | 
            +
                  elsif ENV["MPD_HOST"]
         | 
| 41 | 
            +
                    @hostname = parse_hostname(ENV["MPD_HOST"])
         | 
| 42 | 
            +
                    @password = parse_password(ENV["MPD_HOST"])
         | 
| 43 | 
            +
                  else
         | 
| 44 | 
            +
                    @hostname = DEFAULT_HOSTNAME
         | 
| 45 | 
            +
                    @password = DEFAULT_PASSWORD
         | 
| 46 | 
            +
                  end
         | 
| 47 | 
            +
                end
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                def init_port(config)
         | 
| 50 | 
            +
                  if config["port"]
         | 
| 51 | 
            +
                    @port = config["port"].to_i
         | 
| 52 | 
            +
                  elsif ENV["MPD_PORT"]
         | 
| 53 | 
            +
                    @port = ENV["MPD_PORT"].to_i
         | 
| 54 | 
            +
                  else
         | 
| 55 | 
            +
                    @port = DEFAULT_PORT
         | 
| 56 | 
            +
                  end
         | 
| 57 | 
            +
                end
         | 
| 58 | 
            +
             | 
| 59 | 
            +
                def parse_hostname(hostname)
         | 
| 60 | 
            +
                  if HOSTNAME_RE === hostname
         | 
| 61 | 
            +
                    $~[2]
         | 
| 62 | 
            +
                  else
         | 
| 63 | 
            +
                    hostname
         | 
| 64 | 
            +
                  end
         | 
| 65 | 
            +
                end
         | 
| 66 | 
            +
             | 
| 67 | 
            +
                def parse_password(hostname)
         | 
| 68 | 
            +
                  if HOSTNAME_RE === hostname
         | 
| 69 | 
            +
                    $~[1]
         | 
| 70 | 
            +
                  else
         | 
| 71 | 
            +
                    nil
         | 
| 72 | 
            +
                  end
         | 
| 73 | 
            +
                end
         | 
| 74 | 
            +
             | 
| 75 | 
            +
              end
         | 
| 76 | 
            +
             | 
| 77 | 
            +
            end
         | 
| @@ -0,0 +1,84 @@ | |
| 1 | 
            +
            require "socket"
         | 
| 2 | 
            +
             | 
| 3 | 
            +
             | 
| 4 | 
            +
            module Rmpd
         | 
| 5 | 
            +
              class Connection
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                include Rmpd::Commands
         | 
| 8 | 
            +
                include Socket::Constants
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                attr_reader :error
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                def initialize(config_file=nil)
         | 
| 13 | 
            +
                  @config = Rmpd::Config.new(config_file)
         | 
| 14 | 
            +
                  @socket = nil
         | 
| 15 | 
            +
                end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                def server_version
         | 
| 18 | 
            +
                  "#{@server_version_major}.#{@server_version_minor}.#{@server_version_patch}"
         | 
| 19 | 
            +
                end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                attr_reader :socket
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                private
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                def authenticate
         | 
| 26 | 
            +
                  raise "Socket no good!" if (@socket.nil? || @socket.closed?)
         | 
| 27 | 
            +
                  send_command("password", @config.password)
         | 
| 28 | 
            +
                  read_response
         | 
| 29 | 
            +
                  true
         | 
| 30 | 
            +
                end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                def connect
         | 
| 33 | 
            +
                  return unless @socket.nil? || @socket.closed?
         | 
| 34 | 
            +
                  error = nil
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                  Socket::getaddrinfo(@config.hostname, @config.port, nil, SOCK_STREAM).each do |info|
         | 
| 37 | 
            +
                    begin
         | 
| 38 | 
            +
                      puts "args: #{info.inspect}" if $DEBUG
         | 
| 39 | 
            +
                      sockaddr = Socket.pack_sockaddr_in(info[1], info[3])
         | 
| 40 | 
            +
                      @socket = Socket.new(info[4], info[5], 0)
         | 
| 41 | 
            +
                      @socket.connect(sockaddr)
         | 
| 42 | 
            +
                    rescue StandardError => error
         | 
| 43 | 
            +
                      $stderr.puts "Failed to connect to #{info[3]}: #{error}"
         | 
| 44 | 
            +
                      @socket = nil
         | 
| 45 | 
            +
                    else
         | 
| 46 | 
            +
                      break
         | 
| 47 | 
            +
                    end
         | 
| 48 | 
            +
                  end
         | 
| 49 | 
            +
                  raise MpdConnRefusedError.new(error) if @socket.nil?
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                  parse_server_version(@socket.readline)
         | 
| 52 | 
            +
                  authenticate if @config.password
         | 
| 53 | 
            +
                end
         | 
| 54 | 
            +
             | 
| 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
         | 
| 61 | 
            +
             | 
| 62 | 
            +
                def read_response(klass=Response, *args)
         | 
| 63 | 
            +
                  x = klass.new(receive_server_response, *args)
         | 
| 64 | 
            +
                  @error = x.error
         | 
| 65 | 
            +
                  x
         | 
| 66 | 
            +
                end
         | 
| 67 | 
            +
             | 
| 68 | 
            +
                def server_version_at_least(major, minor, patch)
         | 
| 69 | 
            +
                  connect
         | 
| 70 | 
            +
                  e = MpdError.new("Requires server version #{major}.#{minor}.#{patch}")
         | 
| 71 | 
            +
             | 
| 72 | 
            +
                  raise e if major > @server_version_major
         | 
| 73 | 
            +
                  return true if major < @server_version_major
         | 
| 74 | 
            +
             | 
| 75 | 
            +
                  raise e if minor > @server_version_minor
         | 
| 76 | 
            +
                  return true if minor < @server_version_minor
         | 
| 77 | 
            +
             | 
| 78 | 
            +
                  raise e if patch > @server_version_patch
         | 
| 79 | 
            +
                  return true if patch < @server_version_patch
         | 
| 80 | 
            +
                  true
         | 
| 81 | 
            +
                end
         | 
| 82 | 
            +
             | 
| 83 | 
            +
              end
         | 
| 84 | 
            +
            end
         | 
| @@ -0,0 +1,43 @@ | |
| 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? {|x| x.ok?}
         | 
| 17 | 
            +
                end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                def ack?
         | 
| 20 | 
            +
                  !ok?
         | 
| 21 | 
            +
                end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                def error
         | 
| 24 | 
            +
                  self.find(nil) {|x| x.error}
         | 
| 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
         | 
| @@ -0,0 +1,47 @@ | |
| 1 | 
            +
            require "delegate"
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Rmpd
         | 
| 4 | 
            +
             | 
| 5 | 
            +
              KEY_VALUE_RE = /^([^:]+):\s*(.*)$/
         | 
| 6 | 
            +
              KNOWN_INT_FIELDS = [:time, :pos, :id, :track, :playlistlength, :playlist,
         | 
| 7 | 
            +
                :xfade, :repeat, :random, :queued, :volume, :song]
         | 
| 8 | 
            +
             | 
| 9 | 
            +
              class Response < DelegateClass(Hash)
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                attr_reader :error
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                def initialize(data)
         | 
| 14 | 
            +
                  super({})
         | 
| 15 | 
            +
                  @error = nil
         | 
| 16 | 
            +
                  parse(data)
         | 
| 17 | 
            +
                end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                def ok?
         | 
| 20 | 
            +
                  @error.nil?
         | 
| 21 | 
            +
                end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                def ack?
         | 
| 24 | 
            +
                  !ok?
         | 
| 25 | 
            +
                end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                private
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                def register_key_val_pair(r)
         | 
| 30 | 
            +
                  key, val = r[1].downcase.to_sym, r[2]
         | 
| 31 | 
            +
                  val = val.to_i if KNOWN_INT_FIELDS.include?(key)
         | 
| 32 | 
            +
                  self[key] = include?(key) ? ([self[key]] << val).flatten : val
         | 
| 33 | 
            +
                  self.class.send(:define_method, key) {self[key]}
         | 
| 34 | 
            +
                end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                def parse(data)
         | 
| 37 | 
            +
                  data.split("\n").each do |line|
         | 
| 38 | 
            +
                    case line
         | 
| 39 | 
            +
                    when KEY_VALUE_RE: register_key_val_pair($~)
         | 
| 40 | 
            +
                    when OK_RE: @error = nil
         | 
| 41 | 
            +
                    when ACK_RE: @error = MpdAckError.new($~.values_at(0..-1))
         | 
| 42 | 
            +
                    end
         | 
| 43 | 
            +
                  end
         | 
| 44 | 
            +
                end
         | 
| 45 | 
            +
             | 
| 46 | 
            +
              end
         | 
| 47 | 
            +
            end
         | 
    
        data/lib/rmpd/version.rb
    ADDED
    
    
    
        data/rmpd.gemspec
    ADDED
    
    | @@ -0,0 +1,24 @@ | |
| 1 | 
            +
            # -*- encoding: utf-8 -*-
         | 
| 2 | 
            +
            $:.push File.expand_path("../lib", __FILE__)
         | 
| 3 | 
            +
            require "rmpd/version"
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            Gem::Specification.new do |s|
         | 
| 6 | 
            +
              s.name        = "rmpd"
         | 
| 7 | 
            +
              s.version     = Rmpd::VERSION
         | 
| 8 | 
            +
              s.platform    = Gem::Platform::RUBY
         | 
| 9 | 
            +
              s.authors     = ["Eric Wollesen"]
         | 
| 10 | 
            +
              s.email       = ["ericw@xmtp.net"]
         | 
| 11 | 
            +
              s.homepage    = ""
         | 
| 12 | 
            +
              s.summary     = %q{Music Player Daemon client in Ruby}
         | 
| 13 | 
            +
              s.description = %q{Music Player Daemon client in Ruby}
         | 
| 14 | 
            +
             | 
| 15 | 
            +
              # s.rubyforge_project = "rmpd"
         | 
| 16 | 
            +
             | 
| 17 | 
            +
              s.files         = `git ls-files`.split("\n")
         | 
| 18 | 
            +
              s.test_files    = `git ls-files -- {spec}/*`.split("\n")
         | 
| 19 | 
            +
              s.executables   = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
         | 
| 20 | 
            +
              s.require_paths = ["lib"]
         | 
| 21 | 
            +
             | 
| 22 | 
            +
              s.add_development_dependency("rspec", "~> 2.6.0")
         | 
| 23 | 
            +
              s.add_development_dependency("ruby-debug")
         | 
| 24 | 
            +
            end
         | 
| @@ -0,0 +1 @@ | |
| 1 | 
            +
            port: 1234
         | 
| @@ -0,0 +1 @@ | |
| 1 | 
            +
            hostname: juggernaught@test.host
         | 
| @@ -0,0 +1,135 @@ | |
| 1 | 
            +
            require "spec_helper"
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            include Rmpd
         | 
| 4 | 
            +
             | 
| 5 | 
            +
             | 
| 6 | 
            +
            shared_examples_for "a command with a song pos" do
         | 
| 7 | 
            +
              it "should pass along the song pos" do
         | 
| 8 | 
            +
                @socket.stub!(:readline).and_return(*@responses)
         | 
| 9 | 
            +
                @socket.stub!(:puts).and_return(@socket.puts)
         | 
| 10 | 
            +
                @socket.stub!(:eof?).and_return(false)
         | 
| 11 | 
            +
                @command.call
         | 
| 12 | 
            +
              end
         | 
| 13 | 
            +
            end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
            describe Rmpd::Commands do
         | 
| 16 | 
            +
             | 
| 17 | 
            +
              before(:each) do
         | 
| 18 | 
            +
                @socket = mock_socket
         | 
| 19 | 
            +
                @config = mock_config
         | 
| 20 | 
            +
                @conn = Connection.new
         | 
| 21 | 
            +
              end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
              def set_password(password="foobar")
         | 
| 24 | 
            +
                @config.stub!(:password).and_return(password)
         | 
| 25 | 
            +
              end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
              it "should handle a closed connection gracefully" do
         | 
| 28 | 
            +
                @socket.stub!(:puts).and_raise(EOFError)
         | 
| 29 | 
            +
                @socket.stub!(:readline).and_return(*(connect_response + ok))
         | 
| 30 | 
            +
                @socket.should_receive(:close).exactly(5).times
         | 
| 31 | 
            +
                lambda do
         | 
| 32 | 
            +
                  @conn.ping
         | 
| 33 | 
            +
                end.should raise_error(Rmpd::MpdError, "Retry count exceeded")
         | 
| 34 | 
            +
              end
         | 
| 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 | 
            +
              it "should abort after a limited number of tries against a closed connection" do
         | 
| 47 | 
            +
                @socket.stub!(:puts).and_raise(EOFError)
         | 
| 48 | 
            +
                @socket.stub!(:readline).and_return(*(connect_response + ok))
         | 
| 49 | 
            +
                @socket.should_receive(:close).exactly(5).times
         | 
| 50 | 
            +
                lambda do
         | 
| 51 | 
            +
                  @conn.ping
         | 
| 52 | 
            +
                end.should raise_error(MpdError, "Retry count exceeded")
         | 
| 53 | 
            +
              end
         | 
| 54 | 
            +
             | 
| 55 | 
            +
              describe "close" do
         | 
| 56 | 
            +
                it "should close the socket" do
         | 
| 57 | 
            +
                  @socket.should_receive(:puts).with("close")
         | 
| 58 | 
            +
                  @socket.stub!(:readline).and_return(*connect_response)
         | 
| 59 | 
            +
                  @socket.should_receive(:close).once
         | 
| 60 | 
            +
                  @conn.close
         | 
| 61 | 
            +
                end
         | 
| 62 | 
            +
              end
         | 
| 63 | 
            +
             | 
| 64 | 
            +
              describe "volume" do
         | 
| 65 | 
            +
                it "should be deprecated" do
         | 
| 66 | 
            +
                  set_password
         | 
| 67 | 
            +
                  @socket.stub!(:readline).and_return(*connect_and_auth_responses)
         | 
| 68 | 
            +
                  $stderr.should_receive(:puts).with(/deprecated/i)
         | 
| 69 | 
            +
                  @conn.volume(0)
         | 
| 70 | 
            +
                end
         | 
| 71 | 
            +
              end
         | 
| 72 | 
            +
             | 
| 73 | 
            +
              describe "kill" do
         | 
| 74 | 
            +
                before(:each) do
         | 
| 75 | 
            +
                  set_password
         | 
| 76 | 
            +
                end
         | 
| 77 | 
            +
             | 
| 78 | 
            +
                it "should kill the daemon" do
         | 
| 79 | 
            +
                  @socket.should_receive(:puts).with("kill")
         | 
| 80 | 
            +
                  @socket.stub!(:readline).and_return(*connect_and_auth_responses)
         | 
| 81 | 
            +
                  @socket.should_receive(:close)
         | 
| 82 | 
            +
                  @conn.kill
         | 
| 83 | 
            +
                end
         | 
| 84 | 
            +
              end
         | 
| 85 | 
            +
             | 
| 86 | 
            +
              describe "playlistinfo" do
         | 
| 87 | 
            +
                before(:each) do
         | 
| 88 | 
            +
                  set_password
         | 
| 89 | 
            +
                  @song_id = 1
         | 
| 90 | 
            +
                  @cmd = "playlistinfo \"#{@song_id}\""
         | 
| 91 | 
            +
                  @responses = connect_and_auth_responses + ["file: blah\n", "boobies: yay!\n"] + ok
         | 
| 92 | 
            +
                  @command = Proc.new {@conn.playlistinfo(@song_id)}
         | 
| 93 | 
            +
                end
         | 
| 94 | 
            +
             | 
| 95 | 
            +
                it_should_behave_like "a command with a song pos"
         | 
| 96 | 
            +
              end
         | 
| 97 | 
            +
             | 
| 98 | 
            +
              describe "play" do
         | 
| 99 | 
            +
                before(:each) do
         | 
| 100 | 
            +
                  set_password
         | 
| 101 | 
            +
                  @song_id = 12
         | 
| 102 | 
            +
                  @cmd = "play \"#{@song_id}\""
         | 
| 103 | 
            +
                  @responses = connect_and_auth_responses + ok
         | 
| 104 | 
            +
                  @command = Proc.new {@conn.play(@song_id)}
         | 
| 105 | 
            +
                end
         | 
| 106 | 
            +
             | 
| 107 | 
            +
                it_should_behave_like "a command with a song pos"
         | 
| 108 | 
            +
              end
         | 
| 109 | 
            +
             | 
| 110 | 
            +
              describe "delete" do
         | 
| 111 | 
            +
                before(:each) do
         | 
| 112 | 
            +
                  set_password
         | 
| 113 | 
            +
                  @song_id = 23
         | 
| 114 | 
            +
                  @cmd = "delete \"#{@song_id}\""
         | 
| 115 | 
            +
                  @responses = connect_and_auth_responses + ok
         | 
| 116 | 
            +
                  @command = Proc.new {@conn.delete(@song_id)}
         | 
| 117 | 
            +
                end
         | 
| 118 | 
            +
             | 
| 119 | 
            +
                it_should_behave_like "a command with a song pos"
         | 
| 120 | 
            +
              end
         | 
| 121 | 
            +
             | 
| 122 | 
            +
              describe "command_list" do
         | 
| 123 | 
            +
                it "shouldn't wait for a response from the first command" do
         | 
| 124 | 
            +
                  @responses = connect_response + ok
         | 
| 125 | 
            +
                  @socket.stub!(:readline).and_return(*@responses)
         | 
| 126 | 
            +
                  @socket.stub!(:puts).and_return(@socket.puts)
         | 
| 127 | 
            +
                  @socket.stub!(:eof?).and_return(false)
         | 
| 128 | 
            +
             | 
| 129 | 
            +
                  @conn.command_list do |c|
         | 
| 130 | 
            +
                    c.status
         | 
| 131 | 
            +
                    c.stats
         | 
| 132 | 
            +
                  end
         | 
| 133 | 
            +
                end
         | 
| 134 | 
            +
              end
         | 
| 135 | 
            +
            end
         | 
| @@ -0,0 +1,128 @@ | |
| 1 | 
            +
            require "spec_helper"
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            include Rmpd
         | 
| 4 | 
            +
             | 
| 5 | 
            +
             | 
| 6 | 
            +
            describe Rmpd::Config do
         | 
| 7 | 
            +
             | 
| 8 | 
            +
              before(:each) do
         | 
| 9 | 
            +
                @filename = File.join(File.dirname(__FILE__), "../../spec/fixtures/config.yml")
         | 
| 10 | 
            +
              end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
              describe "initialization" do
         | 
| 13 | 
            +
                it "should parse successfully" do
         | 
| 14 | 
            +
                  lambda do
         | 
| 15 | 
            +
                    Rmpd::Config.new(@filename)
         | 
| 16 | 
            +
                  end.should_not raise_error
         | 
| 17 | 
            +
                end
         | 
| 18 | 
            +
              end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
              describe "rails" do
         | 
| 21 | 
            +
                before(:each) do
         | 
| 22 | 
            +
                  class Rails
         | 
| 23 | 
            +
                    def self.env ; "development" ; end
         | 
| 24 | 
            +
                  end
         | 
| 25 | 
            +
                  @filename = File.join(File.dirname(__FILE__), "../../spec/fixtures/config_rails.yml")
         | 
| 26 | 
            +
                end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                it "should load the development environment" do
         | 
| 29 | 
            +
                  lambda do
         | 
| 30 | 
            +
                    Rmpd::Config.new(@filename)
         | 
| 31 | 
            +
                  end.should_not raise_error
         | 
| 32 | 
            +
                end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                it "should load the development environment" do
         | 
| 35 | 
            +
                  config = Rmpd::Config.new(@filename)
         | 
| 36 | 
            +
                  config.hostname.should == "localhost"
         | 
| 37 | 
            +
                  config.port.should == 3000
         | 
| 38 | 
            +
                end
         | 
| 39 | 
            +
              end
         | 
| 40 | 
            +
             | 
| 41 | 
            +
              describe "operation" do
         | 
| 42 | 
            +
                before(:each) do
         | 
| 43 | 
            +
                  @config = Rmpd::Config.new(@filename)
         | 
| 44 | 
            +
                end
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                it "should have the correct port" do
         | 
| 47 | 
            +
                  1234.should == @config.port
         | 
| 48 | 
            +
                end
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                it "should have the correct hostname" do
         | 
| 51 | 
            +
                  @config.hostname.should == "test.host"
         | 
| 52 | 
            +
                end
         | 
| 53 | 
            +
             | 
| 54 | 
            +
                it "should have the correct password" do
         | 
| 55 | 
            +
                  @config.password.should == "juggernaught"
         | 
| 56 | 
            +
                end
         | 
| 57 | 
            +
              end
         | 
| 58 | 
            +
             | 
| 59 | 
            +
              describe "without password" do
         | 
| 60 | 
            +
                before(:each) do
         | 
| 61 | 
            +
                  @filename = File.join(File.dirname(__FILE__), "../../spec/fixtures/config_no_password.yml")
         | 
| 62 | 
            +
                  @config = Rmpd::Config.new(@filename)
         | 
| 63 | 
            +
                end
         | 
| 64 | 
            +
             | 
| 65 | 
            +
                it "should have the default password" do
         | 
| 66 | 
            +
                  @config.password.should == Rmpd::Config::DEFAULT_PASSWORD
         | 
| 67 | 
            +
                end
         | 
| 68 | 
            +
              end
         | 
| 69 | 
            +
             | 
| 70 | 
            +
              describe "without hostname" do
         | 
| 71 | 
            +
                before(:each) do
         | 
| 72 | 
            +
                  @filename = File.join(File.dirname(__FILE__), "../../spec/fixtures/config_no_hostname.yml")
         | 
| 73 | 
            +
                  @config = Rmpd::Config.new(@filename)
         | 
| 74 | 
            +
                end
         | 
| 75 | 
            +
             | 
| 76 | 
            +
                it "should have the default hostname" do
         | 
| 77 | 
            +
                  @config.hostname.should == Rmpd::Config::DEFAULT_HOSTNAME
         | 
| 78 | 
            +
                end
         | 
| 79 | 
            +
              end
         | 
| 80 | 
            +
             | 
| 81 | 
            +
              describe "without port" do
         | 
| 82 | 
            +
                before(:each) do
         | 
| 83 | 
            +
                  @filename = File.join(File.dirname(__FILE__), "../../spec/fixtures/config_no_port.yml")
         | 
| 84 | 
            +
                  @config = Rmpd::Config.new(@filename)
         | 
| 85 | 
            +
                end
         | 
| 86 | 
            +
             | 
| 87 | 
            +
                it "should have the default port" do
         | 
| 88 | 
            +
                  @config.port.should == Rmpd::Config::DEFAULT_PORT
         | 
| 89 | 
            +
                end
         | 
| 90 | 
            +
              end
         | 
| 91 | 
            +
             | 
| 92 | 
            +
              describe "by default" do
         | 
| 93 | 
            +
                before(:each) do
         | 
| 94 | 
            +
                  @filename = nil
         | 
| 95 | 
            +
                  ENV["MPD_HOST"] = nil
         | 
| 96 | 
            +
                  ENV["MPD_PORT"] = nil
         | 
| 97 | 
            +
                  @config = Rmpd::Config.new(@filename)
         | 
| 98 | 
            +
                end
         | 
| 99 | 
            +
             | 
| 100 | 
            +
                it "should have the default hostname" do
         | 
| 101 | 
            +
                  @config.hostname.should == Rmpd::Config::DEFAULT_HOSTNAME
         | 
| 102 | 
            +
                end
         | 
| 103 | 
            +
             | 
| 104 | 
            +
                it "should have the default port" do
         | 
| 105 | 
            +
                  @config.port.should == Rmpd::Config::DEFAULT_PORT
         | 
| 106 | 
            +
                end
         | 
| 107 | 
            +
             | 
| 108 | 
            +
                it "should have the default password" do
         | 
| 109 | 
            +
                  @config.password.should == Rmpd::Config::DEFAULT_PASSWORD
         | 
| 110 | 
            +
                end
         | 
| 111 | 
            +
              end
         | 
| 112 | 
            +
             | 
| 113 | 
            +
              describe "with environment variables set" do
         | 
| 114 | 
            +
                before(:each) do
         | 
| 115 | 
            +
                  ENV["MPD_HOST"] = "testing:host"
         | 
| 116 | 
            +
                  ENV["MPD_PORT"] = -1.to_s
         | 
| 117 | 
            +
                  @config = Rmpd::Config.new
         | 
| 118 | 
            +
                end
         | 
| 119 | 
            +
             | 
| 120 | 
            +
                it "should pickup the environment's host" do
         | 
| 121 | 
            +
                  @config.hostname.should == ENV["MPD_HOST"]
         | 
| 122 | 
            +
                end
         | 
| 123 | 
            +
             | 
| 124 | 
            +
                it "should pickup the environment's port" do
         | 
| 125 | 
            +
                  @config.port.should == ENV["MPD_PORT"].to_i
         | 
| 126 | 
            +
                end
         | 
| 127 | 
            +
              end
         | 
| 128 | 
            +
            end
         | 
| @@ -0,0 +1,61 @@ | |
| 1 | 
            +
            require "spec_helper"
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            include Rmpd
         | 
| 4 | 
            +
             | 
| 5 | 
            +
             | 
| 6 | 
            +
            describe Connection do
         | 
| 7 | 
            +
             | 
| 8 | 
            +
              before(:each) do
         | 
| 9 | 
            +
                @socket = mock_socket
         | 
| 10 | 
            +
                @config = mock_config
         | 
| 11 | 
            +
                @conn = Connection.new
         | 
| 12 | 
            +
              end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
              def set_password(password="foobar")
         | 
| 15 | 
            +
                @config.stub!(:password).and_return(password)
         | 
| 16 | 
            +
              end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
              it "should connect successfully" do
         | 
| 19 | 
            +
                responses = connect_response + ok
         | 
| 20 | 
            +
                @socket.should_receive(:readline).and_return(*responses)
         | 
| 21 | 
            +
                @conn.ping
         | 
| 22 | 
            +
              end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
              it "should authenticate successfully" do
         | 
| 25 | 
            +
                responses = connect_and_auth_responses + ok
         | 
| 26 | 
            +
                set_password
         | 
| 27 | 
            +
                @socket.should_receive(:readline).and_return(*responses)
         | 
| 28 | 
            +
                @conn.ping
         | 
| 29 | 
            +
              end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
              it "should generate a server version" do
         | 
| 32 | 
            +
                version = "0.1.2"
         | 
| 33 | 
            +
                responses = connect_response(version) + ok
         | 
| 34 | 
            +
                @socket.should_receive(:readline).and_return(*responses)
         | 
| 35 | 
            +
                @conn.ping # so we connect, and get the server version
         | 
| 36 | 
            +
                @conn.server_version.should == version
         | 
| 37 | 
            +
              end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
              it "should handle connection failures gracefully" do
         | 
| 40 | 
            +
                @socket.stub!(:connect).and_raise(Errno::ECONNREFUSED.new("test"))
         | 
| 41 | 
            +
                lambda do
         | 
| 42 | 
            +
                  @conn.ping
         | 
| 43 | 
            +
                end.should raise_error(Rmpd::MpdError)
         | 
| 44 | 
            +
              end
         | 
| 45 | 
            +
             | 
| 46 | 
            +
              it "should restrict access based on server version" do
         | 
| 47 | 
            +
                responses = connect_response("0.1.0")
         | 
| 48 | 
            +
                @socket.stub!(:readline).and_return(*responses)
         | 
| 49 | 
            +
                @conn.instance_eval do
         | 
| 50 | 
            +
                  def sval(*args)
         | 
| 51 | 
            +
                    server_version_at_least(*args)
         | 
| 52 | 
            +
                  end
         | 
| 53 | 
            +
                end
         | 
| 54 | 
            +
                lambda do
         | 
| 55 | 
            +
                  @conn.sval(0, 1, 2)
         | 
| 56 | 
            +
                end.should raise_error(MpdError)
         | 
| 57 | 
            +
                lambda do
         | 
| 58 | 
            +
                  @conn.sval(0, 1, 0)
         | 
| 59 | 
            +
                end.should_not raise_error(MpdError)
         | 
| 60 | 
            +
              end
         | 
| 61 | 
            +
            end
         | 
| @@ -0,0 +1,66 @@ | |
| 1 | 
            +
            require "spec_helper"
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Rmpd
         | 
| 4 | 
            +
              describe Commands, "Generators" do
         | 
| 5 | 
            +
             | 
| 6 | 
            +
                before(:each) do
         | 
| 7 | 
            +
                  @config = mock_config
         | 
| 8 | 
            +
                  @socket = mock_socket
         | 
| 9 | 
            +
                  @conn = Connection.new
         | 
| 10 | 
            +
                end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                describe "when version is ok" do
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                  before(:each) do
         | 
| 15 | 
            +
                    version = "0.1.1"
         | 
| 16 | 
            +
                    responses = connect_response(version) + ok
         | 
| 17 | 
            +
                    @socket.should_receive(:readline).and_return(*responses)
         | 
| 18 | 
            +
                  end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                  describe "simple_command" do
         | 
| 21 | 
            +
                    it "should allow based on server version" do
         | 
| 22 | 
            +
                      Commands::simple_command(:test, :min_version => [0, 1, 1])
         | 
| 23 | 
            +
                      lambda do
         | 
| 24 | 
            +
                        @conn.test
         | 
| 25 | 
            +
                      end.should_not raise_error(MpdError)
         | 
| 26 | 
            +
                    end
         | 
| 27 | 
            +
                  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 | 
            +
                end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                describe "when version is NOT ok" do
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                  before(:each) do
         | 
| 42 | 
            +
                    version = "0.1.1"
         | 
| 43 | 
            +
                    responses = connect_response(version)
         | 
| 44 | 
            +
                    @socket.should_receive(:readline).and_return(*responses)
         | 
| 45 | 
            +
                  end
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                  describe "simple_command" do
         | 
| 48 | 
            +
                    it "should disallow based on server version" do
         | 
| 49 | 
            +
                      Rmpd::Commands::simple_command(:test, :min_version => [0, 1, 2])
         | 
| 50 | 
            +
                      lambda do
         | 
| 51 | 
            +
                        @conn.test
         | 
| 52 | 
            +
                      end.should raise_error(MpdError, /^Requires server version/)
         | 
| 53 | 
            +
                    end
         | 
| 54 | 
            +
                  end
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                  describe "complex_command" do
         | 
| 57 | 
            +
                    it "should disallow based on server version" do
         | 
| 58 | 
            +
                      Rmpd::Commands::complex_command(:test, :min_version => [0, 1, 2])
         | 
| 59 | 
            +
                      lambda do
         | 
| 60 | 
            +
                        @conn.test
         | 
| 61 | 
            +
                      end.should raise_error(MpdError, /^Requires server version/)
         | 
| 62 | 
            +
                    end
         | 
| 63 | 
            +
                  end
         | 
| 64 | 
            +
                end
         | 
| 65 | 
            +
              end
         | 
| 66 | 
            +
            end
         | 
| @@ -0,0 +1,15 @@ | |
| 1 | 
            +
            require "spec_helper"
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            include Rmpd
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            describe MpdAckError do
         | 
| 6 | 
            +
              before(:each) do
         | 
| 7 | 
            +
                @err_msg = "ACK [2@0] {search} too few arguments for \"search\""
         | 
| 8 | 
            +
                ACK_RE.match(@err_msg)
         | 
| 9 | 
            +
                @error = MpdAckError.new($~)
         | 
| 10 | 
            +
              end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
              it "should generate a useful string message" do
         | 
| 13 | 
            +
                @error.to_s.should == @err_msg
         | 
| 14 | 
            +
              end
         | 
| 15 | 
            +
            end
         | 
| @@ -0,0 +1,54 @@ | |
| 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
         | 
| 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
         | 
| @@ -0,0 +1,36 @@ | |
| 1 | 
            +
            require "spec_helper"
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            include Rmpd
         | 
| 4 | 
            +
             | 
| 5 | 
            +
             | 
| 6 | 
            +
            describe Rmpd::Response do
         | 
| 7 | 
            +
              before(:each) do
         | 
| 8 | 
            +
                data = <<-EOF
         | 
| 9 | 
            +
            foo: bar
         | 
| 10 | 
            +
            OK
         | 
| 11 | 
            +
            EOF
         | 
| 12 | 
            +
                @response = Rmpd::Response.new(data)
         | 
| 13 | 
            +
              end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
              it "should have a foo method" do
         | 
| 16 | 
            +
                @response.respond_to?(:foo).should be_true
         | 
| 17 | 
            +
                @response.foo.should == "bar"
         | 
| 18 | 
            +
              end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
              it "should have a foo key" do
         | 
| 21 | 
            +
                @response.should include(:foo)
         | 
| 22 | 
            +
                @response[:foo].should == "bar"
         | 
| 23 | 
            +
              end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
              it "should be ok" do
         | 
| 26 | 
            +
                # @response.should be_ok
         | 
| 27 | 
            +
                # doesn't work correctly, see rspec bug #11526
         | 
| 28 | 
            +
                @response.ok?.should == true
         | 
| 29 | 
            +
              end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
              it "should not be ack" do
         | 
| 32 | 
            +
                # @response.should_not be_ack
         | 
| 33 | 
            +
                # doesn't work correctly, see rspec bug #11526
         | 
| 34 | 
            +
                @response.ack?.should_not == true
         | 
| 35 | 
            +
              end
         | 
| 36 | 
            +
            end
         | 
    
        data/spec/spec.opts
    ADDED
    
    
    
        data/spec/spec_helper.rb
    ADDED
    
    | @@ -0,0 +1,43 @@ | |
| 1 | 
            +
            require File.dirname(__FILE__) + "/../lib/rmpd"
         | 
| 2 | 
            +
             | 
| 3 | 
            +
             | 
| 4 | 
            +
            def mock_socket
         | 
| 5 | 
            +
              socket = mock(Socket)
         | 
| 6 | 
            +
              socket.stub!(:puts)
         | 
| 7 | 
            +
              socket.stub!(:closed?).and_return(false)
         | 
| 8 | 
            +
              socket.stub!(:connect)
         | 
| 9 | 
            +
              socket.stub!(:eof?).and_return(false)
         | 
| 10 | 
            +
              Socket.stub!(:pack_sockaddr_in).and_return("pack_sockaddr_in")
         | 
| 11 | 
            +
              Socket.stub!(:new).and_return(socket)
         | 
| 12 | 
            +
              socket
         | 
| 13 | 
            +
            end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
            def mock_config(opts={})
         | 
| 16 | 
            +
              opts = {
         | 
| 17 | 
            +
                :hostname => Rmpd::Config::DEFAULT_HOSTNAME,
         | 
| 18 | 
            +
                :port => Rmpd::Config::DEFAULT_PORT,
         | 
| 19 | 
            +
                :password => Rmpd::Config::DEFAULT_PASSWORD,
         | 
| 20 | 
            +
              }.merge(opts)
         | 
| 21 | 
            +
              config = mock(Rmpd::Config)
         | 
| 22 | 
            +
              config.stub!(:hostname).and_return(opts[:hostname])
         | 
| 23 | 
            +
              config.stub!(:password).and_return(opts[:password])
         | 
| 24 | 
            +
              config.stub!(:port).and_return(opts[:port])
         | 
| 25 | 
            +
              Rmpd::Config.stub!(:new).and_return(config)
         | 
| 26 | 
            +
              config
         | 
| 27 | 
            +
            end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
            def connect_response(version="0.12.0")
         | 
| 30 | 
            +
              ok("MPD #{version}")
         | 
| 31 | 
            +
            end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
            def connect_and_auth_responses
         | 
| 34 | 
            +
              connect_response + ok
         | 
| 35 | 
            +
            end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
            def ok(txt="")
         | 
| 38 | 
            +
              txt ? ["OK #{txt}\n"] : ["OK\n"]
         | 
| 39 | 
            +
            end
         | 
| 40 | 
            +
             | 
| 41 | 
            +
            def ack(x=1, y=2, cmd="foo_command", msg="No one's home dopey!")
         | 
| 42 | 
            +
              ["ACK [#{x}@#{y}] {#{cmd}} #{msg}"]
         | 
| 43 | 
            +
            end
         | 
    
        data/spec/spec_rcov.opts
    ADDED
    
    
    
        metadata
    ADDED
    
    | @@ -0,0 +1,129 @@ | |
| 1 | 
            +
            --- !ruby/object:Gem::Specification 
         | 
| 2 | 
            +
            name: rmpd
         | 
| 3 | 
            +
            version: !ruby/object:Gem::Version 
         | 
| 4 | 
            +
              hash: 23
         | 
| 5 | 
            +
              prerelease: false
         | 
| 6 | 
            +
              segments: 
         | 
| 7 | 
            +
              - 1
         | 
| 8 | 
            +
              - 0
         | 
| 9 | 
            +
              - 0
         | 
| 10 | 
            +
              version: 1.0.0
         | 
| 11 | 
            +
            platform: ruby
         | 
| 12 | 
            +
            authors: 
         | 
| 13 | 
            +
            - Eric Wollesen
         | 
| 14 | 
            +
            autorequire: 
         | 
| 15 | 
            +
            bindir: bin
         | 
| 16 | 
            +
            cert_chain: []
         | 
| 17 | 
            +
             | 
| 18 | 
            +
            date: 2011-06-12 00:00:00 -06:00
         | 
| 19 | 
            +
            default_executable: 
         | 
| 20 | 
            +
            dependencies: 
         | 
| 21 | 
            +
            - !ruby/object:Gem::Dependency 
         | 
| 22 | 
            +
              name: rspec
         | 
| 23 | 
            +
              prerelease: false
         | 
| 24 | 
            +
              requirement: &id001 !ruby/object:Gem::Requirement 
         | 
| 25 | 
            +
                none: false
         | 
| 26 | 
            +
                requirements: 
         | 
| 27 | 
            +
                - - ~>
         | 
| 28 | 
            +
                  - !ruby/object:Gem::Version 
         | 
| 29 | 
            +
                    hash: 23
         | 
| 30 | 
            +
                    segments: 
         | 
| 31 | 
            +
                    - 2
         | 
| 32 | 
            +
                    - 6
         | 
| 33 | 
            +
                    - 0
         | 
| 34 | 
            +
                    version: 2.6.0
         | 
| 35 | 
            +
              type: :development
         | 
| 36 | 
            +
              version_requirements: *id001
         | 
| 37 | 
            +
            - !ruby/object:Gem::Dependency 
         | 
| 38 | 
            +
              name: ruby-debug
         | 
| 39 | 
            +
              prerelease: false
         | 
| 40 | 
            +
              requirement: &id002 !ruby/object:Gem::Requirement 
         | 
| 41 | 
            +
                none: false
         | 
| 42 | 
            +
                requirements: 
         | 
| 43 | 
            +
                - - ">="
         | 
| 44 | 
            +
                  - !ruby/object:Gem::Version 
         | 
| 45 | 
            +
                    hash: 3
         | 
| 46 | 
            +
                    segments: 
         | 
| 47 | 
            +
                    - 0
         | 
| 48 | 
            +
                    version: "0"
         | 
| 49 | 
            +
              type: :development
         | 
| 50 | 
            +
              version_requirements: *id002
         | 
| 51 | 
            +
            description: Music Player Daemon client in Ruby
         | 
| 52 | 
            +
            email: 
         | 
| 53 | 
            +
            - ericw@xmtp.net
         | 
| 54 | 
            +
            executables: []
         | 
| 55 | 
            +
             | 
| 56 | 
            +
            extensions: []
         | 
| 57 | 
            +
             | 
| 58 | 
            +
            extra_rdoc_files: []
         | 
| 59 | 
            +
             | 
| 60 | 
            +
            files: 
         | 
| 61 | 
            +
            - .gitignore
         | 
| 62 | 
            +
            - .rspec
         | 
| 63 | 
            +
            - Gemfile
         | 
| 64 | 
            +
            - Rakefile
         | 
| 65 | 
            +
            - lib/rmpd.rb
         | 
| 66 | 
            +
            - lib/rmpd/commands.rb
         | 
| 67 | 
            +
            - lib/rmpd/commands/admin.rb
         | 
| 68 | 
            +
            - lib/rmpd/commands/database.rb
         | 
| 69 | 
            +
            - lib/rmpd/commands/generators.rb
         | 
| 70 | 
            +
            - lib/rmpd/commands/miscellaneous.rb
         | 
| 71 | 
            +
            - lib/rmpd/commands/playback.rb
         | 
| 72 | 
            +
            - lib/rmpd/commands/playlist.rb
         | 
| 73 | 
            +
            - lib/rmpd/config.rb
         | 
| 74 | 
            +
            - lib/rmpd/connection.rb
         | 
| 75 | 
            +
            - lib/rmpd/multi_response.rb
         | 
| 76 | 
            +
            - lib/rmpd/response.rb
         | 
| 77 | 
            +
            - lib/rmpd/version.rb
         | 
| 78 | 
            +
            - rmpd.gemspec
         | 
| 79 | 
            +
            - spec/fixtures/config.yml
         | 
| 80 | 
            +
            - spec/fixtures/config_no_hostname.yml
         | 
| 81 | 
            +
            - spec/fixtures/config_no_password.yml
         | 
| 82 | 
            +
            - spec/fixtures/config_no_port.yml
         | 
| 83 | 
            +
            - spec/fixtures/config_rails.yml
         | 
| 84 | 
            +
            - spec/models/commands_spec.rb
         | 
| 85 | 
            +
            - spec/models/config_spec.rb
         | 
| 86 | 
            +
            - spec/models/connection_spec.rb
         | 
| 87 | 
            +
            - spec/models/generators_spec.rb
         | 
| 88 | 
            +
            - spec/models/mildred_mpd_spec.rb
         | 
| 89 | 
            +
            - spec/models/multi_response_spec.rb
         | 
| 90 | 
            +
            - spec/models/response_spec.rb
         | 
| 91 | 
            +
            - spec/spec.opts
         | 
| 92 | 
            +
            - spec/spec_helper.rb
         | 
| 93 | 
            +
            - spec/spec_rcov.opts
         | 
| 94 | 
            +
            has_rdoc: true
         | 
| 95 | 
            +
            homepage: ""
         | 
| 96 | 
            +
            licenses: []
         | 
| 97 | 
            +
             | 
| 98 | 
            +
            post_install_message: 
         | 
| 99 | 
            +
            rdoc_options: []
         | 
| 100 | 
            +
             | 
| 101 | 
            +
            require_paths: 
         | 
| 102 | 
            +
            - lib
         | 
| 103 | 
            +
            required_ruby_version: !ruby/object:Gem::Requirement 
         | 
| 104 | 
            +
              none: false
         | 
| 105 | 
            +
              requirements: 
         | 
| 106 | 
            +
              - - ">="
         | 
| 107 | 
            +
                - !ruby/object:Gem::Version 
         | 
| 108 | 
            +
                  hash: 3
         | 
| 109 | 
            +
                  segments: 
         | 
| 110 | 
            +
                  - 0
         | 
| 111 | 
            +
                  version: "0"
         | 
| 112 | 
            +
            required_rubygems_version: !ruby/object:Gem::Requirement 
         | 
| 113 | 
            +
              none: false
         | 
| 114 | 
            +
              requirements: 
         | 
| 115 | 
            +
              - - ">="
         | 
| 116 | 
            +
                - !ruby/object:Gem::Version 
         | 
| 117 | 
            +
                  hash: 3
         | 
| 118 | 
            +
                  segments: 
         | 
| 119 | 
            +
                  - 0
         | 
| 120 | 
            +
                  version: "0"
         | 
| 121 | 
            +
            requirements: []
         | 
| 122 | 
            +
             | 
| 123 | 
            +
            rubyforge_project: 
         | 
| 124 | 
            +
            rubygems_version: 1.3.7
         | 
| 125 | 
            +
            signing_key: 
         | 
| 126 | 
            +
            specification_version: 3
         | 
| 127 | 
            +
            summary: Music Player Daemon client in Ruby
         | 
| 128 | 
            +
            test_files: []
         | 
| 129 | 
            +
             |