hallon 0.8.0 → 0.9.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 (59) hide show
  1. data/.travis.yml +2 -0
  2. data/CHANGELOG +43 -0
  3. data/Gemfile +2 -0
  4. data/README.markdown +21 -13
  5. data/Rakefile +84 -23
  6. data/dev/login.rb +16 -0
  7. data/examples/adding_tracks_to_playlist.rb +49 -0
  8. data/examples/logging_in.rb +1 -6
  9. data/examples/show_published_playlists_of_user.rb +9 -19
  10. data/hallon.gemspec +1 -1
  11. data/lib/hallon.rb +3 -2
  12. data/lib/hallon/album.rb +55 -41
  13. data/lib/hallon/album_browse.rb +41 -37
  14. data/lib/hallon/artist.rb +30 -21
  15. data/lib/hallon/artist_browse.rb +59 -41
  16. data/lib/hallon/base.rb +68 -5
  17. data/lib/hallon/enumerator.rb +1 -0
  18. data/lib/hallon/error.rb +3 -0
  19. data/lib/hallon/ext/spotify.rb +169 -36
  20. data/lib/hallon/image.rb +30 -44
  21. data/lib/hallon/link.rb +29 -43
  22. data/lib/hallon/linkable.rb +68 -20
  23. data/lib/hallon/observable.rb +0 -1
  24. data/lib/hallon/player.rb +21 -7
  25. data/lib/hallon/playlist.rb +291 -0
  26. data/lib/hallon/playlist_container.rb +27 -0
  27. data/lib/hallon/search.rb +52 -45
  28. data/lib/hallon/session.rb +129 -81
  29. data/lib/hallon/toplist.rb +37 -19
  30. data/lib/hallon/track.rb +68 -45
  31. data/lib/hallon/user.rb +69 -33
  32. data/lib/hallon/version.rb +1 -1
  33. data/spec/hallon/album_browse_spec.rb +15 -9
  34. data/spec/hallon/album_spec.rb +15 -15
  35. data/spec/hallon/artist_browse_spec.rb +28 -9
  36. data/spec/hallon/artist_spec.rb +30 -14
  37. data/spec/hallon/enumerator_spec.rb +0 -1
  38. data/spec/hallon/hallon_spec.rb +20 -1
  39. data/spec/hallon/image_spec.rb +18 -41
  40. data/spec/hallon/link_spec.rb +10 -12
  41. data/spec/hallon/linkable_spec.rb +37 -18
  42. data/spec/hallon/player_spec.rb +8 -0
  43. data/spec/hallon/playlist_container_spec.rb +75 -0
  44. data/spec/hallon/playlist_spec.rb +204 -0
  45. data/spec/hallon/search_spec.rb +19 -16
  46. data/spec/hallon/session_spec.rb +61 -29
  47. data/spec/hallon/spotify_spec.rb +30 -0
  48. data/spec/hallon/toplist_spec.rb +22 -14
  49. data/spec/hallon/track_spec.rb +62 -21
  50. data/spec/hallon/user_spec.rb +47 -36
  51. data/spec/mockspotify.rb +35 -10
  52. data/spec/mockspotify/mockspotify_spec.rb +22 -0
  53. data/spec/spec_helper.rb +7 -3
  54. data/spec/support/common_objects.rb +91 -16
  55. data/spec/support/shared_for_linkable_objects.rb +39 -0
  56. metadata +30 -20
  57. data/Termfile +0 -7
  58. data/lib/hallon/synchronizable.rb +0 -32
  59. data/spec/hallon/synchronizable_spec.rb +0 -19
data/lib/hallon/album.rb CHANGED
@@ -1,85 +1,99 @@
1
+ # coding: utf-8
1
2
  module Hallon
2
3
  # Albums are non-detailed metadata about actual music albums.
3
4
  #
4
5
  # To retrieve copyrights, album review and tracks you need to browse
5
- # the album, which is not currently supported by Hallon. You can retrieve
6
- # the {#pointer} use the raw Spotify API on it, however.
6
+ # the album. You do this by calling {Album#browse} to retrieve an
7
+ # {AlbumBrowse} instance.
7
8
  #
