hallon 0.0.0 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.autotest +6 -0
- data/.gemtest +0 -0
- data/.gitignore +29 -0
- data/.rspec +7 -0
- data/.yardopts +8 -0
- data/CHANGELOG +20 -0
- data/Gemfile +2 -0
- data/LICENSE.txt +21 -0
- data/QUIRKS +11 -0
- data/README.markdown +58 -0
- data/Rakefile +75 -0
- data/Termfile +7 -0
- data/examples/logging_in.rb +26 -0
- data/examples/printing_link_information.rb +27 -0
- data/hallon.gemspec +31 -0
- data/lib/hallon.rb +34 -0
- data/lib/hallon/error.rb +54 -0
- data/lib/hallon/ext/ffi.rb +26 -0
- data/lib/hallon/ext/spotify.rb +101 -0
- data/lib/hallon/image.rb +70 -0
- data/lib/hallon/link.rb +101 -0
- data/lib/hallon/linkable.rb +50 -0
- data/lib/hallon/observable.rb +91 -0
- data/lib/hallon/session.rb +189 -0
- data/lib/hallon/synchronizable.rb +32 -0
- data/lib/hallon/user.rb +69 -0
- data/lib/hallon/version.rb +7 -0
- data/spec/fixtures/example_uris.rb +11 -0
- data/spec/fixtures/pink_cover.jpg +0 -0
- data/spec/hallon/error_spec.rb +30 -0
- data/spec/hallon/ffi_spec.rb +5 -0
- data/spec/hallon/hallon_spec.rb +16 -0
- data/spec/hallon/image_spec.rb +41 -0
- data/spec/hallon/link_spec.rb +84 -0
- data/spec/hallon/linkable_spec.rb +43 -0
- data/spec/hallon/observable_spec.rb +103 -0
- data/spec/hallon/session_spec.rb +61 -0
- data/spec/hallon/synchronizable_spec.rb +19 -0
- data/spec/hallon/user_spec.rb +73 -0
- data/spec/spec_helper.rb +71 -0
- data/spec/support/.gitkeep +0 -0
- data/spec/support/context_initialized_session.rb +3 -0
- data/spec/support/context_logged_in.rb +16 -0
- data/spec/support/cover_me.rb +5 -0
- data/spec/support/shared_for_loadable_objects.rb +7 -0
- metadata +271 -96
@@ -0,0 +1,101 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
# Extensions to the Spotify gem.
|
4
|
+
#
|
5
|
+
# @see https://github.com/Burgestrand/libspotify-ruby
|
6
|
+
module Spotify
|
7
|
+
extend FFI::Library
|
8
|
+
ffi_lib ['libspotify', '/Library/Frameworks/libspotify.framework/libspotify']
|
9
|
+
|
10
|
+
# The Pointer is a kind of AutoPointer specially tailored for Spotify
|
11
|
+
# objects. It will automatically release the inner pointer with the
|
12
|
+
# proper function, based on the given type to #initialize.
|
13
|
+
class Pointer < FFI::AutoPointer
|
14
|
+
# Initialize the Spotify::Pointer
|
15
|
+
#
|
16
|
+
# @param [FFI::Pointer] ptr
|
17
|
+
# @param [Symbol] type session, link, etc
|
18
|
+
# @param [Boolean[ add_ref increase reference count
|
19
|
+
# @return [FFI::AutoPointer]
|
20
|
+
def initialize(ptr, type, add_ref = false)
|
21
|
+
super ptr, releaser_for(@type = type)
|
22
|
+
Spotify::send(:"#{type}_add_ref", ptr) if add_ref
|
23
|
+
end
|
24
|
+
|
25
|
+
# Create a proc that will accept a pointer of a given type and
|
26
|
+
# release it with the correct function if it’s not null.
|
27
|
+
#
|
28
|
+
# @param [Symbol]
|
29
|
+
# @return [Proc]
|
30
|
+
def releaser_for(type)
|
31
|
+
lambda do |ptr|
|
32
|
+
unless ptr.null?
|
33
|
+
$stdout.puts "Spotify::#{type}_release(#{ptr})" if $DEBUG
|
34
|
+
Spotify::send(:"#{type}_release", ptr)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# Extensions to SessionCallbacks, making it easier to define callbacks.
|
41
|
+
class SessionCallbacks < FFI::Struct
|
42
|
+
# Assigns the callbacks to call the given target; the callback
|
43
|
+
# procs are stored in the `storage` parameter. **Make sure the
|
44
|
+
# storage does not get garbage collected as long as these callbacks
|
45
|
+
# are needed!**
|
46
|
+
#
|
47
|
+
# @param [Object] target
|
48
|
+
# @param [#[]=] storage
|
49
|
+
def initialize(target, storage)
|
50
|
+
members.each do |member|
|
51
|
+
callback = lambda { |ptr, *args| target.trigger(member, *args) }
|
52
|
+
self[member] = storage[member] = callback
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# Extensions to SessionConfig, allowing more sensible configuration names.
|
58
|
+
class SessionConfig < FFI::Struct
|
59
|
+
[:cache_location, :settings_location, :user_agent].each do |field|
|
60
|
+
method = field.to_s.gsub('location', 'path')
|
61
|
+
define_method(:"#{method}") { self[field].read_string }
|
62
|
+
define_method(:"#{method}=") do |string|
|
63
|
+
self[field] = FFI::MemoryPointer.from_string(string)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# Also sets application_key_size.
|
68
|
+
#
|
69
|
+
# @param [#to_s]
|
70
|
+
def application_key=(appkey)
|
71
|
+
self[:application_key] = FFI::MemoryPointer.from_string(appkey)
|
72
|
+
self[:application_key_size] = appkey.bytesize
|
73
|
+
end
|
74
|
+
|
75
|
+
# Allows setting compress_playlists using a boolean.
|
76
|
+
#
|
77
|
+
# @param [Boolean]
|
78
|
+
# @return [Boolean]
|
79
|
+
def compress_playlists=(bool)
|
80
|
+
self[:compress_playlists] = !! bool
|
81
|
+
end
|
82
|
+
|
83
|
+
# Allows setting initially_unload_playlists using a boolean.
|
84
|
+
#
|
85
|
+
# @note Set to the inverse of the requested value.
|
86
|
+
# @param [Boolean]
|
87
|
+
# @return [Boolean]
|
88
|
+
def load_playlists=(bool)
|
89
|
+
self[:initially_unload_playlists] = ! bool
|
90
|
+
end
|
91
|
+
|
92
|
+
# Allows setting dont_save_metadata_for_playlists using a boolean.
|
93
|
+
#
|
94
|
+
# @note Set to the inverse of the requested value.
|
95
|
+
# @param [Boolean]
|
96
|
+
# @return [Boolean]
|
97
|
+
def cache_playlist_metadata=(bool)
|
98
|
+
self[:dont_save_metadata_for_playlists] = ! bool
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
data/lib/hallon/image.rb
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
module Hallon
|
3
|
+
# Images are JPEG images that can be linked to and saved.
|
4
|
+
#
|
5
|
+
# @see http://developer.spotify.com/en/libspotify/docs/group__image.html
|
6
|
+
class Image
|
7
|
+
extend Linkable
|
8
|
+
|
9
|
+
from_link(:image) do |link, session|
|
10
|
+
Spotify::image_create_from_link(session.pointer, link)
|
11
|
+
end
|
12
|
+
|
13
|
+
to_link(:image)
|
14
|
+
|
15
|
+
# Image triggers `:load` when loaded
|
16
|
+
include Hallon::Observable
|
17
|
+
|
18
|
+
# Create a new instance of an Image.
|
19
|
+
#
|
20
|
+
# @param [String, Link, FFI::Pointer] link
|
21
|
+
# @param [Hallon::Session] session
|
22
|
+
def initialize(link, session = Session.instance)
|
23
|
+
@callback = proc { trigger(:load) }
|
24
|
+
@pointer = Spotify::Pointer.new from_link(link, session), :image
|
25
|
+
Spotify::image_add_load_callback(@pointer, @callback, nil)
|
26
|
+
|
27
|
+
# TODO: remove load_callback when @pointer is released
|
28
|
+
# TODO: this makes libspotify segfault, figure out why
|
29
|
+
# on(:load) { Spotify::image_remove_load_callback(@pointer, @callback, nil) }
|
30
|
+
end
|
31
|
+
|
32
|
+
# True if the image has been loaded.
|
33
|
+
#
|
34
|
+
# @return [Boolean]
|
35
|
+
def loaded?
|
36
|
+
Spotify::image_is_loaded(@pointer)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Retrieve the current error status.
|
40
|
+
#
|
41
|
+
# @return [Symbol] error
|
42
|
+
def status
|
43
|
+
Spotify::image_error(@pointer)
|
44
|
+
end
|
45
|
+
|
46
|
+
# Retrieve image format.
|
47
|
+
#
|
48
|
+
# @return [Symbol] `:jpeg` or `:unknown`
|
49
|
+
def format
|
50
|
+
Spotify::image_format(@pointer)
|
51
|
+
end
|
52
|
+
|
53
|
+
# Retrieve image ID as a hexadecimal string.
|
54
|
+
#
|
55
|
+
# @return [String]
|
56
|
+
def id
|
57
|
+
Spotify::image_image_id(@pointer).read_string(20).unpack('H*')[0]
|
58
|
+
end
|
59
|
+
|
60
|
+
# Raw image data as a binary encoded string.
|
61
|
+
#
|
62
|
+
# @return [String]
|
63
|
+
def data
|
64
|
+
FFI::MemoryPointer.new(:size_t) do |size|
|
65
|
+
data = Spotify::image_data(@pointer, size)
|
66
|
+
return data.read_bytes(size.read_size_t)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
data/lib/hallon/link.rb
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
module Hallon
|
3
|
+
# Wraps Spotify URIs in a class, giving access to methods performable on them.
|
4
|
+
#
|
5
|
+
# @see http://developer.spotify.com/en/libspotify/docs/group__link.html
|
6
|
+
class Link
|
7
|
+
include Comparable
|
8
|
+
|
9
|
+
# True if the given Spotify URI is valid (parsable by libspotify).
|
10
|
+
#
|
11
|
+
# @param (see Hallon::Link#initialize)
|
12
|
+
# @return [Boolean]
|
13
|
+
def self.valid?(spotify_uri)
|
14
|
+
!! new(spotify_uri)
|
15
|
+
rescue ArgumentError
|
16
|
+
false
|
17
|
+
end
|
18
|
+
|
19
|
+
# Overloaded to short-circuit when given a Link.
|
20
|
+
#
|
21
|
+
# @return [Hallon::Link]
|
22
|
+
def self.new(uri)
|
23
|
+
uri.is_a?(Link) ? uri : super
|
24
|
+
end
|
25
|
+
|
26
|
+
# Parse the given Spotify URI into a Link.
|
27
|
+
#
|
28
|
+
# @note Unless you have a {Session} initialized, this will segfault!
|
29
|
+
# @param [#to_str] uri
|
30
|
+
# @raise [ArgumentError] link could not be parsed
|
31
|
+
def initialize(uri)
|
32
|
+
if (link = uri).respond_to? :to_str
|
33
|
+
link = Spotify::link_create_from_string(link.to_str)
|
34
|
+
end
|
35
|
+
|
36
|
+
@pointer = Spotify::Pointer.new(link, :link)
|
37
|
+
|
38
|
+
raise ArgumentError, "#{uri} is not a valid Spotify link" if @pointer.null?
|
39
|
+
end
|
40
|
+
|
41
|
+
# Link type as a symbol.
|
42
|
+
#
|
43
|
+
# @return [Symbol]
|
44
|
+
def type
|
45
|
+
Spotify::link_type(@pointer)
|
46
|
+
end
|
47
|
+
|
48
|
+
# Spotify URI length.
|
49
|
+
#
|
50
|
+
# @return [Fixnum]
|
51
|
+
def length
|
52
|
+
Spotify::link_as_string(@pointer, nil, 0)
|
53
|
+
end
|
54
|
+
|
55
|
+
# Get the Spotify URI this Link represents.
|
56
|
+
#
|
57
|
+
# @see #length
|
58
|
+
# @param [Fixnum] length truncate to this size
|
59
|
+
# @return [String]
|
60
|
+
def to_str(length = length)
|
61
|
+
FFI::Buffer.alloc_out(length + 1) do |b|
|
62
|
+
Spotify::link_as_string(@pointer, b, b.size)
|
63
|
+
return b.get_string(0)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# Retrieve the full Spotify HTTP URL for this Link.
|
68
|
+
#
|
69
|
+
# @return [String]
|
70
|
+
def to_url
|
71
|
+
"http://open.spotify.com/%s" % to_str[8..-1].gsub(':', '/')
|
72
|
+
end
|
73
|
+
|
74
|
+
# Compare this Link to another object
|
75
|
+
#
|
76
|
+
# @param [#to_str] other
|
77
|
+
# @return [Integer]
|
78
|
+
def <=>(other)
|
79
|
+
to_str <=> String.try_convert(other)
|
80
|
+
end
|
81
|
+
|
82
|
+
# String representation of the given Link.
|
83
|
+
#
|
84
|
+
# @return [String]
|
85
|
+
def to_s
|
86
|
+
"<#{self.class.name} #{to_str}>"
|
87
|
+
end
|
88
|
+
|
89
|
+
# Retrieve the underlying pointer.
|
90
|
+
#
|
91
|
+
# @param [Symbol] expected_type if given, makes sure the link is of this type
|
92
|
+
# @return [FFI::Pointer]
|
93
|
+
# @raise ArgumentError if `type` is given and does not match link {#type}
|
94
|
+
def pointer(expected_type = nil)
|
95
|
+
unless type == expected_type
|
96
|
+
raise ArgumentError, "expected #{expected_type} link, but it is of type #{type}"
|
97
|
+
end if expected_type
|
98
|
+
@pointer
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
module Hallon
|
3
|
+
# Methods shared between objects that can be created from Spotify URIs,
|
4
|
+
# or can be turned into Spotify URIs.
|
5
|
+
#
|
6
|
+
# @note Linkable is not part of Hallons’ public API.
|
7
|
+
# @private
|
8
|
+
module Linkable
|
9
|
+
# These are extended onto a class when {Linkable} is included.
|
10
|
+
include Forwardable
|
11
|
+
|
12
|
+
# Creates `from_link` class & instance method which’ll convert a link to a pointer
|
13
|
+
#
|
14
|
+
# @example
|
15
|
+
# # Creates instance method `from_link(link)`
|
16
|
+
# from_link(:playlist) { |link| Spotify::link_as_playlist(link) }
|
17
|
+
#
|
18
|
+
# @param [Symbol] type expected link type
|
19
|
+
# @yield [link, *args] called when conversion is needed from Link pointer
|
20
|
+
# @yieldparam [Hallon::Link] link
|
21
|
+
# @yieldparam *args any extra arguments given to `#from_link`
|
22
|
+
# @see Link#pointer
|
23
|
+
def from_link(type)
|
24
|
+
define_singleton_method(:from_link) do |link, *args|
|
25
|
+
if link.is_a? FFI::Pointer then link else
|
26
|
+
yield Link.new(link).pointer(type), *args
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def_delegators 'self.class', :from_link
|
31
|
+
end
|
32
|
+
|
33
|
+
# Defines `to_link` class & instance method.
|
34
|
+
#
|
35
|
+
# @example
|
36
|
+
# to_link(:artist)
|
37
|
+
#
|
38
|
+
# @note Calls down to `Spotify::link_create_from_#{type}(@pointer)`
|
39
|
+
# @param [Symbol] type object kind
|
40
|
+
# @return [Link]
|
41
|
+
def to_link(type)
|
42
|
+
define_singleton_method(:to_link) do |ptr, *args|
|
43
|
+
link = Spotify.__send__(:"link_create_from_#{type}", ptr, *args)
|
44
|
+
Hallon::Link.new(link)
|
45
|
+
end
|
46
|
+
|
47
|
+
define_method(:to_link) { |*args, &block| self.class.to_link(@pointer, *args, &block) }
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
module Hallon
|
3
|
+
# A module providing event capabilities to Hallon objects.
|
4
|
+
#
|
5
|
+
# @private
|
6
|
+
module Observable
|
7
|
+
# Required for maintaining thread-safety around #handlers
|
8
|
+
include Hallon::Synchronizable
|
9
|
+
|
10
|
+
# Defines a handler for the given event.
|
11
|
+
#
|
12
|
+
# @example defining a handler and triggering it
|
13
|
+
# on(:callback) do |message|
|
14
|
+
# puts message
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# trigger(:callback, "Moo!") # => prints "Moo!"
|
18
|
+
#
|
19
|
+
# @example multiple events with one handler
|
20
|
+
# on(:a, :b, :c) do |name, *args|
|
21
|
+
# puts "#{name} called with: #{args.inspect}"
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# trigger(:a) # => prints ":a called with: []"
|
25
|
+
# trigger(:b, :c) # => prints ":b called with: [:c]"
|
26
|
+
#
|
27
|
+
# @note when defining a handler for multiple events, the
|
28
|
+
# first argument passed to the handler is the name
|
29
|
+
# of the event that called it
|
30
|
+
# @param [#to_sym] event name of event to handle
|
31
|
+
# @yield (*args) event handler block
|
32
|
+
# @see #initialize
|
33
|
+
def on(*events, &block)
|
34
|
+
raise ArgumentError, "no block given" unless block
|
35
|
+
wrap = events.length > 1
|
36
|
+
events.each do |event|
|
37
|
+
block = proc { |*args| yield(event, *args) } if wrap
|
38
|
+
__handlers[event] = [] unless __handlers.has_key?(event)
|
39
|
+
__handlers[event] << block
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Trigger a handler for a given event.
|
44
|
+
#
|
45
|
+
# @param [#to_sym] event
|
46
|
+
# @param [Object, ...] params given to each handler
|
47
|
+
def trigger(event, *params, &block)
|
48
|
+
catch :return do
|
49
|
+
return_value = nil
|
50
|
+
__handlers[event.to_sym].each do |handler|
|
51
|
+
return_value = handler.call(*params, &block)
|
52
|
+
end
|
53
|
+
return_value
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# Run the given block, protecting all previous event handlers.
|
58
|
+
#
|
59
|
+
# @example
|
60
|
+
# o = Object.new
|
61
|
+
# o.instance_eval { include Hallon::Base }
|
62
|
+
# o.on(:method) { "outside" }
|
63
|
+
#
|
64
|
+
# puts o.on_method # => "outside"
|
65
|
+
# o.protecting_handlers do
|
66
|
+
# o.on(:method) { "inside" }
|
67
|
+
# puts o.on_method # => "inside"
|
68
|
+
# end
|
69
|
+
# puts o.on_method # => "outside"
|
70
|
+
#
|
71
|
+
# @yield
|
72
|
+
# @return whatever the given block returns
|
73
|
+
def protecting_handlers
|
74
|
+
deep_copy = __handlers.dup.clear
|
75
|
+
__handlers.each do |k, v|
|
76
|
+
deep_copy[k] = v.dup
|
77
|
+
end
|
78
|
+
yield
|
79
|
+
ensure
|
80
|
+
__handlers.replace deep_copy
|
81
|
+
end
|
82
|
+
|
83
|
+
private
|
84
|
+
# Hash mapping events to handlers.
|
85
|
+
#
|
86
|
+
# @return [Hash]
|
87
|
+
def __handlers
|
88
|
+
@__handlers ||= Hash.new([])
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,189 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
require 'singleton'
|
3
|
+
require 'timeout'
|
4
|
+
require 'thread'
|
5
|
+
|
6
|
+
module Hallon
|
7
|
+
# The Session is fundamental for all communication with Spotify.
|
8
|
+
# Pretty much all API calls require you to have established a session
|
9
|
+
# with Spotify before using them.
|
10
|
+
#
|
11
|
+
# @see https://developer.spotify.com/en/libspotify/docs/group__session.html
|
12
|
+
class Session
|
13
|
+
# The options Hallon used at {Session#initialize}.
|
14
|
+
#
|
15
|
+
# @return [Hash]
|
16
|
+
attr_reader :options
|
17
|
+
|
18
|
+
# Underlying Spotify pointer.
|
19
|
+
#
|
20
|
+
# @return [FFI::Pointer]
|
21
|
+
attr_reader :pointer
|
22
|
+
|
23
|
+
# libspotify only allows one session per process.
|
24
|
+
include Singleton
|
25
|
+
class << self
|
26
|
+
undef :instance
|
27
|
+
end
|
28
|
+
|
29
|
+
# Session allows you to define your own callbacks.
|
30
|
+
include Hallon::Observable
|
31
|
+
|
32
|
+
# Allows you to create a Spotify session. Subsequent calls to this method
|
33
|
+
# will return the previous instance, ignoring any passed arguments.
|
34
|
+
#
|
35
|
+
# @param (see Session#initialize)
|
36
|
+
# @see Session#initialize
|
37
|
+
# @return [Session]
|
38
|
+
def Session.instance(*args, &block)
|
39
|
+
@__instance__ ||= new(*args, &block)
|
40
|
+
end
|
41
|
+
|
42
|
+
# Create a new Spotify session.
|
43
|
+
#
|
44
|
+
# @param [#to_s] appkey
|
45
|
+
# @param [Hash] options
|
46
|
+
# @option options [String] :user_agent ("Hallon") User-Agent to use (length < 256)
|
47
|
+
# @option options [String] :settings_path ("tmp") where to save settings and user-specific cache
|
48
|
+
# @option options [String] :cache_path ("") where to save cache files (set to "" to disable)
|
49
|
+
# @option options [Bool] :load_playlists (true) load playlists into RAM on startup
|
50
|
+
# @option options [Bool] :compress_playlists (true) compress local copies of playlists
|
51
|
+
# @option options [Bool] :cache_playlist_metadata (true) cache metadata for playlists locally
|
52
|
+
# @yield allows you to define handlers for events (see {Hallon::Base#on})
|
53
|
+
# @raise [ArgumentError] if `options[:user_agent]` is more than 256 characters long
|
54
|
+
# @raise [Hallon::Error] if `sp_session_create` fails
|
55
|
+
# @see http://developer.spotify.com/en/libspotify/docs/structsp__session__config.html
|
56
|
+
def initialize(appkey, options = {}, &block)
|
57
|
+
@appkey = appkey.to_s
|
58
|
+
@options = {
|
59
|
+
:user_agent => "Hallon",
|
60
|
+
:settings_path => "tmp",
|
61
|
+
:cache_path => "",
|
62
|
+
:load_playlists => true,
|
63
|
+
:compress_playlists => true,
|
64
|
+
:cache_playlist_metadata => true
|
65
|
+
}.merge(options)
|
66
|
+
|
67
|
+
if @options[:user_agent].bytesize > 255
|
68
|
+
raise ArgumentError, "User-agent must be less than 256 bytes long"
|
69
|
+
end
|
70
|
+
|
71
|
+
# Set configuration, as well as callbacks
|
72
|
+
config = Spotify::SessionConfig.new
|
73
|
+
config[:api_version] = Hallon::API_VERSION
|
74
|
+
config.application_key = @appkey
|
75
|
+
@options.each { |(key, value)| config.send(:"#{key}=", value) }
|
76
|
+
config[:callbacks] = Spotify::SessionCallbacks.new(self, @sp_callbacks = {})
|
77
|
+
|
78
|
+
instance_eval(&block) if block_given?
|
79
|
+
|
80
|
+
# You pass a pointer to the session pointer to libspotify >:)
|
81
|
+
FFI::MemoryPointer.new(:pointer) do |p|
|
82
|
+
Hallon::Error::maybe_raise Spotify::session_create(config, p)
|
83
|
+
@pointer = p.read_pointer
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# Process pending Spotify events (might fire callbacks).
|
88
|
+
#
|
89
|
+
# @return [Fixnum] minimum time until it should be called again
|
90
|
+
def process_events
|
91
|
+
FFI::MemoryPointer.new(:int) do |p|
|
92
|
+
Spotify::session_process_events(@pointer, p)
|
93
|
+
return p.read_int
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
# Wait for the given callbacks to fire until the block returns true
|
98
|
+
#
|
99
|
+
# @param [Symbol, ...] *events list of events to wait for
|
100
|
+
# @yield [Symbol, *args] name of the callback that fired, and its’ arguments
|
101
|
+
# @return [Hash<Event, Arguments>]
|
102
|
+
def process_events_on(*events, &block)
|
103
|
+
channel = SizedQueue.new(1)
|
104
|
+
|
105
|
+
protecting_handlers do
|
106
|
+
on(*events) { |*args| channel << args }
|
107
|
+
on(:notify_main_thread) { channel << :notify }
|
108
|
+
|
109
|
+
loop do
|
110
|
+
begin
|
111
|
+
process_events
|
112
|
+
params = Timeout::timeout(0.25) { channel.pop }
|
113
|
+
redo if params == :notify
|
114
|
+
rescue Timeout::Error
|
115
|
+
params = nil
|
116
|
+
end
|
117
|
+
|
118
|
+
if result = block.call(*params)
|
119
|
+
return result
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
# Log into Spotify using the given credentials.
|
126
|
+
#
|
127
|
+
# @param [String] username
|
128
|
+
# @param [String] password
|
129
|
+
# @return [self]
|
130
|
+
def login(username, password)
|
131
|
+
Spotify::session_login(@pointer, username, password)
|
132
|
+
self
|
133
|
+
end
|
134
|
+
|
135
|
+
# Logs out of Spotify. Does nothing if not logged in.
|
136
|
+
#
|
137
|
+
# @return [self]
|
138
|
+
def logout
|
139
|
+
Spotify::session_logout(@pointer) if logged_in?
|
140
|
+
self
|
141
|
+
end
|
142
|
+
|
143
|
+
# Retrieve the currently logged in {User}.
|
144
|
+
#
|
145
|
+
# @return [User]
|
146
|
+
def user
|
147
|
+
User.new Spotify::session_user(@pointer)
|
148
|
+
end
|
149
|
+
|
150
|
+
# Retrieve the relation type between logged in {User} and `user`.
|
151
|
+
#
|
152
|
+
# @return [Symbol] :unknown, :none, :unidirectional or :bidirectional
|
153
|
+
def relation_type?(user)
|
154
|
+
Spotify::user_relation_type(@pointer, user.pointer)
|
155
|
+
end
|
156
|
+
|
157
|
+
# Retrieve current connection status.
|
158
|
+
#
|
159
|
+
# @return [Symbol]
|
160
|
+
def status
|
161
|
+
Spotify::session_connectionstate(@pointer)
|
162
|
+
end
|
163
|
+
|
164
|
+
# True if currently logged in.
|
165
|
+
# @see #status
|
166
|
+
def logged_in?
|
167
|
+
status == :logged_in
|
168
|
+
end
|
169
|
+
|
170
|
+
# True if logged out.
|
171
|
+
# @see #status
|
172
|
+
def logged_out?
|
173
|
+
status == :logged_out
|
174
|
+
end
|
175
|
+
|
176
|
+
# True if session has been disconnected.
|
177
|
+
# @see #status
|
178
|
+
def disconnected?
|
179
|
+
status == :disconnected
|
180
|
+
end
|
181
|
+
|
182
|
+
# String representation of the Session.
|
183
|
+
#
|
184
|
+
# @return [String]
|
185
|
+
def to_s
|
186
|
+
"<#{self.class.name}>"
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|