scrobbler-ng 2.0.0 → 2.0.1

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.
@@ -5,64 +5,109 @@ $KCODE = 'u'
5
5
  include LibXML
6
6
 
7
7
  module Scrobbler
8
-
8
+
9
9
  API_URL = 'http://ws.audioscrobbler.com/'
10
10
 
11
- class Base
11
+ class Base
12
+ # Set the default API key.
13
+ #
14
+ # This key will be used by all Scrobbler classes and objects.
15
+ #
16
+ # @param [String] api_key The default API key.
17
+ # @return [nil]
12
18
  def Base.api_key=(api_key)
13
- @@api_key = api_key
19
+ @@api_key = api_key
14
20
  end
15
-
21
+
22
+ # Set the default API secret.
23
+ #
24
+ # This secret will be used by all Scrobbler classes and objects.
25
+ #
26
+ # @param [String] secret The default API secret.
27
+ # @return [nil]
16
28
  def Base.secret=(secret)
17
- @@secret = secret
29
+ @@secret = secret
18
30
  end
19
-
31
+
32
+ # Get a HTTP/REST connection to the webservice.
33
+ #
34
+ # @return [REST::Connection]
20
35
  def Base.connection
21
- @connection ||= REST::Connection.new(API_URL)
36
+ @connection ||= REST::Connection.new(API_URL)
22
37
  end
23
38
 
39
+ # Clean up a URL parameter.
40
+ #
41
+ # @param [String, Symbol] param The parameter which needs cleanup.
42
+ # @return [String]
24
43
  def Base.sanitize(param)
25
44
  URI.escape(param.to_s, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))
26
45
  end
27
-
28
- def Base.camelize(lower_case_and_underscored_word, first_letter_in_uppercase = true)
29
- if first_letter_in_uppercase
30
- lower_case_and_underscored_word.to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase }
31
- else
32
- lower_case_and_underscored_word.first.downcase + camelize(lower_case_and_underscored_word)[1..-1]
33
- end
34
- end
35
-
36
- def Base.constanize(camel_cased_word)
37
- names = camel_cased_word.split('::')
46
+
47
+ # Camelize and Constanize a string.
48
+ #
49
+ # @param [String,Symbol] word The word which should be camelized and
50
+ # constanized.
51
+ # @return [Constant]
52
+ def Base.constanize(word)
53
+ names = word.to_s.gsub(/\/(.?)/) do
54
+ "::#{$1.upcase}"
55
+ end.gsub(/(?:^|_)(.)/) { $1.upcase }.split('::')
38
56
  names.shift if names.empty? || names.first.empty?
39
-
57
+
40
58
  constant = Object
41
59
  names.each do |name|
42
60
  constant = constant.const_defined?(name) ? constant.const_get(name) : constant.const_missing(name)
43
61
  end
44
62
  constant
45
63
  end
46
-
64
+
65
+ # Initiate a API and parse it.
66
+ #
67
+ # @param [String,Symbol] api_method The API method to call.
68
+ # @param [String,Symbol] parent The parent node to inspect.
69
+ # @param [Class,String,Symbol] element The name of the node to turn into objects.
70
+ # @param [Hash<String,Symbol>] parameters The parameters for the method call.
71
+ # @return [Array<Scrobbler::Base>]
47
72
  def Base.get(api_method, parent, element, parameters = {})
48
- scrobbler_class = constanize(camelize("scrobbler/#{element.to_s}"))
49
- doc = request(api_method, parameters)
50
- elements = []
51
- doc.root.children.each do |child|
52
- next unless child.name == parent.to_s
53
- child.children.each do |child2|
54
- next unless child2.name == element.to_s
55
- elements << scrobbler_class.new_from_libxml(child2)
56
- end
57
- end
58
- elements
73
+ if (element.is_a?(Class))
74
+ scrobbler_class = element
75
+ element = element.to_s.sub("Scrobbler::","").downcase
76
+ else
77
+ element = element.to_s
78
+ scrobbler_class = constanize("scrobbler/#{element}")
79
+ end
80
+ doc = request(api_method, parameters)
81
+ elements = []
82
+ doc.root.children.each do |child|
83
+ next unless child.name == parent.to_s
84
+ child.children.each do |child2|
85
+ next unless child2.name == element
86
+ elements << scrobbler_class.new_from_libxml(child2)
87
+ end
88
+ end
89
+ elements
59
90
  end