8
- # @note All metadata methods require the album to be {Album#loaded?}
9
+ # It does still allow you to query some metadata information, such as
10
+ # its’ {#name}, {#release_year}, {#type}, {#artist}, {#cover}…
11
+ #
12
+ # @note Pretty much all methods require the album to be {Album#loaded?}
13
+ # to return meaningful results.
9
14
  #
10
15
  # @see http://developer.spotify.com/en/libspotify/docs/group__album.html
11
16
  class Album < Base
12
- # An array of different kinds of albums. Singles, compilations etc.
17
+ # @example
18
+ #
19
+ # Hallon::Album.types # => [:album, :single, :compilation, :unknown]
20
+ #
21
+ # @return [Array<Symbol>] an array of different kinds of albums (compilations, singles, …)
13
22
  def self.types
14
23
  Spotify.enum_type(:albumtype).symbols
15
24
  end
16
25
 
17
26
  extend Linkable
18
27
 
19
- from_link :as_album
20
28
  to_link :from_album
21
29
 
30
+ from_link :as_album
31
+
22
32
  # Construct an Album from a link.
23
33
  #
24
- # @param [String, Link, FFI::Pointer] link
34
+ # @example from a spotify URI
35
+ #
36
+ # album = Hallon::Album.new("spotify:album:6TECAywzyJGh0kwxfeBgGc")
37
+ #
38
+ # @example from a link
39
+ #
40
+ # link = Hallon::Link.new("spotify:album:6TECAywzyJGh0kwxfeBgGc")
41
+ # album = Hallon::Album.new(link)
42
+ #
43
+ # @param [String, Link, Spotify::Pointer] link
25
44
  def initialize(link)
26
- @pointer = Spotify::Pointer.new from_link(link), :album, true
45
+ @pointer = to_pointer(link, :album)
27
46
  end
28
47
 
29
- # Name of album.
30
- #
31
- # @return [String]
48
+ # @return [String] name of the album.
32
49
  def name
33
- Spotify.album_name(@pointer)
50
+ Spotify.album_name(pointer)
34
51
  end
35
52
 
36
- # Release year of album.
37
- #
38
- # @return [Integer]
39
- def year
40
- Spotify.album_year(@pointer)
53
+ # @return [Integer] release year of the album.
54
+ def release_year
55
+ Spotify.album_year(pointer)
41
56
  end
42
57
 
43
- # Retrieve album type.
44
- #
45
- # @return [Symbol] one of {Album.types}
58
+ # @see Album.types
59
+ # @return [Symbol] album type.
46
60
  def type
47
- Spotify.album_type(@pointer)
61
+ Spotify.album_type(pointer)
48
62
  end
49
63
 
50
- # True if the album is available from the current session.
51
- #
52
- # @return [Boolean]
64
+ # @return [Boolean] true if the album is available.
53
65
  def available?
54
- Spotify.album_is_available(@pointer)
66
+ Spotify.album_is_available(pointer)
55
67
  end
56
68
 
57
- # True if album has been loaded.
58
- #
59
- # @return [Boolean]
69
+ # @return [Boolean] true if the album is loaded.
60
70
  def loaded?
61
- Spotify.album_is_loaded(@pointer)
71
+ Spotify.album_is_loaded(pointer)
62
72
  end
63
73
 
64
- # Retrieve album cover art.
65
- #
66
- # @return [Image]
67
- def cover
68
- image_id = Spotify.album_cover(@pointer)
69
- Image.new(image_id.read_string(20)) unless image_id.null?
74
+ # @return [Artist, nil] album artist.
75
+ def artist
76
+ artist = Spotify.album_artist!(pointer)
77
+ Artist.new(artist) unless artist.null?
70
78
  end
71
79
 
72
- # Retrieve the album Artist.
80
+ # Retrieves album cover art as an {Image} or a {Link}.
73
81
  #
74
- # @return [Artist, nil]
75
- def artist
76
- artist = Spotify.album_artist(@pointer)
77
- Artist.new(artist) unless artist.null?
82
+ # @param [Boolean] as_image true if you want it as an {Image}.
83
+ # @return [Image, Link, nil] album cover, the link to it, or nil.
84
+ def cover(as_image = true)
85
+ if as_image
86
+ image_id = Spotify.album_cover(pointer)
87
+ Image.new(image_id.read_string(20)) unless image_id.null?
88
+ else
89
+ link = Spotify.link_create_from_album_cover!(pointer)
90
+ Link.new(link)
91
+ end
78
92
  end
