hallon 0.4.0 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. data/.gitmodules +3 -0
  2. data/.travis.yml +2 -0
  3. data/CHANGELOG +30 -6
  4. data/README.markdown +7 -7
  5. data/Rakefile +70 -16
  6. data/examples/logging_in.rb +3 -3
  7. data/examples/printing_link_information.rb +1 -1
  8. data/examples/show_published_playlists_of_user.rb +92 -0
  9. data/hallon.gemspec +7 -4
  10. data/lib/hallon.rb +16 -4
  11. data/lib/hallon/album.rb +16 -6
  12. data/lib/hallon/album_browse.rb +78 -0
  13. data/lib/hallon/artist.rb +59 -0
  14. data/lib/hallon/artist_browse.rb +89 -0
  15. data/lib/hallon/base.rb +7 -0
  16. data/lib/hallon/enumerator.rb +64 -0
  17. data/lib/hallon/error.rb +8 -6
  18. data/lib/hallon/ext/spotify.rb +3 -3
  19. data/lib/hallon/image.rb +25 -12
  20. data/lib/hallon/link.rb +4 -4
  21. data/lib/hallon/linkable.rb +4 -2
  22. data/lib/hallon/observable.rb +1 -4
  23. data/lib/hallon/player.rb +130 -0
  24. data/lib/hallon/search.rb +128 -0
  25. data/lib/hallon/session.rb +226 -25
  26. data/lib/hallon/toplist.rb +83 -0
  27. data/lib/hallon/track.rb +62 -7
  28. data/lib/hallon/user.rb +6 -6
  29. data/lib/hallon/version.rb +1 -1
  30. data/spec/hallon/album_browse_spec.rb +20 -0
  31. data/spec/hallon/album_spec.rb +12 -7
  32. data/spec/hallon/artist_browse_spec.rb +29 -0
  33. data/spec/hallon/artist_spec.rb +32 -0
  34. data/spec/hallon/enumerator_spec.rb +106 -0
  35. data/spec/hallon/error_spec.rb +10 -0
  36. data/spec/hallon/hallon_spec.rb +5 -1
  37. data/spec/hallon/image_spec.rb +39 -25
  38. data/spec/hallon/linkable_spec.rb +12 -4
  39. data/spec/hallon/observable_spec.rb +5 -0
  40. data/spec/hallon/player_spec.rb +73 -0
  41. data/spec/hallon/search_spec.rb +80 -0
  42. data/spec/hallon/session_spec.rb +187 -6
  43. data/spec/hallon/toplist_spec.rb +40 -0
  44. data/spec/hallon/track_spec.rb +43 -8
  45. data/spec/mockspotify.rb +47 -0
  46. data/spec/mockspotify/.gitignore +5 -0
  47. data/spec/mockspotify/extconf.rb +5 -0
  48. data/spec/mockspotify/mockspotify_spec.rb +41 -0
  49. data/spec/spec_helper.rb +20 -0
  50. data/spec/support/common_objects.rb +84 -7
  51. metadata +72 -20
  52. data/lib/hallon/ext/object.rb +0 -16
