scrobbler-ng 2.0.0 → 2.0.1

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