79
93
 
80
- # Retrieve an AlbumBrowse object for this Album.
94
+ # Browse the Album by creating an {AlbumBrowse} instance from it.
81
95
  #
82
- # @return [AlbumBrowse]
96
+ # @return [AlbumBrowse] an album browsing object
83
97
  def browse
84
98
  AlbumBrowse.new(pointer)
85
99
  end
@@ -1,18 +1,9 @@
1
+ # coding: utf-8
1
2
  module Hallon
2
3
  # AlbumBrowse objects are for retrieving additional data from
3
4
  # an album that cannot otherwise be acquired. This includes
4
5
  # tracks, reviews, copyright information.
5
6
  #
6
- # AlbumBrowse object triggers the `:load` callback on itself
7
- # when it loads.
8
- #
9
- # @example
10
- # browse = album.browse # album is a Hallon::Album
11
- # browse.on(:load) do
12
- # puts "Album browser for #{browse.album.name} has been loaded!"
13
- # end
14
- # session.wait_for { browse.loaded? } # will eventually trigger above callback
15
- #
16
7
  # @see Album
17
8
  # @see http://developer.spotify.com/en/libspotify/docs/group__albumbrowse.html
18
9
  class AlbumBrowse < Base
@@ -20,58 +11,71 @@ module Hallon
20
11
 
21
12
  # Creates an AlbumBrowse instance from an Album or an Album pointer.
22
13
  #
23
- # @note Use {Album#browse} to browse an Album.
24
- # @param [Album, FFI::Pointer] album
14
+ # @note Also {Album#browse} to browse an Album.
15
+ # @param [Album, Spotify::Pointer] album
25
16
  def initialize(album)
26
- album = album.pointer if album.respond_to?(:pointer)
27
- @callback = proc { trigger(:load) }
17
+ pointer = album
18
+ pointer = pointer.pointer if pointer.respond_to?(:pointer)
19
+
20
+ unless Spotify::Pointer.typechecks?(pointer, :album)
21
+ given = pointer.respond_to?(:type) ? pointer.type : pointer.inspect
22
+ raise TypeError, "expected album pointer, was given #{given}"
23
+ end
28
24
 
29
- albumbrowse = Spotify.albumbrowse_create(session.pointer, album, @callback, nil)
30
- @pointer = Spotify::Pointer.new(albumbrowse, :albumbrowse, false)
25
+ @callback = proc { trigger(:load) }
26
+ @pointer = Spotify.albumbrowse_create!(session.pointer, pointer, @callback, nil)
31
27
  end
32
28
 
33
- # @return [Boolean] true if the album is loaded
29
+ # @return [Boolean] true if the album browser is loaded.
34
30
  def loaded?
35
- Spotify.albumbrowse_is_loaded(@pointer)
31
+ Spotify.albumbrowse_is_loaded(pointer)
36
32
  end
37
33
 
38
- # @see Error
39
- # @return [Symbol] album browser error status
40
- def error
41
- Spotify.albumbrowse_error(@pointer)
34
+ # @see Error.explain
35
+ # @return [Symbol] album browser error status.
36
+ def status
37
+ Spotify.albumbrowse_error(pointer)
42
38
  end
43
39
 
44
- # @return [String] album review
40
+ # @return [String] album review.
45
41
  def review
46
- Spotify.albumbrowse_review(@pointer)
42
+ Spotify.albumbrowse_review(pointer)
47
43
  end
48
44
 
49
- # @return [Artist] artist performing this album
45
+ # @return [Artist, nil] artist performing this album.
50
46
  def artist
51
- pointer = Spotify.albumbrowse_artist(@pointer)
52
- Artist.new(pointer) unless pointer.null?
47
+ artist = Spotify.albumbrowse_artist!(pointer)
48
+ Artist.new(artist) unless artist.null?
53
49
  end
54
50
 