@@ -0,0 +1,130 @@
1
+ module Hallon
2
+ # A wrapper around Session for playing, stopping and otherwise
3
+ # controlling the playback features of libspotify.
4
+ #
5
+ # @note This is very much a work in progress. Given Session still
6
+ # takes care of all callbacks, and the callbacks themselves
7
+ # must still be handled by means of Ruby FFI.
8
+ # @see Session
9
+ class Player
10
+ include Observable
11
+
12
+ # @return [Array<Symbol>] a list of available playback bitrates.
13
+ def self.bitrates
14
+ Spotify.enum_type(:bitrate).symbols.sort_by do |sym|
15
+ # sort by bitrate quality
16
+ sym.to_s.to_i
17
+ end
18
+ end
19
+
20
+ # Constructs a Player, given a Session.
21
+ #
22
+ # @example
23
+ # Hallon::Player.new(session) do
24
+ # on(:music_delivery) do |*frames|
25
+ # end
26
+ #
27
+ # on(:start_playback) do
28
+ # end
29
+ #
30
+ # on(:stop_playback) do
31
+ # end
32
+ #
33
+ # on(:play_token_lost) do
34
+ # end
35
+ #
36
+ # on(:end_of_track) do
37
+ # end
38
+ #
39
+ # on(:streaming_error) do |error|
40
+ # end
41
+ #
42
+ # on(:buffer_size?) do
43
+ # # return the pair of [samples, dropouts] of your audiobuffer
44
+ # end
45
+ # end
46
+ #
47
+ # @param [Session] session
48
+ # @yield instance_evals itself, allowing you to define callbacks using `on`
49
+ def initialize(session, &block)
50
+ instance_eval(&block) if block_given?
51
+
52
+ @session = session
53
+ @pointer = @session.pointer
54
+
55
+ %w[start_playback stop_playback play_token_lost end_of_track streaming_error].each do |cb|
56
+ @session.on(cb) { |*args| trigger(cb, *args) }
57
+ end
58
+
59
+ @session.on(:audio_buffer_stats) do |stats_ptr|
60
+ stats = Spotify::AudioBufferStats.new(stats_ptr)
61
+ samples, dropouts = trigger(:buffer_size?)
62
+ stats[:samples] = samples || 0
63
+ stats[:dropouts] = dropouts || 0
64
+ end
65
+
66
+ @session.on(:music_delivery) do |format, frames, num_frames|
67
+ trigger(:music_delivery, format, frames, num_frames)
68
+ num_frames # assume we consume all data
69
+ end
70
+ end
71
+
72
+ # Set preferred playback bitrate.
73
+ #
74
+ # @param [Symbol] bitrate one of :96k, :160k, :320k
75
+ # @return [Symbol]
76
+ def bitrate=(bitrate)
77
+ Spotify.session_preferred_bitrate(@pointer, bitrate)
78
+ end
79
+
80
+ # Loads a Track for playing.
81
+ #
82
+ # @param [Track] track
83
+ # @return [Player]
84
+ # @raise [Error] if the track could not be loaded
85
+ def load(track)
86
+ error = Spotify.session_player_load(@pointer, track.pointer)
87
+ tap { Error.maybe_raise(error) }
88
+ end
89
+
90
+ # Prepares a Track for playing, without loading it.
91
+ #
92
+ # @note You can only prefetch if caching is on.
93
+ # @param [Track] track
94
+ # @return [Player]
95
+ def prefetch(track)
96
+ error = Spotify.session_player_prefetch(@pointer, track.pointer)
97
+ tap { Error.maybe_raise(error) }
98
+ end
99
+
100
+ # Starts playing a Track by feeding data to your application.
101
+ #
102
+ # @return [Player]
103
+ def play(track = nil)
104
+ load(track) unless track.nil?
105
+ tap { Spotify.session_player_play(@pointer, true) }
106
+ end
107
+
108
+ # Pause playback of a Track.
109
+ #
110
+ # @return [Player]
111
+ def pause
112
+ tap { Spotify.session_player_play(@pointer, false) }
113
+ end
114
+
115
+ # Stop playing current track and unload it.
116
+ #
117
+ # @return [Player]
118
+ def stop
119
+ tap { Spotify.session_player_unload(@pointer) }
120
+ end
121
+
122
+ # Seek to the desired position of the currently loaded Track.
123
+ #
124
+ # @param [Numeric] seconds offset position in seconds
125
+ # @return [Player]
126
+ def seek(seconds)
127
+ tap { Spotify.session_player_seek(@pointer, seconds * 1000) }
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,128 @@
1
+ # coding: utf-8
2
+ module Hallon
3
+ # Search allows you to search Spotify for tracks, albums
4
+ # and artists, just like in the client.
5
+ #
6
+ # @see http://developer.spotify.com/en/libspotify/docs/group__search.html
7
+ class Search < Base
8
+ include Observable
9
+
10
+ # @return [Array<Symbol>] a list of radio genres available for search
11
+ def self.genres
12
+ Spotify.enum_type(:radio_genre).symbols
13
+ end
14
+
15
+ # @param [Range<Integer>] range (from_year..to_year)
16
+ # @param [Symbol, …] genres
17
+ # @return [Search] radio search in given period and genres
18
+ def self.radio(range, *genres)
19
+ from_year, to_year = range.begin, range.end
20
+ genres = genres.reduce(0) do |mask, genre|
21
+ mask | (Spotify.enum_value(genre) || 0)
22
+ end
23
+
24
+ search = allocate
25
+ search.instance_eval do
26
+ @callback = proc { search.trigger(:load) }
27
+ pointer = Spotify.radio_search_create(session.pointer, from_year, to_year, genres, @callback, nil)
28
+ @pointer = Spotify::Pointer.new(pointer, :search, false)
29
+
30
+ self
31
+ end
32
+ end
33
+
34
+ # Construct a new search with given query.
35
+ #
36
+ # @param [String] query search query
37
+ # @param [Hash] options additional search options
38
+ # @option options [#to_i] :tracks (25) max number of tracks you want in result
39
+ # @option options [#to_i] :albums (25) max number of albums you want in result
40
+ # @option options [#to_i] :artists (25) max number of artists you want in result
41
+ # @option options [#to_i] :tracks_offset (0) offset of tracks in search result
42
+ # @option options [#to_i] :albums_offset (0) offset of albums in search result
43
+ # @option options [#to_i] :artists_offset (0) offset of artists in search result
44
+ # @see http://developer.spotify.com/en/libspotify/docs/group__search.html#gacf0b5e902e27d46ef8b1f40e332766df
45
+ def initialize(query, options = {})
46
+ o = {
47
+ :tracks => 25,
48
+ :albums => 25,
49
+ :artists => 25,
50
+ :tracks_offset => 0,
51
+ :albums_offset => 0,
52
+ :artists_offset => 0
53
+ }.merge(options)
54
+
55
+ @callback = proc { trigger(:load) }
56
+ pointer = Spotify.search_create(session.pointer, query, o[:tracks_offset].to_i, o[:tracks].to_i, o[:albums_offset].to_i, o[:albums].to_i, o[:artists_offset].to_i, o[:artists].to_i, @callback, nil)
57
+ @pointer = Spotify::Pointer.new(pointer, :search, false)
58
+ end
59
+
60
+ # @return [Boolean] true if the search has been fully loaded
61
+ def loaded?
62
+ Spotify.search_is_loaded(@pointer)
63
+ end
64
+
65
+ # @return [Symbol] error status
66
+ def error
67
+ Spotify.search_error(@pointer)
68
+ end
69
+
70
+ # @return [String] search query this search was created with
71
+ def query
72
+ Spotify.search_query(@pointer)
73
+ end
74
+
75
+ # @return [String] “did you mean?” suggestion for current search
76
+ def did_you_mean
77
+ Spotify.search_did_you_mean(@pointer)
78
+ end
79
+
80
+ # @return [Enumerator<Track>] enumerate over all tracks in the search result
81
+ def tracks
82
+ size = Spotify.search_num_tracks(@pointer)
83
+ Enumerator.new(size) do |i|
84
+ track = Spotify.search_track(@pointer, i)
85
+ Track.new(track) unless track.null?
86
+ end
87
+ end
88
+
89
+ # @return [Integer] total tracks available for this search query
90
+ def total_tracks
91
+ Spotify.search_total_tracks(@pointer)
92
+ end
93
+
94
+ # @return [Enumerator<Album>] enumerate over all albums in the search result
95
+ def albums
96
+ size = Spotify.search_num_albums(@pointer)
97
+ Enumerator.new(size) do |i|
98
+ album = Spotify.search_album(@pointer, i)
99
+ Album.new(album) unless album.null?
100
+ end
101
+ end
102
+
103
+ # @return [Integer] total tracks available for this search query
104
+ def total_albums
105
+ Spotify.search_total_albums(@pointer)
106
+ end
107
+
108
+ # @return [Enumerator<Artist>] enumerate over all artists in the search result
109
+ def artists
110
+ size = Spotify.search_num_artists(@pointer)
111
+ Enumerator.new(size) do |i|
112
+ artist = Spotify.search_artist(@pointer, i)
113
+ Artist.new(artist) unless artist.null?
114
+ end
115
+ end
116
+
117
+ # @return [Integer] total tracks available for this search query
118
+ def total_artists
119
+ Spotify.search_total_artists(@pointer)
120
+ end
121
+
122
+ # @return [Link] link for this search query
123
+ def to_link
124
+ pointer = Spotify.link_create_from_search(@pointer)
125
+ Link.new(pointer) unless pointer.null?
126
+ end
127
+ end
128
+ end
@@ -15,6 +15,15 @@ module Hallon
15
15
  # @return [Hash]
