audioscrobbler 0.0.1

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