55
- # @return [Album] album this object is browsing
51
+ # @return [Album, nil] album this object is browsing.
56
52
  def album
57
- pointer = Spotify.albumbrowse_album(@pointer)
58
- Album.new(pointer) unless pointer.null?
53
+ album = Spotify.albumbrowse_album!(pointer)
54
+ Album.new(album) unless album.null?
55
+ end
56
+
57
+ # @note If the object is not loaded, the result is undefined.
58
+ # @note Returns nil if the request was served from the local libspotify cache.
59
+ # @return [Rational, nil] time it took for the albumbrowse request to complete (in seconds).
60
+ def request_duration
61
+ duration = Spotify.albumbrowse_backend_request_duration(pointer)
62
+ Rational(duration, 1000) if duration > 0
59
63
  end
60
64
 
61
- # @return [Enumerator<String>] list of copyright notices
65
+ # @return [Enumerator<String>] list of copyright notices.
62
66
  def copyrights
63
- size = Spotify.albumbrowse_num_copyrights(@pointer)
67
+ size = Spotify.albumbrowse_num_copyrights(pointer)
64
68
  Enumerator.new(size) do |i|
65
- Spotify.albumbrowse_copyright(@pointer, i)
69
+ Spotify.albumbrowse_copyright(pointer, i)
66
70
  end
67
71
  end
68
72
 
69
- # @return [Enumerator<Track>] list of tracks
73
+ # @return [Enumerator<Track>] list of tracks.
70
74
  def tracks
71
- size = Spotify.albumbrowse_num_tracks(@pointer)
75
+ size = Spotify.albumbrowse_num_tracks(pointer)
72
76
  Enumerator.new(size) do |i|
73
- pointer = Spotify.albumbrowse_track(@pointer, i)
74
- Track.new(pointer) unless pointer.null?
77
+ track = Spotify.albumbrowse_track!(pointer, i)
78
+ Track.new(track) unless track.null?
75
79
  end
76
80
  end
77
81
  end
data/lib/hallon/artist.rb CHANGED
@@ -1,12 +1,11 @@
1
+ # coding: utf-8
1
2
  module Hallon
2
3
  # Artists in Hallon are the people behind the songs. Methods
3
4
  # are defined for retrieving their names and loaded status.
4
5
  #
5
- # To retrieve more information about an artist, you can browse
6
+ # To retrieve more information about an artist, you can {#browse}
6
7
  # it. This will give access to more detailed data such as bio,
7
- # portraits and more. Hallon does not support this as of yet,
8
- # but you can use the underlying Spotify API for this, just like
9
- # we have for {Album}s.
8
+ # portraits and more.
10
9
  #
11
10
  # Both Albums and Tracks can have more than one artist.
12
11
  #
@@ -17,43 +16,53 @@ module Hallon
17
16
  from_link :as_artist
18
17
  to_link :from_artist
19
18
 
20
- # Construct an artist given a link.
19
+ # Construct an Artist from a link.
20
+ #
21
+ # @example from a spotify URI
22
+ #
23
+ # artist = Hallon::Artist.new("spotify:artist:6uSKeCyQEhvPC2NODgiqFE")
24
+ #
25
+ # @example from a link
26
+ #
27
+ # link = Hallon::Link.new("spotify:artist:6uSKeCyQEhvPC2NODgiqFE")
28
+ # artist = Hallon::Artist.new(link)
21
29
  #
22
30
  # @param [String, Link, FFI::Pointer] link
23
31
  def initialize(link)
24
- @pointer = Spotify::Pointer.new from_link(link), :artist, true
32
+ @pointer = to_pointer(link, :artist)
25
33
  end
26
34
 
27
- # Retrieve Artist name. Empty string if Artist is not loaded.
28
- #
29
- # @return [String]
35
+ # @return [String] name of the artist.
30
36
  def name
31
- Spotify.artist_name(@pointer)
37
+ Spotify.artist_name(pointer)
32
38
  end
33
39
 
34
- # True if the Artist is loaded.
35
- #
36
- # @return [Boolean]
40
+ # @return [Boolean] true if the artist is loaded.
37
41
  def loaded?
38
- Spotify.artist_is_loaded(@pointer)
42
+ Spotify.artist_is_loaded(pointer)
39
43
  end