60
91
 
61
- def Base.post_request(api_method, parameters = {}, request_method = 'get')
92
+ # Execute a request to the Audioscrobbler webservice
93
+ #
94
+ # @param [String,Symbol] api_method The method which shall be called.
95
+ # @param [Hash] parameter The parameters passed as URL params.
96
+ # @return [LibXML::XML::Document]
97
+ def Base.post_request(api_method, parameters = {})
62
98
  Base.request(api_method, parameters, 'post')
63
99
  end
64
-
100
+
101
+ # Execute a request to the Audioscrobbler webservice
102
+ #
103
+ # @param [String,Symbol] api_method The method which shall be called.
104
+ # @param [Hash] parameter The parameters passed as URL params.
105
+ # @param [String] request_method The HTTP verb to be used.
106
+ # @return [LibXML::XML::Document]
65
107
  def Base.request(api_method, parameters = {}, request_method = 'get')
108
+ raise ArgumentError unless [String, Symbol].member?(api_method.class)
109
+ raise ArgumentError unless parameters.kind_of?(Hash)
110
+
66
111
  parameters = {:signed => false}.merge(parameters)
67
112
  parameters['api_key'] = @@api_key
68
113
  parameters['method'] = api_method.to_s
@@ -70,7 +115,7 @@ class Base
70
115
  # Check if we want a signed call and pop :signed
71
116
  if parameters.delete :signed
72
117
  #1: Sort alphabetically
73
- params = parameters.sort{|a,b| a[0].to_s<=>b[0].to_s}
118
+ params = parameters.sort{|a,b| a.at(0).to_s<=>b.at(0).to_s}
74
119
  #2: concat them into one string
75
120
  str = params.join('')
76
121
  #3: Append secret
@@ -79,7 +124,7 @@ class Base
79
124
  md5 = Digest::MD5.hexdigest(str)
80
125
  params << [:api_sig, md5]
81
126
  params.each do |a|
82
- paramlist << "#{sanitize(a[0])}=#{sanitize(a[1])}"
127
+ paramlist << "#{sanitize(a.at(0))}=#{sanitize(a.at(1))}"
83
128
  end
84
129
  else
85
130
  parameters.each do |key, value|
@@ -90,35 +135,40 @@ class Base
90
135
  XML::Document.string(self.connection.send(request_method,url))
91
136
  end
92
137
 
93
-
94
- private
95
-
96
- def Base.mixins(*args)
97
- args.each do |arg|
98
- if arg == :image
99
- extend Scrobbler::ImageClassFuncs
100
- include Scrobbler::ImageObjectFuncs
101
- elsif arg == :streamable
102
- attr_reader :streamable
103
- extend StreamableClassFuncs
104
- include StreamableObjectFuncs
105
- else
106
- raise ArgumentError, "#{arg} is not a known mixin"
107
- end
108
- end
138
+ # @deprecated
139
+ def get_response(api_method, instance_name, parent, element, params, force=true)
140
+ Base.get(api_method, parent, element, params)
109
141
  end
110
-
111
- def populate_data(data = {})
112
- data.each do |key, value|
113
- instance_variable_set("@#{key.to_s}", value)
114
- end
115
- end
116
-
117
- def get_response(api_method, instance_name, parent, element, params, force=false)
118
- if instance_variable_get("@#{instance_name}").nil? || force
119
- instance_variable_set("@#{instance_name}", Base.get(api_method, parent, element, params))
120
- end
121
- instance_variable_get("@#{instance_name}")
142
+
143
+ # Load information into instance variables.
144
+ #
145
+ # @param [Hash<String,Symbol>] data Each entry will be stored as a variable.
146
+ # @return [nil]
147
+ def populate_data(data = {})
148
+ data.each do |key, value|
149
+ instance_variable_set("@#{key.to_s}", value)
122
150
  end
