hallon 0.16.0 → 0.17.0
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.
- data/.gitignore +2 -1
- data/.travis.yml +2 -0
- data/CHANGELOG.md +22 -1
- data/Gemfile +2 -2
- data/README.markdown +2 -2
- data/Rakefile +69 -42
- data/hallon.gemspec +1 -1
- data/lib/hallon.rb +3 -2
- data/lib/hallon/album.rb +6 -4
- data/lib/hallon/artist.rb +6 -4
- data/lib/hallon/audio_queue.rb +1 -1
- data/lib/hallon/base.rb +4 -0
- data/lib/hallon/blob.rb +6 -0
- data/lib/hallon/error.rb +10 -41
- data/lib/hallon/ext/spotify.rb +1 -146
- data/lib/hallon/image.rb +8 -0
- data/lib/hallon/linkable.rb +6 -0
- data/lib/hallon/loadable.rb +6 -0
- data/lib/hallon/observable.rb +1 -1
- data/lib/hallon/observable/playlist_container.rb +2 -2
- data/lib/hallon/observable/session.rb +34 -0
- data/lib/hallon/player.rb +7 -3
- data/lib/hallon/playlist.rb +5 -1
- data/lib/hallon/playlist_container.rb +9 -8
- data/lib/hallon/scrobbler.rb +103 -0
- data/lib/hallon/search.rb +1 -0
- data/lib/hallon/session.rb +69 -13
- data/lib/hallon/toplist.rb +1 -1
- data/lib/hallon/track.rb +2 -2
- data/lib/hallon/version.rb +1 -1
- data/spec/hallon/album_spec.rb +16 -0
- data/spec/hallon/artist_spec.rb +16 -0
- data/spec/hallon/base_spec.rb +1 -1
- data/spec/hallon/error_spec.rb +3 -3
- data/spec/hallon/hallon_spec.rb +1 -1
- data/spec/hallon/image_spec.rb +6 -0
- data/spec/hallon/observable/session_spec.rb +20 -0
- data/spec/hallon/scrobbler_spec.rb +119 -0
- data/spec/hallon/session_spec.rb +38 -4
- data/spec/hallon/spotify_spec.rb +0 -45
- data/spec/mockspotify.rb +6 -1
- data/spec/spec_helper.rb +4 -5
- metadata +59 -20
- data/spec/support/cover_me.rb +0 -7
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -4,6 +4,25 @@ Hallon’s Changelog
|
|
4
4
|
[HEAD][]
|
5
5
|
------------------
|
6
6
|
|
7
|
+
[v0.17.0][]
|
8
|
+
------------------
|
9
|
+
Updated to libspotify v12.1.45, with all the good parts included!
|
10
|
+
|
11
|
+
__Added__
|
12
|
+
|
13
|
+
- New session callbacks [2f6e27f9]
|
14
|
+
- Support for image sizes [5f8156c]
|
15
|
+
- Add Session#username [4cce4c55]
|
16
|
+
- Support for specifying session proxy in Session#initialize [c61e5c5]
|
17
|
+
- Add Session#private? and Session#private= [bcf2805] (1000th commit!)
|
18
|
+
- Support for audio scrobbling [955a1b6, 627e0b, cf7957]
|
19
|
+
|
20
|
+
__Changed__
|
21
|
+
|
22
|
+
- Extracted Spotify::Pointer into Spotify gem [5cc88e9d]
|
23
|
+
- Move GC-wrapped functions to the Spotify gem [d8eda4]
|
24
|
+
- Moved error handling to Spotify gem [d31af5a]
|
25
|
+
|
7
26
|
[v0.16.0][]
|
8
27
|
------------------
|
9
28
|
This release brings a lot of changes to the Hallon test suite, mainly to make
|
@@ -361,4 +380,6 @@ easier to handle.
|
|
361
380
|
[v0.13.0]: https://github.com/Burgestrand/Hallon/compare/v0.12.0...v0.13.0
|
362
381
|
[v0.14.0]: https://github.com/Burgestrand/Hallon/compare/v0.13.0...v0.14.0
|
363
382
|
[v0.15.0]: https://github.com/Burgestrand/Hallon/compare/v0.14.0...v0.15.0
|
364
|
-
[
|
383
|
+
[v0.16.0]: https://github.com/Burgestrand/Hallon/compare/v0.15.0...v0.16.0
|
384
|
+
[v0.17.0]: https://github.com/Burgestrand/Hallon/compare/v0.16.0...v0.17.0
|
385
|
+
[HEAD]: https://github.com/Burgestrand/Hallon/compare/v0.17.0...HEAD
|
data/Gemfile
CHANGED
data/README.markdown
CHANGED
@@ -129,7 +129,7 @@ Hallon makes use of Ruby’s own garbage collection to automatically release lib
|
|
129
129
|
|
130
130
|
Audio support
|
131
131
|
-------------
|
132
|
-
Hallon supports streaming audio from Spotify via [Hallon::Player][]. When you create the player you give it your
|
132
|
+
Hallon supports streaming audio from Spotify via [Hallon::Player][]. When you create the player you give it your audio driver of choice, which the player will then use for audio playback.
|
133
133
|
|
134
134
|
```ruby
|
135
135
|
require 'hallon'
|
@@ -141,7 +141,7 @@ session.login!('username', 'password')
|
|
141
141
|
track = Hallon::Track.new("spotify:track:1ZPsdTkzhDeHjA5c2Rnt2I")
|
142
142
|
track.load
|
143
143
|
|
144
|
-
player = Hallon::Player.new(
|
144
|
+
player = Hallon::Player.new(Hallon::OpenAL)
|
145
145
|
player.play!(track)
|
146
146
|
```
|
147
147
|
|
data/Rakefile
CHANGED
@@ -14,23 +14,55 @@ YARD::Rake::YardocTask.new
|
|
14
14
|
require 'rspec/core/rake_task'
|
15
15
|
RSpec::Core::RakeTask.new('spec') do |task|
|
16
16
|
task.ruby_opts = '-W2'
|
17
|
-
|
17
|
+
task.rspec_opts = '-rsimplecov'
|
18
18
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
19
|
+
# rspec does not do this for us if we specify rspec_opts
|
20
|
+
opts_file = File.expand_path('.rspec', File.dirname(__FILE__))
|
21
|
+
opts_string = File.readlines(opts_file).map(&:rstrip).join(' ')
|
22
|
+
task.rspec_opts += ' ' + opts_string
|
23
|
+
end
|
24
24
|
|
25
|
-
|
26
|
-
|
25
|
+
desc "Start a console with Hallon loaded (no other setup is done)"
|
26
|
+
task :console do
|
27
|
+
exec 'pry -Ilib -rhallon'
|
27
28
|
end
|
28
29
|
|
29
30
|
desc "Process the Hallon codebase, finding out which Spotify methods are being used"
|
30
31
|
task 'spotify:coverage' do
|
31
32
|
require 'bundler/setup'
|
32
33
|
|
34
|
+
require 'pry'
|
33
35
|
require 'set'
|
36
|
+
|
37
|
+
module Spotify
|
38
|
+
# Wrapped functions return pointers that are auto-GC’d by Ruby,
|
39
|
+
# so we ignore add_ref and release for these methods; but since
|
40
|
+
# we don’t know their type by mere name, we must resort to this
|
41
|
+
# hack to do it automatically (because we can).
|
42
|
+
class << self
|
43
|
+
def lookup_return_value(name)
|
44
|
+
@function_to_return_type[name.to_s]
|
45
|
+
end
|
46
|
+
|
47
|
+
def define_singleton_method(name, &block)
|
48
|
+
return_type = block.binding.eval <<-CODE
|
49
|
+
begin
|
50
|
+
return_type if __method__ == :wrap_function
|
51
|
+
rescue NameError
|
52
|
+
nil
|
53
|
+
end
|
54
|
+
CODE
|
55
|
+
|
56
|
+
if return_type
|
57
|
+
@function_to_return_type ||= {}
|
58
|
+
@function_to_return_type[name.to_s] = return_type
|
59
|
+
end
|
60
|
+
|
61
|
+
super
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
34
66
|
require 'spotify'
|
35
67
|
|
36
68
|
begin
|
@@ -40,33 +72,17 @@ task 'spotify:coverage' do
|
|
40
72
|
abort
|
41
73
|
end
|
42
74
|
|
43
|
-
# Wrapped functions return pointers that are auto-GC’d by Ruby,
|
44
|
-
# so we ignore add_ref and release for these methods; but since
|
45
|
-
# we don’t know their type by mere name, we must resort to this
|
46
|
-
# hack to do it automatically (because we can).
|
47
|
-
class << Spotify
|
48
|
-
def lookup_return_value(name)
|
49
|
-
@function_to_return_type[name.to_s]
|
50
|
-
end
|
51
|
-
|
52
|
-
def define_singleton_method(name, &block)
|
53
|
-
return_type = block.binding.eval("return_type")
|
54
|
-
@function_to_return_type ||= {}
|
55
|
-
@function_to_return_type[name.to_s] = return_type
|
56
|
-
|
57
|
-
super
|
58
|
-
end
|
59
|
-
end
|
60
|
-
require 'hallon/ext/spotify'
|
61
|
-
|
62
75
|
methods = Spotify.methods(false).map(&:to_s)
|
63
|
-
auto_gc = Set.new(methods.grep(/!\z/))
|
76
|
+
auto_gc = Set.new(methods.grep(/!\z/).select { |m| Spotify.lookup_return_value(m) })
|
77
|
+
auto_err = Set.new(methods.grep(/!\z/)).difference(auto_gc)
|
64
78
|
covered = Set.new(methods)
|
65
79
|
warning = []
|
66
80
|
ignored = [
|
67
81
|
'attach_function', # spotify overloads this
|
68
|
-
'session_release', # segfaults on libspotify <= 9
|
82
|
+
'session_release', # segfaults on libspotify <= 9, and sometimes deadlocks on libspotify <= v12
|
83
|
+
'session_release!', # …
|
69
84
|
'session_userdata', # wont support this
|
85
|
+
'error_message', # supported by Hallon::Error.explain
|
70
86
|
'link_as_track', # using link_as_track_and_offset instead
|
71
87
|
'link_as_track!', # using link_as_track_and_offset! instead
|
72
88
|
'wrap_function', # not a spotify function
|
@@ -89,13 +105,25 @@ task 'spotify:coverage' do
|
|
89
105
|
|
90
106
|
result = [meth]
|
91
107
|
|
92
|
-
|
93
|
-
|
94
|
-
|
108
|
+
auto_err_lookup = meth.to_s.delete('!') + '!' # just one !
|
109
|
+
|
110
|
+
# if it has auto-error, we account for both versions, just assume
|
111
|
+
# we are doing the right thing here
|
112
|
+
if auto_err.member?(auto_err_lookup)
|
113
|
+
result << auto_err_lookup
|
114
|
+
result << auto_err_lookup.delete('!')
|
115
|
+
result.uniq!
|
116
|
+
end
|
95
117
|
|
96
|
-
|
97
|
-
|
98
|
-
|
118
|
+
if meth =~ /(.+)!\z/
|
119
|
+
# if it’s auto-GC’d, we can also account for _release and _add_ref
|
120
|
+
if (return_type = Spotify.lookup_return_value(meth))
|
121
|
+
result << $1
|
122
|
+
result << "#{return_type}_release"
|
123
|
+
result << "#{return_type}_add_ref"
|
124
|
+
result << "#{return_type}_release!"
|
125
|
+
result << "#{return_type}_add_ref!"
|
126
|
+
end
|
99
127
|
end
|
100
128
|
|
101
129
|
result
|
@@ -104,21 +132,19 @@ task 'spotify:coverage' do
|
|
104
132
|
# DSL Methods
|
105
133
|
no_receiver = handlers[nil] = Hash.new(silencer)
|
106
134
|
no_receiver[:from_link] = no_receiver[:to_link] = proc do |recv, meth, (_, name)|
|
107
|
-
next unless name.respond_to?(:value)
|
108
135
|
prefix = meth == :to_link ? "link_create" : "link"
|
109
|
-
method = "%s_%s" % [prefix, name
|
136
|
+
method = "%s_%s" % [prefix, name]
|
110
137
|
[method, "#{method}!"]
|
111
138
|
end
|
112
139
|
|
113
140
|
# Hallon::Enumerator
|
114
141
|
no_receiver[:size] = proc do |recv, meth, (_, name)|
|
115
|
-
name
|
142
|
+
name
|
116
143
|
end
|
117
144
|
|
118
145
|
# Hallon::Enumerator
|
119
146
|
no_receiver[:item] = proc do |recv, meth, (_, name)|
|
120
|
-
|
121
|
-
method = name.value.to_s
|
147
|
+
method = name.to_s
|
122
148
|
[method.delete("!"), method]
|
123
149
|
end
|
124
150
|
|
@@ -133,13 +159,14 @@ task 'spotify:coverage' do
|
|
133
159
|
covered.subtract Array(name).map(&:to_s)
|
134
160
|
end
|
135
161
|
rescue => e
|
136
|
-
fails[file] = e.message.strip + " (#{e.class.name})"
|
162
|
+
fails[file] = e.message.strip + " (#{e.class.name} #{e.backtrace[0..3]})"
|
137
163
|
end
|
138
164
|
end
|
139
165
|
|
140
166
|
covered.group_by { |m| m[/[^_]+/] }.each_pair do |group, methods|
|
141
167
|
puts "#{group.capitalize}:"
|
142
|
-
methods.
|
168
|
+
no_bangs = methods.map(&:to_s).map { |m| m.delete('!') }.uniq
|
169
|
+
no_bangs.each do |m|
|
143
170
|
puts " #{m}"
|
144
171
|
end
|
145
172
|
puts
|
data/hallon.gemspec
CHANGED
@@ -22,7 +22,7 @@ Gem::Specification.new do |gem|
|
|
22
22
|
gem.required_ruby_version = '>= 1.9'
|
23
23
|
|
24
24
|
gem.add_dependency 'ref', '~> 1.0'
|
25
|
-
gem.add_dependency 'spotify', '~>
|
25
|
+
gem.add_dependency 'spotify', '~> 12.0.2'
|
26
26
|
gem.add_development_dependency 'rake', '~> 0.8'
|
27
27
|
gem.add_development_dependency 'rspec', '~> 2'
|
28
28
|
gem.add_development_dependency 'yard'
|
data/lib/hallon.rb
CHANGED
@@ -13,6 +13,7 @@ require 'hallon/base'
|
|
13
13
|
require 'hallon/enumerator'
|
14
14
|
require 'hallon/audio_queue'
|
15
15
|
require 'hallon/blob'
|
16
|
+
require 'hallon/scrobbler'
|
16
17
|
|
17
18
|
require 'hallon/observable/album_browse'
|
18
19
|
require 'hallon/observable/artist_browse'
|
@@ -68,7 +69,7 @@ module Hallon
|
|
68
69
|
))
|
69
70
|
/x
|
70
71
|
|
71
|
-
# Thrown by {Loadable#load} and {Playlist#
|
72
|
+
# Thrown by {Loadable#load} and {Playlist#upload} on failure.
|
72
73
|
TimeoutError = Class.new(Hallon::Error)
|
73
74
|
|
74
75
|
# Raised by Session.instance
|
@@ -85,7 +86,7 @@ module Hallon
|
|
85
86
|
# @return [Numeric] default load timeout in seconds, used in {Loadable#load}.
|
86
87
|
attr_reader :load_timeout
|
87
88
|
|
88
|
-
# @param [Numeric]
|
89
|
+
# @param [Numeric] new_timeout default load_timeout in seconds for {Loadable#load}.
|
89
90
|
def load_timeout=(new_timeout)
|
90
91
|
if new_timeout < 0
|
91
92
|
raise ArgumentError, "timeout cannot be negative"
|
data/lib/hallon/album.rb
CHANGED
@@ -79,16 +79,18 @@ module Hallon
|
|
79
79
|
end
|
80
80
|
|
81
81
|
# @see cover_link
|
82
|
+
# @param [Symbol] size (see {Image.sizes})
|
82
83
|
# @return [Image, nil] album cover as an Image.
|
83
|
-
def cover
|
84
|
-
cover = Spotify.album_cover(pointer)
|
84
|
+
def cover(size = :normal)
|
85
|
+
cover = Spotify.album_cover(pointer, size)
|
85
86
|
Image.from(cover)
|
86
87
|
end
|
87
88
|
|
88
89
|
# @see cover
|
90
|
+
# @param [Symbol] size (see {Image.sizes})
|
89
91
|
# @return [Link, nil] album cover as a spotify URI.
|
90
|
-
def cover_link
|
91
|
-
cover = Spotify.link_create_from_album_cover!(pointer)
|
92
|
+
def cover_link(size = :normal)
|
93
|
+
cover = Spotify.link_create_from_album_cover!(pointer, size)
|
92
94
|
Link.from(cover)
|
93
95
|
end
|
94
96
|
|
data/lib/hallon/artist.rb
CHANGED
@@ -44,16 +44,18 @@ module Hallon
|
|
44
44
|
end
|
45
45
|
|
46
46
|
# @see portrait_link
|
47
|
+
# @param [Symbol] size (see {Image.sizes})
|
47
48
|
# @return [Image, nil] artist portrait as an Image.
|
48
|
-
def portrait
|
49
|
-
portrait = Spotify.artist_portrait(pointer)
|
49
|
+
def portrait(size = :normal)
|
50
|
+
portrait = Spotify.artist_portrait(pointer, size)
|
50
51
|
Image.from(portrait)
|
51
52
|
end
|
52
53
|
|
53
54
|
# @see portrait
|
55
|
+
# @param [Symbol] size (see {Image.sizes})
|
54
56
|
# @return [Link, nil] artist portrait as a Link.
|
55
|
-
def portrait_link
|
56
|
-
portrait = Spotify.link_create_from_artist_portrait!(pointer)
|
57
|
+
def portrait_link(size = :normal)
|
58
|
+
portrait = Spotify.link_create_from_artist_portrait!(pointer, size)
|
57
59
|
Link.from(portrait)
|
58
60
|
end
|
59
61
|
|
data/lib/hallon/audio_queue.rb
CHANGED
data/lib/hallon/base.rb
CHANGED
@@ -40,6 +40,8 @@ module Hallon
|
|
40
40
|
|
41
41
|
private
|
42
42
|
|
43
|
+
# See {Linkable::ClassMethods#to_link}.
|
44
|
+
#
|
43
45
|
# @macro [attach] to_link
|
44
46
|
# @method to_link
|
45
47
|
# @scope instance
|
@@ -49,6 +51,8 @@ module Hallon
|
|
49
51
|
# {Linkable} for the actual source
|
50
52
|
end
|
51
53
|
|
54
|
+
# See {Linkable::ClassMethods#from_link}.
|
55
|
+
#
|
52
56
|
# @macro [attach] from_link
|
53
57
|
# @method from_link
|
54
58
|
# @scope instance
|
data/lib/hallon/blob.rb
CHANGED
@@ -5,6 +5,12 @@ module Hallon
|
|
5
5
|
module Blob
|
6
6
|
end
|
7
7
|
|
8
|
+
# Used to mark strings as Hallon::Blob for Session#login.
|
9
|
+
#
|
10
|
+
# @example creating a string blob
|
11
|
+
# blob = Hallon::Blob("this is now a blob")
|
12
|
+
#
|
13
|
+
# @return [String<Hallon::Blob>] a string that is now a Blob
|
8
14
|
def self.Blob(string)
|
9
15
|
string.extend(Blob)
|
10
16
|
end
|
data/lib/hallon/error.rb
CHANGED
@@ -2,8 +2,13 @@
|
|
2
2
|
module Hallon
|
3
3
|
# Thrown by Hallon on libspotify errors.
|
4
4
|
#
|
5
|
+
# Hallon::Error inherits two methods from Spotify::Error:
|
6
|
+
#
|
7
|
+
# - Hallon::Error.explain(error) - from a Spotify error, create a descriptive string of it
|
8
|
+
# - Hallon::Error.disambiguate(error) - return the tuple of [code, symbol] of a given Spotify error
|
9
|
+
#
|
5
10
|
# @see http://developer.spotify.com/en/libspotify/docs/group__error.html
|
6
|
-
class Error <
|
11
|
+
class Error < Spotify::Error
|
7
12
|
class << self
|
8
13
|
# Hash of error (Symbol) to code (Integer).
|
9
14
|
#
|
@@ -12,38 +17,6 @@ module Hallon
|
|
12
17
|
Spotify.enum_type(:error).to_hash
|
13
18
|
end
|
14
19
|
|
15
|
-
# Given a number or a symbol, find both the symbol and the error
|
16
|
-
# number it represents.
|
17
|
-
#
|
18
|
-
# @param [Symbol, Fixnum] error
|
19
|
-
# @return [[Fixnum, Symbol]] (error code, error symbol)
|
20
|
-
def disambiguate(error)
|
21
|
-
@enum ||= Spotify.enum_type(:error)
|
22
|
-
|
23
|
-
if error.is_a? Symbol
|
24
|
-
error = @enum[symbol = error]
|
25
|
-
else
|
26
|
-
symbol = @enum[error]
|
27
|
-
end
|
28
|
-
|
29
|
-
if error.nil? || symbol.nil?
|
30
|
-
[-1, nil]
|
31
|
-
else
|
32
|
-
[error, symbol]
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
# Explain a Spotify error with a string message.
|
37
|
-
#
|
38
|
-
# @example
|
39
|
-
# Hallon::Error.explain(:ok) # => "No error"
|
40
|
-
#
|
41
|
-
# @param [Fixnum, Symbol]
|
42
|
-
# @return [String]
|
43
|
-
def explain(error)
|
44
|
-
Spotify.error_message disambiguate(error)[0]
|
45
|
-
end
|
46
|
-
|
47
20
|
# Raise an {Error} with the given errno, unless it is `nil`, `:timeout`, `0` or `:ok`.
|
48
21
|
#
|
49
22
|
# @example
|
@@ -54,18 +27,14 @@ module Hallon
|
|
54
27
|
# @param [Hash] options
|
55
28
|
# @option options [Array] :ignore ([]) other values to ignore of error
|
56
29
|
# @return [nil]
|
57
|
-
def maybe_raise(
|
30
|
+
def maybe_raise(error, options = {})
|
58
31
|
ignore = [nil, :timeout] + Array(options[:ignore])
|
59
|
-
return nil if ignore.include?(
|
32
|
+
return nil if ignore.include?(error)
|
60
33
|
|
61
|
-
error, symbol = disambiguate(
|
34
|
+
error, symbol = disambiguate(error)
|
62
35
|
return symbol if symbol == :ok
|
63
36
|
|
64
|
-
|
65
|
-
message << "[#{symbol.to_s.upcase}]"
|
66
|
-
message << explain(error)
|
67
|
-
message << "(#{error})"
|
68
|
-
raise Hallon::Error, message.join(' ')
|
37
|
+
raise self, explain(error)
|
69
38
|
end
|
70
39
|
end
|
71
40
|
end
|