40
44
 
45
+ # Retrieve artist portrait as an {Image} or a {Link}.
46
+ #
41
47
  # @param [Boolean] as_image true if you want it as an Image
42
- # @return [Image, Link, nil] artist portrait, or the link to it, or nil
48
+ # @return [Image, Link, nil] artist portrait, the link to it, or nil.
43
49
  def portrait(as_image = true)
44
- portrait = Spotify.link_create_from_artist_portrait(@pointer)
45
- unless portrait.null?
46
- klass = as_image ? Image : Link
47
- klass.new(portrait)
50
+ if as_image
51
+ portrait = Spotify.artist_portrait(pointer)
52
+ Image.new(portrait.read_bytes(20)) unless portrait.null?
53
+ else
54
+ portrait = Spotify.link_create_from_artist_portrait!(pointer)
55
+ Link.new(portrait) unless portrait.null?
48
56
  end
49
57
  end
50
58
 
51
59
  # Browse the Artist, giving you the ability to explore its’
52
60
  # portraits, biography and more.
53
61
  #
62
+ # @param [Symbol] type browsing type (see {ArtistBrowse.types})
54
63
  # @return [ArtistBrowse] an artist browsing object
55
- def browse
56
- ArtistBrowse.new(pointer)
64
+ def browse(type = :full)
65
+ ArtistBrowse.new(pointer, type)
57
66
  end
58
67
  end
59
68
  end
@@ -1,87 +1,105 @@
1
+ # coding: utf-8
1
2
  module Hallon
2
3
  # ArtistBrowse is like AlbumBrowse, only that it’s for {Track}s.
3
4
  #
4
- # When it loads, it triggers the load callback on itself, that
5
- # can be utilized by giving {#on}(:load) a block to execute.
6
- #
7
- # @example
8
- # browse = artist.browse # artist is a Hallon::Artist
9
- # browse.on(:load) do
10
- # puts "#{browse.artist.name} browser has been loaded!"
11
- # end
12
- # session.wait_for { browse.loaded? }
13
- #
14
5
  # @see Artist
15
6
  # @see http://developer.spotify.com/en/libspotify/docs/group__artistbrowse.html
16
7
  class ArtistBrowse < Base
17
8
  include Observable
18
9
 
10
+ # @return [Array<Symbol>] artist browsing types for use in {#initialize}
11
+ def self.types
12
+ Spotify.enum_type(:artistbrowse_type).symbols
13
+ end
14
+
19
15
  # Creates an ArtistBrowse instance from an Artist or an Artist pointer.
20
16
  #
21
- # @note Use {Artist#browse} to browse an Artist.
22
- # @param [Artist, FFI::Pointer] artist
23
- def initialize(artist)
24
- artist = artist.pointer if artist.respond_to?(:pointer)
25
- @callback = proc { trigger(:load) }
17
+ # @note Also use {Artist#browse} to browse an Artist.
18
+ # @param [Artist, Spotify::Pointer] artist
19
+ # @param [Symbol] type (see {.types})
20
+ def initialize(artist, type = :full)
21
+ pointer = artist
22
+ pointer = pointer.pointer if pointer.respond_to?(:pointer)
26
23
 
27
- artistbrowse = Spotify.artistbrowse_create(session.pointer, artist, @callback, nil)
28
- @pointer = Spotify::Pointer.new(artistbrowse, :artistbrowse, false)
24
+ unless Spotify::Pointer.typechecks?(pointer, :artist)
25
+ given = pointer.respond_to?(:type) ? pointer.type : pointer.inspect
26
+ raise TypeError, "expected artist pointer, was given #{given}"
27
+ end
28
+
29
+ @callback = proc { trigger(:load) }
30
+ @pointer = Spotify.artistbrowse_create!(session.pointer, pointer, type, @callback, nil)
29
31
  end
30
32
 
31
- # @return [Boolean] true if the album browser is loaded
33
+ # @return [Boolean] true if the artist browser is loaded.
32
34
  def loaded?
33
- Spotify.artistbrowse_is_loaded(@pointer)
35
+ Spotify.artistbrowse_is_loaded(pointer)
34
36
  end
