spotify 12.3.0 → 12.4.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/CHANGELOG.md +19 -0
- data/Gemfile +1 -0
- data/README.markdown +29 -10
- data/Rakefile +10 -3
- data/examples/.gitignore +4 -0
- data/examples/README.md +15 -0
- data/examples/audio-stream.rb +170 -0
- data/examples/logging-in.rb +95 -0
- data/lib/spotify.rb +2 -1
- data/lib/spotify/error.rb +4 -1
- data/lib/spotify/managed_pointer.rb +53 -23
- data/lib/spotify/monkey_patches/ffi_pointer.rb +18 -0
- data/lib/spotify/structs.rb +14 -0
- data/lib/spotify/structs/session_callbacks.rb +2 -2
- data/lib/spotify/structs/session_config.rb +1 -1
- data/lib/spotify/types/image_id.rb +33 -26
- data/lib/spotify/types/nul_string.rb +23 -34
- data/lib/spotify/types/utf8_string.rb +19 -25
- data/lib/spotify/util.rb +1 -0
- data/lib/spotify/version.rb +1 -2
- data/spec/bench_helper.rb +29 -0
- data/spec/benchmarks/managed_pointer_bench.rb +32 -0
- data/spec/spec_helper.rb +1 -1
- data/spec/spotify/api/functions_spec.rb +67 -0
- data/spec/spotify/api_spec.rb +14 -58
- data/spec/spotify/defines_spec.rb +1 -1
- data/spec/spotify/managed_pointer_spec.rb +60 -11
- data/spec/spotify/monkey_patches/ffi_pointer_spec.rb +9 -0
- data/spec/spotify/types/nul_string_spec.rb +3 -3
- data/spec/{api-linux.h → support/api-linux.h} +0 -0
- data/spec/support/api-linux.xml +1893 -0
- data/spec/{api-mac.h → support/api-mac.h} +0 -0
- data/spec/{api-mac.xml → support/api-mac.xml} +0 -0
- data/spec/support/hook_spotify.rb +1 -1
- data/spec/{linux-platform.rb → support/linux-platform.rb} +0 -0
- data/spec/{mac-platform.rb → support/mac-platform.rb} +0 -0
- metadata +58 -46
- data/LICENSE +0 -19
- data/spec/api-linux.xml +0 -1887
@@ -23,6 +23,10 @@ module Spotify
|
|
23
23
|
# @param [FFI::Pointer] pointer
|
24
24
|
def release(pointer)
|
25
25
|
unless pointer.null?
|
26
|
+
# this is to circumvent the type protection
|
27
|
+
pointer = base_class.new(pointer)
|
28
|
+
pointer.autorelease = false
|
29
|
+
|
26
30
|
$stderr.puts "Spotify.#{type}_release(#{pointer.inspect})" if $DEBUG
|
27
31
|
Spotify.public_send("#{type}_release", pointer)
|
28
32
|
end
|
@@ -32,7 +36,7 @@ module Spotify
|
|
32
36
|
#
|
33
37
|
# This method derives the retain method from the class name.
|
34
38
|
#
|
35
|
-
# @param [
|
39
|
+
# @param [self] pointer must be an instance of {#base_class}
|
36
40
|
def retain(pointer)
|
37
41
|
unless pointer.null?
|
38
42
|
$stderr.puts "Spotify.#{type}_add_ref(#{pointer.inspect})" if $DEBUG
|
@@ -40,44 +44,70 @@ module Spotify
|
|
40
44
|
end
|
41
45
|
end
|
42
46
|
|
47
|
+
# Makes all ManagedPointers typesafe in the sense that they will raise
|
48
|
+
# an argument error on any value that is not of the same kind.
|
49
|
+
def to_native(value, ctx)
|
50
|
+
if value.nil? or value.null?
|
51
|
+
raise TypeError, "#{name} pointers cannot be null, was #{value.inspect}"
|
52
|
+
elsif value.kind_of?(base_class)
|
53
|
+
super
|
54
|
+
else
|
55
|
+
raise TypeError, "expected a kind of #{name}, was #{value.class}"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# Retaining class is needed for the functions that return a pointer that
|
60
|
+
# does not have its reference count increased. This class is a subclass
|
61
|
+
# of the ManagedPointer, and should behave the same in all circumstances
|
62
|
+
# except for during initialization.
|
63
|
+
#
|
64
|
+
# This dynamic method is needed to DRY the pointers up. We have about ten
|
65
|
+
# subclasses of ManagedPointer; all of them need a subclass that retains
|
66
|
+
# its pointer on initialization. We could create one manually for each
|
67
|
+
# Album, Artist, Track, and so on, but that would be annoying.
|
68
|
+
#
|
43
69
|
# @return [self] subclass that retains its pointer on initialization.
|
44
70
|
def retaining_class
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
alias_method :==, :<=
|
71
|
+
if defined?(self::Retaining)
|
72
|
+
self::Retaining
|
73
|
+
else
|
74
|
+
subclass = Class.new(self) do
|
75
|
+
class << self
|
76
|
+
def type
|
77
|
+
superclass.type
|
78
|
+
end
|
54
79
|
|
55
|
-
|
56
|
-
def name
|
57
|
-
superclass.name
|
58
|
-
end
|
80
|
+
protected
|
59
81
|
|
60
|
-
|
61
|
-
|
62
|
-
|
82
|
+
def base_class
|
83
|
+
superclass
|
84
|
+
end
|
63
85
|
end
|
64
86
|
|
65
|
-
|
66
|
-
|
67
|
-
|
87
|
+
def initialize(*)
|
88
|
+
super
|
89
|
+
self.class.retain(self)
|
68
90
|
end
|
69
91
|
end
|
92
|
+
|
93
|
+
const_set("Retaining", subclass)
|
70
94
|
end
|
71
95
|
end
|
72
96
|
|
73
|
-
alias_method :==, :>=
|
74
|
-
|
75
97
|
protected
|
76
98
|
|
77
|
-
#
|
99
|
+
# Retrieves the normalized and downcased name of self, so for
|
100
|
+
# Spotify::Album we’ll receive just “album”.
|
78
101
|
def type
|
79
102
|
name.split('::')[-1].downcase
|
80
103
|
end
|
104
|
+
|
105
|
+
# Retrieves the base class for this pointer. This is overridden
|
106
|
+
# by the {.retaining_class}. It is used for type-checking inside
|
107
|
+
# {.to_native}.
|
108
|
+
def base_class
|
109
|
+
self
|
110
|
+
end
|
81
111
|
end
|
82
112
|
|
83
113
|
# @return [String] string representation of self.
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require "ffi"
|
2
|
+
|
3
|
+
module FFI
|
4
|
+
class AbstractMemory
|
5
|
+
unless method_defined?(:read_size_t)
|
6
|
+
type = FFI.find_type(:size_t)
|
7
|
+
type, _ = FFI::TypeDefs.find do |(name, t)|
|
8
|
+
method_defined? "read_#{name}" if t == type
|
9
|
+
end
|
10
|
+
|
11
|
+
if type.nil?
|
12
|
+
raise "Missing method to read a size_t from #{klass}"
|
13
|
+
end
|
14
|
+
|
15
|
+
alias_method(:read_size_t, "read_#{type}")
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/lib/spotify/structs.rb
CHANGED
@@ -33,6 +33,20 @@ module Spotify
|
|
33
33
|
self[key] = value
|
34
34
|
end
|
35
35
|
end
|
36
|
+
|
37
|
+
# Convert the struct to a hash.
|
38
|
+
#
|
39
|
+
# @return [Hash]
|
40
|
+
def to_h
|
41
|
+
Hash[members.zip(values)]
|
42
|
+
end
|
43
|
+
|
44
|
+
# String representation of the struct. Looks like a Hash.
|
45
|
+
#
|
46
|
+
# @return [String]
|
47
|
+
def to_s
|
48
|
+
"<#{self.class.name} #{to_h}>"
|
49
|
+
end
|
36
50
|
end
|
37
51
|
end
|
38
52
|
|
@@ -29,7 +29,7 @@ module Spotify
|
|
29
29
|
:connection_error => callback([ Session, :error ], :void),
|
30
30
|
:message_to_user => callback([ Session, UTF8String ], :void),
|
31
31
|
:notify_main_thread => callback([ Session ], :void),
|
32
|
-
:music_delivery => callback([ Session, AudioFormat, :frames, :int ], :int),
|
32
|
+
:music_delivery => callback([ Session, AudioFormat.by_ref, :frames, :int ], :int),
|
33
33
|
:play_token_lost => callback([ Session ], :void),
|
34
34
|
:log_message => callback([ Session, UTF8String ], :void),
|
35
35
|
:end_of_track => callback([ Session ], :void),
|
@@ -37,7 +37,7 @@ module Spotify
|
|
37
37
|
:userinfo_updated => callback([ Session ], :void),
|
38
38
|
:start_playback => callback([ Session ], :void),
|
39
39
|
:stop_playback => callback([ Session ], :void),
|
40
|
-
:get_audio_buffer_stats => callback([ Session, AudioBufferStats ], :void),
|
40
|
+
:get_audio_buffer_stats => callback([ Session, AudioBufferStats.by_ref ], :void),
|
41
41
|
:offline_status_updated => callback([ Session ], :void),
|
42
42
|
:offline_error => callback([ Session, :error ], :void),
|
43
43
|
:credentials_blob_updated => callback([ Session, :string ], :void),
|
@@ -51,7 +51,7 @@ module Spotify
|
|
51
51
|
when :application_key
|
52
52
|
if value.is_a?(String)
|
53
53
|
pointer = FFI::MemoryPointer.new(:char, value.bytesize)
|
54
|
-
pointer.
|
54
|
+
pointer.write_string(value)
|
55
55
|
super(key, pointer)
|
56
56
|
self[:application_key_size] = pointer.size
|
57
57
|
else
|
@@ -8,36 +8,43 @@ module Spotify
|
|
8
8
|
extend FFI::DataConverter
|
9
9
|
native_type FFI::Type::POINTER
|
10
10
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
# @return [FFI::Pointer] pointer to the image ID
|
16
|
-
def self.to_native(value, ctx)
|
17
|
-
pointer = if value
|
18
|
-
if value.bytesize != 20
|
19
|
-
raise ArgumentError, "image id bytesize must be 20, was #{value.bytesize}"
|
20
|
-
end
|
21
|
-
|
22
|
-
pointer = FFI::MemoryPointer.new(:char, 20)
|
23
|
-
pointer.write_string(value.to_s)
|
11
|
+
class << self
|
12
|
+
# @return [Integer] bytesize of image ID pointers.
|
13
|
+
def size
|
14
|
+
20
|
24
15
|
end
|
25
16
|
|
26
|
-
|
27
|
-
|
17
|
+
# Given a string, convert it to an image ID pointer.
|
18
|
+
#
|
19
|
+
# @param [#to_str, nil] value image id as a string
|
20
|
+
# @param ctx
|
21
|
+
# @return [FFI::Pointer] pointer to the image ID
|
22
|
+
def to_native(value, ctx)
|
23
|
+
value && begin
|
24
|
+
value = value.to_str
|
28
25
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
26
|
+
if value.bytesize != size
|
27
|
+
raise ArgumentError, "image id bytesize must be #{size}, was #{value.bytesize}"
|
28
|
+
end
|
29
|
+
|
30
|
+
pointer = FFI::MemoryPointer.new(:char, size)
|
31
|
+
pointer.write_string(value)
|
32
|
+
end
|
33
|
+
end
|
37
34
|
|
38
|
-
|
39
|
-
|
40
|
-
|
35
|
+
# Given a pointer, read a {.size}-byte image ID from it.
|
36
|
+
#
|
37
|
+
# @param [FFI::Pointer] value
|
38
|
+
# @param ctx
|
39
|
+
# @return [String, nil] the image ID as a string, or nil
|
40
|
+
def from_native(value, ctx)
|
41
|
+
value.read_string(size) unless value.null?
|
42
|
+
end
|
43
|
+
|
44
|
+
# @see NulString.reference_required?
|
45
|
+
def reference_required?
|
46
|
+
true
|
47
|
+
end
|
41
48
|
end
|
42
49
|
end
|
43
50
|
end
|
@@ -8,44 +8,33 @@ module Spotify
|
|
8
8
|
extend FFI::DataConverter
|
9
9
|
native_type FFI::Type::POINTER
|
10
10
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
unless value.respond_to?(:to_str)
|
21
|
-
raise TypeError, "#{value.inspect} cannot be converted to string"
|
11
|
+
class << self
|
12
|
+
# Given either a String or nil, make an actual FFI::Pointer
|
13
|
+
# of that value.
|
14
|
+
#
|
15
|
+
# @param [#to_str, nil] value
|
16
|
+
# @param ctx
|
17
|
+
# @return [FFI::Pointer]
|
18
|
+
def to_native(value, ctx)
|
19
|
+
value && FFI::MemoryPointer.from_string(value.to_str)
|
22
20
|
end
|
23
21
|
|
24
|
-
#
|
25
|
-
#
|
26
|
-
#
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
#
|
32
|
-
# @param [FFI::Pointer] value
|
33
|
-
# @param [Object] ctx
|
34
|
-
# @return [String, nil]
|
35
|
-
def self.from_native(value, ctx)
|
36
|
-
if value.null?
|
37
|
-
nil
|
38
|
-
else
|
39
|
-
value.read_string
|
22
|
+
# Given a pointer, read out it’s string.
|
23
|
+
#
|
24
|
+
# @param [FFI::Pointer] value
|
25
|
+
# @param ctx
|
26
|
+
# @return [String, nil]
|
27
|
+
def from_native(value, ctx)
|
28
|
+
value.read_string unless value.null?
|
40
29
|
end
|
41
|
-
end
|
42
30
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
31
|
+
# Used by FFI::StructLayoutField to know if this field
|
32
|
+
# requires the reference to be maintained by FFI. If we
|
33
|
+
# return false here, the MemoryPointer from to_native
|
34
|
+
# will be garbage collected before the struct.
|
35
|
+
def reference_required?
|
36
|
+
true
|
37
|
+
end
|
49
38
|
end
|
50
39
|
end
|
51
40
|
end
|
@@ -8,33 +8,27 @@ module Spotify
|
|
8
8
|
extend FFI::DataConverter
|
9
9
|
native_type FFI::Type::STRING
|
10
10
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
value.encode('UTF-8')
|
22
|
-
else
|
23
|
-
super
|
11
|
+
class << self
|
12
|
+
# Given a value, encodes it to UTF-8 no matter what.
|
13
|
+
#
|
14
|
+
# @note if the value is already in UTF-8, ruby does nothing
|
15
|
+
# @note if the given value is falsy, default behaviour is used
|
16
|
+
#
|
17
|
+
# @param [String, nil] value
|
18
|
+
# @param ctx
|
19
|
+
# @return [String] value, but in UTF-8 if it wasn’t already
|
20
|
+
def to_native(value, ctx)
|
21
|
+
value && value.encode('UTF-8')
|
24
22
|
end
|
25
|
-
end
|
26
23
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
value.force_encoding('UTF-8')
|
36
|
-
else
|
37
|
-
super
|
24
|
+
# Given an original string, assume it is in UTF-8.
|
25
|
+
#
|
26
|
+
# @note NO error checking is made, the string is just forced to UTF-8
|
27
|
+
# @param [String] value can be in any encoding
|
28
|
+
# @param ctx
|
29
|
+
# @return [String] value, but with UTF-8 encoding
|
30
|
+
def from_native(value, ctx)
|
31
|
+
value && value.force_encoding('UTF-8')
|
38
32
|
end
|
39
33
|
end
|
40
34
|
end
|
data/lib/spotify/util.rb
CHANGED
data/lib/spotify/version.rb
CHANGED
@@ -1,9 +1,8 @@
|
|
1
1
|
module Spotify
|
2
2
|
# @note See README for versioning policy.
|
3
3
|
# @return [String] Spotify gem version.
|
4
|
-
VERSION = '12.
|
4
|
+
VERSION = '12.4.0'
|
5
5
|
|
6
|
-
# @note May or may not work with other versions.
|
7
6
|
# @return [String] Compatible libspotify API version.
|
8
7
|
API_VERSION = '12.1.51'
|
9
8
|
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'spotify'
|
2
|
+
require 'benchmark'
|
3
|
+
require 'pry'
|
4
|
+
|
5
|
+
$__benchmarks__ = []
|
6
|
+
|
7
|
+
def bench(name, iterations = 100_000, &block)
|
8
|
+
file, line, _ = caller[0].split(':')
|
9
|
+
$__benchmarks__ << {
|
10
|
+
file: File.basename(file),
|
11
|
+
line: line,
|
12
|
+
name: name,
|
13
|
+
iterations: iterations,
|
14
|
+
block: block
|
15
|
+
}
|
16
|
+
end
|
17
|
+
|
18
|
+
at_exit do
|
19
|
+
Benchmark.bmbm do |x|
|
20
|
+
$__benchmarks__.each do |info|
|
21
|
+
benchname = "#{info[:file]}:#{info[:line]} #{info[:name]}"
|
22
|
+
x.report(benchname) { info[:iterations].times(&info[:block]) }
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
Dir["./**/*_bench.rb"].each do |benchmark|
|
28
|
+
require benchmark
|
29
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Spotify
|
2
|
+
class << API
|
3
|
+
def bogus_add_ref(x)
|
4
|
+
end
|
5
|
+
|
6
|
+
def bogus_release(x)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
class Bogus < ManagedPointer
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
one = FFI::Pointer.new(1)
|
15
|
+
null = FFI::Pointer::NULL
|
16
|
+
|
17
|
+
album = Spotify::Album.new(one)
|
18
|
+
album.autorelease = false
|
19
|
+
|
20
|
+
bench "Album#to_native" do
|
21
|
+
Spotify::Album.to_native(album, nil)
|
22
|
+
end
|
23
|
+
|
24
|
+
bench "Bogus#retain" do
|
25
|
+
Spotify::Bogus.retain(one)
|
26
|
+
Spotify::Bogus.retaining_class.retain(one)
|
27
|
+
end
|
28
|
+
|
29
|
+
bench "Bogus#release" do
|
30
|
+
Spotify::Bogus.release(one)
|
31
|
+
Spotify::Bogus.retaining_class.release(one)
|
32
|
+
end
|