squeezer-ruby 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +8 -0
- data/Gemfile +3 -0
- data/LICENSE.mkd +7 -0
- data/README.mkd +67 -0
- data/Rakefile +19 -0
- data/lib/squeezer/api.rb +19 -0
- data/lib/squeezer/client/database.rb +49 -0
- data/lib/squeezer/client/general.rb +22 -0
- data/lib/squeezer/client/players.rb +25 -0
- data/lib/squeezer/client/playlist.rb +7 -0
- data/lib/squeezer/client/utils.rb +7 -0
- data/lib/squeezer/client.rb +12 -0
- data/lib/squeezer/configuration.rb +44 -0
- data/lib/squeezer/connection.rb +48 -0
- data/lib/squeezer/core_extentions.rb +6 -0
- data/lib/squeezer/models/artist.rb +8 -0
- data/lib/squeezer/models/player.rb +190 -0
- data/lib/squeezer/models.rb +25 -0
- data/lib/squeezer/version.rb +4 -0
- data/lib/squeezer.rb +25 -0
- data/spec/spec_helper.rb +41 -0
- data/spec/squeezer/client/database_spec.rb +48 -0
- data/spec/squeezer/client/general_spec.rb +36 -0
- data/spec/squeezer/client/players_spec.rb +48 -0
- data/spec/squeezer_spec.rb +56 -0
- data/squeezer.gemspec +28 -0
- metadata +190 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.mkd
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
Copyright (c) 2011 Daniël van Hoesel
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
4
|
+
|
5
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
6
|
+
|
7
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.mkd
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
Squeezer: Ruby Squeezebox Gem
|
2
|
+
====================
|
3
|
+
A Ruby wrapper for the [Squeezebox](http://www.logitech.com/en-us/speakers-audio/wireless-music-systems) [Server](http://www.squeezenetwork.com/download) CLI API.
|
4
|
+
|
5
|
+
WORK IN PROGRESS
|
6
|
+
----------------
|
7
|
+
This project is in it's early stages, it is not finished by any means.
|
8
|
+
**Feel free to comment with help or criticism!**
|
9
|
+
|
10
|
+
It also lacks proper error handling and documentation.
|
11
|
+
Most of the critical refactor points have been marked with a 'todo' comment.
|
12
|
+
|
13
|
+
- active record like search methods and model relations (database chained search engine?)
|
14
|
+
- playlist handling
|
15
|
+
- add convenience methods
|
16
|
+
- add some more sugar
|
17
|
+
|
18
|
+
Installation
|
19
|
+
------------
|
20
|
+
gem install squeezer-ruby
|
21
|
+
|
22
|
+
Usage Examples
|
23
|
+
--------------
|
24
|
+
General:
|
25
|
+
|
26
|
+
require "squeezer"
|
27
|
+
|
28
|
+
Squeezer.configure do |config|
|
29
|
+
config.server = "127.0.0.1"
|
30
|
+
config.port = 9090
|
31
|
+
end
|
32
|
+
|
33
|
+
client = Squeezer::Client.new
|
34
|
+
|
35
|
+
puts client.version
|
36
|
+
|
37
|
+
client.players.each do |id, player|
|
38
|
+
puts "Name: #{player.name}"
|
39
|
+
puts "Model: #{player.model}"
|
40
|
+
puts "Signal: #{player.signal_strength}" if player.wireless?
|
41
|
+
puts "Volume: #{player.volume}"
|
42
|
+
|
43
|
+
player.on! if player.off?
|
44
|
+
player.volume = "+40"
|
45
|
+
end
|
46
|
+
|
47
|
+
puts client.total_artists
|
48
|
+
|
49
|
+
client.artists.each do |artist|
|
50
|
+
puts artist.name
|
51
|
+
end
|
52
|
+
|
53
|
+
client.exit
|
54
|
+
|
55
|
+
Models:
|
56
|
+
|
57
|
+
begin
|
58
|
+
include Squeezer::Models
|
59
|
+
|
60
|
+
puts Player.find_by_name("living room").ip
|
61
|
+
puts Player.find_by_ip("192.168.1.1").name
|
62
|
+
end
|
63
|
+
|
64
|
+
Copyright
|
65
|
+
---------
|
66
|
+
Copyright (c) 2011 Daniël van Hoesel.
|
67
|
+
See [LICENSE](https://github.com/s0meone/squeezer/blob/master/LICENSE.mkd) for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'bundler'
|
2
|
+
Bundler::GemHelper.install_tasks
|
3
|
+
|
4
|
+
require 'rspec/core/rake_task'
|
5
|
+
RSpec::Core::RakeTask.new(:spec)
|
6
|
+
|
7
|
+
task :default => :spec
|
8
|
+
|
9
|
+
namespace :doc do
|
10
|
+
require 'yard'
|
11
|
+
YARD::Rake::YardocTask.new do |task|
|
12
|
+
task.files = ['HISTORY.mkd', 'LICENSE.mkd', 'lib/**/*.rb']
|
13
|
+
task.options = [
|
14
|
+
'--protected',
|
15
|
+
'--output-dir', 'doc/yard',
|
16
|
+
'--markup', 'markdown',
|
17
|
+
]
|
18
|
+
end
|
19
|
+
end
|
data/lib/squeezer/api.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require File.expand_path('../connection', __FILE__)
|
2
|
+
|
3
|
+
module Squeezer
|
4
|
+
class API
|
5
|
+
|
6
|
+
# @private
|
7
|
+
attr_accessor *Configuration::VALID_OPTIONS_KEYS
|
8
|
+
|
9
|
+
# Creates a new API
|
10
|
+
def initialize(options={})
|
11
|
+
options = Squeezer.options.merge(options)
|
12
|
+
Configuration::VALID_OPTIONS_KEYS.each do |key|
|
13
|
+
send("#{key}=", options[key])
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
include Connection
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Squeezer
|
2
|
+
class Client
|
3
|
+
module Database
|
4
|
+
|
5
|
+
# doesn't include 'Various Artists'
|
6
|
+
def total_artists
|
7
|
+
count(:artists)
|
8
|
+
end
|
9
|
+
|
10
|
+
def total_albums
|
11
|
+
count(:albums)
|
12
|
+
end
|
13
|
+
|
14
|
+
def total_songs
|
15
|
+
count(:songs)
|
16
|
+
end
|
17
|
+
|
18
|
+
def total_genres
|
19
|
+
count(:genres)
|
20
|
+
end
|
21
|
+
|
22
|
+
def count(entity)
|
23
|
+
raise "unknown entity" unless %w{artists albums songs genres}.include?(entity.to_s)
|
24
|
+
cmd("info total #{entity.to_s} ?").to_i
|
25
|
+
end
|
26
|
+
|
27
|
+
# TODO this is ugly, refactor!
|
28
|
+
def artists
|
29
|
+
Models::Model.entities(Models::Artist, extract_data([:id, :artist, :textkey], cmd("artists 0 #{total_artists + 1} charset:utf8 tags:s")))
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
# TODO optimize this, attributes have to be in the correct order for the regex to match, which is stupid
|
35
|
+
def extract_data(attributes, data)
|
36
|
+
result = Array.new
|
37
|
+
data.scan Regexp.new(attributes.map{|a| "#{a.to_s}%3A([^\s]+)" }.join(" ")) do |match|
|
38
|
+
record = Hash.new
|
39
|
+
match.each_with_index do |field, index|
|
40
|
+
record[attributes[index]] = URI.unescape(field)
|
41
|
+
end
|
42
|
+
result << record
|
43
|
+
end
|
44
|
+
result.size == 1 ? result.first : result
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Squeezer
|
2
|
+
class Client
|
3
|
+
module General
|
4
|
+
|
5
|
+
def version
|
6
|
+
cmd "version ?"
|
7
|
+
end
|
8
|
+
|
9
|
+
# TODO seems like my server doesn't respond to this command
|
10
|
+
def can(command)
|
11
|
+
cmd("can #{command} ?").to_boolean
|
12
|
+
end
|
13
|
+
|
14
|
+
def shutdown_server(sure=:no)
|
15
|
+
return false unless sure == :yes
|
16
|
+
cmd("shutdown")
|
17
|
+
true
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Squeezer
|
2
|
+
class Client
|
3
|
+
module Players
|
4
|
+
|
5
|
+
# beware of offline players, they still show up on the list
|
6
|
+
# test if those players are connected with Player#connected?
|
7
|
+
def players
|
8
|
+
Models::Player.all
|
9
|
+
end
|
10
|
+
|
11
|
+
def find_player_by_name(name)
|
12
|
+
Models::Player.find_by_name(name)
|
13
|
+
end
|
14
|
+
|
15
|
+
def find_player_by_ip(ip)
|
16
|
+
Models::Player.find_by_ip(ip)
|
17
|
+
end
|
18
|
+
|
19
|
+
def find_player_by_id(id)
|
20
|
+
Models::Player.find_by_id(id)
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module Squeezer
|
2
|
+
class Client < API
|
3
|
+
Dir[File.expand_path('../client/*.rb', __FILE__)].each{|f| require f}
|
4
|
+
|
5
|
+
include Squeezer::Client::Database
|
6
|
+
include Squeezer::Client::General
|
7
|
+
include Squeezer::Client::Players
|
8
|
+
include Squeezer::Client::Playlist
|
9
|
+
|
10
|
+
include Squeezer::Client::Utils
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require File.expand_path('../version', __FILE__)
|
2
|
+
|
3
|
+
module Squeezer
|
4
|
+
# Defines constants and methods related to configuration
|
5
|
+
module Configuration
|
6
|
+
# An array of valid keys in the options hash when configuring an {Squeezer::API}
|
7
|
+
VALID_OPTIONS_KEYS = [:server, :port].freeze
|
8
|
+
|
9
|
+
# By default, don't set an server
|
10
|
+
DEFAULT_SERVER = nil.freeze
|
11
|
+
|
12
|
+
# The port where the CLI interface is running on
|
13
|
+
#
|
14
|
+
# @note The default is set to 9090
|
15
|
+
DEFAULT_PORT = 9090.freeze
|
16
|
+
|
17
|
+
# @private
|
18
|
+
attr_accessor *VALID_OPTIONS_KEYS
|
19
|
+
|
20
|
+
# When this module is extended, set all configuration options to their default values
|
21
|
+
def self.extended(base)
|
22
|
+
base.reset
|
23
|
+
end
|
24
|
+
|
25
|
+
# Convenience method to allow configuration options to be set in a block
|
26
|
+
def configure
|
27
|
+
yield self
|
28
|
+
end
|
29
|
+
|
30
|
+
# Create a hash of options and their values
|
31
|
+
def options
|
32
|
+
VALID_OPTIONS_KEYS.inject({}) do |option, key|
|
33
|
+
option.merge!(key => send(key))
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Reset all configuration options to defaults
|
38
|
+
def reset
|
39
|
+
self.server = DEFAULT_SERVER
|
40
|
+
self.port = DEFAULT_PORT
|
41
|
+
self
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'net/telnet'
|
2
|
+
|
3
|
+
module Squeezer
|
4
|
+
# @private
|
5
|
+
module Connection
|
6
|
+
|
7
|
+
# don't forget to close the connection! Or else the connection stay's
|
8
|
+
# in memory of the eigenclass!
|
9
|
+
def exit
|
10
|
+
cmd("exit")
|
11
|
+
Connection.close_connection
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def cmd(command, options={})
|
17
|
+
# TODO raise exceptions instead of returning false
|
18
|
+
response = connection.cmd(command)
|
19
|
+
puts response if options == :debug
|
20
|
+
return false if response.nil?
|
21
|
+
return true if response.strip.eql?(command)
|
22
|
+
result = response.gsub(command.gsub('?', '').strip, '').strip
|
23
|
+
result.force_encoding("UTF-8") unless /^1\.8/ === RUBY_VERSION
|
24
|
+
result
|
25
|
+
end
|
26
|
+
|
27
|
+
def connection
|
28
|
+
Connection.retrieve_connection
|
29
|
+
end
|
30
|
+
|
31
|
+
class << self
|
32
|
+
def close_connection
|
33
|
+
@connection.sock.close
|
34
|
+
@connection = nil
|
35
|
+
end
|
36
|
+
|
37
|
+
def retrieve_connection
|
38
|
+
@connection ||= open_connection
|
39
|
+
end
|
40
|
+
|
41
|
+
def open_connection
|
42
|
+
# do authentication and stuff
|
43
|
+
Net::Telnet.new("Host" => Squeezer.server, "Port" => Squeezer.port, "Telnetmode" => false, "Prompt" => /\n/)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,190 @@
|
|
1
|
+
module Squeezer
|
2
|
+
module Models
|
3
|
+
class Player < Model
|
4
|
+
|
5
|
+
# most of these attributes are cached for the lifetime of the object, since they never change anyway
|
6
|
+
|
7
|
+
attr_reader :id
|
8
|
+
|
9
|
+
def initialize(id, options={})
|
10
|
+
super options
|
11
|
+
@id = id
|
12
|
+
end
|
13
|
+
|
14
|
+
def name
|
15
|
+
@name ||= cmd "player name #{id} ?"
|
16
|
+
end
|
17
|
+
|
18
|
+
def ip
|
19
|
+
@ip ||= URI.unescape(cmd("player ip #{id} ?")).split(":").first
|
20
|
+
end
|
21
|
+
|
22
|
+
def model
|
23
|
+
@model ||= cmd "player model #{id} ?"
|
24
|
+
end
|
25
|
+
|
26
|
+
# "transporter", "squeezebox2", "squeezebox", "slimp3", "softsqueeze", or "http"
|
27
|
+
def is_a?(value)
|
28
|
+
value = value.to_s if value.is_a?(Symbol)
|
29
|
+
model == value
|
30
|
+
end
|
31
|
+
|
32
|
+
def display_type
|
33
|
+
@display_type ||= cmd "player displaytype #{id} ?"
|
34
|
+
end
|
35
|
+
|
36
|
+
def can_power_off
|
37
|
+
@canpoweroff ||= cmd("player canpoweroff #{id} ?").to_boolean
|
38
|
+
end
|
39
|
+
|
40
|
+
def signal_strength
|
41
|
+
cmd("#{id} signalstrength ?").to_i
|
42
|
+
end
|
43
|
+
|
44
|
+
def wireless?
|
45
|
+
@wireless ||= (signal_strength != 0)
|
46
|
+
end
|
47
|
+
|
48
|
+
def connected?
|
49
|
+
cmd("#{id} connected ?").to_boolean
|
50
|
+
end
|
51
|
+
|
52
|
+
def volume=(value)
|
53
|
+
mixer(:volume, value)
|
54
|
+
end
|
55
|
+
|
56
|
+
def volume
|
57
|
+
mixer(:volume)
|
58
|
+
end
|
59
|
+
|
60
|
+
def bass=(value)
|
61
|
+
raise "command is not supported on this player's model: #{model}" unless %w{slimp3 squeezebox}.include?(model)
|
62
|
+
mixer(:bass, value)
|
63
|
+
end
|
64
|
+
|
65
|
+
def bass
|
66
|
+
mixer(:bass)
|
67
|
+
end
|
68
|
+
|
69
|
+
def treble=(value)
|
70
|
+
raise "command is not supported on this player's model: #{model}" unless %w{slimp3 squeezebox}.include?(model)
|
71
|
+
mixer(:treble, value)
|
72
|
+
end
|
73
|
+
|
74
|
+
def treble
|
75
|
+
mixer(:treble)
|
76
|
+
end
|
77
|
+
|
78
|
+
def pitch=(value)
|
79
|
+
raise "command is not supported on this player's model: #{model}" unless is_a?("squeezebox")
|
80
|
+
mixer(:pitch, value)
|
81
|
+
end
|
82
|
+
|
83
|
+
def pitch
|
84
|
+
mixer(:pitch)
|
85
|
+
end
|
86
|
+
|
87
|
+
def mixer(attribute, value="?")
|
88
|
+
raise "unknown attribute" unless %w{volume bass treble pitch}.include?(attribute.to_s)
|
89
|
+
modifier = "+" if value.is_a?(String) and value.include?("+")
|
90
|
+
result = cmd("#{id} mixer #{attribute.to_s} #{modifier}#{value == "?" ? "?" : value.to_i}")
|
91
|
+
result.respond_to?(:to_i) ? result.to_i : result
|
92
|
+
end
|
93
|
+
|
94
|
+
def power(state)
|
95
|
+
state_map = {:on => "1", :off => "0"}
|
96
|
+
raise "unknown power state" unless state_map.keys.include?(state)
|
97
|
+
result = cmd "#{id} power #{state_map[state]}"
|
98
|
+
result
|
99
|
+
end
|
100
|
+
|
101
|
+
def power?
|
102
|
+
on = cmd("#{id} power ?").to_boolean
|
103
|
+
return :on if on
|
104
|
+
return :off if not on
|
105
|
+
end
|
106
|
+
|
107
|
+
def on?
|
108
|
+
:on == power?
|
109
|
+
end
|
110
|
+
|
111
|
+
def off?
|
112
|
+
:off == power?
|
113
|
+
end
|
114
|
+
|
115
|
+
def on!
|
116
|
+
power(:on)
|
117
|
+
end
|
118
|
+
|
119
|
+
def off!
|
120
|
+
power(:off)
|
121
|
+
end
|
122
|
+
|
123
|
+
def <=>(target)
|
124
|
+
self.name <=> target.name
|
125
|
+
end
|
126
|
+
|
127
|
+
# TODO method_missing anyone? Those find methods could be nice with a ghost method.
|
128
|
+
|
129
|
+
# beware of offline players, they still show up on the list
|
130
|
+
# test if those players are connected with Player#connected?
|
131
|
+
def players
|
132
|
+
player_map = Hash.new
|
133
|
+
count = cmd("player count ?").to_i
|
134
|
+
count.to_i.times do |index|
|
135
|
+
id = cmd("player id #{index} ?")
|
136
|
+
player_map[id] = Models::Player.new(id)
|
137
|
+
end
|
138
|
+
player_map
|
139
|
+
end
|
140
|
+
|
141
|
+
def self.all
|
142
|
+
self.new(nil).players
|
143
|
+
end
|
144
|
+
|
145
|
+
# handle duplicates in these find methods, specs expect only one result
|
146
|
+
def find_by_name(name)
|
147
|
+
find(:name => name)
|
148
|
+
end
|
149
|
+
|
150
|
+
def self.find_by_name(name)
|
151
|
+
self.new(nil).find_by_name(name)
|
152
|
+
end
|
153
|
+
|
154
|
+
def find_by_ip(ip)
|
155
|
+
find(:ip => ip)
|
156
|
+
end
|
157
|
+
|
158
|
+
def self.find_by_ip(ip)
|
159
|
+
p = self.new(nil)
|
160
|
+
result = p.find_by_ip(ip)
|
161
|
+
p = nil
|
162
|
+
result
|
163
|
+
end
|
164
|
+
|
165
|
+
def find_by_id(id)
|
166
|
+
find(:id => id)
|
167
|
+
end
|
168
|
+
|
169
|
+
def self.find_by_id(id)
|
170
|
+
self.new(nil).find_by_id(id)
|
171
|
+
end
|
172
|
+
|
173
|
+
def find(values)
|
174
|
+
players.each do |id,player|
|
175
|
+
match = true
|
176
|
+
values.each do |property,value|
|
177
|
+
match = false unless player.send(property.to_sym) == value
|
178
|
+
end
|
179
|
+
return player if match == true
|
180
|
+
end
|
181
|
+
return nil
|
182
|
+
end
|
183
|
+
|
184
|
+
def self.find(values)
|
185
|
+
self.new(nil).find(values)
|
186
|
+
end
|
187
|
+
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Squeezer
|
2
|
+
module Models
|
3
|
+
|
4
|
+
class Model < API
|
5
|
+
|
6
|
+
def self.entities(model, data)
|
7
|
+
result = Array.new
|
8
|
+
return result if data.nil?
|
9
|
+
data.each do |record|
|
10
|
+
entity = model.send(:new)
|
11
|
+
record.each do |attribute, value|
|
12
|
+
entity.send("#{attribute}=", value)
|
13
|
+
end
|
14
|
+
result << entity
|
15
|
+
end
|
16
|
+
result
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
Dir[File.expand_path('../models/*.rb', __FILE__)].each{|f| require f}
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
data/lib/squeezer.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'uri'
|
2
|
+
|
3
|
+
require File.expand_path('../squeezer/core_extentions', __FILE__)
|
4
|
+
require File.expand_path('../squeezer/configuration', __FILE__)
|
5
|
+
require File.expand_path('../squeezer/api', __FILE__)
|
6
|
+
require File.expand_path('../squeezer/client', __FILE__)
|
7
|
+
require File.expand_path('../squeezer/models', __FILE__)
|
8
|
+
|
9
|
+
module Squeezer
|
10
|
+
extend Configuration
|
11
|
+
|
12
|
+
# Alias for Squeezer::Client.new
|
13
|
+
#
|
14
|
+
# @return [Squeezer::Client]
|
15
|
+
def self.client(options={})
|
16
|
+
Squeezer::Client.new(options)
|
17
|
+
end
|
18
|
+
|
19
|
+
# Delegate to Squeezer::Client
|
20
|
+
def self.method_missing(method, *args, &block)
|
21
|
+
return super unless client.respond_to?(method)
|
22
|
+
client.send(method, *args, &block)
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'simplecov'
|
2
|
+
SimpleCov.start do
|
3
|
+
add_group 'Squeezer', 'lib/squeezer'
|
4
|
+
add_group 'Specs', 'spec'
|
5
|
+
end
|
6
|
+
|
7
|
+
#require 'mocha'
|
8
|
+
RSpec.configure do |config|
|
9
|
+
config.mock_with :mocha
|
10
|
+
|
11
|
+
config.before(:each) do
|
12
|
+
# TODO add authentication stuff to the mock
|
13
|
+
@connection = mock("connection")
|
14
|
+
sock = mock("sock")
|
15
|
+
sock.stubs(:close).returns(true)
|
16
|
+
@connection.stubs(:sock).returns(sock)
|
17
|
+
@connection.stubs(:cmd).with("exit")
|
18
|
+
|
19
|
+
# Net::Telnet.stubs(:new).returns(@connection)
|
20
|
+
|
21
|
+
# maybe find a better way to mock the connection? Because the mock
|
22
|
+
# stays in memory of the API's eigenclass if we mocked the telnet
|
23
|
+
# connection directly. So we mock the connection getter instead.
|
24
|
+
# But since we didn't find a way to mock a module method, we mock the
|
25
|
+
# getter per class.
|
26
|
+
Squeezer::Models::Player.any_instance.stubs(:connection).returns(@connection)
|
27
|
+
Squeezer::Client.any_instance.stubs(:connection).returns(@connection)
|
28
|
+
end
|
29
|
+
|
30
|
+
config.after(:each) do
|
31
|
+
# Squeezer.exit
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
require File.expand_path('../../lib/squeezer', __FILE__)
|
36
|
+
|
37
|
+
require 'rspec'
|
38
|
+
|
39
|
+
def stub_connection
|
40
|
+
@connection.stubs(:cmd)
|
41
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require File.expand_path('../../../spec_helper', __FILE__)
|
2
|
+
|
3
|
+
describe Squeezer::Client do
|
4
|
+
before do
|
5
|
+
@client = Squeezer::Client.new
|
6
|
+
end
|
7
|
+
|
8
|
+
describe ".total_*" do
|
9
|
+
it "should return the entity's total artists" do
|
10
|
+
stub_connection.with("info total artists ?").returns("info total artists 2\n")
|
11
|
+
@client.total_artists.should == 2
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should return the entity's total albums" do
|
15
|
+
stub_connection.with("info total albums ?").returns("info total albums 4\n")
|
16
|
+
@client.total_albums.should == 4
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should return the entity's total genres" do
|
20
|
+
stub_connection.with("info total genres ?").returns("info total genres 6\n")
|
21
|
+
@client.total_genres.should == 6
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should return the entity's total songs" do
|
25
|
+
stub_connection.with("info total songs ?").returns("info total songs 8\n")
|
26
|
+
@client.total_songs.should == 8
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe ".artists" do
|
31
|
+
before do
|
32
|
+
stub_connection.with("info total artists ?").returns("info total artists 1\n")
|
33
|
+
stub_connection.with("artists 0 2 charset:utf8 tags:s").returns("artists 0 2 charset%3Autf8 tags%3As id%3A4631 artist%3AVarious%20Artists textkey%3A%20 id%3A3998 artist%3A1200%20Micrograms textkey%3A1 count%3A434\n")
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should return a list with Artist models" do
|
37
|
+
artists = @client.artists
|
38
|
+
artists.first.should be_a Squeezer::Models::Artist
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should return a list with all the artists" do
|
42
|
+
artists = @client.artists
|
43
|
+
artists.should have(2).things
|
44
|
+
artists.first.name.should == "Various Artists"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require File.expand_path('../../../spec_helper', __FILE__)
|
2
|
+
|
3
|
+
describe Squeezer::Client do
|
4
|
+
before do
|
5
|
+
@client = Squeezer::Client.new
|
6
|
+
end
|
7
|
+
|
8
|
+
describe ".version" do
|
9
|
+
it "should return the server's API version" do
|
10
|
+
stub_connection.with("version ?").returns("version x.x.x\n")
|
11
|
+
@client.version.should == "x.x.x"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
describe ".can" do
|
16
|
+
it "should test if the server can respond to the given command" do
|
17
|
+
stub_connection.with("can version ?").returns("can version 1\n")
|
18
|
+
@client.can("version").should be true
|
19
|
+
stub_connection.with("can foobar ?").returns("can foobar 0\n")
|
20
|
+
@client.can("foobar").should be false
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe ".shutdown_server" do
|
25
|
+
it "should shutdown the server" do
|
26
|
+
stub_connection.with("shutdown")
|
27
|
+
@client.shutdown_server(:yes).should be true
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should not shutdown the server" do
|
31
|
+
stub_connection.with("shutdown")
|
32
|
+
@client.shutdown_server.should be false
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require File.expand_path('../../../spec_helper', __FILE__)
|
2
|
+
|
3
|
+
describe Squeezer::Client do
|
4
|
+
before do
|
5
|
+
stub_connection.with("player count ?").returns("1\n")
|
6
|
+
stub_connection.with("player id 0 ?").returns("player id 0 player_id\n")
|
7
|
+
stub_connection.with("player name player_id ?").returns("player name player_id Squeezebox\n")
|
8
|
+
stub_connection.with("player ip player_id ?").returns("player ip player_id 127.0.0.1%3A12345\n")
|
9
|
+
@client = Squeezer::Client.new
|
10
|
+
end
|
11
|
+
|
12
|
+
describe ".players" do
|
13
|
+
|
14
|
+
it "should return all connected players" do
|
15
|
+
@client.players.should have(1).things
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should return a player's id" do
|
19
|
+
@client.players["player_id"].id.should == "player_id"
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should return a player's name" do
|
23
|
+
@client.players["player_id"].name.should == "Squeezebox"
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
describe ".find" do
|
29
|
+
|
30
|
+
it "should find a player by it's name" do
|
31
|
+
@client.find_player_by_name("Squeezebox").name.should == "Squeezebox"
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should find a player by it's id" do
|
35
|
+
@client.find_player_by_id("player_id").name.should == "Squeezebox"
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should find a player by it's ip" do
|
39
|
+
@client.find_player_by_ip("127.0.0.1").name.should == "Squeezebox"
|
40
|
+
end
|
41
|
+
|
42
|
+
# it "should find a player by a combination of search arguments" do
|
43
|
+
# @client.find(:id => "player_id", :ip => "127.0.0.1", :name => "Squeezebox").nil?.should be false
|
44
|
+
# end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require File.expand_path('../spec_helper', __FILE__)
|
2
|
+
|
3
|
+
describe Squeezer do
|
4
|
+
after do
|
5
|
+
Squeezer.reset
|
6
|
+
end
|
7
|
+
|
8
|
+
context "when delegating to a client" do
|
9
|
+
|
10
|
+
it "should return the same results as a client" do
|
11
|
+
stub_connection.with("version ?").returns("version x.x.x\n")
|
12
|
+
Squeezer.version.should == Squeezer::Client.new.version
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
|
17
|
+
describe ".client" do
|
18
|
+
it "should be a Squeezer::Client" do
|
19
|
+
Squeezer.client.should be_a Squeezer::Client
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe ".port" do
|
24
|
+
it "should return the default port" do
|
25
|
+
Squeezer.port.should == Squeezer::Configuration::DEFAULT_PORT
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
describe ".port=" do
|
30
|
+
it "should set the port" do
|
31
|
+
Squeezer.port = 1234
|
32
|
+
Squeezer.port.should == 1234
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe ".server=" do
|
37
|
+
it "should set the server" do
|
38
|
+
Squeezer.server = 'localhost'
|
39
|
+
Squeezer.server.should == 'localhost'
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe ".configure" do
|
44
|
+
|
45
|
+
Squeezer::Configuration::VALID_OPTIONS_KEYS.each do |key|
|
46
|
+
|
47
|
+
it "should set the #{key}" do
|
48
|
+
Squeezer.configure do |config|
|
49
|
+
config.send("#{key}=", key)
|
50
|
+
Squeezer.send(key).should == key
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
data/squeezer.gemspec
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/squeezer/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |s|
|
5
|
+
s.add_development_dependency('bundler', '~> 1.0')
|
6
|
+
s.add_development_dependency('rake', '~> 0.8')
|
7
|
+
s.add_development_dependency('rspec', '~> 2.3')
|
8
|
+
s.add_development_dependency('mocha', '~> 0.9.10')
|
9
|
+
s.add_development_dependency('simplecov', '~> 0.3')
|
10
|
+
s.add_development_dependency('maruku', '~> 0.6')
|
11
|
+
s.add_development_dependency('yard', '~> 0.6')
|
12
|
+
|
13
|
+
s.authors = ["Daniël van Hoesel"]
|
14
|
+
s.description = %q{A Ruby wrapper for the Squeezebox Server CLI API}
|
15
|
+
# s.post_install_message
|
16
|
+
s.email = ['daniel@danielvanhoesel.nl']
|
17
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
18
|
+
s.files = `git ls-files`.split("\n")
|
19
|
+
s.homepage = 'http://squeezer.rubyforge.org/'
|
20
|
+
s.name = 'squeezer-ruby'
|
21
|
+
s.platform = Gem::Platform::RUBY
|
22
|
+
s.require_paths = ['lib']
|
23
|
+
s.required_rubygems_version = Gem::Requirement.new('>= 1.3.6') if s.respond_to? :required_rubygems_version=
|
24
|
+
s.rubyforge_project = s.name
|
25
|
+
s.summary = %q{Ruby wrapper for the Squeezebox Server CLI API}
|
26
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
27
|
+
s.version = Squeezer::VERSION.dup
|
28
|
+
end
|
metadata
ADDED
@@ -0,0 +1,190 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: squeezer-ruby
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 1
|
8
|
+
- 1
|
9
|
+
version: 0.1.1
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- "Dani\xC3\xABl van Hoesel"
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2011-01-08 00:00:00 +01:00
|
18
|
+
default_executable:
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: bundler
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ~>
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
segments:
|
29
|
+
- 1
|
30
|
+
- 0
|
31
|
+
version: "1.0"
|
32
|
+
type: :development
|
33
|
+
version_requirements: *id001
|
34
|
+
- !ruby/object:Gem::Dependency
|
35
|
+
name: rake
|
36
|
+
prerelease: false
|
37
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
38
|
+
none: false
|
39
|
+
requirements:
|
40
|
+
- - ~>
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
segments:
|
43
|
+
- 0
|
44
|
+
- 8
|
45
|
+
version: "0.8"
|
46
|
+
type: :development
|
47
|
+
version_requirements: *id002
|
48
|
+
- !ruby/object:Gem::Dependency
|
49
|
+
name: rspec
|
50
|
+
prerelease: false
|
51
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
52
|
+
none: false
|
53
|
+
requirements:
|
54
|
+
- - ~>
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
segments:
|
57
|
+
- 2
|
58
|
+
- 3
|
59
|
+
version: "2.3"
|
60
|
+
type: :development
|
61
|
+
version_requirements: *id003
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: mocha
|
64
|
+
prerelease: false
|
65
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
66
|
+
none: false
|
67
|
+
requirements:
|
68
|
+
- - ~>
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
segments:
|
71
|
+
- 0
|
72
|
+
- 9
|
73
|
+
- 10
|
74
|
+
version: 0.9.10
|
75
|
+
type: :development
|
76
|
+
version_requirements: *id004
|
77
|
+
- !ruby/object:Gem::Dependency
|
78
|
+
name: simplecov
|
79
|
+
prerelease: false
|
80
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ~>
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
segments:
|
86
|
+
- 0
|
87
|
+
- 3
|
88
|
+
version: "0.3"
|
89
|
+
type: :development
|
90
|
+
version_requirements: *id005
|
91
|
+
- !ruby/object:Gem::Dependency
|
92
|
+
name: maruku
|
93
|
+
prerelease: false
|
94
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
95
|
+
none: false
|
96
|
+
requirements:
|
97
|
+
- - ~>
|
98
|
+
- !ruby/object:Gem::Version
|
99
|
+
segments:
|
100
|
+
- 0
|
101
|
+
- 6
|
102
|
+
version: "0.6"
|
103
|
+
type: :development
|
104
|
+
version_requirements: *id006
|
105
|
+
- !ruby/object:Gem::Dependency
|
106
|
+
name: yard
|
107
|
+
prerelease: false
|
108
|
+
requirement: &id007 !ruby/object:Gem::Requirement
|
109
|
+
none: false
|
110
|
+
requirements:
|
111
|
+
- - ~>
|
112
|
+
- !ruby/object:Gem::Version
|
113
|
+
segments:
|
114
|
+
- 0
|
115
|
+
- 6
|
116
|
+
version: "0.6"
|
117
|
+
type: :development
|
118
|
+
version_requirements: *id007
|
119
|
+
description: A Ruby wrapper for the Squeezebox Server CLI API
|
120
|
+
email:
|
121
|
+
- daniel@danielvanhoesel.nl
|
122
|
+
executables: []
|
123
|
+
|
124
|
+
extensions: []
|
125
|
+
|
126
|
+
extra_rdoc_files: []
|
127
|
+
|
128
|
+
files:
|
129
|
+
- .gitignore
|
130
|
+
- Gemfile
|
131
|
+
- LICENSE.mkd
|
132
|
+
- README.mkd
|
133
|
+
- Rakefile
|
134
|
+
- lib/squeezer.rb
|
135
|
+
- lib/squeezer/api.rb
|
136
|
+
- lib/squeezer/client.rb
|
137
|
+
- lib/squeezer/client/database.rb
|
138
|
+
- lib/squeezer/client/general.rb
|
139
|
+
- lib/squeezer/client/players.rb
|
140
|
+
- lib/squeezer/client/playlist.rb
|
141
|
+
- lib/squeezer/client/utils.rb
|
142
|
+
- lib/squeezer/configuration.rb
|
143
|
+
- lib/squeezer/connection.rb
|
144
|
+
- lib/squeezer/core_extentions.rb
|
145
|
+
- lib/squeezer/models.rb
|
146
|
+
- lib/squeezer/models/artist.rb
|
147
|
+
- lib/squeezer/models/player.rb
|
148
|
+
- lib/squeezer/version.rb
|
149
|
+
- spec/spec_helper.rb
|
150
|
+
- spec/squeezer/client/database_spec.rb
|
151
|
+
- spec/squeezer/client/general_spec.rb
|
152
|
+
- spec/squeezer/client/players_spec.rb
|
153
|
+
- spec/squeezer_spec.rb
|
154
|
+
- squeezer.gemspec
|
155
|
+
has_rdoc: true
|
156
|
+
homepage: http://squeezer.rubyforge.org/
|
157
|
+
licenses: []
|
158
|
+
|
159
|
+
post_install_message:
|
160
|
+
rdoc_options: []
|
161
|
+
|
162
|
+
require_paths:
|
163
|
+
- lib
|
164
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
165
|
+
none: false
|
166
|
+
requirements:
|
167
|
+
- - ">="
|
168
|
+
- !ruby/object:Gem::Version
|
169
|
+
segments:
|
170
|
+
- 0
|
171
|
+
version: "0"
|
172
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
173
|
+
none: false
|
174
|
+
requirements:
|
175
|
+
- - ">="
|
176
|
+
- !ruby/object:Gem::Version
|
177
|
+
segments:
|
178
|
+
- 1
|
179
|
+
- 3
|
180
|
+
- 6
|
181
|
+
version: 1.3.6
|
182
|
+
requirements: []
|
183
|
+
|
184
|
+
rubyforge_project: squeezer-ruby
|
185
|
+
rubygems_version: 1.3.7
|
186
|
+
signing_key:
|
187
|
+
specification_version: 3
|
188
|
+
summary: Ruby wrapper for the Squeezebox Server CLI API
|
189
|
+
test_files: []
|
190
|
+
|