123
- end # class Base
151
+ end
152
+
153
+ # Execute a request to the Audioscrobbler webservice
154
+ #
155
+ # @param [String,Symbol] api_method The method which shall be called.
156
+ # @param [Hash] parameter The parameters passed as URL params.
157
+ # @return [LibXML::XML::Document]
158
+ def request(api_method, parameters = {}, request_method = 'get')
159
+ Base.request(api_method, parameters, request_method)
160
+ end
161
+
162
+ # Call a API method
163
+ #
164
+ # @param [String,Symbol] api_method The method which shall be called.
165
+ # @param [Hash] params The parameters passed as URL params.
166
+ # @param [String,Symbol] parent the parent XML node to look for.
167
+ # @param [Class,String,Symbol] element The xml node name which shall
168
+ # be converted into an object.
169
+ # @return [Array]
170
+ def call(api_method, parent, element, params)
171
+ Base.get(api_method, parent, element, params)
172
+ end
173
+ end # class Base
124
174
  end # module Scrobbler
@@ -0,0 +1,17 @@
1
+ # encoding: utf-8
2
+
3
+ require File.expand_path('base.rb', File.dirname(__FILE__))
4
+
5
+ module Scrobbler
6
+ class BaseXml < Base
7
+ # Load data out of a XML node
8
+ def initialize(data = {})
9
+ raise ArgumentError unless data.kind_of?(Hash)
10
+ super()
11
+ unless data[:xml].nil?
12
+ load_from_xml(data[:xml])
13
+ data.delete(:xml)
14
+ end
15
+ end
16
+ end
17
+ end
@@ -1,81 +1,125 @@
1
+ # encoding: utf-8
2
+
3
+ require File.expand_path('basexml.rb', File.dirname(__FILE__))
4
+
1
5
  module Scrobbler
2
- class Event < Base
6
+ class Event < BaseXml
3
7
  # Load Helper modules
4
8
  include ImageObjectFuncs
5
- extend ImageClassFuncs
6
9
 
7
- attr_accessor :id, :title, :start_date, :start_time, :description
8
- attr_accessor :reviews, :tag, :url, :artists, :headliner, :image_small
9
- attr_accessor :image_medium, :image_large, :attendance, :venue
10
+ attr_reader :id, :title, :start_date, :start_time, :description
11
+ attr_reader :reviews, :tag, :url, :artists, :headliner
12
+ attr_reader :attendance, :venue, :website, :end_date
10
13
 
11
- class << self
12
-
13
- def update_or_create_from_xml(xml, event=nil)
14
- data = {}
15
- artists = []
16
- headliner = nil
17
- venue = nil
14
+ # Alias for Event.new(:xml => xml)
15
+ #
16
+ # @deprecated
17
+ def self.new_from_libxml(xml)
18
+ Event.new(:xml => xml)
19
+ end
18
20
 
19
- xml.children.each do |child|
20
- data[:id] = child.content.to_i if child.name == 'id'
21
- data[:title] = child.content if child.name == 'title'
21
+ # Create a new Scrobbler::Event instance
22
+ #
23
+ # @param [Hash] data The options to initialize the class
24
+ def initialize(data = {})
25
+ raise ArgumentError unless data.kind_of?(Hash)
26
+ super(data)
27
+ data = {:include_info => false}.merge(data)
28
+ # Load data given as method-parameter
29
+ load_info() if data.delete(:include_info)
30
+ populate_data(data)
22
31
 