16
16
  attr_reader :options
17
17
 
18
+ # The current session cache size (in megabytes).
19
+ #
20
+ # @note This is not provided by libspotify, and the value is only valid
21
+ # as long as the cache size is only adjusted through {#cache_size=}
22
+ # and not the Spotify FFI interface.
23
+ #
24
+ # @return [Integer]
25
+ attr_reader :cache_size
26
+
18
27
  # libspotify only allows one session per process.
19
28
  include Singleton
20
29
  class << self
@@ -22,16 +31,38 @@ module Hallon
22
31
  end
23
32
 
24
33
  # Session allows you to define your own callbacks.
25
- include Hallon::Observable
34
+ include Observable
26
35
 
27
- # Allows you to create a Spotify session. Subsequent calls to this method
28
- # will return the previous instance, ignoring any passed arguments.
36
+ # Initializes the Spotify session. If you need to access the
37
+ # instance at a later time, you can use {instance}.
29
38
  #
30
- # @param (see Session#initialize)
39
+ # @see Session.instance
31
40
  # @see Session#initialize
41
+ #
42
+ # @param (see Session#initialize)
43
+ # @return [Session]
44
+ def Session.initialize(*args, &block)
45
+ raise "Session has already been initialized" if @__instance__
46
+ @__instance__ = new(*args, &block)
47
+ end
48
+
49
+ # Returns the previously initialized Session.
50
+ #
51
+ # @see Session.instance
52
+ #
32
53
  # @return [Session]
