squeezer-ruby 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/.autotest ADDED
@@ -0,0 +1,14 @@
1
+ require "autotest/timestamp"
2
+ require 'autotest/growl' if RUBY_PLATFORM.to_s.include?("darwin")
3
+ require 'autotest/fsevent' if RUBY_PLATFORM.to_s.include?("darwin")
4
+
5
+ # adds exceptions from .gitignore file, please modify exceptions there!
6
+ imported_exceptions = IO.readlines('.gitignore').inject([]) do |acc, line|
7
+ acc << line.strip if line.to_s[0] != '#' && line.strip != ''; acc
8
+ end
9
+
10
+ Autotest.add_hook :initialize do |autotest|
11
+ imported_exceptions.each do |exception|
12
+ autotest.add_exception(exception)
13
+ end
14
+ end
data/.gitignore CHANGED
@@ -1,3 +1,4 @@
1
+ .DS_Store
1
2
  ._*
2
3
  Gemfile.lock
3
4
  coverage/*
@@ -6,3 +7,4 @@ test.rb
6
7
  .yardoc
7
8
  .bundle
8
9
  .AppleDouble
10
+ *.gem
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --drb
data/README.mkd CHANGED
@@ -10,7 +10,7 @@ This project is in it's early stages, it is not finished by any means.
10
10
  It also lacks proper error handling and documentation.
11
11
  Most of the critical refactor points have been marked with a 'todo' comment.
12
12
 
13
- - active record like search methods and model relations (database chained search engine?)
13
+ - active record like search methods and model relations (database, chained search engine?)
14
14
  - playlist handling
15
15
  - add convenience methods
16
16
  - add some more sugar
@@ -30,10 +30,14 @@ General:
30
30
  config.port = 9090
31
31
  end
32
32
 
33
- client = Squeezer::Client.new
34
-
35
- puts client.version
33
+ # execute block and close the connection when done
34
+ Squeezer.open do |client|
35
+ puts client.version
36
+ puts Track.total
37
+ end
36
38
 
39
+ # or start a client and call exit manually
40
+ client = Squeezer::Client.new
37
41
  client.players.each do |id, player|
38
42
  puts "Name: #{player.name}"
39
43
  puts "Model: #{player.model}"
@@ -41,10 +45,9 @@ General:
41
45
  puts "Volume: #{player.volume}"
42
46
 
43
47
  player.on! if player.off?
48
+
44
49
  player.volume = "+40"
45
50
  end
46
-
47
- puts client.total_artists
48
51
 
49
52
  client.artists.each do |artist|
50
53
  puts artist.name
@@ -59,8 +62,28 @@ Models:
59
62
 
60
63
  puts Player.find_by_name("living room").ip
61
64
  puts Player.find_by_ip("192.168.1.1").name
65
+
66
+ Artist.all.each do |artist|
67
+ puts artist.name
68
+ end
69
+
70
+ puts Album.total
62
71
  end
63
72
 
73
+ Testing
74
+ -------
75
+ Run all tests:
76
+
77
+ rake tests:run
78
+
79
+ Start Spork DRb server, for faster testing:
80
+
81
+ rake tests:spork
82
+
83
+ Run a continuous testing environment with autotest (configure .autotest for your own needs):
84
+
85
+ rake tests:autotest
86
+
64
87
  Copyright
65
88
  ---------
66
89
  Copyright (c) 2011 Daniël van Hoesel.
data/Rakefile CHANGED
@@ -17,3 +17,20 @@ namespace :doc do
17
17
  ]
18
18
  end
19
19
  end
20
+
21
+ namespace :tests do
22
+ desc "Run Spork DRb server, for faster tests"
23
+ task :spork do |task|
24
+ sh "spork"
25
+ end
26
+
27
+ desc "Run a continuous testing environment"
28
+ task :autotest do |task|
29
+ sh "autotest"
30
+ end
31
+
32
+ desc "Run all tests"
33
+ task :run do |task|
34
+ Rake::Task[:spec].execute
35
+ end
36
+ end
data/lib/squeezer.rb CHANGED
@@ -3,12 +3,12 @@ require 'uri'
3
3
  require File.expand_path('../squeezer/core_extentions', __FILE__)
4
4
  require File.expand_path('../squeezer/configuration', __FILE__)
5
5
  require File.expand_path('../squeezer/api', __FILE__)
6
- require File.expand_path('../squeezer/client', __FILE__)
7
6
  require File.expand_path('../squeezer/models', __FILE__)
7
+ require File.expand_path('../squeezer/client', __FILE__)
8
8
 
9
9
  module Squeezer
10
10
  extend Configuration
11
-
11
+
12
12
  # Alias for Squeezer::Client.new
13
13
  #
14
14
  # @return [Squeezer::Client]
@@ -22,4 +22,13 @@ module Squeezer
22
22
  client.send(method, *args, &block)
23
23
  end
24
24
 
25
- end
25
+ # Convenience method to execute client calls in a block.
26
+ # The connection closes automatically at the end of the block.
27
+ def self.open(&block)
28
+ client.instance_eval(&block)
29
+ Squeezer.exit
30
+ end
31
+
32
+ end
33
+
34
+ include Squeezer::Models if $DEBUG
@@ -1,7 +1,9 @@
1
1
  module Squeezer
2
2
  class Client < API
3
3
  Dir[File.expand_path('../client/*.rb', __FILE__)].each{|f| require f}
4
-
4
+
5
+ include Squeezer::Models
6
+
5
7
  include Squeezer::Client::Database
6
8
  include Squeezer::Client::General
7
9
  include Squeezer::Client::Players
@@ -2,48 +2,69 @@ module Squeezer
2
2
  class Client
3
3
  module Database
4
4
 
5
+ # TODO do we really need this methods?
6
+ # can't we just use the model's class methods?
7
+
5
8
  # doesn't include 'Various Artists'
6
9
  def total_artists
7
- count(:artists)
10
+ Models::Artist.total
8
11
  end
9
12
 
10
13
  def total_albums
11
- count(:albums)
14
+ Models::Album.total
12
15
  end
13
16
 
14
- def total_songs
15
- count(:songs)
17
+ def total_tracks
18
+ Models::Track.total
16
19
  end
17
20
 
18
21
  def total_genres
19
- count(:genres)
22
+ Models::Genre.total
20
23
  end
21
24
 
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
+ def artists
26
+ Models::Artist.all
25
27
  end
26
28
 
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")))
29
+ def albums
30
+ Models::Album.all
31
+ end
32
+
33
+ def tracks
34
+ Models::Track.all
35
+ end
36
+
37
+ def genres
38
+ Models::Genre.all
39
+ end
40
+
41
+ def rescan!
42
+ cmd("rescan")
43
+ end
44
+
45
+ def scanning?
46
+ cmd("rescan ?").to_boolean
30
47
  end
31
48
 
32
- private
49
+ # "The "abortscan" command causes Squeezebox Server to cancel a running scan.
50
+ # Please note that after stopping a scan this way you'll have to fully rescan
51
+ # your music collection to get consistent data."
52
+ def abortscan!
53
+ cmd("abortscan")
54
+ end
33
55
 
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
56
+ # The "wipecache" command allows the caller to have the Squeezebox Server rescan
57
+ # its music library, reloading the music file information. This differs from the
58
+ # "rescan!" command in that it first clears the tag database. During a rescan
59
+ # triggered by "wipecache!", "rescan?" returns true.
60
+ def wipecache!
61
+ cmd("wipecache")
45
62
  end
46
63
 
64
+ # TODO format the return data better and make it useful
65
+ def rescan_progress
66
+ extract_hash_from_data(cmd("rescanprogress"))
67
+ end
47
68
  end
48
69
  end
49
70
  end
@@ -1,7 +1,16 @@
1
1
  module Squeezer
2
2
  class Client
3
3
  module Utils
4
+ private
4
5
 
6
+ # update regex to play nice with : chars
7
+ def extract_hash_from_data(data)
8
+ record = Hash.new
9
+ data.scan(/([^\s]+)%3A([^\s]+)/) do |match|
10
+ record[match[0].to_sym] = URI.unescape(match[1])
11
+ end
12
+ record
13
+ end
5
14
  end
6
15
  end
7
16
  end
@@ -6,8 +6,8 @@ module Squeezer
6
6
  # An array of valid keys in the options hash when configuring an {Squeezer::API}
7
7
  VALID_OPTIONS_KEYS = [:server, :port].freeze
8
8
 
9
- # By default, don't set an server
10
- DEFAULT_SERVER = nil.freeze
9
+ # By default, set localhost as the server
10
+ DEFAULT_SERVER = "127.0.0.1".freeze
11
11
 
12
12
  # The port where the CLI interface is running on
13
13
  #
@@ -14,18 +14,7 @@ module Squeezer
14
14
  private
15
15
 
16
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
17
+ Connection.exec(command, options)
29
18
  end
30
19
 
31
20
  class << self
@@ -34,13 +23,30 @@ module Squeezer
34
23
  @connection = nil
35
24
  end
36
25
 
26
+ def exec(command, options={})
27
+ # TODO raise exceptions instead of returning false
28
+ response = retrieve_connection.cmd(command)
29
+ puts response if options == :debug
30
+ return false if response.nil?
31
+ return true if response.strip.eql?(command)
32
+ result = response.gsub(command.gsub('?', '').strip, '').strip
33
+ result.force_encoding("UTF-8") unless /^1\.8/ === RUBY_VERSION
34
+ result
35
+ end
36
+
37
37
  def retrieve_connection
38
38
  @connection ||= open_connection
39
39
  end
40
40
 
41
- def open_connection
42
- # do authentication and stuff
43
- Net::Telnet.new("Host" => Squeezer.server, "Port" => Squeezer.port, "Telnetmode" => false, "Prompt" => /\n/)
41
+ def open_connection
42
+ # TODO do authentication and stuff
43
+ # TODO fix the timeout if we receive a large ammount of data
44
+ # TODO add error handling when the host is unavailable
45
+ begin
46
+ Net::Telnet.new("Host" => Squeezer.server, "Port" => Squeezer.port, "Telnetmode" => false, "Prompt" => /\n/)
47
+ rescue
48
+ raise "connection failed"
49
+ end
44
50
  end
45
51
  end
46
52
 
@@ -1,21 +1,24 @@
1
1
  module Squeezer
2
2
  module Models
3
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)
4
+ class Model < API
5
+
6
+ # update regex to play nice with : chars
7
+ def self.extract_records(data)
8
+ records = Array.new
9
+ record = Hash.new
10
+ data.scan(/([^\s]+)%3A([^\s]+)/) do |match|
11
+ if match[0] == "id"
12
+ records << record unless record.empty?
13
+ record = {:id => URI.unescape(match[1])}
13
14
  end
14
- result << entity
15
+ record[match[0].to_sym] = URI.unescape(match[1]) unless record.empty?
15
16
  end
16
- result
17
+ record.delete(:count)
18
+ records << record unless record.empty?
19
+ records
17
20
  end
18
-
21
+
19
22
  end
20
23
 
21
24
  Dir[File.expand_path('../models/*.rb', __FILE__)].each{|f| require f}
@@ -0,0 +1,34 @@
1
+ module Squeezer
2
+ module Models
3
+
4
+ class Album < Model
5
+ attr_reader :id, :name
6
+
7
+ def initialize(record)
8
+ unless record.nil?
9
+ @id = record[:id] if record.key?(:id)
10
+ @name = record[:title] if record.key?(:title)
11
+ @discs = record[:disccount] if record.key?(:disccount)
12
+ end
13
+ end
14
+
15
+ def discs
16
+ @discs.nil? ? 1 : @discs.to_i
17
+ end
18
+
19
+ def self.total
20
+ Connection.exec("info total albums ?").to_i
21
+ end
22
+
23
+ def self.all
24
+ results = Array.new
25
+ Model.extract_records(Connection.exec("albums 0 #{total} charset:utf8 tags:tqs")).each do |record|
26
+ results << Album.new(record)
27
+ end
28
+ results
29
+ end
30
+
31
+ end
32
+
33
+ end
34
+ end
@@ -1,8 +1,28 @@
1
1
  module Squeezer
2
2
  module Models
3
+
3
4
  class Artist < Model
4
- attr_accessor :id, :artist, :textkey
5
- alias :name :artist
5
+ attr_accessor :id, :name
6
+
7
+ def initialize(record)
8
+ unless record.nil?
9
+ @id = record[:id] if record.key?(:id)
10
+ @name = record[:artist] if record.key?(:artist)
11
+ end
12
+ end
13
+
14
+ def self.total
15
+ Connection.exec("info total artists ?").to_i
16
+ end
17
+
18
+ def self.all
19
+ results = Array.new
20
+ Model.extract_records(Connection.exec("artists 0 #{total + 1} charset:utf8 tags:s")).each do |record|
21
+ results << Artist.new(record)
22
+ end
23
+ results
24
+ end
6
25
  end
26
+
7
27
  end
8
28
  end
@@ -0,0 +1,28 @@
1
+ module Squeezer
2
+ module Models
3
+
4
+ class Genre < Model
5
+ attr_accessor :id, :name
6
+
7
+ def initialize(record)
8
+ unless record.nil?
9
+ @id = record[:id] if record.key?(:id)
10
+ @name = record[:genre] if record.key?(:genre)
11
+ end
12
+ end
13
+
14
+ def self.total
15
+ Connection.exec("info total genres ?").to_i
16
+ end
17
+
18
+ def self.all
19
+ results = Array.new
20
+ Model.extract_records(Connection.exec("genres 0 #{total} charset:utf8 tags:s")).each do |record|
21
+ results << Genre.new(record)
22
+ end
23
+ results
24
+ end
25
+ end
26
+
27
+ end
28
+ end