rbrainz 0.1.1 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES +31 -0
- data/LICENSE +1 -1
- data/README +3 -2
- data/Rakefile +40 -22
- data/TODO +6 -23
- data/doc/README.rdoc +50 -21
- data/examples/getartist.rb +6 -4
- data/examples/getuser.rb +30 -0
- data/examples/searchartists.rb +35 -0
- data/lib/rbrainz.rb +12 -7
- data/lib/rbrainz/core_ext.rb +8 -0
- data/lib/rbrainz/core_ext/mbid.rb +30 -0
- data/lib/rbrainz/core_ext/net_http_digest.rb +52 -0
- data/lib/rbrainz/core_ext/range.rb +28 -0
- data/lib/rbrainz/core_ext/range/equality.rb +232 -0
- data/lib/rbrainz/data/countrynames.rb +7 -5
- data/lib/rbrainz/data/languagenames.rb +8 -5
- data/lib/rbrainz/data/releasetypenames.rb +34 -0
- data/lib/rbrainz/data/scriptnames.rb +8 -5
- data/lib/rbrainz/model.rb +27 -35
- data/lib/rbrainz/model/alias.rb +31 -7
- data/lib/rbrainz/model/artist.rb +30 -41
- data/lib/rbrainz/model/collection.rb +102 -0
- data/lib/rbrainz/model/default_factory.rb +78 -0
- data/lib/rbrainz/model/disc.rb +45 -8
- data/lib/rbrainz/model/entity.rb +122 -53
- data/lib/rbrainz/model/incomplete_date.rb +31 -47
- data/lib/rbrainz/model/individual.rb +103 -0
- data/lib/rbrainz/model/label.rb +42 -33
- data/lib/rbrainz/model/mbid.rb +111 -40
- data/lib/rbrainz/model/relation.rb +78 -14
- data/lib/rbrainz/model/release.rb +119 -31
- data/lib/rbrainz/model/release_event.rb +38 -9
- data/lib/rbrainz/model/scored_collection.rb +99 -0
- data/lib/rbrainz/model/tag.rb +39 -0
- data/lib/rbrainz/model/track.rb +37 -13
- data/lib/rbrainz/model/user.rb +48 -0
- data/lib/rbrainz/utils.rb +9 -0
- data/lib/rbrainz/utils/data.rb +78 -0
- data/lib/rbrainz/utils/helper.rb +22 -0
- data/lib/rbrainz/version.rb +15 -0
- data/lib/rbrainz/webservice.rb +32 -6
- data/lib/rbrainz/webservice/filter.rb +124 -47
- data/lib/rbrainz/webservice/includes.rb +49 -10
- data/lib/rbrainz/webservice/mbxml.rb +228 -173
- data/lib/rbrainz/webservice/query.rb +312 -25
- data/lib/rbrainz/webservice/webservice.rb +164 -27
- data/test/lib/mock_webservice.rb +53 -0
- data/test/lib/test_entity.rb +27 -8
- data/test/lib/test_factory.rb +47 -0
- data/test/lib/testing_helper.rb +7 -5
- data/test/test-data/invalid/artist/tags_1.xml +6 -0
- data/test/test-data/valid/artist/Tchaikovsky-2.xml +12 -0
- data/test/test-data/valid/label/Atlantic_Records_2.xml +3 -0
- data/test/test-data/valid/label/Atlantic_Records_3.xml +11 -0
- data/test/test-data/valid/release/Highway_61_Revisited_2.xml +12 -0
- data/test/test-data/valid/track/Silent_All_These_Years_6.xml +8 -0
- data/test/test_alias.rb +13 -7
- data/test/test_artist.rb +26 -4
- data/test/test_artist_filter.rb +11 -6
- data/test/test_artist_includes.rb +11 -6
- data/test/test_collection.rb +66 -0
- data/test/test_default_factory.rb +75 -0
- data/test/test_disc.rb +9 -4
- data/test/test_incomplete_date.rb +21 -14
- data/test/test_label.rb +56 -18
- data/test/test_label_filter.rb +10 -5
- data/test/test_label_includes.rb +11 -6
- data/test/test_mbid.rb +34 -19
- data/test/test_mbxml.rb +242 -72
- data/test/test_query.rb +92 -7
- data/test/test_range_equality.rb +144 -0
- data/test/test_relation.rb +18 -7
- data/test/test_release.rb +15 -4
- data/test/test_release_event.rb +16 -4
- data/test/test_release_filter.rb +11 -5
- data/test/test_release_includes.rb +11 -6
- data/test/test_scored_collection.rb +86 -0
- data/test/test_tag.rb +39 -0
- data/test/test_track.rb +15 -4
- data/test/test_track_filter.rb +11 -5
- data/test/test_track_includes.rb +11 -6
- data/test/test_utils.rb +41 -0
- data/test/test_webservice.rb +16 -17
- metadata +93 -57
@@ -1,7 +1,9 @@
|
|
1
|
-
# $Id: query.rb
|
2
|
-
#
|
3
|
-
#
|
4
|
-
#
|
1
|
+
# $Id: query.rb 147 2007-07-19 17:10:26Z phw $
|
2
|
+
#
|
3
|
+
# Author:: Philipp Wolfer (mailto:phw@rubyforge.org)
|
4
|
+
# Copyright:: Copyright (c) 2007, Nigel Graham, Philipp Wolfer
|
5
|
+
# License:: RBrainz is free software distributed under a BSD style license.
|
6
|
+
# See LICENSE[file:../LICENSE.html] for permissions.
|
5
7
|
|
6
8
|
require 'rbrainz/webservice/webservice'
|
7
9
|
require 'rbrainz/webservice/mbxml'
|
@@ -9,54 +11,339 @@ require 'rbrainz/webservice/mbxml'
|
|
9
11
|
module MusicBrainz
|
10
12
|
module Webservice
|
11
13
|
|
14
|
+
# A simple interface to the MusicBrainz web service.
|
15
|
+
#
|
16
|
+
# This is a facade which provides a simple interface to the MusicBrainz
|
17
|
+
# web service. It hides all the details like fetching data from a server,
|
18
|
+
# parsing the XML and creating an object tree. Using this class, you can
|
19
|
+
# request data by ID or search the _collection_ of all resources
|
20
|
+
# (artists, labels, releases or tracks) to retrieve those matching given
|
21
|
+
# criteria. This document contains examples to get you started.
|
22
|
+
#
|
23
|
+
#
|
24
|
+
# == Working with Identifiers
|
25
|
+
# MusicBrainz uses absolute URIs as identifiers. For example, the artist
|
26
|
+
# 'Tori Amos' is identified using the following URI:
|
27
|
+
# http://musicbrainz.org/artist/c0b2500e-0cef-4130-869d-732b23ed9df5
|
28
|
+
#
|
29
|
+
# In some situations it is obvious from the context what type of
|
30
|
+
# resource an ID refers to. In these cases, abbreviated identifiers may
|
31
|
+
# be used, which are just the _UUID_ part of the URI. Thus the ID above
|
32
|
+
# may also be written like this:
|
33
|
+
# c0b2500e-0cef-4130-869d-732b23ed9df5
|
34
|
+
#
|
35
|
+
# All methods in this class which require IDs accept both the absolute
|
36
|
+
# URI and the abbreviated form (aka the relative URI).
|
37
|
+
#
|
38
|
+
#
|
39
|
+
# == Creating a Query Object
|
40
|
+
#
|
41
|
+
# In most cases, creating a Query object is as simple as this:
|
42
|
+
# require 'rbrainz'
|
43
|
+
# q = MusicBrainz::Webservice::Query.new
|
44
|
+
#
|
45
|
+
# The instantiated object uses the standard Webservice class to
|
46
|
+
# access the MusicBrainz web service. If you want to use a different
|
47
|
+
# server or you have to pass user name and password because one of
|
48
|
+
# your queries requires authentication, you have to create the
|
49
|
+
# Webservice object yourself and configure it appropriately.
|
50
|
+
#
|
51
|
+
# This example uses the MusicBrainz test server and also sets
|
52
|
+
# authentication data:
|
53
|
+
# require 'rbrainz'
|
54
|
+
# service = MusicBrainz::Webservice::Webservice.new(
|
55
|
+
# :host => 'test.musicbrainz.org',
|
56
|
+
# :username => 'whatever',
|
57
|
+
# :password => 'secret'
|
58
|
+
# )
|
59
|
+
# q = MusicBrainz::Webservice::Query.new(service)
|
60
|
+
#
|
61
|
+
#
|
62
|
+
# == Querying for Individual Resources
|
63
|
+
#
|
64
|
+
# If the MusicBrainz ID of a resource is known, then the get_artist_by_id,
|
65
|
+
# get_label_by_id, get_release_by_id or get_track_by_id method can be used
|
66
|
+
# to retrieve it.
|
67
|
+
#
|
68
|
+
# Example:
|
69
|
+
# require 'rbrainz'
|
70
|
+
# q = MusicBrainz::Webservice::Query.new
|
71
|
+
# artist = q.get_artist_by_id('c0b2500e-0cef-4130-869d-732b23ed9df5')
|
72
|
+
# puts artist.name
|
73
|
+
# puts artist.sort_name
|
74
|
+
# puts artist.type
|
75
|
+
#
|
76
|
+
# _produces_:
|
77
|
+
# Tori Amos
|
78
|
+
# Amos, Tori
|
79
|
+
# http://musicbrainz.org/ns/mmd-1.0#Person
|
80
|
+
#
|
81
|
+
# This returned just the basic artist data, however. To get more detail
|
82
|
+
# about a resource, the _includes_ parameters may be used
|
83
|
+
# which expect an ArtistIncludes, ReleaseIncludes, TrackIncludes or
|
84
|
+
# LabelIncludes object, depending on the resource type.
|
85
|
+
# It is also possible to use a Hash for the _includes_ parameter where it
|
86
|
+
# will then get automaticly wrapped in the appropriate includes class.
|
87
|
+
#
|
88
|
+
# To get data about a release which also includes the main artist
|
89
|
+
# and all tracks, for example, the following query can be used:
|
90
|
+
# require 'rbrainz'
|
91
|
+
# q = MusicBrainz::Webservice::Query.new
|
92
|
+
# release_id = '33dbcf02-25b9-4a35-bdb7-729455f33ad7'
|
93
|
+
# release = q.get_release_by_id(release_id, :artist=>true, :tracks=>true)
|
94
|
+
# puts release.title
|
95
|
+
# puts release.artist.name
|
96
|
+
# puts release.tracks[0].title
|
97
|
+
#
|
98
|
+
# _produces_:
|
99
|
+
# Tales of a Librarian
|
100
|
+
# Tori Amos
|
101
|
+
# Precious Things
|
102
|
+
#
|
103
|
+
# Note that the query gets more expensive for the server the more
|
104
|
+
# data you request, so please be nice.
|
105
|
+
#
|
106
|
+
#
|
107
|
+
# == Searching in Collections
|
108
|
+
#
|
109
|
+
# For each resource type (artist, release, and track), there is one
|
110
|
+
# collection which contains all resources of a type. You can search
|
111
|
+
# these collections using the get_artists, get_releases, and
|
112
|
+
# get_tracks methods. The collections are huge, so you have to
|
113
|
+
# use filters (ArtistFilter, ReleaseFilter, TrackFilter or LabelFilter)
|
114
|
+
# to retrieve only resources matching given criteria.
|
115
|
+
# As with includes it is also possible to use a Hash as the filter where
|
116
|
+
# it will then get wrapped in the appropriate filter class.
|
117
|
+
#
|
118
|
+
# For example, If you want to search the release collection for
|
119
|
+
# releases with a specified DiscID, you would use get_releases:
|
120
|
+
# require 'rbrainz'
|
121
|
+
# q = MusicBrainz::Webservice::Query.new
|
122
|
+
# results = q.get_releases(ReleaseFilter.new(:disc_id=>discId='8jJklE258v6GofIqDIrE.c5ejBE-'))
|
123
|
+
# puts results[0].score
|
124
|
+
# puts results[0].entity.title
|
125
|
+
#
|
126
|
+
# _produces_:
|
127
|
+
# 100
|
128
|
+
# Under the Pink
|
129
|
+
#
|
130
|
+
#
|
131
|
+
# The query returns a list of results in a ScoredCollection,
|
132
|
+
# which orders entities by score, with a higher score
|
133
|
+
# indicating a better match. Note that those results don't contain
|
134
|
+
# all the data about a resource. If you need more detail, you can then
|
135
|
+
# use the get_artist_by_id, get_label_by_id, get_release_by_id, or
|
136
|
+
# get_track_by_id methods to request the resource.
|
137
|
+
#
|
138
|
+
# All filters support the _limit_ argument to limit the number of
|
139
|
+
# results returned. This defaults to 25, but the server won't send
|
140
|
+
# more than 100 results to save bandwidth and processing power. In case
|
141
|
+
# you want to retrieve results above the 100 results limit you can use the
|
142
|
+
# _offset_ argument in the filters. The _offset_ specifies how many entries
|
143
|
+
# at the beginning of the collection should be skipped.
|
144
|
+
#
|
12
145
|
class Query
|
13
146
|
|
14
|
-
|
147
|
+
# Create a new Query object.
|
148
|
+
#
|
149
|
+
# You can pass a custom Webservice[link:classes/MusicBrainz/Webservice/Webservice.html]
|
150
|
+
# object. If none is given a default webservice will be used.
|
151
|
+
#
|
152
|
+
# If the constructor is called without arguments, an instance
|
153
|
+
# of WebService is used, preconfigured to use the MusicBrainz
|
154
|
+
# server. This should be enough for most users.
|
155
|
+
#
|
156
|
+
# If you want to use queries which require authentication you
|
157
|
+
# have to pass a Webservice instance where user name and
|
158
|
+
# password have been set.
|
159
|
+
#
|
160
|
+
# The _client_id_ option is required for data submission.
|
161
|
+
# The format is <em>'application-version'</em>, where _application_
|
162
|
+
# is your application's name and _version_ is a version
|
163
|
+
# number which may not include a '-' character.
|
164
|
+
#
|
165
|
+
# Available options:
|
166
|
+
# [:client_id] a unicode string containing the application's ID.
|
167
|
+
# [:factory] A model factory. An instance of Model::DefaultFactory
|
168
|
+
# will be used if none is given.
|
169
|
+
def initialize(webservice = nil, options={ :client_id=>nil, :factory=>nil })
|
170
|
+
Utils.check_options options, :client_id, :factory
|
15
171
|
@webservice = webservice.nil? ? Webservice.new : webservice
|
172
|
+
@client_id = options[:client_id] ? options[:client_id] : nil
|
173
|
+
@factory = options[:factory] ? options[:factory] : nil
|
16
174
|
end
|
17
175
|
|
18
|
-
|
19
|
-
|
176
|
+
# Returns an artist.
|
177
|
+
#
|
178
|
+
# The parameter _includes_ must be an instance of ArtistIncludes
|
179
|
+
# or a options hash as expected by ArtistIncludes.
|
180
|
+
#
|
181
|
+
# If no artist with that ID can be found, include contains invalid tags
|
182
|
+
# or there's a server problem, and exception is raised.
|
183
|
+
#
|
184
|
+
# Raises:: ConnectionError, RequestError, ResourceNotFoundError, ResponseError
|
185
|
+
def get_artist_by_id(id, includes = nil)
|
186
|
+
includes = ArtistIncludes.new(includes) unless includes.nil? || includes.is_a?(ArtistIncludes)
|
187
|
+
return get_entity_by_id(Model::Artist.entity_type, id, includes)
|
20
188
|
end
|
21
189
|
|
22
|
-
#
|
190
|
+
# Returns artists matching given criteria.
|
191
|
+
#
|
192
|
+
# The parameter _filter_ must be an instance of ArtistFilter
|
193
|
+
# or a options hash as expected by ArtistFilter.
|
194
|
+
#
|
195
|
+
# Raises:: ConnectionError, RequestError, ResponseError
|
23
196
|
def get_artists(filter)
|
24
|
-
|
197
|
+
filter = ArtistFilter.new(filter) unless filter.nil? || filter.is_a?(ArtistFilter)
|
198
|
+
return get_entities(Model::Artist.entity_type, filter)
|
25
199
|
end
|
26
200
|
|
27
|
-
|
28
|
-
|
201
|
+
# Returns an release.
|
202
|
+
#
|
203
|
+
# The parameter _includes_ must be an instance of ReleaseIncludes
|
204
|
+
# or a options hash as expected by ReleaseIncludes.
|
205
|
+
#
|
206
|
+
# If no release with that ID can be found, include contains invalid tags
|
207
|
+
# or there's a server problem, and exception is raised.
|
208
|
+
#
|
209
|
+
# Raises:: ConnectionError, RequestError, ResourceNotFoundError, ResponseError
|
210
|
+
def get_release_by_id(id, includes = nil)
|
211
|
+
includes = ReleaseIncludes.new(includes) unless includes.nil? || includes.is_a?(ReleaseIncludes)
|
212
|
+
return get_entity_by_id(Model::Release.entity_type, id, includes)
|
29
213
|
end
|
30
214
|
|
31
|
-
#
|
215
|
+
# Returns releases matching given criteria.
|
216
|
+
#
|
217
|
+
# The parameter _filter_ must be an instance of ReleaseFilter
|
218
|
+
# or a options hash as expected by ReleaseFilter.
|
219
|
+
#
|
220
|
+
# Raises:: ConnectionError, RequestError, ResponseError
|
32
221
|
def get_releases(filter)
|
33
|
-
|
222
|
+
filter = ReleaseFilter.new(filter) unless filter.nil? || filter.is_a?(ReleaseFilter)
|
223
|
+
return get_entities(Model::Release.entity_type, filter)
|
34
224
|
end
|
35
225
|
|
36
|
-
|
37
|
-
|
226
|
+
# Returns an track.
|
227
|
+
#
|
228
|
+
# The parameter _includes_ must be an instance of TrackIncludes
|
229
|
+
# or a options hash as expected by TrackIncludes.
|
230
|
+
#
|
231
|
+
# If no track with that ID can be found, include contains invalid tags
|
232
|
+
# or there's a server problem, and exception is raised.
|
233
|
+
#
|
234
|
+
# Raises:: ConnectionError, RequestError, ResourceNotFoundError, ResponseError
|
235
|
+
def get_track_by_id(id, includes = nil)
|
236
|
+
includes = TrackIncludes.new(includes) unless includes.nil? || includes.is_a?(TrackIncludes)
|
237
|
+
return get_entity_by_id(Model::Track.entity_type, id, includes)
|
38
238
|
end
|
39
239
|
|
40
|
-
#
|
240
|
+
# Returns tracks matching given criteria.
|
241
|
+
#
|
242
|
+
# The parameter _filter_ must be an instance of TrackFilter
|
243
|
+
# or a options hash as expected by TrackFilter.
|
244
|
+
#
|
245
|
+
# Raises:: ConnectionError, RequestError, ResponseError
|
41
246
|
def get_tracks(filter)
|
42
|
-
|
247
|
+
filter = TrackFilter.new(filter) unless filter.nil? || filter.is_a?(TrackFilter)
|
248
|
+
return get_entities(Model::Track.entity_type, filter)
|
43
249
|
end
|
44
250
|
|
45
|
-
|
46
|
-
|
251
|
+
# Returns an label.
|
252
|
+
#
|
253
|
+
# The parameter _includes_ must be an instance of LabelIncludes
|
254
|
+
# or a options hash as expected by LabelIncludes.
|
255
|
+
#
|
256
|
+
# If no label with that ID can be found, include contains invalid tags
|
257
|
+
# or there's a server problem, and exception is raised.
|
258
|
+
#
|
259
|
+
# Raises:: ConnectionError, RequestError, ResourceNotFoundError, ResponseError
|
260
|
+
def get_label_by_id(id, includes = nil)
|
261
|
+
includes = LabelIncludes.new(includes) unless includes.nil? || includes.is_a?(LabelIncludes)
|
262
|
+
return get_entity_by_id(Model::Label.entity_type, id, includes)
|
47
263
|
end
|
48
264
|
|
49
|
-
#
|
265
|
+
# Returns labels matching given criteria.
|
266
|
+
#
|
267
|
+
# The parameter _filter_ must be an instance of LabelFilter
|
268
|
+
# or a options hash as expected by LabelFilter.
|
269
|
+
#
|
270
|
+
# Raises:: ConnectionError, RequestError, ResponseError
|
50
271
|
def get_labels(filter)
|
51
|
-
|
272
|
+
filter = LabelFilter.new(filter) unless filter.nil? || filter.is_a?(LabelFilter)
|
273
|
+
return get_entities(Model::Label.entity_type, filter)
|
52
274
|
end
|
53
275
|
|
54
|
-
|
276
|
+
# Returns information about a MusicBrainz user.
|
277
|
+
#
|
278
|
+
# You can only request user data if you know the user name and password
|
279
|
+
# for that account. If username and/or password are incorrect, an
|
280
|
+
# AuthenticationError is raised.
|
281
|
+
#
|
282
|
+
# See the example in Query on how to supply user name and password.
|
283
|
+
#
|
284
|
+
# Raises:: ConnectionError, RequestError, AuthenticationError, ResponseError
|
285
|
+
def get_user_by_name(name)
|
286
|
+
xml = @webservice.get(:user, :filter => UserFilter.new(name))
|
287
|
+
collection = MBXML.new(xml).get_entity_list(:user, Model::NS_EXT_1)
|
288
|
+
unless collection and collection.size > 0
|
289
|
+
raise ResponseError("response didn't contain user data")
|
290
|
+
else
|
291
|
+
return collection[0].entity
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
# Submit track to PUID mappings.
|
296
|
+
#
|
297
|
+
# The <em>tracks2puids</em> parameter has to be a dictionary, with the
|
298
|
+
# keys being MusicBrainz track IDs (either as absolute URIs or
|
299
|
+
# in their 36 character ASCII representation) and the values
|
300
|
+
# being PUIDs (ASCII, 36 characters).
|
301
|
+
#
|
302
|
+
# Note that this method only works if a valid user name and
|
303
|
+
# password have been set. If username and/or password are incorrect,
|
304
|
+
# an AuthenticationError is raised. See the example in Query on
|
305
|
+
# how to supply authentication data.
|
306
|
+
#
|
307
|
+
# Raises:: ConnectionError, RequestError, AuthenticationError
|
308
|
+
def submit_puids(tracks2puids)
|
309
|
+
raise RequestError, 'Please supply a client ID' unless @client_id
|
310
|
+
params = [['client', @client_id.to_s]] # Encoded as utf-8
|
311
|
+
|
312
|
+
tracks2puids.each {|track_id, puid| params << ['puid', track_id + ' ' + puid ]}
|
313
|
+
|
314
|
+
@webservice.post('track', :querystring=>params)
|
315
|
+
end
|
316
|
+
|
317
|
+
private # ----------------------------------------------------------------
|
55
318
|
|
56
319
|
# Helper method which will return any entity by ID.
|
57
|
-
|
58
|
-
|
59
|
-
|
320
|
+
#
|
321
|
+
# Raises:: ConnectionError, RequestError, ResourceNotFoundError, ResponseError
|
322
|
+
def get_entity_by_id(entity_type, id, includes)
|
323
|
+
stream = @webservice.get(entity_type, :id => id, :include => includes)
|
324
|
+
begin
|
325
|
+
entity = MBXML.new(stream, @factory).get_entity(entity_type)
|
326
|
+
rescue MBXML::ParseError => e
|
327
|
+
raise ResponseError.new(e.to_s)
|
328
|
+
end
|
329
|
+
unless entity
|
330
|
+
raise ResponseError.new("server didn't return #{entity_type.to_s} with the MBID #{id.to_s}")
|
331
|
+
else
|
332
|
+
return entity
|
333
|
+
end
|
334
|
+
end
|
335
|
+
|
336
|
+
# Helper method which will search for the given entity type.
|
337
|
+
#
|
338
|
+
# Raises:: ConnectionError, RequestError, ResponseError
|
339
|
+
def get_entities(entity_type, filter)
|
340
|
+
stream = @webservice.get(entity_type, :filter => filter)
|
341
|
+
begin
|
342
|
+
collection = MBXML.new(stream, @factory).get_entity_list(entity_type)
|
343
|
+
rescue MBXML::ParseError => e
|
344
|
+
raise ResponseError.new(e.to_s)
|
345
|
+
end
|
346
|
+
return collection
|
60
347
|
end
|
61
348
|
|
62
349
|
end
|
@@ -1,54 +1,107 @@
|
|
1
|
-
# $Id: webservice.rb
|
2
|
-
#
|
3
|
-
#
|
4
|
-
#
|
1
|
+
# $Id: webservice.rb 148 2007-07-19 17:26:33Z phw $
|
2
|
+
#
|
3
|
+
# Author:: Philipp Wolfer (mailto:phw@rubyforge.org)
|
4
|
+
# Copyright:: Copyright (c) 2007, Nigel Graham, Philipp Wolfer
|
5
|
+
# License:: RBrainz is free software distributed under a BSD style license.
|
6
|
+
# See LICENSE[file:../LICENSE.html] for permissions.
|
5
7
|
|
6
8
|
require 'rbrainz/webservice/includes'
|
7
9
|
require 'rbrainz/webservice/filter'
|
8
10
|
require 'net/http'
|
11
|
+
require 'stringio'
|
9
12
|
|
10
13
|
module MusicBrainz
|
11
14
|
module Webservice
|
12
15
|
|
16
|
+
# An interface all concrete web service classes have to implement.
|
17
|
+
#
|
18
|
+
# All web service classes have to implement this and follow the method
|
19
|
+
# specifications.
|
13
20
|
class IWebservice
|
14
21
|
|
15
|
-
# Query the
|
16
|
-
#
|
17
|
-
|
18
|
-
|
22
|
+
# Query the web service.
|
23
|
+
#
|
24
|
+
# This method must be implemented by the concrete webservices and
|
25
|
+
# should return an IO object on success.
|
26
|
+
#
|
27
|
+
# Options:
|
28
|
+
# [:id] A MBID if querying for a single ressource.
|
29
|
+
# [:include] An include object (see AbstractIncludes).
|
30
|
+
# [:filter] A filter object (see AbstractFilter).
|
31
|
+
# [:version] The version of the webservice to use. Defaults to 1.
|
32
|
+
def get(entity_type, options={ :id=>nil, :include=>nil, :filter=>nil, :version=>1 })
|
33
|
+
raise NotImplementedError.new('Called abstract method.')
|
19
34
|
end
|
20
35
|
|
21
|
-
#
|
22
|
-
#
|
23
|
-
#
|
24
|
-
|
36
|
+
# Submit data to the web service.
|
37
|
+
#
|
38
|
+
# This method must be implemented by the concrete webservices and
|
39
|
+
# should return an IO object on success.
|
40
|
+
#
|
41
|
+
# Options:
|
42
|
+
# [:id] A MBID if querying for a single ressource.
|
43
|
+
# [:querystring] A string containing the data to post.
|
44
|
+
# [:version] The version of the webservice to use. Defaults to 1.
|
45
|
+
def post(entity_type, options={ :id=>nil, :querystring=>[], :version=>1 })
|
25
46
|
raise NotImplementedError.new('Called abstract method.')
|
26
47
|
end
|
27
48
|
|
28
49
|
end
|
29
50
|
|
30
|
-
#
|
31
|
-
#
|
51
|
+
# An interface to the MusicBrainz XML web service via HTTP.
|
52
|
+
#
|
53
|
+
# By default, this class uses the MusicBrainz server but may be configured
|
54
|
+
# for accessing other servers as well using the constructor. This implements
|
55
|
+
# IWebService, so additional documentation on method parameters can be found
|
56
|
+
# there.
|
32
57
|
class Webservice < IWebservice
|
33
58
|
|
34
59
|
# Timeouts for opening and reading connections (in seconds)
|
35
60
|
attr_accessor :open_timeout, :read_timeout
|
36
61
|
|
37
|
-
|
62
|
+
# If no options are given the default MusicBrainz webservice will be used.
|
63
|
+
# User authentication with username and password is only needed for some
|
64
|
+
# services. If you want to query an alternative webservice you can do so
|
65
|
+
# by setting the appropriate options.
|
66
|
+
#
|
67
|
+
# Available options:
|
68
|
+
# [:host] Host, defaults to 'musicbrainz.org'.
|
69
|
+
# [:port] Port, defaults to 80.
|
70
|
+
# [:path_prefix] The path prefix under which the webservice is located on
|
71
|
+
# the server. Defaults to '/ws'.
|
72
|
+
# [:username] The username to authenticate with.
|
73
|
+
# [:password] The password to authenticate with.
|
74
|
+
# [:user_agent] Value sent in the User-Agent HTTP header. Defaults to 'rbrainz/0.2'
|
75
|
+
def initialize(options={ :host=>nil, :port=>nil, :path_prefix=>'/ws', :username=>nil, :password=>nil, :user_agent=>'rbrainz/0.2' })
|
76
|
+
Utils.check_options options, :host, :port, :path_prefix, :username, :password, :user_agent
|
38
77
|
@host = options[:host] ? options[:host] : 'musicbrainz.org'
|
39
78
|
@port = options[:port] ? options[:port] : 80
|
40
79
|
@path_prefix = options[:path_prefix] ? options[:path_prefix] : '/ws'
|
80
|
+
@username = options[:username]
|
81
|
+
@password = options[:password]
|
82
|
+
@user_agent = options[:user_agent] ? options[:user_agent] : 'rbrainz/0.2'
|
41
83
|
@open_timeout = nil
|
42
84
|
@read_timeout = nil
|
43
85
|
end
|
44
86
|
|
45
87
|
# Query the Webservice with HTTP GET.
|
46
88
|
#
|
47
|
-
#
|
48
|
-
#
|
49
|
-
|
50
|
-
|
89
|
+
# Returns an IO object on success.
|
90
|
+
#
|
91
|
+
# Options:
|
92
|
+
# [:id] A MBID if querying for a single ressource.
|
93
|
+
# [:include] An include object (see AbstractIncludes).
|
94
|
+
# [:filter] A filter object (see AbstractFilter).
|
95
|
+
# [:version] The version of the webservice to use. Defaults to 1.
|
96
|
+
#
|
97
|
+
# Raises:: RequestError, ResourceNotFoundError, AuthenticationError,
|
98
|
+
# ConnectionError
|
99
|
+
# See:: IWebservice#get
|
100
|
+
def get(entity_type, options={ :id=>nil, :include=>nil, :filter=>nil, :version=>1 })
|
101
|
+
Utils.check_options options, :id, :include, :filter, :version
|
102
|
+
url = URI.parse(create_uri(entity_type, options))
|
51
103
|
request = Net::HTTP::Get.new(url.request_uri)
|
104
|
+
request['User-Agent'] = @user_agent
|
52
105
|
connection = Net::HTTP.new(url.host, url.port)
|
53
106
|
|
54
107
|
# Set timeouts
|
@@ -57,9 +110,16 @@ module MusicBrainz
|
|
57
110
|
|
58
111
|
# Make the request
|
59
112
|
begin
|
60
|
-
response = connection.start
|
61
|
-
http.request(request)
|
62
|
-
|
113
|
+
response = connection.start do |http|
|
114
|
+
response = http.request(request)
|
115
|
+
if response.is_a?(Net::HTTPUnauthorized) && @username && @password
|
116
|
+
request = Net::HTTP::Get.new(url.request_uri)
|
117
|
+
request['User-Agent'] = @user_agent
|
118
|
+
request.digest_auth @username, @password, response
|
119
|
+
response = http.request(request)
|
120
|
+
end
|
121
|
+
response
|
122
|
+
end
|
63
123
|
rescue Timeout::Error, Errno::ETIMEDOUT
|
64
124
|
raise ConnectionError.new('%s timed out' % url.to_s)
|
65
125
|
end
|
@@ -71,23 +131,100 @@ module MusicBrainz
|
|
71
131
|
raise AuthenticationError.new(url.to_s)
|
72
132
|
elsif response.is_a? Net::HTTPNotFound # 404
|
73
133
|
raise ResourceNotFoundError.new(url.to_s)
|
134
|
+
elsif response.is_a? Net::HTTPForbidden
|
135
|
+
raise AuthenticationError.new(url.to_s)
|
74
136
|
elsif not response.is_a? Net::HTTPSuccess
|
75
137
|
raise ConnectionError.new(response.class.name)
|
76
138
|
end
|
77
139
|
|
78
|
-
return response.body
|
140
|
+
return ::StringIO.new(response.body)
|
141
|
+
end
|
142
|
+
|
143
|
+
# Send data to the web service via HTTP-POST.
|
144
|
+
#
|
145
|
+
# Note that this may require authentication. You can set
|
146
|
+
# user name, password and realm in the constructor.
|
147
|
+
#
|
148
|
+
# Returns an IO object on success.
|
149
|
+
#
|
150
|
+
# Options:
|
151
|
+
# [:id] A MBID if querying for a single ressource.
|
152
|
+
# [:querystring] A string containing the data to post.
|
153
|
+
# [:version] The version of the webservice to use. Defaults to 1.
|
154
|
+
#
|
155
|
+
# Raises:: ConnectionError, RequestError, AuthenticationError,
|
156
|
+
# ResourceNotFoundError
|
157
|
+
#
|
158
|
+
# See:: IWebservice#post
|
159
|
+
def post(entity_type, options={:id=>nil, :querystring=>[], :version=>1})
|
160
|
+
Utils.check_options options, :id, :querystring, :version
|
161
|
+
url = URI.parse(create_uri(entity_type, options))
|
162
|
+
request = Net::HTTP::Post.new(url.request_uri)
|
163
|
+
request['User-Agent'] = @user_agent
|
164
|
+
request.set_form_data(options[:querystring])
|
165
|
+
connection = Net::HTTP.new(url.host, url.port)
|
166
|
+
|
167
|
+
# Set timeouts
|
168
|
+
connection.open_timeout = @open_timeout if @open_timeout
|
169
|
+
connection.read_timeout = @read_timeout if @read_timeout
|
170
|
+
|
171
|
+
# Make the request
|
172
|
+
begin
|
173
|
+
response = connection.start do |http|
|
174
|
+
response = http.request(request)
|
175
|
+
|
176
|
+
if response.is_a?(Net::HTTPUnauthorized) && @username && @password
|
177
|
+
request = Net::HTTP::Post.new(url.request_uri)
|
178
|
+
request['User-Agent'] = @user_agent
|
179
|
+
request.digest_auth @username, @password, response
|
180
|
+
request.set_form_data(options[:querystring])
|
181
|
+
response = http.request(request)
|
182
|
+
end
|
183
|
+
response
|
184
|
+
end
|
185
|
+
rescue Timeout::Error, Errno::ETIMEDOUT
|
186
|
+
raise ConnectionError.new('%s timed out' % url.to_s)
|
187
|
+
end
|
188
|
+
|
189
|
+
# Handle response errors.
|
190
|
+
if response.is_a? Net::HTTPBadRequest # 400
|
191
|
+
raise RequestError.new(url.to_s)
|
192
|
+
elsif response.is_a? Net::HTTPUnauthorized # 401
|
193
|
+
raise AuthenticationError.new(url.to_s)
|
194
|
+
elsif response.is_a? Net::HTTPNotFound # 404
|
195
|
+
raise ResourceNotFoundError.new(url.to_s)
|
196
|
+
elsif response.is_a? Net::HTTPForbidden
|
197
|
+
raise AuthenticationError.new(url.to_s)
|
198
|
+
elsif not response.is_a? Net::HTTPSuccess
|
199
|
+
raise ConnectionError.new(response.class.name)
|
200
|
+
end
|
201
|
+
|
202
|
+
return ::StringIO.new(response.body)
|
79
203
|
end
|
80
204
|
|
81
205
|
private # ----------------------------------------------------------------
|
82
206
|
|
83
207
|
# Builds a request URI for querying the webservice.
|
84
|
-
def create_uri(
|
208
|
+
def create_uri(entity_type, options = {:id => nil, :include => nil, :filter => nil, :version => 1})
|
85
209
|
# Make sure the version is set
|
86
210
|
options[:version] = 1 if options[:version].nil?
|
87
211
|
|
88
212
|
# Build the URI
|
89
|
-
|
90
|
-
|
213
|
+
if options[:id]
|
214
|
+
# Make sure the id is a MBID object
|
215
|
+
id = options[:id]
|
216
|
+
unless id.is_a? Model::MBID
|
217
|
+
id = Model::MBID.parse(id, entity_type)
|
218
|
+
end
|
219
|
+
|
220
|
+
uri = 'http://%s:%d%s/%d/%s/%s?type=%s' %
|
221
|
+
[@host, @port, @path_prefix, options[:version],
|
222
|
+
entity_type, id.uuid, 'xml']
|
223
|
+
else
|
224
|
+
uri = 'http://%s:%d%s/%d/%s/?type=%s' %
|
225
|
+
[@host, @port, @path_prefix, options[:version],
|
226
|
+
entity_type, 'xml']
|
227
|
+
end
|
91
228
|
uri += '&' + options[:include].to_s unless options[:include].nil?
|
92
229
|
uri += '&' + options[:filter].to_s unless options[:filter].nil?
|
93
230
|
return uri
|
@@ -96,4 +233,4 @@ module MusicBrainz
|
|
96
233
|
end
|
97
234
|
|
98
235
|
end
|
99
|
-
end
|
236
|
+
end
|