spotify 12.3.0 → 12.4.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|