23
- if child.name == 'artists'
24
- child.children.each do |artist_element|
25
- artists << Artist.new(artist_element.content) if artist_element.name == 'artist'
26
- headliner = Artist.new(artist_element.content) if artist_element.name == 'headliner'
32
+ raise ArgumentError, "ID is required" if @id.nil?
33
+ end
34
+
35
+ # Load the data for this object out of a XML-Node
36
+ #
37
+ # @param [LibXML::XML::Node] node The XML node containing the information
38
+ # @return [nil]
39
+ def load_from_xml(node)
40
+ # Get all information from the root's children nodes
41
+ node.children.each do |child|
42
+ case child.name.to_s
43
+ when 'id'
44
+ @id = child.content.to_i
45
+ when 'title'
46
+ @title = child.content.to_s
47
+ when 'artists'
48
+ @artists = [] if @artists.nil?
49
+ child.children.each do |artist|
50
+ @artists << Artist.new(:name => artist.content) if artist.name == 'artist'
51
+ @headliner = Artist.new(:name => artist.content) if artist.name == 'headliner'
27
52
  end
28
- artists << headliner unless headliner.nil? || headliner_alrady_listed_in_artist_list?(artists,headliner)
29
- end
30
-
31
- maybe_image_node(data, child)
32
- data[:url] = child.content if child.name == 'url'
33
- data[:description] = child.content if child.name == 'description'
34
- data[:attendance] = child.content.to_i if child.name == 'attendance'
35
- data[:reviews] = child.content.to_i if child.name == 'reviews'
36
- data[:tag] = child.content if child.name == 'tag'
37
- data[:start_date] = Time.parse(child.content) if child.name == 'startDate'
38
- data[:start_time] = child.content if child.name == 'startTime'
39
- venue = Venue.new_from_xml(child) if child.name == 'venue'
40
- end
41
-
42
- if event.nil?
43
- event = Event.new(data[:id],data)
44
- else
45
- event.send :populate_data, data
46
- end
47
-
48
- event.artists = artists.uniq
49
- event.headliner = headliner
50
- event.venue = venue
51
- event
52
- end
53
-
54
- def headliner_alrady_listed_in_artist_list?(artists,headliner)
55
- artists.each do |artist|
56
- return true if artist.name == headliner.name
57
- end
58
- false
59
- end
60
-
61
- def new_from_libxml(xml)
62
- update_or_create_from_xml(xml)
53
+ @artists << @headliner unless @headliner.nil? || headliner_alrady_listed_in_artist_list?
54
+ when 'image'
55
+ check_image_node(child)
56
+ when 'url'
57
+ @url = child.content
58
+ when 'description'
59
+ @description = child.content
60
+ when 'attendance'
61
+ @attendance = child.content.to_i
62
+ when 'reviews'
63
+ @reviews = child.content.to_i
64
+ when 'tag'
65
+ @tag = child.content
66
+ when 'startDate'
67
+ @start_date = Time.parse(child.content)
68
+ when 'startTime'
69
+ @start_time = child.content
70
+ when 'endDate'
71
+ @end_date = Time.parse(child.content)
72
+ when 'tickets'
73
+ # @todo Handle tickets
74
+ when 'venue'
75
+ @venue = Venue.new_from_xml(child)
76
+ when 'website'
77
+ @website = child.content.to_s
78
+ when 'text'
79
+ # ignore, these are only blanks
80
+ when '#text'
81
+ # libxml-jruby version of blanks
82
+ else
83
+ raise NotImplementedError, "Field '#{child.name}' not known (#{child.content})"
84
+ end #^ case
85
+ end #^ do |child|
86
+ @artists.uniq!
87
+ end #^ load_from_xml
88
+
89
+ def headliner_alrady_listed_in_artist_list?
90
+ @artists.each do |artist|
91
+ return true if artist.name == @headliner.name
63
92
  end
93
+ false
64
94
  end
65
95
 