35
37
 
36
- # @see Error
37
- # @return [Symbol] artist browser error status
38
- def error
39
- Spotify.artistbrowse_error(@pointer)
38
+ # @see Error.explain
39
+ # @return [Symbol] artist browser error status.
40
+ def status
41
+ Spotify.artistbrowse_error(pointer)
40
42
  end
41
43
 
42
- # @return [Artist, nil] artist this browser is browsing
44
+ # @return [Artist, nil] artist this browser is browsing.
43
45
  def artist
44
- artist = Spotify.artistbrowse_artist(@pointer)
46
+ artist = Spotify.artistbrowse_artist!(pointer)
45
47
  Artist.new(artist) unless artist.null?
46
48
  end
47
49
 
48
- # @return [String] artist biography
50
+ # @return [String] artist biography.
49
51
  def biography
50
- Spotify.artistbrowse_biography(@pointer)
52
+ Spotify.artistbrowse_biography(pointer)
51
53
  end
52
54
 
53
- # @return [Enumerator<Image>] artist portraits
54
- def portraits
55
- size = Spotify.artistbrowse_num_portraits(@pointer)
55
+ # @note If the object is not loaded, the result is undefined.
56
+ # @note Returns nil if the request was served from the local libspotify cache.
57
+ # @return [Rational, nil] time it took for the albumbrowse request to complete (in seconds).
58
+ def request_duration
59
+ duration = Spotify.artistbrowse_backend_request_duration(pointer)
60
+ Rational(duration, 1000) if duration > 0
61
+ end
62
+
63
+ # Retrieve artist portraits as an {Image} or a {Link}.
64
+ #
65
+ # @param [Boolean] as_image true if you want an enumerator of Images (false for Links)
66
+ # @return [Enumerator<Image>, Enumerator<Link>] artist portraits.
67
+ def portraits(as_image = true)
68
+ size = Spotify.artistbrowse_num_portraits(pointer)
56
69
  Enumerator.new(size) do |i|
57
- id = Spotify.artistbrowse_portrait(@pointer, i).read_string(20)
58
- Image.new(id)
70
+ if as_image
71
+ id = Spotify.artistbrowse_portrait(pointer, i).read_string(20)
72
+ Image.new(id)
73
+ else
74
+ link = Spotify.link_create_from_artistbrowse_portrait!(pointer, i)
75
+ Link.new(link)
76
+ end
59
77
  end
60
78
  end
61
79
 
62
- # @return [Enumerator<Track>] artist authored tracks
80
+ # @return [Enumerator<Track>] artist authored tracks.
63
81
  def tracks
64
- size = Spotify.artistbrowse_num_tracks(@pointer)
82
+ size = Spotify.artistbrowse_num_tracks(pointer)
65
83
  Enumerator.new(size) do |i|
66
- track = Spotify.artistbrowse_track(@pointer, i)
84
+ track = Spotify.artistbrowse_track!(pointer, i)
67
85
  Track.new(track)
68
86
  end
69
87
  end
70
88
 
71
- # @return [Enumerator<Album>] artist authored albums
89
+ # @return [Enumerator<Album>] artist authored albums.
72
90
  def albums
73
- size = Spotify.artistbrowse_num_albums(@pointer)
91
+ size = Spotify.artistbrowse_num_albums(pointer)
74
92
  Enumerator.new(size) do |i|
75
- album = Spotify.artistbrowse_album(@pointer, i)
93
+ album = Spotify.artistbrowse_album!(pointer, i)
76
94
  Album.new(album)
77
95
  end
78
96
  end
79
97
 
80
- # @return [Enumartor<Artist>] similar artists to this artist
98
+ # @return [Enumartor<Artist>] similar artists to this artist.
81
99
  def similar_artists
82
- size = Spotify.artistbrowse_num_similar_artists(@pointer)
100
+ size = Spotify.artistbrowse_num_similar_artists(pointer)
83
101
  Enumerator.new(size) do |i|
84
- artist = Spotify.artistbrowse_similar_artist(@pointer, i)
102
+ artist = Spotify.artistbrowse_similar_artist!(pointer, i)
85
103
  Artist.new(artist)
86
104
  end
87
105
  end