audioscrobbler 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,266 @@
1
+ #!/usr/bin/ruby -w
2
+ #
3
+ # = Name
4
+ # TestAudioscrobbler
5
+ #
6
+ # == Description
7
+ # This file contains a regression test for the Audioscrobbler class, in the
8
+ # unfortunate form of a small, useless implementation of an Audioscrobbler
9
+ # track submission server. :(
10
+ #
11
+ # == Author
12
+ # Daniel Erat <dan-ruby@erat.org>
13
+ #
14
+ # == Copyright
15
+ # Copyright 2005 Daniel Erat
16
+ #
17
+ # == License
18
+ # GNU GPL; see COPYING
19
+
20
+ require 'audioscrobbler'
21
+ require 'cgi'
22
+ require 'md5'
23
+ require 'test/unit'
24
+ require 'thread'
25
+ require 'time'
26
+ require 'webrick'
27
+
28
+ # Stores the outcome of handshakes received by a
29
+ # AudioscrobblerHandshakeServlet object.
30
+ class AudioscrobblerHandshakeStatus
31
+ def initialize
32
+ clear
33
+ end
34
+ attr_accessor :attempts, :successes, :failures
35
+
36
+ def clear
37
+ @attempts = 0
38
+ @successes = 0
39
+ @failures = 0
40
+ self
41
+ end
42
+ end
43
+
44
+
45
+ # Implements the handshake portion of the Audioscrobbler protocol.
46
+ class AudioscrobblerHandshakeServlet < WEBrick::HTTPServlet::AbstractServlet
47
+ def initialize(server, status, user, challenge, submit_url,
48
+ plugin_name='tst', plugin_version=0.1)
49
+ @status = status
50
+ @user = user
51
+ @challenge = challenge
52
+ @submit_url = submit_url
53
+ @plugin_name = plugin_name
54
+ @plugin_version = plugin_version
55
+ @failure_interval = 1
56
+ end
57
+
58
+ def do_GET(req, res)
59
+ @status.attempts += 1
60
+ res['Content-Type'] = "text/plain"
61
+
62
+ # Make sure that all of the parameters were supplied and match what we
63
+ # were expecting.
64
+ begin
65
+ if req.query['hs'] != 'true'
66
+ throw "FAILED Handshake not requested"
67
+ elsif req.query['p'] != '1.1'
68
+ throw "FAILED Wrong or missing protocol version"
69
+ elsif req.query['c'] != @plugin_name
70
+ throw "FAILED Wrong or missing plugin name"
71
+ elsif not req.query['v']
72
+ throw "FAILED Missing plugin version"
73
+ elsif req.query['u'] != @user
74
+ throw "BADUSER"
75
+ end
76
+
77
+ # TODO(derat): Test UPDATE responses?
78
+ @status.successes += 1
79
+ res.body = "UPTODATE\n#@challenge\n#@submit_url\nINTERVAL 0"
80
+
81
+ rescue RuntimeError
82
+ @status.failures += 1
83
+ res.body = "#{$!.message}\nINTERVAL #@failure_interval"
84
+ end
85
+ end # do_GET
86
+ end # AudioscrobblerHandshakeServlet
87
+
88
+
89
+ # Stores a submitted track. Used by AudioscrobblerSubmitStatus.
90
+ class Track
91
+ def initialize(artist=nil, title=nil, length=nil, start_time=nil,
92
+ album=nil, mbid=nil)
93
+ @artist = artist
94
+ @title = title
95
+ @length = length
96
+ @start_time = start_time
97
+ @album = album
98
+ @mbid = mbid
99
+ end
100
+ attr_accessor :artist, :title, :length, :start_time, :album, :mbid
101
+
102
+ def ==(other)
103
+ other.class == Track and @artist == other.artist and
104
+ @title == other.title and @length == other.length and
105
+ @start_time == other.start_time and @album == other.album and
106
+ @mbid == other.mbid
107
+ end
108
+ end
109
+
110
+
111
+ # Stores the outcome of track submissions received by a
112
+ # AudioscrobblerSubmitServlet object.
113
+ class AudioscrobblerSubmitStatus
114
+ def initialize
115
+ clear
116
+ end
117
+ attr_accessor :attempts, :successes, :failures, :tracks
118
+
119
+ def clear
120
+ @attempts = 0
121
+ @successes = 0
122
+ @failures = 0
123
+ @tracks = []
124
+ self
125
+ end
126
+ end
127
+
128
+
129
+ # Implements the track submission portion of the Audioscrobbler protocol.
130
+ class AudioscrobblerSubmitServlet < WEBrick::HTTPServlet::AbstractServlet
131
+ def initialize(server, status, user, password, challenge)
132
+ @status = status
133
+ @user = user
134
+ @md5 = MD5.hexdigest(MD5.hexdigest(password) + challenge)
135
+ @failure_interval = 1
136
+ end
137
+
138
+ # POST is supposed to be used rather than GET.
139
+ def do_GET(req, res)
140
+ @status.attempts += 1
141
+ @status.failures += 1
142
+ res.body = "FAILED Use POST, not GET\nINTERVAL #@failure_interval"
143
+ end
144
+
145
+ def do_POST(req, res)
146
+ @status.attempts += 1
147
+ res['Content-Type'] = "text/plain"
148
+
149
+ begin
150
+ # Make sure that they authenticated correctly.
151
+ if req.query['u'] != @user
152
+ raise "BADAUTH"
153
+ elsif req.query['s'] != @md5
154
+ raise "BADAUTH"
155
+ end
156
+
157
+ # Handle the track parameters.
158
+ tracks = []
159
+ req.query.each_pair do |k, v|
160
+ if k =~ /^([atbmli])\[(\d+)\]$/
161
+ track = (tracks[$2.to_i] ||= Track.new)
162
+ v = CGI.unescape(v)
163
+ case
164
+ when $1 == 'a': track.artist = v
165
+ when $1 == 't': track.title = v
166
+ when $1 == 'b': track.album = v
167
+ when $1 == 'm': track.mbid = v
168
+ when $1 == 'l': track.length = v.to_i
169
+ when $1 == 'i':
170
+ # We get start times in a strange format, but it's more
171
+ # convenient to store them as seconds since the epoch.
172
+ v =~ /^(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})$/
173
+ track.start_time = Time.gm($1, $2, $3, $4, $5, $6).to_i
174
+ end
175
+ end
176
+ end
177
+
178
+ # Make sure that no data was missing from the submitted tracks.
179
+ tracks.each do |track|
180
+ if not track
181
+ raise "FAILED Missing track"
182
+ elsif not track.artist or not track.title or not track.album or
183
+ not track.mbid or not track.length or not track.start_time
184
+ raise "FAILED Missing parameter"
185
+ elsif track.artist.length == 0 or track.title.length == 0 or
186
+ track.length == 0 or track.start_time == 0
187
+ raise "FAILED Empty required parameter"
188
+ end
189
+ end
190
+
191
+ # Make sure that we didn't get too few or too many tracks.
192
+ if tracks.length == 0
193
+ raise "FAILED No tracks supplied"
194
+ elsif tracks.length > 10
195
+ raise "FAILED More than 10 tracks supplied"
196
+ end
197
+
198
+ # If we get here, then we didn't find any problems.
199
+ @status.tracks += tracks
200
+ @status.successes += 1
201
+ res.body = "OK\nINTERVAL 0"
202
+
203
+ # If we threw an exception, return an error.
204
+ rescue RuntimeError
205
+ @status.failures += 1
206
+ res.body = "#{$!.message}\nINTERVAL #@failure_interval"
207
+ end
208
+ end # do_POST
209
+ end # AudioscrobblerSubmitServlet
210
+
211
+
212
+ # Regression test for the Audioscrobbler class.
213
+ class TestAudioscrobbler < Test::Unit::TestCase
214
+ def test_audioscrobbler
215
+ @handshake_status = AudioscrobblerHandshakeStatus.new
216
+ @submit_status = AudioscrobblerSubmitStatus.new
217
+ # FIXME(derat): Is there some better way to choose a port here?
218
+ @http_port = 16349
219
+
220
+ @server_thread = Thread.new do
221
+ s = WEBrick::HTTPServer.new(:BindAddress => "127.0.0.1",
222
+ :Port => @http_port)
223
+ s.mount("/handshake", AudioscrobblerHandshakeServlet, @handshake_status,
224
+ "username", "challenge", "http://127.0.0.1:#@http_port/submit")
225
+ s.mount("/submit", AudioscrobblerSubmitServlet, @submit_status,
226
+ "username", "password", "challenge")
227
+ trap("INT") { s.shutdown }
228
+ s.start
229
+ end
230
+
231
+ a = Audioscrobbler.new("username", "password")
232
+ a.client = "tst"
233
+ a.version = "1.1"
234
+ a.handshake_uri = "http://127.0.0.1:#@http_port/handshake"
235
+ a.verbose = true
236
+
237
+ a.start_submitter_thread
238
+
239
+ tracks = []
240
+ tracks.push(Track.new("Beck", "Devil's Haircut", 100, 1128285297,
241
+ "Odelay", ''))
242
+ tracks.push(Track.new("Faith No More", "Midlife Crisis", 100, 1128285297,
243
+ "Angel Dust", ''))
244
+ tracks.push(Track.new("Hans Zimmer", "Greed", 100, 1128285297,
245
+ "Broken Arrow", ''))
246
+ tracks.push(Track.new("M�m", "Sleepswim", 100, 1128285297,
247
+ "Finally We Are No One", ''))
248
+
249
+ tracks.each do |t|
250
+ a.enqueue(t.artist, t.title, t.length, t.start_time, t.album, t.mbid)
251
+ end
252
+
253
+ # FIXME(derat): This is awful. I should add functionality to
254
+ # Audioscrobbler.enqueue to block until an attempt has been made to
255
+ # submit the just-enqueued track.
256
+ sleep 3
257
+
258
+ assert_equal(@handshake_status.failures, 0)
259
+ assert(@handshake_status.successes > 0)
260
+
261
+ assert_equal(@submit_status.failures, 0)
262
+ assert(@submit_status.successes > 0)
263
+
264
+ assert_equal(@submit_status.tracks, tracks)
265
+ end # test_audioscrobbler
266
+ end # TestAudioscrobbler
@@ -0,0 +1,86 @@
1
+ #!/usr/bin/ruby -w
2
+ #
3
+ # = Name
4
+ # TestPlayedTrack, TestSubmissionQueue
5
+ #
6
+ # == Description
7
+ # This file contains unit tests for the PlayedTrack and SubmissionQueue
8
+ # components of the Audioscrobbler class.
9
+ #
10
+ # == Author
11
+ # Daniel Erat <dan-ruby@erat.org>
12
+ #
13
+ # == Copyright
14
+ # Copyright 2005 Daniel Erat
15
+ #
16
+ # == License
17
+ # GNU GPL; see COPYING
18
+
19
+ require 'audioscrobbler'
20
+ require 'tempfile'
21
+ require 'test/unit'
22
+
23
+ class Audioscrobbler
24
+ class SubmissionQueue
25
+ class TestPlayedTrack < Test::Unit::TestCase
26
+ def test_members
27
+ t = PlayedTrack.new(
28
+ 'Cylob', 'Stomping FM', 100, 1128285297, 'Lobster Tracks', nil)
29
+ assert_equal(t.artist, 'Cylob')
30
+ assert_equal(t.title, 'Stomping FM')
31
+ assert_equal(t.length, 100)
32
+ assert_equal(t.start_time, 1128285297)
33
+ assert_equal(t.album, 'Lobster Tracks')
34
+ assert_equal(t.mbid, '') # nil should be converted to ''
35
+ end
36
+
37
+ def test_serialization
38
+ t = PlayedTrack.new(
39
+ 'Katamari Damacy', 'WANDA WANDA', 100, 1128285297, 'OST', 'blah')
40
+ assert_equal(t,
41
+ PlayedTrack.deserialize(t.serialize))
42
+
43
+ t = PlayedTrack.new(
44
+ 'Def Leppard', 'Photograph', 100, 1128285297, nil, 'blah')
45
+ assert_equal(t,
46
+ PlayedTrack.deserialize(t.serialize))
47
+ end
48
+ end # class TestPlayedTrack
49
+ end # class SubmissionQueue
50
+
51
+ class TestSubmissionQueue < Test::Unit::TestCase
52
+ def setup
53
+ file = Tempfile.new('audioscrobbler_test_queue')
54
+ @filename = file.path
55
+ file.close
56
+ end
57
+
58
+ def test_it
59
+ q = SubmissionQueue.new(@filename)
60
+
61
+ q.append('Kraftwerk', 'Computer Love', 100, 1128285297,
62
+ 'Computer World', nil)
63
+ q.append('Iron Maiden', 'Aces High', 100, 1128285297,
64
+ 'Powerslave', nil)
65
+ q.append('Herbie Hancock', 'Cantaloupe Island', 100, 1128285297,
66
+ 'Empyrean Isles', nil)
67
+
68
+ # Create a new queue, pointing at the same backup file.
69
+ q = SubmissionQueue.new(@filename)
70
+
71
+ tracks = q.peek(1)
72
+ assert_equal(tracks.length, 1)
73
+ assert_equal(tracks[0].artist, 'Kraftwerk')
74
+
75
+ q.delete(1)
76
+ tracks = q.peek(5)
77
+ assert_equal(tracks.length, 2)
78
+ assert_equal(tracks[0].artist, 'Iron Maiden')
79
+ assert_equal(tracks[1].artist, 'Herbie Hancock')
80
+ end
81
+
82
+ def teardown
83
+ File.delete(@filename) if File.exists?(@filename)
84
+ end
85
+ end # class TestSubmissionQueue
86
+ end # class AudioScrobbler
metadata ADDED
@@ -0,0 +1,51 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.8.11
3
+ specification_version: 1
4
+ name: audioscrobbler
5
+ version: !ruby/object:Gem::Version
6
+ version: 0.0.1
7
+ date: 2005-10-02 00:00:00 -07:00
8
+ summary: Library to submit music playlists to Last.fm
9
+ require_paths:
10
+ - lib
11
+ email: dan-ruby@erat.org
12
+ homepage: http://www.erat.org/ruby/
13
+ rubyforge_project:
14
+ description:
15
+ autorequire: audioscrobbler
16
+ default_executable:
17
+ bindir: bin
18
+ has_rdoc: true
19
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
20
+ requirements:
21
+ -
22
+ - ">"
23
+ - !ruby/object:Gem::Version
24
+ version: 0.0.0
25
+ version:
26
+ platform: ruby
27
+ signing_key:
28
+ cert_chain:
29
+ authors:
30
+ - Daniel Erat
31
+ files:
32
+ - MANIFEST
33
+ - COPYING
34
+ - INSTALL
35
+ - setup.rb
36
+ - README
37
+ - lib
38
+ - test
39
+ - audioscrobbler.gemspec
40
+ - lib/audioscrobbler.rb
41
+ - test/test_audioscrobbler.rb
42
+ - test/test_queue.rb
43
+ test_files:
44
+ - test/test_audioscrobbler.rb
45
+ - test/test_queue.rb
46
+ rdoc_options: []
47
+ extra_rdoc_files: []
48
+ executables: []
49
+ extensions: []
50
+ requirements: []
51
+ dependencies: []