bts-mbws 0.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.
@@ -0,0 +1,84 @@
1
+ module MBWS
2
+ # Top of the artist documentation page
3
+ class Artist < Resource
4
+
5
+ attr_accessor :name,:sort_name,:type,:begin,:end,:disambiguation
6
+
7
+ def initialize(hash,options = {})
8
+ @aliases = nil
9
+
10
+ super
11
+
12
+ attrs = ["name","type","disambiguation","sort-name"]
13
+ attrs.each do |a|
14
+ self.instance_variable_set("@#{a.gsub('-','_')}",hash[a])
15
+ end
16
+
17
+ @begin = hash["life-span"]["begin"] if hash["life-span"]
18
+ @end = hash["life-span"]["end"] if hash["life-span"]
19
+
20
+ end
21
+
22
+ def aliases
23
+ return @aliases if @aliases
24
+ artist_xml = Request.get("artist/#{self.mid}",:inc => "aliases")
25
+ parse_aliases(artist_xml["artist"][0])
26
+ @aliases
27
+ end
28
+
29
+ # Return releases where artist is the primary artist (Single Artist Albums).
30
+ #
31
+ # Cached
32
+ def releases()
33
+ @releases if @releases
34
+ @releases = Release.find(:artistid => self.mid, :inc => "counts",:limit => 100)
35
+ end
36
+
37
+ #
38
+ def releases_find(options = {})
39
+ options.merge!(:artistid => self.mid, :inc => "counts")
40
+ Release.find(options)
41
+ end
42
+
43
+ #
44
+ def va_releases(options)
45
+ _inc_releases("va-",options)
46
+ end
47
+
48
+ def sa_releases(options)
49
+ _inc_releases("sa-",options)
50
+ end
51
+
52
+ def all_va_releases
53
+ _all_inc_releases("va-")
54
+ end
55
+
56
+ def all_sa_releases
57
+ _all_inc_releases("sa-")
58
+ end
59
+
60
+ private
61
+ def _inc_releases(prefix,options)
62
+ inc = []
63
+ [:type, :status].each do |k|
64
+ inc.push(prefix + options[k].to_s) if options.include?(k)
65
+ end
66
+
67
+ releases = []
68
+ xml = Request.get("artist/#{self.mid}", :inc => inc)
69
+ xml["artist"][0]["release-list"]["release"].each do |rxml|
70
+ releases.push(Release.new(rxml,{}))
71
+ end if ["artist"][0]["release-list"]
72
+ releases
73
+ end
74
+
75
+ def _all_inc_releases(prefix)
76
+ all = []
77
+ [:Official, :Promotion, :Bootleg, :PseudoRelease].each do |status|
78
+ sleep 1
79
+ all.push(_inc_releases(prefix,:status => status))
80
+ end
81
+ all.compact
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,6 @@
1
+ module MBWS
2
+ module Relations
3
+ class ArtistRelations < RelationsBase
4
+ end
5
+ end
6
+ end
data/lib/mbws/base.rb ADDED
@@ -0,0 +1,7 @@
1
+ module MBWS
2
+ class Base
3
+ DEFAULT_HOST = "www.musicbrainz.org"
4
+ DEFAULT_PATH = "/ws/1/"
5
+ DEFAULT_LIMIT = 25
6
+ end
7
+ end
@@ -0,0 +1,25 @@
1
+ module MBWS
2
+ class MBWSException < StandardError
3
+ end
4
+
5
+ class ResponseError < MBWSException
6
+ end
7
+
8
+ class MBIDNotFound < ResponseError
9
+ end
10
+
11
+ class BadRequest < ResponseError
12
+ end
13
+
14
+ class InvalidUUID < MBWSException
15
+ def initialize(invalid_id)
16
+ message = "'#{invalid_id} is not a valid MBID (UUID). " +
17
+ "MBIDs must be 36 characters long " +
18
+ "(ex. c0b2500e-0cef-4130-869d-732b23ed9df5). " +
19
+ "Please consult the Web Service Documentation " +
20
+ "(http://musicbrainz.org/doc/XMLWebService) for"+
21
+ " more information. "
22
+ super(message)
23
+ end
24
+ end
25
+ end
data/lib/mbws/label.rb ADDED
@@ -0,0 +1,35 @@
1
+ module MBWS
2
+ class Label < Resource
3
+ attr_accessor :name,:type,:disambiguation,:begin,:end,:label_code,:country
4
+
5
+ def initialize(hash,options)
6
+ @aliases = nil
7
+
8
+ super
9
+
10
+ attrs = ["name","type","disambiguation","label-code","country"]
11
+ attrs.each do |a|
12
+ self.instance_variable_set("@#{a.gsub('-','_')}",hash[a])
13
+ end
14
+
15
+ if options.has_key?(:inc)
16
+ options[:inc].map {|i| i.gsub(/-/,"_")}.each do |s|
17
+ self.send "parse_" + s,hash unless s.match(/rels/)
18
+ end
19
+ end
20
+
21
+ @begin = hash["life-span"]["begin"] if hash["life-span"]
22
+ @end = hash["life-span"]["end"] if hash["life-span"]
23
+ end
24
+
25
+ def aliases
26
+ return @aliases if @aliases
27
+ label_xml = Request.get("label/#{self.mid}",:inc => "aliases")
28
+ parse_aliases(label_xml["label"][0])
29
+ @aliases
30
+ end
31
+
32
+ private
33
+
34
+ end
35
+ end
@@ -0,0 +1,6 @@
1
+ module MBWS
2
+ module Relations
3
+ class LabelRelations < RelationsBase
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,43 @@
1
+ #:stopdoc:
2
+ # A lot of this is based on...errr taken...from the AWS::S3 module (http://amazon.rubyforge.org/)
3
+ # by Marcel Molina <marcel@vernix.org>
4
+ module MBWS
5
+ module Parsing
6
+ class XmlParser < Hash
7
+
8
+ attr_reader :body, :xml_in, :root
9
+
10
+ def initialize(body)
11
+ @body = body
12
+ update(parse)
13
+ # set_root
14
+ # typecast_xml_in
15
+ end
16
+
17
+ private
18
+
19
+ def parse
20
+ @xml_in = XmlSimple.xml_in(@body, parsing_options)
21
+ end
22
+
23
+ def parsing_options
24
+ {
25
+ # Force nested elements to be put into an array, even if there is only on of them.
26
+ 'forcearray' => ['relation','artist','release','relation-list','disc','track','label','url'],
27
+
28
+ 'KeyAttr' => ['target-type']
29
+ }
30
+ end
31
+ def set_root
32
+ @root = @xml_in.keys.first.underscore
33
+ end
34
+ def typecast_xml_in
35
+ typecast_xml = {}
36
+ @xml_in.dup.each do |k,v|
37
+ #typecast_xml[k.underscore] = typecast(v)
38
+ end
39
+ update(typecast_xml[root]) if typecase_xml[root].is_a?(Hash)
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,43 @@
1
+ module MBWS
2
+ module Relations
3
+ class RelationsBase < Array
4
+
5
+ def initialize(xml = {})
6
+ parse(xml)
7
+ end
8
+
9
+ def relations_by_type(type)
10
+ rels = []
11
+ self.each do |r|
12
+ rels.push(r) if r[:type] == type
13
+ end
14
+ rels
15
+ end
16
+
17
+ private
18
+
19
+ def parse(xml)
20
+ klass_str = self.class.to_s.split("::")[2].gsub(/Relations$/,'')
21
+ klass = MBWS.const_get(klass_str)
22
+ obj_sym = klass_str.downcase.to_sym
23
+
24
+ xml["relation"].each do |r|
25
+ h = Hash.new
26
+
27
+ h[:type] = r["type"]
28
+ h[:direction] = r["direction"] if r["direction"]
29
+
30
+ # Url's don't have their own tag
31
+ if obj_sym == :url
32
+ h[obj_sym] = klass.new(r)
33
+ else
34
+ h[obj_sym] = klass.new(r[klass_str.downcase][0])
35
+ end
36
+
37
+ self.push(h)
38
+ end if xml["relation"]
39
+ end
40
+
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,78 @@
1
+ module MBWS
2
+ class Release < Resource
3
+
4
+ attr_accessor :title,:mid,:type,:status,:release_events,:asin
5
+
6
+ def initialize(hash,options = {})
7
+ @discs = nil
8
+ @discs_count = nil
9
+ @tracks_count = nil
10
+ @artist = nil
11
+ @tracks = nil
12
+ @release_events = nil
13
+
14
+ super
15
+
16
+ @title = hash["title"]
17
+ @asin = hash["asin"]
18
+ @mid = hash["id"]
19
+ @type = hash["type"].split(" ")[0] if hash["type"]
20
+ @status = hash["type"].split(" ")[1] if hash["type"]
21
+ end
22
+
23
+ def artist
24
+ return @artist if @artist
25
+ release_xml = Request.get("release/#{self.mid}",:inc => "artist")
26
+ parse_artist(release_xml["release"][0])
27
+ end
28
+
29
+ def discs
30
+ return @discs if @discs
31
+ release_xml = Request.get("release/#{self.mid}",:inc => "discs")
32
+ parse_discs(release_xml["release"][0])
33
+ end
34
+
35
+ def tracks
36
+ return @tracks if @tracks
37
+ release_xml = Request.get("release/#{self.mid}",:inc => "tracks")
38
+ parse_tracks(release_xml["release"][0])
39
+ @tracks
40
+ end
41
+
42
+ def tracks_count
43
+ return @tracks_count if @tracks_count
44
+ nil
45
+ #release_xml = Request.get("release/#{self.mid}",:inc => "counts")
46
+ #parse_counts(release_xml["release"][0])
47
+ #@tracks_count
48
+ end
49
+
50
+ private
51
+
52
+ def parse_counts(xml)
53
+ @tracks_count = xml["track-list"]["count"]
54
+ end
55
+
56
+ def parse_discs(xml)
57
+ @discs = []
58
+ xml["disc-list"]["disc"].each do |d|
59
+ @discs.push({:sectors => d["sectors"], :id => d["id"]})
60
+ end
61
+ @discs_count = @discs.size
62
+ end
63
+
64
+ def parse_tracks(xml)
65
+ @tracks = []
66
+ xml["track-list"]["track"].each do |t|
67
+ @tracks.push(Track.new(t))
68
+ end
69
+ end
70
+
71
+ def parse_release_events(xml)
72
+ @release_events = []
73
+ xml["release-event-list"]["event"].each do |re|
74
+ @release_events.push(re)
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,6 @@
1
+ module MBWS
2
+ module Relations
3
+ class ReleaseRelations < RelationsBase
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,45 @@
1
+ module MBWS
2
+ class Request
3
+ @@requests = []
4
+ def self.get(path,options = {})
5
+ # MB limits us to 13 requests every 10 seconds.
6
+ # We police ourselves a bit stricter to make certain
7
+ # that we don't exceed this limit.
8
+ #
9
+ # Another way to do this would be to pace ourselves
10
+ # so that there is always 10/13 of a second between
11
+ # requests but this method allows short bursts
12
+
13
+ # Delete requests if they happened more than 11 seconds ago
14
+ now = Time.now
15
+ @@requests.delete_if{ |t| (now - t) > 11}
16
+
17
+ # If there are 11 or more requests sleep until
18
+ # only 10 have occured in the past 10 seconds
19
+ if @@requests.size > 10
20
+ interval = 10 - (now - @@requests[-11])
21
+ sleep interval if interval > 0
22
+ end
23
+
24
+ opts = options.collect {|k,v|
25
+ if k == :inc and v.class == Array
26
+ k.to_s + "=" + v.join("+")
27
+ else
28
+ k.to_s + "=" + v.to_s
29
+ end
30
+ }.join("&")
31
+ path += "?type=xml&" + opts
32
+ http = Net::HTTP.new('musicbrainz.org',80)
33
+ http.start do
34
+ request = Net::HTTP::Get.new('/ws/1/' + URI.escape(path))
35
+ @@requests.push(Time.now)
36
+ res = http.request(request)
37
+ if res.code == 404
38
+ raise MBIDNotFound
39
+ else
40
+ return Parsing::XmlParser.new(res.body)
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,117 @@
1
+ module MBWS #:nodoc:
2
+ class Resource
3
+ include Relations
4
+
5
+ attr_accessor :mid, :score
6
+
7
+ def initialize(hash,options)
8
+ @mid = hash["id"]
9
+ @score = hash["ext:score"]
10
+ @artist_rels = nil
11
+ @url_rels = nil
12
+ @track_rels = nil
13
+ @release_rels = nil
14
+ if options.has_key?(:inc)
15
+ options[:inc].map {|i| i.to_s.gsub(/-/,"_")}.each do |s|
16
+ self.send "parse_" + s,hash #if s.match(/rels/)
17
+ end
18
+ end
19
+ end
20
+
21
+ def self.find(*args)
22
+ case args[0]
23
+ when String
24
+ self.find_by_mbid(args)
25
+ when Hash
26
+ self.find_by_query(args)
27
+ end
28
+ end
29
+
30
+ # Relations
31
+
32
+ def track_rels
33
+ return @track_rels if @track_rels
34
+ xml = Request.get("#{self.resource_name}/#{self.mid}",:inc => "track-rels")
35
+ parse_track_rels(xml[self.resource_name][0])
36
+ @track_rels
37
+ end
38
+
39
+ def url_rels
40
+ return @url_rels if @url_rels
41
+ xml = Request.get("#{self.resource_name}/#{self.mid}",:inc => "url-rels")
42
+ parse_url_rels(xml[self.resource_name][0])
43
+ @url_rels
44
+ end
45
+
46
+ def artist_rels
47
+ return @artist_rels if @artist_rels
48
+ xml = Request.get("#{self.resource_name}/#{self.mid}",:inc => "artist-rels")
49
+ parse_artist_rels(xml[self.resource_name][0])
50
+ @artist_rels
51
+ end
52
+
53
+ def release_rels
54
+ return @release_rels if @release_rels
55
+ xml = Request.get("#{self.resource_name}/#{self.mid}",:inc => "release-rels")
56
+ parse_release_rels(xml[self.resource_name][0])
57
+ @release_rels
58
+ end
59
+
60
+ def resource_name
61
+ self.class.to_s.downcase.split("::")[1]
62
+ end
63
+
64
+ def self.resource_name
65
+ self.to_s.downcase.split("::")[1]
66
+ end
67
+
68
+ private
69
+
70
+ def self.find_by_mbid(args)
71
+ mid = args[0]
72
+ options = args[1] || {}
73
+
74
+ raise InvalidUUID.new(mid) unless mid =~ /^\w{8}-\w{4}-\w{4}-\w{4}-\w{12}$/
75
+
76
+ xml = Request.get("#{self.resource_name}/#{mid}",options)
77
+ return self.new(xml[self.resource_name][0],options)
78
+ end
79
+
80
+ def self.find_by_query(args)
81
+ options = args[0]
82
+
83
+ objs = []
84
+
85
+ xml = Request.get("#{self.resource_name}/",options)
86
+ xml[self.resource_name + "-list"][self.resource_name].each do |o|
87
+ objs.push(self.new(o,options))
88
+ end
89
+ objs.sort_by { |o| o.score }
90
+ objs
91
+ end
92
+
93
+ def parse_aliases(xml)
94
+ @aliases = []
95
+ xml["alias-list"]["alias"].each do |a|
96
+ @aliases.push(a)
97
+ end if xml["alias-list"]
98
+ end
99
+
100
+ def parse_artist(xml)
101
+ @artist = Artist.new(xml["artist"][0])
102
+ end
103
+
104
+ [:track, :artist, :release, :label, :url].each do |symbol|
105
+ class_eval(<<-EVAL,__FILE__,__LINE__)
106
+ def parse_#{symbol}_rels(xml)
107
+ if xml["relation-list"] && xml["relation-list"]["#{symbol.to_s.capitalize}"]
108
+ @#{symbol}_rels =
109
+ #{symbol.to_s.capitalize}Relations.new(xml["relation-list"]["#{symbol.to_s.capitalize}"])
110
+ else
111
+ @#{symbol}_rels = #{symbol.to_s.capitalize}Relations.new
112
+ end
113
+ end
114
+ EVAL
115
+ end
116
+ end
117
+ end