hallon 0.8.0 → 0.9.0

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