66
- def initialize(id,input={})
67
- raise ArgumentError if id.nil?
68
- @id = id
69
- populate_data(input)
70
- load_info() if input[:include_info]
96
+ # Indicates if the info was already loaded
97
+ @info_loaded = false
98
+
99
+ # Load additional information about this album
100
+ #
101
+ # Calls "album.getinfo" REST method
102
+ #
103
+ # @todo Parse wiki content
104
+ # @todo Add language code for wiki translation
105
+ def load_info
106
+ return nil if @info_loaded
107
+ xml = Base.request('event.getinfo', {:event => @id})
108
+ unless xml.root['status'] == 'failed'
109
+ xml.root.children.each do |childL1|
110
+ next unless childL1.name == 'event'
111
+ load_from_xml(childL1)
112
+ end # xml.children.each do |childL1|
113
+ @info_loaded = true
114
+ end
71
115
  end
72
116
 
73
- def shouts(force = false)
74
- get_response('event.getshouts', :shouts, 'shouts', 'shout', {'event'=>@id}, force)
117
+ def shouts
118
+ call('event.getshouts', :shouts, Shout, {:event => @id})
75
119
  end
76
120
 
77
- def attendees(force = false)
78
- get_response('event.getattendees', :attendees, 'attendees', 'user', {'event'=>@id}, force)
121
+ def attendees
122
+ call('event.getattendees', :attendees, User, {:event => @id})
79
123
  end
80
124
 
81
125
  def shout
@@ -84,22 +128,13 @@ module Scrobbler
84
128
  end
85
129
 
86
130
  def attend(session, attendance_status)
87
- doc = Base.post_request('event.attend',{:event => @id, :signed => true, :status => attendance_status, :sk => session.key})
131
+ Base.post_request('event.attend',{:event => @id, :signed => true, :status => attendance_status, :sk => session.key})
88
132
  end
89
133
 
90
134
  def share
91
- # This function require authentication, but SimpleAuth is not yet 2.0
135
+ # This function requires authentication, but SimpleAuth is not yet 2.0
92
136
  raise NotImplementedError
93
137
  end
94
138
 
95
- # Load additional informatalbumion about this event
96
- #
97
- # Calls "event.getinfo" REST method
98
- def load_info
99
- doc = Base.request('event.getinfo', {'event' => @id})
100
- doc.root.children.each do |child|
101
- Event.update_or_create_from_xml(child, self) if child.name == 'event'
102
- end
103
- end
104
139
  end
105
140
  end
data/lib/scrobbler/geo.rb CHANGED
@@ -4,24 +4,16 @@ module Scrobbler
4
4
  # Gets a list of events based on the location that
5
5
  # the Geo object is set to
6
6
  def events(options={})
7
- options = set_default_options(options)
8
- get_response('geo.getevents', :events, 'events', 'event', options, options[:force])
7
+ call('geo.getevents', :events, Event, options)
9
8
  end
10
9
 
11
10
  def top_artists(options={})
12
- options = set_default_options(options)
13
- get_response('geo.gettopartists', :artists, 'topartists', 'artist', options, options[:force])
11
+ call('geo.gettopartists', :topartists, Artist, options)
14
12
  end
15
13
 
16
14
  def top_tracks(options={})
17
- options = set_default_options(options)
18
- get_response('geo.gettoptracks', :tracks, 'toptracks', 'track', options, options[:force])
15
+ call('geo.gettoptracks', :toptracks, Track, options)
19
16
  end
20
17
 
21
- private
22
-
23
- def set_default_options(options={})
24
- {:force => false, :page => 1}.merge options
25
- end
26
18
  end
27
19
  end
@@ -7,14 +7,28 @@ module Scrobbler
7
7
  module ImageClassFuncs
8
8
  # Check if the given libxml node is an image referencing node and in case
9
9
  # of, read it into that given hash of data
10
+ #
11
+ # @param [Hash<Symbol, String>] data The data extracted from an API response
12
+ # @param [LibXML::XML::Node] node XML node, part of the API response
10
13
  def maybe_image_node(data, node)
14
+ raise ArgumentError unless data.kind_of?(Hash)
15
+ raise ArgumentError unless node.kind_of?(LibXML::XML::Node)
11
16
  if node.name == 'image'