33
- def Session.instance(*args, &block)
34
- @__instance__ ||= new(*args, &block)
54
+ def Session.instance
55
+ @__instance__ or raise "Session has not been initialized"
56
+ end
57
+
58
+ # @return [Array<Symbol>] list of available connection types.
59
+ def self.connection_types
60
+ Spotify.enum_type(:connection_type).symbols
61
+ end
62
+
63
+ # @return [Array<Symbol>] list of available connection rules
64
+ def self.connection_rules
65
+ Spotify.enum_type(:connection_rules).symbols
35
66
  end
36
67
 
37
68
  # Create a new Spotify session.
@@ -49,7 +80,6 @@ module Hallon
49
80
  # @raise [Hallon::Error] if `sp_session_create` fails
50
81
  # @see http://developer.spotify.com/en/libspotify/docs/structsp__session__config.html
51
82
  def initialize(appkey, options = {}, &block)
52
- @appkey = appkey.to_s
53
83
  @options = {
54
84
  :user_agent => "Hallon",
55
85
  :settings_path => "tmp",
@@ -66,15 +96,18 @@ module Hallon
66
96
  # Set configuration, as well as callbacks
67
97
  config = Spotify::SessionConfig.new
68
98
  config[:api_version] = Hallon::API_VERSION
69
- config.application_key = @appkey
99
+ config.application_key = appkey
70
100
  @options.each { |(key, value)| config.send(:"#{key}=", value) }
71
101
  config[:callbacks] = Spotify::SessionCallbacks.new(self, @sp_callbacks = {})
72
102
 
103
+ # Default cache size is 0 (automatic)
104
+ @cache_size = 0
105
+
73
106
  instance_eval(&block) if block_given?
74
107
 
75
108
  # You pass a pointer to the session pointer to libspotify >:)
76
109
  FFI::MemoryPointer.new(:pointer) do |p|
77
- Hallon::Error::maybe_raise Spotify::session_create(config, p)
110
+ Error::maybe_raise Spotify.session_create(config, p)
78
111
  @pointer = p.read_pointer
79
112
  end
80
113
  end
@@ -84,20 +117,22 @@ module Hallon
84
117
  # @return [Fixnum] minimum time until it should be called again
85
118
  def process_events
86
119
  FFI::MemoryPointer.new(:int) do |p|
87
- Spotify::session_process_events(@pointer, p)
120
+ Spotify.session_process_events(@pointer, p)
88
121
  return p.read_int
89
122
  end
90
123
  end
91
124
 
92
125
  # Wait for the given callbacks to fire until the block returns true
93
126
  #
127
+ # @note Given block will be called once instantly without parameters.
128
+ # @note If no events happen for 0.25 seconds, the given block will be called
129
+ # with `:timeout` as parameter.
94
130
  # @param [Symbol, ...] *events list of events to wait for
95
131
  # @yield [Symbol, *args] name of the callback that fired, and its’ arguments
96
132
  # @return [Hash<Event, Arguments>]
97
- def process_events_on(*events, &block)
98
- channel = SizedQueue.new(1)
99
-
100
- protecting_handlers do
133
+ def process_events_on(*events)
134
+ yield or protecting_handlers do
135
+ channel = SizedQueue.new(1)
101
136
  on(*events) { |*args| channel << args }
102
137
  on(:notify_main_thread) { channel << :notify }
103
138
 
@@ -107,10 +142,10 @@ module Hallon
107
142
  params = Timeout::timeout(0.25) { channel.pop }
108
143
  redo if params == :notify
109
144
  rescue Timeout::Error
110
- params = nil
145
+ params = :timeout
111
146
  end
112
147
 
113
- if result = block.call(*params)
148
+ if result = yield(*params)
114
149
  return result
115
150
  end
116
151
  end
@@ -122,39 +157,187 @@ module Hallon
122
157
  #
123
158
  # @param [String] username
124
159
  # @param [String] password
160
+ # @param [Boolean] remember_me have libspotify remember credentials for {#relogin}
161
+ # @return [self]
162
+ def login(username, password, remember_me = false)
163
+ tap { Spotify.session_login(@pointer, username, password, @remembered = remember_me) }
164
+ end
165
+
166
+ # Login the remembered user (see {#login}).
167
+ #
168
+ # @raise [Hallon::Error] if no credentials are stored in libspotify
169
+ def relogin
170
+ Error.maybe_raise Spotify.session_relogin(@pointer)
171
+ end
172
+
173
+ # Username of the user stored in libspotify-remembered credentials.
174
+ #
175
+ # @return [String]
176
+ def remembered_user
177
+ bufflen = Spotify.session_remembered_user(@pointer, nil, 0)
178
+ FFI::Buffer.alloc_out(bufflen + 1) do |b|
179
+ Spotify.session_remembered_user(@pointer, b, b.size)
180
+ return b.get_string(0)
181
+ end if bufflen > 0
182
+ end
183
+
184
+ # Remove stored login credentials in libspotify.
185
+ #
186
+ # @note If no credentials are stored nothing’ll happen.
125
187
  # @return [self]
126
- def login(username, password)
127
- Spotify::session_login(@pointer, username, password)
128
- self
188
+ def forget_me!
189
+ tap { Spotify.session_forget_me(@pointer) }
129
190
  end
130
191
 
131
192
  # Logs out of Spotify. Does nothing if not logged in.
132
193
  #
133
194
  # @return [self]
134
195
  def logout
135
- Spotify::session_logout(@pointer) if logged_in?
136
- self
196
+ tap { Spotify.session_logout(@pointer) if logged_in? }
137
197
  end
138
198
 
139
199
  # Retrieve the currently logged in {User}.
140
200
  #
141
201
  # @return [User]
142
202
  def user
143
- User.new Spotify::session_user(@pointer)
203
+ User.new Spotify.session_user(@pointer)
144
204
  end
145
205
 
146
206
  # Retrieve the relation type between logged in {User} and `user`.
147
207
  #
148
208
  # @return [Symbol] :unknown, :none, :unidirectional or :bidirectional
149
209
  def relation_type?(user)
150
- Spotify::user_relation_type(@pointer, user.pointer)
210
+ Spotify.user_relation_type(@pointer, user.pointer)
151
211
  end
152
212
 
153
213
  # Retrieve current connection status.
154
214
  #
155
215
  # @return [Symbol]
156
216
  def status
157
- Spotify::session_connectionstate(@pointer)
217
+ Spotify.session_connectionstate(@pointer)
218
+ end
219
+
220
+ # Set session cache size in megabytes.
221
+ #
222
+ # @param [Integer]
223
+ # @return [Integer]
224
+ def cache_size=(size)
225
+ Spotify.session_set_cache_size(@pointer, @cache_size = size)
226
+ end
227
+
228
+ # @return [String] Currently logged in users’ country.
229
+ def country
230
+ coded = Spotify.session_user_country(@pointer)
231
+ country = ((coded >> 8) & 0xFF).chr
232
+ country << (coded & 0xFF).chr
233
+ end
234
+
235
+ # Star the given tracks.
236
+ #
237
+ # @example
238
+ # track = Hallon::Track.new("spotify:track:2LFQV2u6wXZmmySCWBkYGu")
239
+ # session.star(track)
240
+ #
241
+ # @param [Track…]
242
+ # @return [Session]
243
+ def star(*tracks)
244
+ tap { tracks_starred(tracks, true) }
245
+ end
246
+
247
+ # Unstar the given tracks.
248
+ #
249
+ # @example
250
+ # track = Hallon::Track.new("spotify:track:2LFQV2u6wXZmmySCWBkYGu")
251
+ # session.unstar(track)
252
+ #
253
+ # @param [Track…]
254
+ # @return [Session]
255
+ def unstar(*tracks)
256
+ tap { tracks_starred(tracks, false) }
257
+ end
258
+
259
+ # @note This will be 0 if not logged in.
260
+ # @note As of current writing, I am unsure if there’s a good way to find out
261
+ # when this enumerator will be populated. No callbacks or other status
262
+ # field can tell you when the current sessions’ friends are available.
263
+ # @return [Enumerator<User>] friends of currently logged in user
264
+ def friends
265
+ size = if logged_in?
266
+ # segfaults unless logged in
267
+ Spotify.session_num_friends(@pointer)
268
+ else
269
+ 0
270
+ end
271
+
272
+ Enumerator.new(size) do |i|
273
+ friend = Spotify.session_friend(@pointer, i)
274
+ User.new(friend)
275
+ end
276
+ end
277
+
278
+ # Set the connection rules for this session.
279
+ #
280
+ # @param [Symbol, …] connection_rules
281
+ # @see Session.connection_rules
282
+ def connection_rules=(connection_rules)
283
+ rules = Array(connection_rules).reduce(0) do |mask, rule|
284
+ mask | (Spotify.enum_value(rule) || 0)
285
+ end
286
+
287
+ Spotify.session_set_connection_rules(@pointer, rules)
288
+ end
289
+
290
+ # Set the connection type for this session.
291
+ #
292
+ # @param [Symbol] connection_type
293
+ # @see Session.connection_types
294
+ def connection_type=(connection_type)
295
+ Spotify.session_set_connection_type(@pointer, connection_type)
296
+ end
297
+
298
+ # Remaining time left you can stay offline before needing to relogin.
299
+ #
300
+ # @return [Integer] offline time left in seconds
301
+ def offline_time_left
302
+ Spotify.offline_time_left(@pointer)
303
+ end
304
+
305
+ # Offline synchronization status.
306
+ #
307
+ # @return [Hash, nil] sync status, or nil if not applicable
308
+ # @see http://developer.spotify.com/en/libspotify/docs/structsp__offline__sync__status.html
309
+ def offline_sync_status
310
+ struct = Spotify::OfflineSyncStatus.new
311
+ if Spotify.offline_sync_get_status(@pointer, struct.pointer)
312
+ Hash[struct.members.zip(struct.values)]
313
+ end
314
+ end
315
+
316
+ # Number of playlists marked for offline sync.
317
+ #
318
+ # @return [Integer]
319
+ def offline_playlists_count
320
+ Spotify.offline_num_playlists(@pointer)
321
+ end
322
+
323
+ # Number of offline tracks left to sync for offline mode.
324
+ #
325
+ # @return [Integer]
326
+ def offline_tracks_to_sync
327
+ Spotify.offline_tracks_to_sync(@pointer)
328
+ end
329
+
330
+ # Set preferred offline bitrate.
331
+ #
332
+ # @example
333
+ # session.offline_bitrate = :'96k', true
334
+ #
335
+ # @param [Symbol] bitrate
336
+ # @param [Boolean] resync (default: false)
337
+ # @see Player.bitrates
338
+ def offline_bitrate=(bitrate)
339
+ bitrate, resync = Array(bitrate)
340
+ Spotify.session_preferred_offline_bitrate(@pointer, bitrate, !! resync)
158
341
  end
159
342
 
160
343
  # True if currently logged in.
@@ -175,11 +358,29 @@ module Hallon
175
358
  status == :disconnected
176
359
  end
177
360
 
361
+ # True if offline.
362
+ # @see #status
363
+ def offline?
364
+ status == :offline
365
+ end
366
+
178
367
  # String representation of the Session.
179
368
  #
180
369
  # @return [String]
181
370
  def to_s
182
- "<#{self.class.name}>"
371
+ "<#{self.class.name}:0x#{object_id.to_s(16)} status=#{status} @options=#{options.inspect}>"
183
372
  end
373
+
374
+ private
375
+ # Set starred status of given tracks.
376
+ #
377
+ # @param [Array<Track>] tracks
378
+ # @param [Boolean] starred
379
+ def tracks_starred(tracks, starred)
380
+ FFI::MemoryPointer.new(:pointer, tracks.size) do |ptr|
381
+ ptr.write_array_of_pointer tracks.map(&:pointer)
382
+ Spotify.track_set_starred(pointer, ptr, tracks.size, starred)
383
+ end
384
+ end
184
385
  end
185
386
  end