hallon 0.8.0 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.travis.yml +2 -0
- data/CHANGELOG +43 -0
- data/Gemfile +2 -0
- data/README.markdown +21 -13
- data/Rakefile +84 -23
- data/dev/login.rb +16 -0
- data/examples/adding_tracks_to_playlist.rb +49 -0
- data/examples/logging_in.rb +1 -6
- data/examples/show_published_playlists_of_user.rb +9 -19
- data/hallon.gemspec +1 -1
- data/lib/hallon.rb +3 -2
- data/lib/hallon/album.rb +55 -41
- data/lib/hallon/album_browse.rb +41 -37
- data/lib/hallon/artist.rb +30 -21
- data/lib/hallon/artist_browse.rb +59 -41
- data/lib/hallon/base.rb +68 -5
- data/lib/hallon/enumerator.rb +1 -0
- data/lib/hallon/error.rb +3 -0
- data/lib/hallon/ext/spotify.rb +169 -36
- data/lib/hallon/image.rb +30 -44
- data/lib/hallon/link.rb +29 -43
- data/lib/hallon/linkable.rb +68 -20
- data/lib/hallon/observable.rb +0 -1
- data/lib/hallon/player.rb +21 -7
- data/lib/hallon/playlist.rb +291 -0
- data/lib/hallon/playlist_container.rb +27 -0
- data/lib/hallon/search.rb +52 -45
- data/lib/hallon/session.rb +129 -81
- data/lib/hallon/toplist.rb +37 -19
- data/lib/hallon/track.rb +68 -45
- data/lib/hallon/user.rb +69 -33
- data/lib/hallon/version.rb +1 -1
- data/spec/hallon/album_browse_spec.rb +15 -9
- data/spec/hallon/album_spec.rb +15 -15
- data/spec/hallon/artist_browse_spec.rb +28 -9
- data/spec/hallon/artist_spec.rb +30 -14
- data/spec/hallon/enumerator_spec.rb +0 -1
- data/spec/hallon/hallon_spec.rb +20 -1
- data/spec/hallon/image_spec.rb +18 -41
- data/spec/hallon/link_spec.rb +10 -12
- data/spec/hallon/linkable_spec.rb +37 -18
- data/spec/hallon/player_spec.rb +8 -0
- data/spec/hallon/playlist_container_spec.rb +75 -0
- data/spec/hallon/playlist_spec.rb +204 -0
- data/spec/hallon/search_spec.rb +19 -16
- data/spec/hallon/session_spec.rb +61 -29
- data/spec/hallon/spotify_spec.rb +30 -0
- data/spec/hallon/toplist_spec.rb +22 -14
- data/spec/hallon/track_spec.rb +62 -21
- data/spec/hallon/user_spec.rb +47 -36
- data/spec/mockspotify.rb +35 -10
- data/spec/mockspotify/mockspotify_spec.rb +22 -0
- data/spec/spec_helper.rb +7 -3
- data/spec/support/common_objects.rb +91 -16
- data/spec/support/shared_for_linkable_objects.rb +39 -0
- metadata +30 -20
- data/Termfile +0 -7
- data/lib/hallon/synchronizable.rb +0 -32
- data/spec/hallon/synchronizable_spec.rb +0 -19
data/lib/hallon/link.rb
CHANGED
@@ -6,92 +6,78 @@ module Hallon
|
|
6
6
|
class Link < Base
|
7
7
|
# True if the given Spotify URI is valid (parsable by libspotify).
|
8
8
|
#
|
9
|
-
# @param
|
9
|
+
# @param [#to_s] spotify_uri
|
10
10
|
# @return [Boolean]
|
11
11
|
def self.valid?(spotify_uri)
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
end
|
12
|
+
if spotify_uri.is_a?(Link)
|
13
|
+
return true
|
14
|
+
end
|
16
15
|
|
17
|
-
|
18
|
-
|
19
|
-
# @return [Hallon::Link]
|
20
|
-
def self.new(uri)
|
21
|
-
uri.is_a?(Link) ? uri : super
|
16
|
+
link = Spotify.link_create_from_string!(spotify_uri.to_s)
|
17
|
+
not link.null?
|
22
18
|
end
|
23
19
|
|
24
20
|
# Parse the given Spotify URI into a Link.
|
25
21
|
#
|
26
|
-
# @note
|
22
|
+
# @note You must initialize a Session before you call this method.
|
27
23
|
# @param [#to_str] uri
|
28
24
|
# @raise [ArgumentError] link could not be parsed
|
29
25
|
def initialize(uri)
|
30
|
-
if
|
31
|
-
|
26
|
+
# if no session instance exists, libspotify segfaults, so assert that we have one
|
27
|
+
unless Session.instance?
|
28
|
+
raise "Link.new requires an existing Session instance"
|
32
29
|
end
|
33
30
|
|
34
|
-
@pointer =
|
35
|
-
|
36
|
-
|
31
|
+
@pointer = to_pointer(uri, :link) do
|
32
|
+
Spotify.link_create_from_string!(uri.to_str)
|
33
|
+
end
|
37
34
|
end
|
38
35
|
|
39
|
-
#
|
40
|
-
#
|
41
|
-
# @return [Symbol]
|
36
|
+
# @return [Symbol] link type as a symbol (e.g. `:playlist`).
|
42
37
|
def type
|
43
|
-
Spotify.link_type(
|
38
|
+
Spotify.link_type(pointer)
|
44
39
|
end
|
45
40
|
|
46
|
-
#
|
47
|
-
#
|
48
|
-
# @return [Fixnum]
|
41
|
+
# @return [Fixnum] spotify URI length.
|
49
42
|
def length
|
50
|
-
Spotify.link_as_string(
|
43
|
+
Spotify.link_as_string(pointer, nil, 0)
|
51
44
|
end
|
52
45
|
|
53
|
-
# Get the Spotify URI this Link represents.
|
54
|
-
#
|
55
46
|
# @see #length
|
56
47
|
# @param [Fixnum] length truncate to this size
|
57
|
-
# @return [String]
|
48
|
+
# @return [String] spotify URI representation of this Link.
|
58
49
|
def to_str(length = length)
|
59
50
|
FFI::Buffer.alloc_out(length + 1) do |b|
|
60
|
-
Spotify.link_as_string(
|
51
|
+
Spotify.link_as_string(pointer, b, b.size)
|
61
52
|
return b.get_string(0)
|
62
53
|
end
|
63
54
|
end
|
64
55
|
|
65
|
-
|
66
|
-
|
67
|
-
# @return [String]
|
56
|
+
alias_method :to_uri, :to_str
|
57
|
+
|
58
|
+
# @return [String] full Spotify HTTP URL.
|
68
59
|
def to_url
|
69
60
|
"http://open.spotify.com/%s" % to_str[8..-1].gsub(':', '/')
|
70
61
|
end
|
71
62
|
|
72
|
-
#
|
73
|
-
#
|
74
|
-
# @param [#to_str] other
|
75
|
-
# @return [Boolean]
|
63
|
+
# @param [Object] other
|
64
|
+
# @return [Boolean] true if this link equals `other.to_str`.
|
76
65
|
def ==(other)
|
77
|
-
return super unless other.respond_to?(:to_str)
|
78
66
|
to_str == other.to_str
|
67
|
+
rescue NoMethodError
|
68
|
+
super
|
79
69
|
end
|
80
70
|
|
81
|
-
# String representation of the
|
82
|
-
#
|
83
|
-
# @return [String]
|
71
|
+
# @return [String] string representation of the Link.
|
84
72
|
def to_s
|
85
73
|
"<#{self.class.name} #{to_str}>"
|
86
74
|
end
|
87
75
|
|
88
|
-
# Retrieve the underlying pointer. Used by {Linkable}.
|
89
|
-
#
|
90
76
|
# @param [Symbol] expected_type if given, makes sure the link is of this type
|
91
|
-
# @return [
|
77
|
+
# @return [Spotify::Pointer] the underlying Spotify::Pointer.
|
92
78
|
# @raise ArgumentError if `type` is given and does not match link {#type}
|
93
79
|
def pointer(expected_type = nil)
|
94
|
-
unless type == expected_type
|
80
|
+
unless type == expected_type or (expected_type == :playlist and type == :starred)
|
95
81
|
raise ArgumentError, "expected #{expected_type} link, but it is of type #{type}"
|
96
82
|
end if expected_type
|
97
83
|
super()
|
data/lib/hallon/linkable.rb
CHANGED
@@ -3,58 +3,106 @@ module Hallon
|
|
3
3
|
# Methods shared between objects that can be created from Spotify URIs,
|
4
4
|
# or can be turned into Spotify URIs.
|
5
5
|
#
|
6
|
-
# @note Linkable is
|
6
|
+
# @note Linkable is part of Hallons’ private API. You probably do not
|
7
|
+
# not need to care about these methods.
|
8
|
+
#
|
7
9
|
# @private
|
8
10
|
module Linkable
|
9
|
-
# Defines `#from_link`, used in converting a link to a pointer.
|
11
|
+
# Defines `#from_link`, used in converting a link to a pointer. You
|
12
|
+
# can either pass it a `method_name`, or a `type` and a block.
|
10
13
|
#
|
11
|
-
# @overload from_link(
|
12
|
-
#
|
14
|
+
# @overload from_link(method_name)
|
15
|
+
# Define `#from_link` simply by giving the name of the method,
|
16
|
+
# minus the `link_` prefix.
|
13
17
|
#
|
14
18
|
# @example
|
15
|
-
#
|
19
|
+
# class Album
|
20
|
+
# extend Linkable
|
21
|
+
#
|
22
|
+
# from_link :as_album # => Spotify.link_as_album(pointer, *args)
|
23
|
+
# # ^ is roughly equivalent to:
|
24
|
+
# def from_link(link, *args)
|
25
|
+
# unless Spotify::Pointer.typechecks?(link, :link)
|
26
|
+
# link = Link.new(link).pointer(:album)
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# Spotify.link_as_album!(link)
|
30
|
+
# end
|
31
|
+
# end
|
16
32
|
#
|
17
|
-
# @param [Symbol]
|
33
|
+
# @param [Symbol] method_name
|
18
34
|
#
|
19
35
|
# @overload from_link(type) { |*args| … }
|
20
|
-
#
|
36
|
+
# Define `#from_link` to use the given block to convert an object
|
37
|
+
# from a link. The link is converted to a pointer and typechecked
|
38
|
+
# to be of the same type as `type` before given to the block.
|
21
39
|
#
|
22
40
|
# @example
|
23
|
-
#
|
24
|
-
#
|
41
|
+
# class User
|
42
|
+
# extend Linkable
|
43
|
+
#
|
44
|
+
# from_link :profile do |pointer|
|
45
|
+
# Spotify.link_as_user!(pointer)
|
46
|
+
# end
|
47
|
+
# # ^ is roughly equivalent to:
|
48
|
+
# def from_link(link, *args)
|
49
|
+
# unless Spotify::Pointer.typechecks?(link, :link)
|
50
|
+
# link = Link.new(link).pointer(:profile)
|
51
|
+
# end
|
52
|
+
#
|
53
|
+
# Spotify.link_as_user!(link)
|
54
|
+
# end
|
25
55
|
# end
|
26
56
|
#
|
27
57
|
# @param [#to_s] type link type
|
28
58
|
# @yield [link, *args] called when conversion is needed from Link pointer
|
29
|
-
# @yieldparam [
|
59
|
+
# @yieldparam [Spotify::Pointer] link
|
30
60
|
# @yieldparam *args any extra arguments given to `#from_link`
|
31
61
|
#
|
32
|
-
# @
|
62
|
+
# @note Private API. You probably do not need to care about this method.
|
33
63
|
def from_link(as_object, &block)
|
34
|
-
block ||= Spotify.method(:"link_#{as_object}")
|
64
|
+
block ||= Spotify.method(:"link_#{as_object}!")
|
35
65
|
type = as_object.to_s[/^(as_)?([^_]+)/, 2].to_sym
|
36
66
|
|
37
67
|
define_method(:from_link) do |link, *args|
|
38
|
-
|
39
|
-
|
40
|
-
|
68
|
+
if link.is_a?(FFI::Pointer) and not link.is_a?(Spotify::Pointer)
|
69
|
+
link
|
70
|
+
else
|
71
|
+
unless Spotify::Pointer.typechecks?(link, :link)
|
72
|
+
link = Link.new(link).pointer(type)
|
73
|
+
end
|
41
74
|
|
42
|
-
|
75
|
+
instance_exec(link, *args, &block)
|
76
|
+
end
|
43
77
|
end
|
78
|
+
|
79
|
+
private :from_link
|
44
80
|
end
|
45
81
|
|
46
|
-
# Defines `#to_link` method,
|
82
|
+
# Defines `#to_link` method, used in converting the object to a {Link}.
|
47
83
|
#
|
48
84
|
# @example
|
49
|
-
#
|
85
|
+
# class Artist
|
86
|
+
# extend Linkable
|
50
87
|
#
|
51
|
-
#
|
88
|
+
# to_link :from_artist
|
89
|
+
# # ^ is the same as:
|
90
|
+
# def to_link(*args)
|
91
|
+
# link = Spotify.link_create_from_artist!(pointer, *args)
|
92
|
+
# Link.new(link)
|
93
|
+
# end
|
94
|
+
# end
|
95
|
+
#
|
96
|
+
# @param [Symbol] cmethod name of the C method, say `from_artist` in `Spotify.link_create_from_artist`.
|
52
97
|
# @return [Link]
|
53
98
|
def to_link(cmethod)
|
54
99
|
define_method(:to_link) do |*args|
|
55
|
-
link = Spotify.__send__(:"link_create_#{cmethod}",
|
100
|
+
link = Spotify.__send__(:"link_create_#{cmethod}!", pointer, *args)
|
56
101
|
Link.new(link)
|
57
102
|
end
|
58
103
|
end
|
104
|
+
|
105
|
+
private :from_link
|
106
|
+
private :to_link
|
59
107
|
end
|
60
108
|
end
|
data/lib/hallon/observable.rb
CHANGED
@@ -26,7 +26,6 @@ module Hallon
|
|
26
26
|
# of the event that called it
|
27
27
|
# @param [#to_sym] event name of event to handle
|
28
28
|
# @yield (*args) event handler block
|
29
|
-
# @see #initialize
|
30
29
|
def on(*events, &block)
|
31
30
|
raise ArgumentError, "no block given" unless block
|
32
31
|
wrap = events.length > 1
|
data/lib/hallon/player.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# coding: utf-8
|
1
2
|
module Hallon
|
2
3
|
# A wrapper around Session for playing, stopping and otherwise
|
3
4
|
# controlling the playback features of libspotify.
|
@@ -9,6 +10,9 @@ module Hallon
|
|
9
10
|
class Player
|
10
11
|
include Observable
|
11
12
|
|
13
|
+
# @return [Spotify::Pointer<Session>] session pointer
|
14
|
+
attr_reader :pointer
|
15
|
+
|
12
16
|
# @return [Array<Symbol>] a list of available playback bitrates.
|
13
17
|
def self.bitrates
|
14
18
|
Spotify.enum_type(:bitrate).symbols.sort_by do |sym|
|
@@ -74,7 +78,7 @@ module Hallon
|
|
74
78
|
# @param [Symbol] bitrate one of :96k, :160k, :320k
|
75
79
|
# @return [Symbol]
|
76
80
|
def bitrate=(bitrate)
|
77
|
-
Spotify.session_preferred_bitrate(
|
81
|
+
Spotify.session_preferred_bitrate(pointer, bitrate)
|
78
82
|
end
|
79
83
|
|
80
84
|
# Loads a Track for playing.
|
@@ -83,7 +87,7 @@ module Hallon
|
|
83
87
|
# @return [Player]
|
84
88
|
# @raise [Error] if the track could not be loaded
|
85
89
|
def load(track)
|
86
|
-
error = Spotify.session_player_load(
|
90
|
+
error = Spotify.session_player_load(pointer, track.pointer)
|
87
91
|
tap { Error.maybe_raise(error) }
|
88
92
|
end
|
89
93
|
|
@@ -93,7 +97,7 @@ module Hallon
|
|
93
97
|
# @param [Track] track
|
94
98
|
# @return [Player]
|
95
99
|
def prefetch(track)
|
96
|
-
error = Spotify.session_player_prefetch(
|
100
|
+
error = Spotify.session_player_prefetch(pointer, track.pointer)
|
97
101
|
tap { Error.maybe_raise(error) }
|
98
102
|
end
|
99
103
|
|
@@ -102,21 +106,21 @@ module Hallon
|
|
102
106
|
# @return [Player]
|
103
107
|
def play(track = nil)
|
104
108
|
load(track) unless track.nil?
|
105
|
-
tap { Spotify.session_player_play(
|
109
|
+
tap { Spotify.session_player_play(pointer, true) }
|
106
110
|
end
|
107
111
|
|
108
112
|
# Pause playback of a Track.
|
109
113
|
#
|
110
114
|
# @return [Player]
|
111
115
|
def pause
|
112
|
-
tap { Spotify.session_player_play(
|
116
|
+
tap { Spotify.session_player_play(pointer, false) }
|
113
117
|
end
|
114
118
|
|
115
119
|
# Stop playing current track and unload it.
|
116
120
|
#
|
117
121
|
# @return [Player]
|
118
122
|
def stop
|
119
|
-
tap { Spotify.session_player_unload(
|
123
|
+
tap { Spotify.session_player_unload(pointer) }
|
120
124
|
end
|
121
125
|
|
122
126
|
# Seek to the desired position of the currently loaded Track.
|
@@ -124,7 +128,17 @@ module Hallon
|
|
124
128
|
# @param [Numeric] seconds offset position in seconds
|
125
129
|
# @return [Player]
|
126
130
|
def seek(seconds)
|
127
|
-
tap { Spotify.session_player_seek(
|
131
|
+
tap { Spotify.session_player_seek(pointer, seconds * 1000) }
|
132
|
+
end
|
133
|
+
|
134
|
+
# @return [Boolean] true if libspotify is set to normalize audio volume.
|
135
|
+
def volume_normalization?
|
136
|
+
Spotify.session_get_volume_normalization(pointer)
|
137
|
+
end
|
138
|
+
|
139
|
+
# @param [Boolean] normalize_volume true if libspotify should normalize audio volume.
|
140
|
+
def volume_normalization=(normalize_volume)
|
141
|
+
Spotify.session_set_volume_normalization(pointer, !! normalize_volume)
|
128
142
|
end
|
129
143
|
end
|
130
144
|
end
|
@@ -0,0 +1,291 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
module Hallon
|
3
|
+
# Playlists are playlists. They contain tracks and track information
|
4
|
+
# such as when tracks were added or by whom. They also contain some
|
5
|
+
# metadata such as their own name.
|
6
|
+
#
|
7
|
+
# @see http://developer.spotify.com/en/libspotify/docs/group__playlist.html
|
8
|
+
class Playlist < Base
|
9
|
+
include Observable
|
10
|
+
extend Linkable
|
11
|
+
|
12
|
+
# Playlist::Track is a {Track} with additional information attached to it,
|
13
|
+
# that is specific to the playlist it was created from. The returned track
|
14
|
+
# is a snapshot of the information, so even if the underlying track moves,
|
15
|
+
# this Playlist::Track will still contain the same information.
|
16
|
+
#
|
17
|
+
# There is no way to refresh the information. You’ll have to retrieve the
|
18
|
+
# track again.
|
19
|
+
class Track < Hallon::Track
|
20
|
+
def initialize(playlist, index)
|
21
|
+
super Spotify.playlist_track!(playlist, index)
|
22
|
+
|
23
|
+
@index = index
|
24
|
+
@create_time = Time.at Spotify.playlist_track_create_time(playlist, index)
|
25
|
+
@message = Spotify.playlist_track_message(playlist, index)
|
26
|
+
@seen = Spotify.playlist_track_seen(playlist, index)
|
27
|
+
@creator = begin
|
28
|
+
creator = Spotify.playlist_track_creator!(playlist, index)
|
29
|
+
User.new(creator) unless creator.null?
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# @note this value never changes, even if the original track is moved/removed
|
34
|
+
# @return [Integer] index this track was created with.
|
35
|
+
attr_reader :index
|
36
|
+
|
37
|
+
# @return [Time] time when track at {#index} was added to playlist.
|
38
|
+
def create_time
|
39
|
+
@create_time
|
40
|
+
end
|
41
|
+
|
42
|
+
# @return [User, nil] person who added track at {#index} to this playlist.
|
43
|
+
def creator
|
44
|
+
@creator
|
45
|
+
end
|
46
|
+
|
47
|
+
# @return [String] message attached to this track at {#index}.
|
48
|
+
def message
|
49
|
+
@message
|
50
|
+
end
|
51
|
+
|
52
|
+
# @see Playlist#seen
|
53
|
+
# @return [Boolean] true if track at {#index} has been seen.
|
54
|
+
def seen?
|
55
|
+
@seen
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
from_link :playlist do |pointer|
|
60
|
+
Spotify.playlist_create!(session.pointer, pointer)
|
61
|
+
end
|
62
|
+
|
63
|
+
to_link :from_playlist
|
64
|
+
|
65
|
+
# Construct a new Playlist, given a pointer.
|
66
|
+
#
|
67
|
+
# @param [String, Link, FFI::Pointer] link
|
68
|
+
def initialize(link)
|
69
|
+
callbacks = Spotify::PlaylistCallbacks.new(self, @sp_callbacks = {})
|
70
|
+
@pointer = to_pointer(link, :playlist)
|
71
|
+
Spotify.playlist_add_callbacks(pointer, callbacks, nil)
|
72
|
+
end
|
73
|
+
|
74
|
+
# @return [Boolean] true if the playlist is loaded
|
75
|
+
def loaded?
|
76
|
+
Spotify.playlist_is_loaded(pointer)
|
77
|
+
end
|
78
|
+
|
79
|
+
# @return [Boolean] true if the playlist is collaborative
|
80
|
+
def collaborative?
|
81
|
+
Spotify.playlist_is_collaborative(pointer)
|
82
|
+
end
|
83
|
+
|
84
|
+
# @param [Boolean] collaborative true to set the playlist to collaborative
|
85
|
+
def collaborative=(collaborative)
|
86
|
+
Spotify.playlist_set_collaborative(pointer, !!collaborative)
|
87
|
+
end
|
88
|
+
|
89
|
+
# @return [Boolean] true if playlist has pending changes
|
90
|
+
def pending?
|
91
|
+
Spotify.playlist_has_pending_changes(pointer)
|
92
|
+
end
|
93
|
+
|
94
|
+
# @return [Boolean] true if the playlist is in RAM
|
95
|
+
def in_ram?
|
96
|
+
Spotify.playlist_is_in_ram(session.pointer, pointer)
|
97
|
+
end
|
98
|
+
|
99
|
+
# @param [Boolean] in_ram true if you want to store the playlist in RAM
|
100
|
+
def in_ram=(in_ram)
|
101
|
+
Spotify.playlist_set_in_ram(session.pointer, pointer, !! in_ram)
|
102
|
+
end
|
103
|
+
|
104
|
+
# @return [Boolean] true if playlist is available offline (fully synced)
|
105
|
+
def available_offline?
|
106
|
+
offline_status == :yes
|
107
|
+
end
|
108
|
+
|
109
|
+
# @return [Boolean] true if playlist is currently syncing
|
110
|
+
def syncing?
|
111
|
+
offline_status == :downloading
|
112
|
+
end
|
113
|
+
|
114
|
+
# @return [Boolean] true if playlist is queued for offline syncing
|
115
|
+
def waiting?
|
116
|
+
offline_status == :waiting
|
117
|
+
end
|
118
|
+
|
119
|
+
# @return [Boolean] true if playlist is requested to be available offline
|
120
|
+
def offline_mode?
|
121
|
+
offline_status != :no
|
122
|
+
end
|
123
|
+
|
124
|
+
# @return [Symbol] one of :no, :yes, :downloading, :waiting
|
125
|
+
def offline_status
|
126
|
+
Spotify.playlist_get_offline_status(session.pointer, pointer)
|
127
|
+
end
|
128
|
+
|
129
|
+
# @param [Boolean] available_offline true if you want this playlist available offline
|
130
|
+
def offline_mode=(available_offline)
|
131
|
+
Spotify.playlist_set_offline_mode(session.pointer, pointer, !! available_offline)
|
132
|
+
end
|
133
|
+
|
134
|
+
# @return [String]
|
135
|
+
def name
|
136
|
+
Spotify.playlist_name(pointer)
|
137
|
+
end
|
138
|
+
|
139
|
+
# @note The name must not consist of only spaces and it must be shorter than 256 characters.
|
140
|
+
# @param [#to_s] name new name for playlist
|
141
|
+
# @raise [Error] if name could not be changed
|
142
|
+
def name=(name)
|
143
|
+
name = name.to_s.encode('UTF-8')
|
144
|
+
|
145
|
+
unless name.length < 256
|
146
|
+
raise ArgumentError, "name must be shorter than 256 characters (UTF-8)"
|
147
|
+
end
|
148
|
+
|
149
|
+
unless name =~ /[^ ]/u
|
150
|
+
raise ArgumentError, "name must not consist of only spaces"
|
151
|
+
end unless name.empty?
|
152
|
+
|
153
|
+
Error.maybe_raise(Spotify.playlist_rename(pointer, name))
|
154
|
+
end
|
155
|
+
|
156
|
+
# @return [User, nil]
|
157
|
+
def owner
|
158
|
+
user = Spotify.playlist_owner!(pointer)
|
159
|
+
User.new(user) unless user.null?
|
160
|
+
end
|
161
|
+
|
162
|
+
# @return [String]
|
163
|
+
def description
|
164
|
+
Spotify.playlist_get_description(pointer)
|
165
|
+
end
|
166
|
+
|
167
|
+
# @return [Image, nil]
|
168
|
+
def image
|
169
|
+
buffer = FFI::Buffer.alloc_out(20)
|
170
|
+
if Spotify.playlist_get_image(pointer, buffer)
|
171
|
+
Image.new buffer.read_bytes(20)
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
# @note this list might be shorter than {#total_subscribers}, as
|
176
|
+
# libspotify does not store more than 500 subscriber names
|
177
|
+
# @return [Array<String>] list of canonical usernames
|
178
|
+
def subscribers
|
179
|
+
ptr = Spotify.playlist_subscribers(pointer)
|
180
|
+
struct = Spotify::Subscribers.new(ptr)
|
181
|
+
|
182
|
+
if struct[:count].zero?
|
183
|
+
[]
|
184
|
+
else
|
185
|
+
struct[:subscribers].map(&:read_string)
|
186
|
+
end
|
187
|
+
ensure
|
188
|
+
Spotify.playlist_subscribers_free(ptr)
|
189
|
+
end
|
190
|
+
|
191
|
+
# @return [Integer] total number of subscribers.
|
192
|
+
def total_subscribers
|
193
|
+
Spotify.playlist_num_subscribers(pointer)
|
194
|
+
end
|
195
|
+
|
196
|
+
# Ask libspotify to update subscriber information
|
197
|
+
#
|
198
|
+
# @return [Playlist]
|
199
|
+
def update_subscribers
|
200
|
+
Spotify.playlist_update_subscribers(session.pointer, pointer)
|
201
|
+
end
|
202
|
+
|
203
|
+
# @note only applicable if {#offline_status} is `:downloading`
|
204
|
+
# @return [Integer] percentage done of playlist offline sync
|
205
|
+
def sync_progress
|
206
|
+
Spotify.playlist_get_offline_download_completed(session.pointer, pointer)
|
207
|
+
end
|
208
|
+
|
209
|
+
# @param [Boolean] autolink_tracks if you want unplayable tracks to be linked to playable tracks (if possible)
|
210
|
+
def autolink_tracks=(autolink_tracks)
|
211
|
+
Spotify.playlist_set_autolink_tracks(pointer, !! autolink_tracks)
|
212
|
+
end
|
213
|
+
|
214
|
+
# @note Will be 0 unless {#loaded?}.
|
215
|
+
# @return [Integer] number of tracks in playlist
|
216
|
+
def size
|
217
|
+
Spotify.playlist_num_tracks(pointer)
|
218
|
+
end
|
219
|
+
|
220
|
+
# @example retrieve track at index 3
|
221
|
+
# track = playlist.tracks[3]
|
222
|
+
# puts track.name
|
223
|
+
#
|
224
|
+
# @return [Enumerable<Playlist::Track>] a list of playlist tracks.
|
225
|
+
def tracks
|
226
|
+
Enumerator.new(size) { |i| Playlist::Track.new(pointer, i) }
|
227
|
+
end
|
228
|
+
|
229
|
+
# Set seen status of the Playlist::Track at the given index.
|
230
|
+
#
|
231
|
+
# @see #tracks
|
232
|
+
# @raise [Error] if the operation could not be completed
|
233
|
+
# @param [Integer] index
|
234
|
+
# @param [Boolean] seen true if the track is now seen
|
235
|
+
# @return [Playlist::Track] track at the given index
|
236
|
+
def seen(index, seen)
|
237
|
+
error = Spotify.playlist_track_set_seen(pointer, index, !! seen)
|
238
|
+
Error.maybe_raise(error)
|
239
|
+
tracks[index]
|
240
|
+
end
|
241
|
+
|
242
|
+
# Add a list of tracks to the playlist starting at given position.
|
243
|
+
#
|
244
|
+
# @param [Integer] index starting index to add tracks from (between 0..{#size})
|
245
|
+
# @param [Track, Array<Track>] tracks
|
246
|
+
# @return [Playlist]
|
247
|
+
# @raise [Hallon::Error] if the operation failed
|
248
|
+
def insert(index = size, tracks)
|
249
|
+
tracks = Array(tracks).map(&:pointer)
|
250
|
+
tracks_ary = FFI::MemoryPointer.new(:pointer, tracks.size)
|
251
|
+
tracks_ary.write_array_of_pointer(tracks)
|
252
|
+
|
253
|
+
tap do
|
254
|
+
error = Spotify.playlist_add_tracks(pointer, tracks_ary, tracks.size, index, session.pointer)
|
255
|
+
Error.maybe_raise(error)
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
# Remove tracks at given indices.
|
260
|
+
#
|
261
|
+
# @param [Integer, ...] indices
|
262
|
+
# @return [Playlist]
|
263
|
+
# @raise [Error] if the operation failed
|
264
|
+
def remove(*indices)
|
265
|
+
indices_ary = FFI::MemoryPointer.new(:int, indices.size)
|
266
|
+
indices_ary.write_array_of_int(indices)
|
267
|
+
|
268
|
+
tap do
|
269
|
+
error = Spotify.playlist_remove_tracks(pointer, indices_ary, indices.size)
|
270
|
+
Error.maybe_raise(error)
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
# Move tracks at given indices to given index.
|
275
|
+
#
|
276
|
+
# @param [Integer] destination index to move tracks to
|
277
|
+
# @param [Integer, Array<Integer>] indices
|
278
|
+
# @return [Playlist]
|
279
|
+
# @raise [Error] if the operation failed
|
280
|
+
def move(destination, indices)
|
281
|
+
indices = Array(indices)
|
282
|
+
indices_ary = FFI::MemoryPointer.new(:int, indices.size)
|
283
|
+
indices_ary.write_array_of_int(indices)
|
284
|
+
|
285
|
+
tap do
|
286
|
+
error = Spotify.playlist_reorder_tracks(pointer, indices_ary, indices.size, destination)
|
287
|
+
Error.maybe_raise(error)
|
288
|
+
end
|
289
|
+
end
|
290
|
+
end
|
291
|
+
end
|