hallon 0.16.0 → 0.17.0

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