12
- data[:image_small] = node.content if node['size'] == 'small'
13
- data[:image_medium] = node.content if node['size'] == 'medium'
14
- data[:image_large] = node.content if node['size'] == 'large'
15
- data[:image_extralarge] = node.content if node['size'] == 'extralarge'
16
- end
17
- end
17
+ case node['size'].to_s # convert to string to fix libxml-ruby bug
18
+ when 'small'
19
+ data[:image_small] = node.content
20
+ when 'medium'
21
+ data[:image_medium] = node.content
22
+ when 'large'
23
+ data[:image_large] = node.content
24
+ when 'extralarge'
25
+ data[:image_extralarge] = node.content
26
+ else
27
+ raise NotImplementedError, "Image size '#{node['size'].to_s}' not supported."
28
+ end #^ case
29
+ end #^ if
30
+ end #^ maybe_ ...
31
+
18
32
  end
19
33
 
20
34
  # Defines some functions that are used nearly all Scrobbler classes which
@@ -27,10 +41,20 @@ module Scrobbler
27
41
  # of, read it into the object
28
42
  def check_image_node(node)
29
43
  if node.name == 'image'
30
- @image_small = node.content if node['size'] == 'small'
31
- @image_medium = node.content if node['size'] == 'medium'
32
- @image_large = node.content if node['size'] == 'large'
33
- @image_extralarge = node.content if node['size'] == 'extralarge'
44
+ case node['size'].to_s # convert to string to fix libxml-ruby bug
45
+ when 'small'
46
+ @image_small = node.content
47
+ when 'medium'
48
+ @image_medium = node.content
49
+ when 'large'
50
+ @image_large = node.content
51
+ when 'extralarge'
52
+ @image_extralarge = node.content
53
+ when 'mega'
54
+ @image_mega = node.content
55
+ else
56
+ raise NotImplementedError, "Image size '#{node['size'].to_s}' not supported."
57
+ end #^ case
34
58
  end
35
59
  end
36
60
 
@@ -40,7 +64,8 @@ module Scrobbler
40
64
  # 'load_info'-function is defined, it will be called.
41
65
  def image(which=:small)
42
66
  which = which.to_s
43
- raise ArgumentError unless ['small', 'medium', 'large', 'extralarge'].include?(which)
67
+ raise ArgumentError unless ['small', 'medium', 'large', 'extralarge',
68
+ 'mega'].include?(which)
44
69
  img_url = instance_variable_get("@image_#{which}")
45
70
  if img_url.nil? && responds_to?(:load_info)
46
71
  load_info
@@ -49,4 +74,4 @@ module Scrobbler
49
74
  img_url
50
75
  end
51
76
  end
52
- end
77
+ end
@@ -7,7 +7,7 @@ module Scrobbler
7
7
  module StreamableClassFuncs
8
8
  # Check if the given libxml node is defining if the given content is
9
9
  # streamable. If so, set the flag in the given hash
10
- def Base.maybe_streamable_node(data, node)
10
+ def maybe_streamable_node(data, node)
11
11
  if node.name == 'streamable'
12
12
  data[:streamable] = ['1', 'true'].include?(node.content)
13
13
  end
@@ -15,7 +15,7 @@ module Scrobbler
15
15
 
16
16
  # Check if the given libxml node has a streamable attribute defining if the
17
17
  # given content is streamable. If so, set the flag in the given hash
18
- def Base.maybe_streamable_attribute(data, node)
18
+ def maybe_streamable_attribute(data, node)
19
19
  if node['streamable']
20
20
  data[:streamable] = ['1', 'true'].include?(node['streamable'])
21
21
  end
@@ -36,5 +36,13 @@ module Scrobbler
36
36
  @streamable = ['1', 'true'].include?(node.content)
37
37
  end
38
38
  end
39
+
40
+ # Check if the given libxml node has a streamable attribute defining if the
41
+ # given content is streamable. If so, set the flag in the given hash
42
+ def maybe_streamable_attribute(node)
43
+ if node['streamable']
44
+ @streamable = ['1', 'true'].include?(node['streamable'])
45
+ end
46
+ end
39
47
  end
40
- end
48
+ end