hallon 0.0.0 → 0.1.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.
- 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
|