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.
Files changed (44) hide show
  1. data/.gitignore +2 -1
  2. data/.travis.yml +2 -0
  3. data/CHANGELOG.md +22 -1
  4. data/Gemfile +2 -2
  5. data/README.markdown +2 -2
  6. data/Rakefile +69 -42
  7. data/hallon.gemspec +1 -1
  8. data/lib/hallon.rb +3 -2
  9. data/lib/hallon/album.rb +6 -4
  10. data/lib/hallon/artist.rb +6 -4
  11. data/lib/hallon/audio_queue.rb +1 -1
  12. data/lib/hallon/base.rb +4 -0
  13. data/lib/hallon/blob.rb +6 -0
  14. data/lib/hallon/error.rb +10 -41
  15. data/lib/hallon/ext/spotify.rb +1 -146
  16. data/lib/hallon/image.rb +8 -0
  17. data/lib/hallon/linkable.rb +6 -0
  18. data/lib/hallon/loadable.rb +6 -0
  19. data/lib/hallon/observable.rb +1 -1
  20. data/lib/hallon/observable/playlist_container.rb +2 -2
  21. data/lib/hallon/observable/session.rb +34 -0
  22. data/lib/hallon/player.rb +7 -3
  23. data/lib/hallon/playlist.rb +5 -1
  24. data/lib/hallon/playlist_container.rb +9 -8
  25. data/lib/hallon/scrobbler.rb +103 -0
  26. data/lib/hallon/search.rb +1 -0
  27. data/lib/hallon/session.rb +69 -13
  28. data/lib/hallon/toplist.rb +1 -1
  29. data/lib/hallon/track.rb +2 -2
  30. data/lib/hallon/version.rb +1 -1
  31. data/spec/hallon/album_spec.rb +16 -0
  32. data/spec/hallon/artist_spec.rb +16 -0
  33. data/spec/hallon/base_spec.rb +1 -1
  34. data/spec/hallon/error_spec.rb +3 -3
  35. data/spec/hallon/hallon_spec.rb +1 -1
  36. data/spec/hallon/image_spec.rb +6 -0
  37. data/spec/hallon/observable/session_spec.rb +20 -0
  38. data/spec/hallon/scrobbler_spec.rb +119 -0
  39. data/spec/hallon/session_spec.rb +38 -4
  40. data/spec/hallon/spotify_spec.rb +0 -45
  41. data/spec/mockspotify.rb +6 -1
  42. data/spec/spec_helper.rb +4 -5
  43. metadata +59 -20
  44. data/spec/support/cover_me.rb +0 -7
data/.gitignore CHANGED
@@ -20,10 +20,11 @@ doc/
20
20
 
21
21
  ## bundler
22
22
  .bundle
23
+ vendor/bundle
23
24
 
24
25
  ## jeweler
25
26
  pkg
26
27
  *.gem
27
28
 
28
29
  ## Mac OS
29
- .DS_Store
30
+ .DS_Store
data/.travis.yml CHANGED
@@ -1,3 +1,5 @@
1
+ language: ruby
2
+ bundler_args: --path vendor/bundle
1
3
  rvm:
2
4
  - 1.9.2
3
5
  - 1.9.3
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
- [HEAD]: https://github.com/Burgestrand/Hallon/compare/v0.15.0...HEAD
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
@@ -1,7 +1,7 @@
1
1
  source :rubygems
2
2
  gemspec
3
3
 
4
- gem 'ruby_parser'
4
+ gem 'ruby_parser', '>= 3.0.0.a1', '< 4.0'
5
5
  gem 'pry'
6
- gem 'cover_me', :platform => :ruby_19
6
+ gem 'simplecov'
7
7
  gem 'rb-readline'
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 current session and an audio driver, which the player will then use for audio playback.
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(session, Hallon::OpenAL)
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
- end
17
+ task.rspec_opts = '-rsimplecov'
18
18
 
19
- desc "Run the full test suite and generate a coverage report"
20
- task 'spec:cov' => ['clean', 'spec'] do
21
- require 'bundler/setup'
22
- require 'cover_me'
23
- require './spec/support/cover_me'
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
- CoverMe.config.at_exit = proc { `open coverage/index.html` }
26
- CoverMe.complete!
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
- # if it’s auto-GC’d, we can also account for _release and _add_ref
93
- if meth =~ /(.+)!\z/
94
- return_type = Spotify.lookup_return_value(meth)
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
- result << $1
97
- result << "#{return_type}_release"
98
- result << "#{return_type}_add_ref"
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.value]
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.value if name.respond_to?(:value)
142
+ name
116
143
  end
117
144
 
118
145
  # Hallon::Enumerator
119
146
  no_receiver[:item] = proc do |recv, meth, (_, name)|
120
- next unless name.respond_to?(:value)
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.each do |m|
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', '~> 11.0.2'
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#update} on failure.
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] sets the default load_timeout in seconds for {Loadable#load}.
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
 
@@ -35,7 +35,7 @@ module Hallon
35
35
  @condvar = @samples.new_cond
36
36
  end
37
37
 
38
- # @param [#take] data
38
+ # @param [#take] samples
39
39
  # @return [Integer] how much of the data that was added to the queue
40
40
  def push(samples)
41
41
  synchronize do
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 < RuntimeError
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(x, options = {})
30
+ def maybe_raise(error, options = {})
58
31
  ignore = [nil, :timeout] + Array(options[:ignore])
59
- return nil if ignore.include?(x)
32
+ return nil if ignore.include?(error)
60
33
 
61
- error, symbol = disambiguate(x)
34
+ error, symbol = disambiguate(error)
62
35
  return symbol if symbol == :ok
63
36
 
64
- message = []
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