audioscrobbler 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/audioscrobbler.gemspec +1 -1
- data/lib/audioscrobbler.rb +109 -62
- data/test/test_audioscrobbler.rb +15 -7
- metadata +2 -2
data/audioscrobbler.gemspec
CHANGED
data/lib/audioscrobbler.rb
CHANGED
@@ -22,20 +22,29 @@
|
|
22
22
|
# GNU GPL; see COPYING
|
23
23
|
#
|
24
24
|
# == Changes
|
25
|
-
# 0.0.1
|
26
|
-
#
|
25
|
+
# 0.0.1 20051002
|
26
|
+
# Initial release
|
27
|
+
# 0.0.2 20070826
|
28
|
+
# Upgraded from v1.1 to v1.2 of the Audioscrobbler protocol. This means:
|
27
29
|
# - "Now Playing" notifications
|
28
30
|
# - callers should now submit when tracks stop playing instead of as
|
29
31
|
# soon as the submission criteria is met
|
30
32
|
# - track numbers can be submitted
|
31
33
|
# Also added a race condition that I haven't bothered fixing (I
|
32
34
|
# think I'm the only person using this library?).
|
33
|
-
# 0.1.0
|
35
|
+
# 0.1.0 20071011
|
36
|
+
# Catch an exception when the server gives us a bogus now-playing
|
34
37
|
# URL, as happened to me yesterday. :-P
|
38
|
+
# 0.1.1 20071030
|
39
|
+
# Merge patches from Patrick Sinclair <Patrick.Sinclair@bbc.co.uk>
|
40
|
+
# adding HTTP proxy support and improving logging. Add validation
|
41
|
+
# of now-playing tracks' metadata, similar to what was already
|
42
|
+
# being done for submitted tracks.
|
35
43
|
|
36
44
|
require "cgi"
|
37
45
|
require "md5"
|
38
46
|
require "net/http"
|
47
|
+
require "logger"
|
39
48
|
require "thread"
|
40
49
|
require "uri"
|
41
50
|
|
@@ -110,12 +119,13 @@ class Audioscrobbler
|
|
110
119
|
# @param password Audioscrobbler account password
|
111
120
|
# @param filename file used for on-disk storage of not-yet-submitted
|
112
121
|
# tracks
|
122
|
+
# @param proxy optional HTTP proxy to use for outgoing connections
|
113
123
|
#
|
114
|
-
def initialize(username, password, filename=nil)
|
124
|
+
def initialize(username, password, filename=nil, proxy=nil)
|
115
125
|
@username = username
|
116
126
|
@password = password
|
117
127
|
@queue = SubmissionQueue.new(filename)
|
118
|
-
@
|
128
|
+
@logger = Logger.new($stdout)
|
119
129
|
@client = DEFAULT_CLIENT
|
120
130
|
@version = DEFAULT_VERSION
|
121
131
|
|
@@ -128,10 +138,57 @@ class Audioscrobbler
|
|
128
138
|
@submit_url = nil
|
129
139
|
@now_playing_url = nil
|
130
140
|
@submit_interval_sec = DEFAULT_SUBMIT_INTERVAL_SEC
|
141
|
+
|
142
|
+
@proxy = {}
|
143
|
+
if proxy
|
144
|
+
uri = URI.parse(proxy)
|
145
|
+
@proxy[:host], @proxy[:port] = uri.host, uri.port
|
146
|
+
if uri.userinfo
|
147
|
+
@proxy[:username], @proxy[:password] = uri.userinfo.split(':', 1)
|
148
|
+
end
|
149
|
+
end
|
131
150
|
end
|
132
|
-
attr_accessor :username, :password, :
|
151
|
+
attr_accessor :username, :password, :logger
|
133
152
|
attr_accessor :client, :version, :handshake_url
|
134
153
|
|
154
|
+
##
|
155
|
+
# Backwards-compatability methods to control the logging level.
|
156
|
+
def verbose=(v)
|
157
|
+
@logger.sev_threshold = v ? Logger::DEBUG : Logger::WARN
|
158
|
+
end
|
159
|
+
def verbose
|
160
|
+
@logger.sev_threshold == Logger::DEBUG
|
161
|
+
end
|
162
|
+
|
163
|
+
##
|
164
|
+
# Checks that a track's metadata meets the Audioscrobbler submission
|
165
|
+
# rules, in case the caller isn't already doing this.
|
166
|
+
#
|
167
|
+
# @param artist artist name
|
168
|
+
# @param title track name
|
169
|
+
# @param length track length
|
170
|
+
# @param start_time track start time, as UTC unix time
|
171
|
+
#
|
172
|
+
def valid_metadata?(artist, title, length, start_time)
|
173
|
+
if not length or length < 30
|
174
|
+
@logger.warn("Track \"#{artist} - #{title}\" is #{length} " \
|
175
|
+
"second(s) long (min is 30 seconds)")
|
176
|
+
return false
|
177
|
+
elsif not artist or artist == ''
|
178
|
+
@logger.warn("Track is missing artist tag (title is \"#{title}\")")
|
179
|
+
return false
|
180
|
+
elsif not title or title == ''
|
181
|
+
@logger.warn("Track is missing title tag (artist is \"#{artist}\")")
|
182
|
+
return false
|
183
|
+
elsif not start_time or start_time <= 0
|
184
|
+
@logger.warn("Track \"#{artist} - #{title}\" has bogus start time " \
|
185
|
+
"#{start_time}")
|
186
|
+
return false
|
187
|
+
end
|
188
|
+
true
|
189
|
+
end
|
190
|
+
private :valid_metadata?
|
191
|
+
|
135
192
|
##
|
136
193
|
# Update the backoff interval after handshake failure. If we haven't
|
137
194
|
# failed yet, we wait a minute; otherwise, we wait twice as long as last
|
@@ -140,7 +197,7 @@ class Audioscrobbler
|
|
140
197
|
# @param message string logged to stderr if verbose logging is enabled
|
141
198
|
#
|
142
199
|
def handle_handshake_failure(message)
|
143
|
-
|
200
|
+
@logger.debug(message)
|
144
201
|
if @handshake_backoff_sec < 60
|
145
202
|
@handshake_backoff_sec = 60
|
146
203
|
elsif @handshake_backoff_sec < 2 * 60 * 60
|
@@ -183,10 +240,15 @@ class Audioscrobbler
|
|
183
240
|
end
|
184
241
|
url = @handshake_url + '?' + arg_pairs.join('&')
|
185
242
|
|
186
|
-
|
243
|
+
@logger.debug("Beginning handshake with #@handshake_url")
|
187
244
|
|
188
245
|
begin
|
189
|
-
|
246
|
+
uri = URI.parse(url)
|
247
|
+
data = Net::HTTP.start(uri.host, uri.port,
|
248
|
+
@proxy[:host], @proxy[:port],
|
249
|
+
@proxy[:username], @proxy[:password]) do |http|
|
250
|
+
http.get(uri.request_uri).body
|
251
|
+
end
|
190
252
|
rescue Exception
|
191
253
|
handle_handshake_failure(
|
192
254
|
"Read of #@handshake_url for handshake failed: #{$!}")
|
@@ -222,8 +284,8 @@ class Audioscrobbler
|
|
222
284
|
# Create our response based on the server's challenge and
|
223
285
|
# save the submission URL.
|
224
286
|
@session_id, @now_playing_url, @submit_url = lines[1,3]
|
225
|
-
|
226
|
-
|
287
|
+
@logger.debug("Got session ID #@session_id, submission URL " \
|
288
|
+
"#@submit_url, and now-playing URL #@now_playing_url")
|
227
289
|
|
228
290
|
@handshake_backoff_sec = 0
|
229
291
|
@hard_failures = 0
|
@@ -246,7 +308,7 @@ class Audioscrobbler
|
|
246
308
|
# Might as well re-check in case more tracks have shown up
|
247
309
|
# during the handshake.
|
248
310
|
tracks = @queue.peek(MAX_TRACKS_IN_SUBMISSION)
|
249
|
-
|
311
|
+
@logger.debug("Submitting #{tracks.length} track(s)")
|
250
312
|
|
251
313
|
# Construct our argument list.
|
252
314
|
args = { "s" => @session_id }
|
@@ -269,36 +331,40 @@ class Audioscrobbler
|
|
269
331
|
begin
|
270
332
|
url = URI.parse(@submit_url)
|
271
333
|
headers = { 'Content-Type' => 'application/x-www-form-urlencoded' }
|
272
|
-
data = Net::HTTP.start(url.host, url.port
|
334
|
+
data = Net::HTTP.start(url.host, url.port,
|
335
|
+
@proxy[:host], @proxy[:port],
|
336
|
+
@proxy[:username], @proxy[:password]) do |http|
|
273
337
|
http.post(url.path, body, headers).body
|
274
338
|
end
|
275
339
|
rescue Exception
|
276
|
-
|
340
|
+
@logger.warn("Submission failed -- couldn't read " \
|
341
|
+
"#@submit_url: #{$!}")
|
277
342
|
else
|
278
343
|
# Check whether the submission was successful.
|
279
344
|
lines = data.split("\n")
|
280
345
|
if not lines[0]
|
281
|
-
|
346
|
+
@logger.warn("Submission failed -- got empty response")
|
282
347
|
elsif lines[0] == "OK"
|
283
|
-
|
348
|
+
@logger.debug("Submission was successful")
|
284
349
|
@queue.delete(tracks.length)
|
285
350
|
elsif lines[0] == "BADSESSION"
|
286
|
-
|
351
|
+
@logger.warn("Submission failed -- session is invalid")
|
287
352
|
# Unset the session ID so we'll re-handshake.
|
288
353
|
@session_id = nil
|
289
354
|
else
|
290
|
-
|
355
|
+
@logger.warn("Submission failed -- got unknown response " \
|
356
|
+
"\"#{lines[0]}\"")
|
291
357
|
@hard_failures += 1
|
292
358
|
end
|
293
359
|
end
|
294
360
|
|
295
361
|
if @hard_failures >= 3
|
296
|
-
|
362
|
+
@logger.warn("Got #@hard_failures failures; re-handshaking")
|
297
363
|
@session_id = nil
|
298
364
|
end
|
299
365
|
|
300
|
-
|
301
|
-
|
366
|
+
@logger.debug("Sleeping #@submit_interval_sec sec before checking " \
|
367
|
+
"for more tracks")
|
302
368
|
sleep(@submit_interval_sec)
|
303
369
|
end
|
304
370
|
end
|
@@ -317,7 +383,12 @@ class Audioscrobbler
|
|
317
383
|
#
|
318
384
|
def report_now_playing(artist, title, length, album="", mbid="",
|
319
385
|
track_num='')
|
320
|
-
|
386
|
+
@logger.debug("Reporting \"#{artist} - #{title}\" as now-playing")
|
387
|
+
|
388
|
+
if not valid_metadata?(artist, title, length, Time.now.to_i)
|
389
|
+
@logger.warn("Ignoring track with invalid metadata for now-playing")
|
390
|
+
return false
|
391
|
+
end
|
321
392
|
|
322
393
|
# FIXME(derat): Huge race condition here between us and the submission
|
323
394
|
# thread, but I am to lazy to fix it right now.
|
@@ -342,23 +413,26 @@ class Audioscrobbler
|
|
342
413
|
begin
|
343
414
|
url = URI.parse(@now_playing_url)
|
344
415
|
rescue Exception
|
345
|
-
|
346
|
-
|
416
|
+
@logger.warn("Submission failed -- couldn't parse now-playing " +
|
417
|
+
"URL \"#@now_playing_url\"")
|
347
418
|
else
|
348
419
|
headers = { 'Content-Type' => 'application/x-www-form-urlencoded' }
|
349
420
|
begin
|
350
|
-
data = Net::HTTP.start(url.host, url.port
|
421
|
+
data = Net::HTTP.start(url.host, url.port,
|
422
|
+
@proxy[:host], @proxy[:port],
|
423
|
+
@proxy[:username], @proxy[:password]) do |http|
|
351
424
|
http.post(url.path, body, headers).body
|
352
425
|
end
|
353
426
|
rescue Exception
|
354
|
-
|
427
|
+
@logger.warn("Submission failed -- couldn't read " \
|
428
|
+
"#@now_playing_url: #{$!}")
|
355
429
|
else
|
356
430
|
data.chomp!
|
357
431
|
if data == "OK"
|
358
|
-
|
432
|
+
@logger.debug("Now-playing report was successful")
|
359
433
|
success = true
|
360
434
|
else
|
361
|
-
|
435
|
+
@logger.warn("Now-playing report failed -- got \"#{data}\"")
|
362
436
|
end
|
363
437
|
end
|
364
438
|
end
|
@@ -367,6 +441,9 @@ class Audioscrobbler
|
|
367
441
|
|
368
442
|
##
|
369
443
|
# Enqueue a track for submission.
|
444
|
+
# Returns true if track was successfully queued and false otherwise
|
445
|
+
# (currently, it only fails if the track's metadata didn't meet the
|
446
|
+
# Audioscrobbler submission rules).
|
370
447
|
#
|
371
448
|
# @param artist artist name
|
372
449
|
# @param title track name
|
@@ -378,44 +455,14 @@ class Audioscrobbler
|
|
378
455
|
#
|
379
456
|
def enqueue(artist, title, length, start_time, album="", mbid="",
|
380
457
|
track_num=nil)
|
381
|
-
if not
|
382
|
-
|
383
|
-
|
384
|
-
return
|
385
|
-
elsif not artist or artist == ''
|
386
|
-
log("Ignoring #{title}, as it is missing an artist tag")
|
387
|
-
return
|
388
|
-
elsif not title or title == ''
|
389
|
-
log("Ignoring #{artist}, as it is missing a title tag")
|
390
|
-
return
|
391
|
-
elsif not start_time or start_time <= 0
|
392
|
-
log("Ignoring #{artist} - #{title} with bogus start time #{start_time}")
|
393
|
-
return
|
458
|
+
if not valid_metadata?(artist, title, length, start_time)
|
459
|
+
@logger.warn("Ignoring track with invalid metadata for submission")
|
460
|
+
return false
|
394
461
|
end
|
395
|
-
|
396
462
|
@queue.append(artist, title, length, start_time, album, mbid, track_num)
|
463
|
+
true
|
397
464
|
end
|
398
465
|
|
399
|
-
##
|
400
|
-
# Log a message, along with the current time, to stderr.
|
401
|
-
#
|
402
|
-
# @param message message to log
|
403
|
-
#
|
404
|
-
def log(message)
|
405
|
-
STDERR.puts(Time.now.strftime("%Y-%m-%d %H:%M:%S") + " " + message.to_s)
|
406
|
-
end
|
407
|
-
private :log
|
408
|
-
|
409
|
-
##
|
410
|
-
# Only log if verbose logging is enabled.
|
411
|
-
#
|
412
|
-
# @param message message to log
|
413
|
-
#
|
414
|
-
def vlog(message)
|
415
|
-
log(message) if @verbose
|
416
|
-
end
|
417
|
-
private :vlog
|
418
|
-
|
419
466
|
##
|
420
467
|
# A synchronized, backed-up-to-disk queue holding tracks for submission.
|
421
468
|
# The synchronization is only sufficient for a single reader and writer.
|
data/test/test_audioscrobbler.rb
CHANGED
@@ -312,8 +312,7 @@ class TestAudioscrobbler < Test::Unit::TestCase
|
|
312
312
|
a.client = "tst"
|
313
313
|
a.version = "1.1"
|
314
314
|
a.handshake_url = "http://127.0.0.1:#@http_port/handshake"
|
315
|
-
a.
|
316
|
-
|
315
|
+
a.logger.sev_threshold = Logger::UNKNOWN
|
317
316
|
a.start_submitter_thread
|
318
317
|
|
319
318
|
tracks = []
|
@@ -332,16 +331,25 @@ class TestAudioscrobbler < Test::Unit::TestCase
|
|
332
331
|
'Airdrawndagger', 'def', 7))
|
333
332
|
|
334
333
|
tracks.each do |t|
|
335
|
-
a.enqueue(t.artist, t.title, t.length, t.start_time, t.album,
|
336
|
-
|
334
|
+
assert(a.enqueue(t.artist, t.title, t.length, t.start_time, t.album,
|
335
|
+
t.mbid, t.track_num))
|
337
336
|
end
|
338
337
|
|
339
338
|
sleep 0.1 # avoid race condition :-/
|
340
339
|
now_playing.each do |t|
|
341
|
-
a.report_now_playing(t.artist, t.title, t.length, t.album, t.mbid,
|
342
|
-
|
340
|
+
assert(a.report_now_playing(t.artist, t.title, t.length, t.album, t.mbid,
|
341
|
+
t.track_num))
|
343
342
|
end
|
344
343
|
|
344
|
+
# This is lame, but since we have everything set up already, we might
|
345
|
+
# as well make sure that we're not able to submit tracks with broken
|
346
|
+
# metadata. The four things we check are missing artists, missing
|
347
|
+
# titles, tracks shorter than 30 seconds, and invalid start times.
|
348
|
+
assert(!a.report_now_playing('', 'Title', 60, Time.now.to_i))
|
349
|
+
assert(!a.report_now_playing('Artist', '', 60, Time.now.to_i))
|
350
|
+
assert(!a.enqueue('Artist', 'Title', 25, Time.now.to_i))
|
351
|
+
assert(!a.enqueue('Artist', 'Title', 60, 0))
|
352
|
+
|
345
353
|
# FIXME(derat): This is awful. I should add functionality to
|
346
354
|
# Audioscrobbler.enqueue to block until an attempt has been made to
|
347
355
|
# submit the just-enqueued track.
|
@@ -389,7 +397,7 @@ class TestAudioscrobbler < Test::Unit::TestCase
|
|
389
397
|
a.client = "tst"
|
390
398
|
a.version = "1.1"
|
391
399
|
a.handshake_url = "http://127.0.0.1:#@http_port/handshake"
|
392
|
-
a.
|
400
|
+
a.logger.sev_threshold = Logger::UNKNOWN
|
393
401
|
a.start_submitter_thread
|
394
402
|
|
395
403
|
assert(!a.report_now_playing('artist', 'title', 100, 'album', 'mbid', 1))
|
metadata
CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.9.3
|
|
3
3
|
specification_version: 1
|
4
4
|
name: audioscrobbler
|
5
5
|
version: !ruby/object:Gem::Version
|
6
|
-
version: 0.1.
|
7
|
-
date: 2007-10-
|
6
|
+
version: 0.1.1
|
7
|
+
date: 2007-10-30 00:00:00 -07:00
|
8
8
|
summary: Library to submit music playlists to Last.fm
|
9
9
|
require_paths:
|
10
10
|
- lib
|