spotify 12.2.0 → 12.3.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.
Files changed (80) hide show
  1. data/.gitignore +3 -1
  2. data/.rspec +5 -0
  3. data/CHANGELOG.md +45 -27
  4. data/Gemfile +4 -0
  5. data/README.markdown +52 -19
  6. data/Rakefile +16 -7
  7. data/lib/spotify.rb +109 -8
  8. data/lib/spotify/api.rb +61 -0
  9. data/lib/spotify/api/album.rb +14 -0
  10. data/lib/spotify/api/album_browse.rb +18 -0
  11. data/lib/spotify/api/artist.rb +10 -0
  12. data/lib/spotify/api/artist_browse.rb +23 -0
  13. data/lib/spotify/api/error.rb +6 -0
  14. data/lib/spotify/api/image.rb +16 -0
  15. data/lib/spotify/api/inbox.rb +9 -0
  16. data/lib/spotify/api/link.rb +25 -0
  17. data/lib/spotify/api/playlist.rb +39 -0
  18. data/lib/spotify/api/playlist_container.rb +23 -0
  19. data/lib/spotify/api/search.rb +27 -0
  20. data/lib/spotify/api/session.rb +46 -0
  21. data/lib/spotify/api/toplist_browse.rb +17 -0
  22. data/lib/spotify/api/track.rb +26 -0
  23. data/lib/spotify/api/user.rb +10 -0
  24. data/lib/spotify/defines.rb +109 -0
  25. data/lib/spotify/error.rb +62 -0
  26. data/lib/spotify/managed_pointer.rb +90 -0
  27. data/lib/spotify/objects.rb +16 -0
  28. data/lib/spotify/objects/album.rb +5 -0
  29. data/lib/spotify/objects/album_browse.rb +5 -0
  30. data/lib/spotify/objects/artist.rb +5 -0
  31. data/lib/spotify/objects/artist_browse.rb +5 -0
  32. data/lib/spotify/objects/image.rb +5 -0
  33. data/lib/spotify/objects/inbox.rb +5 -0
  34. data/lib/spotify/objects/link.rb +5 -0
  35. data/lib/spotify/objects/playlist.rb +5 -0
  36. data/lib/spotify/objects/playlist_container.rb +5 -0
  37. data/lib/spotify/objects/search.rb +5 -0
  38. data/lib/spotify/objects/session.rb +20 -0
  39. data/lib/spotify/objects/toplist_browse.rb +5 -0
  40. data/lib/spotify/objects/track.rb +5 -0
  41. data/lib/spotify/objects/user.rb +5 -0
  42. data/lib/spotify/structs.rb +46 -0
  43. data/lib/spotify/structs/audio_buffer_stats.rb +10 -0
  44. data/lib/spotify/structs/audio_format.rb +12 -0
  45. data/lib/spotify/structs/offline_sync_status.rb +24 -0
  46. data/lib/spotify/structs/playlist_callbacks.rb +32 -0
  47. data/lib/spotify/structs/playlist_container_callbacks.rb +14 -0
  48. data/lib/spotify/structs/session_callbacks.rb +48 -0
  49. data/lib/spotify/structs/session_config.rb +64 -0
  50. data/lib/spotify/structs/subscribers.rb +31 -0
  51. data/lib/spotify/types.rb +3 -0
  52. data/lib/spotify/types/image_id.rb +5 -0
  53. data/lib/spotify/types/nul_string.rb +51 -0
  54. data/lib/spotify/types/utf8_string.rb +24 -28
  55. data/lib/spotify/util.rb +36 -0
  56. data/lib/spotify/version.rb +7 -2
  57. data/spec/api-linux.xml +1887 -0
  58. data/spec/api-mac.xml +1886 -0
  59. data/spec/spec_helper.rb +20 -0
  60. data/spec/spotify/api_spec.rb +62 -0
  61. data/spec/spotify/defines_spec.rb +22 -0
  62. data/spec/spotify/enums_spec.rb +9 -0
  63. data/spec/spotify/managed_pointer_spec.rb +75 -0
  64. data/spec/spotify/spotify_spec.rb +69 -0
  65. data/spec/spotify/structs/session_config_spec.rb +20 -0
  66. data/spec/spotify/structs/struct_spec.rb +34 -0
  67. data/spec/spotify/structs/subscribers_spec.rb +31 -0
  68. data/spec/spotify/structs_spec.rb +19 -0
  69. data/spec/spotify/types/image_id_spec.rb +44 -0
  70. data/spec/spotify/types/nul_string_spec.rb +31 -0
  71. data/spec/spotify/types/utf8_string_spec.rb +24 -0
  72. data/spec/support/hook_spotify.rb +37 -0
  73. data/spec/support/spotify_util.rb +17 -0
  74. data/spotify.gemspec +6 -11
  75. metadata +99 -26
  76. data/lib/spotify/error_wrappers.rb +0 -165
  77. data/lib/spotify/functions.rb +0 -755
  78. data/lib/spotify/gc_wrappers.rb +0 -105
  79. data/lib/spotify/types/pointer.rb +0 -59
  80. data/spec/spotify_spec.rb +0 -467
@@ -0,0 +1,20 @@
1
+ # encoding: utf-8
2
+ require 'rbgccxml'
3
+ require 'rspec'
4
+ require 'pry'
5
+
6
+ require 'spec/support/hook_spotify'
7
+ require 'spec/support/spotify_util'
8
+
9
+ # You can pregenerate new XML files through:
10
+ # gccxml spec/api-mac.h -fxml=spec/api-mac.xml
11
+ # gccxml spec/api-linux.h -fxml=spec/api-linux.xml
12
+ API_H_PATH = File.expand_path("../api-#{Spotify.platform}.h", __FILE__)
13
+ API_H_SRC = File.read(API_H_PATH)
14
+ API_H_XML = RbGCCXML.parse_xml(API_H_PATH.sub('.h', '.xml'))
15
+
16
+ RSpec.configure do |config|
17
+ def api
18
+ Spotify::API
19
+ end
20
+ end
@@ -0,0 +1,62 @@
1
+ describe "Spotify functions" do
2
+ API_H_XML.functions.each do |func|
3
+ next unless func["name"] =~ /\Asp_/
4
+ attached_name = func["name"].sub(/\Asp_/, '')
5
+
6
+ def type_of(type, return_type = false)
7
+ return case type.to_cpp
8
+ when "const char*"
9
+ Spotify::UTF8String
10
+ when /\A(::)?(char|int|size_t|bool|sp_scrobbling_state|sp_session\*|byte)\*/
11
+ return_type ? :pointer : :buffer_out
12
+ when /::(.+_cb)\*/
13
+ $1.to_sym
14
+ when /::(\w+)\*\*/
15
+ :array
16
+ when /::sp_(\w+)\*/
17
+ const_name = $1.delete('_')
18
+ real_const = Spotify.constants.find { |const| const =~ /#{const_name}\z/i }
19
+ Spotify.const_get(real_const)
20
+ else
21
+ :pointer
22
+ end if type.is_a?(RbGCCXML::PointerType)
23
+
24
+ case type["name"]
25
+ when "unsigned int"
26
+ :uint
27
+ else
28
+ type["name"].sub(/\Asp_/, '').to_sym
29
+ end
30
+ end
31
+
32
+ # We test several things in this test because if we make the assertions
33
+ # into separate tests there’s just too much noise on failure.
34
+ specify(func["name"]) do
35
+ # it should be attached
36
+ Spotify.should respond_to attached_name
37
+
38
+ # expect the correct number of arguments
39
+ $attached_methods[attached_name][:args].count.
40
+ should eq func.arguments.count
41
+
42
+ # each argument has the right type
43
+ current = $attached_methods[attached_name][:args]
44
+ actual = func.arguments.map { |arg| type_of(arg.cpp_type) }
45
+
46
+ current = current.map { |x| Spotify.resolve_type(x) }
47
+ actual = actual.map { |x| Spotify.resolve_type(x) }
48
+
49
+ current.should eq actual
50
+
51
+ # returns the correct type
52
+ current_type = $attached_methods[attached_name][:returns]
53
+ actual_type = type_of(func.return_type, true)
54
+
55
+ # loosen restraints on some return types, we don’t have enough info from header file
56
+ current_type = Spotify.resolve_type(current_type)
57
+ actual_type = Spotify.resolve_type(actual_type)
58
+
59
+ current_type.should eq actual_type
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,22 @@
1
+ describe "Spotify enums" do
2
+ API_H_XML.enumerations.each do |enum|
3
+ attached_enum = Spotify.enum_type enum["name"].sub(/\Asp_/, '').to_sym
4
+ original_enum = enum.values.map { |v| [v["name"].downcase, v["init"]] }
5
+
6
+ describe enum["name"] do
7
+ it "should exist" do
8
+ attached_enum.should_not be_nil
9
+ end
10
+
11
+ it "should match the definition" do
12
+ attached_enum_map = attached_enum.symbol_map.dup
13
+ original_enum.each do |(name, value)|
14
+ a_name, a_value = attached_enum_map.max_by { |(n, v)| (n.to_s.length if name.match(n.to_s)).to_i }
15
+ attached_enum_map.delete(a_name)
16
+ a_value.to_s.should eq value
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+
@@ -0,0 +1,9 @@
1
+ describe "Spotify enums" do
2
+ describe "audio sample types" do
3
+ Spotify.enum_type(:sampletype).symbols.each do |symbol|
4
+ specify "#{symbol} has a reader in FFI" do
5
+ FFI::Pointer.new(1).should respond_to "read_array_of_#{symbol}"
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,75 @@
1
+ # coding: utf-8
2
+ describe Spotify::ManagedPointer do
3
+ it "is equal to it’s retaining class" do
4
+ Spotify::User.should eq Spotify::User.retaining_class
5
+ Spotify::User.retaining_class.should eq Spotify::User
6
+ end
7
+
8
+ it "adds a ref if it is a retaining class" do
9
+ api.should_receive(:user_add_ref)
10
+ ptr = Spotify::User.retaining_class.new(FFI::Pointer.new(1))
11
+ ptr.autorelease = false # avoid auto-GC after test
12
+ end
13
+
14
+ it "does not add or release when the pointer is null" do
15
+ api.should_not_receive(:user_add_ref)
16
+ api.should_not_receive(:user_release)
17
+
18
+ ptr = Spotify::User.retaining_class.new(FFI::Pointer::NULL)
19
+ ptr.free
20
+ end
21
+
22
+ describe "garbage collection" do
23
+ module Spotify
24
+ class << API
25
+ def bogus_add_ref(pointer)
26
+ end
27
+
28
+ def bogus_release(pointer)
29
+ end
30
+ end
31
+
32
+ class Bogus < ManagedPointer
33
+ end
34
+ end
35
+
36
+ let(:my_pointer) { FFI::Pointer.new(1) }
37
+
38
+ it "should work" do
39
+ api.stub(:bogus_add_ref)
40
+
41
+ # GC tests are a bit funky, but as long as we garbage_release at least once, then
42
+ # we can assume our GC works properly, but up the stakes just for the sake of it
43
+ api.should_receive(:bogus_release).at_least(1).times
44
+
45
+ 5.times { Spotify::Bogus.retaining_class.new(FFI::Pointer.new(1)) }
46
+ 5.times { GC.start; sleep 0.01 }
47
+ end
48
+ end
49
+
50
+ describe "all managed pointers" do
51
+ Spotify.constants.each do |const|
52
+ klass = Spotify.const_get(const)
53
+ next unless klass.is_a?(Class)
54
+ next unless klass < Spotify::ManagedPointer
55
+
56
+ it "#{klass.name} has a valid retain method" do
57
+ Spotify.should_receive(:public_send).and_return do |method, *args|
58
+ Spotify.should respond_to(method)
59
+ method.should match /_add_ref$/
60
+ end
61
+
62
+ klass.retain(FFI::Pointer.new(1))
63
+ end unless klass == Spotify::Session # has no valid retain
64
+
65
+ it "#{klass.name} has a valid release method" do
66
+ Spotify.should_receive(:public_send).and_return do |method, *args|
67
+ Spotify.should respond_to(method)
68
+ method.should match /_release$/
69
+ end
70
+
71
+ klass.release(FFI::Pointer.new(1))
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,69 @@
1
+ describe Spotify do
2
+ describe "VERSION" do
3
+ it "is defined" do
4
+ defined?(Spotify::VERSION).should eq "constant"
5
+ end
6
+
7
+ it "is the same version as in api.h" do
8
+ spotify_version = API_H_SRC.match(/#define\s+SPOTIFY_API_VERSION\s+(\d+)/)[1]
9
+ Spotify::API_VERSION.to_i.should eq spotify_version.to_i
10
+ end
11
+ end
12
+
13
+ describe "proxying" do
14
+ it "responds to the spotify methods" do
15
+ Spotify.should respond_to :error_message
16
+ end
17
+ end
18
+
19
+ describe ".try" do
20
+ it "raises an error when the result is not OK" do
21
+ api.should_receive(:error_example).and_return(:bad_application_key)
22
+ expect { Spotify.try(:error_example) }.to raise_error(Spotify::Error, /BAD_APPLICATION_KEY/)
23
+ end
24
+
25
+ it "does not raise an error when the result is OK" do
26
+ api.should_receive(:error_example).and_return(:ok)
27
+ Spotify.try(:error_example).should eq :ok
28
+ end
29
+
30
+ it "does not raise an error when the result is not an error-type" do
31
+ result = Object.new
32
+ api.should_receive(:error_example).and_return(result)
33
+ Spotify.try(:error_example).should eq result
34
+ end
35
+ end
36
+
37
+ describe ".enum_value!" do
38
+ it "raises an error if given an invalid enum value" do
39
+ expect { Spotify.enum_value!(:moo, "error value") }.to raise_error(ArgumentError)
40
+ end
41
+
42
+ it "gives back the enum value for that enum" do
43
+ Spotify.enum_value!(:ok, "error value").should eq 0
44
+ end
45
+ end
46
+
47
+ describe ".attach_function" do
48
+ it "is a retaining class if the method is not creating" do
49
+ begin
50
+ Spotify::API.attach_function :whatever, [], Spotify::User
51
+ rescue FFI::NotFoundError
52
+ # expected, this method does not exist
53
+ end
54
+
55
+ $attached_methods["whatever"][:returns].should eq Spotify::User.retaining_class
56
+ end
57
+
58
+ it "is a non-retaining class if the method is creating" do
59
+ begin
60
+ Spotify::API.attach_function :whatever_create, [], Spotify::User
61
+ rescue FFI::NotFoundError
62
+ # expected, this method does not exist
63
+ end
64
+
65
+ $attached_methods["whatever_create"][:returns].should be Spotify::User
66
+ $attached_methods["whatever_create"][:returns].should_not be Spotify::User.retaining_class
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,20 @@
1
+ describe Spotify::SessionConfig do
2
+ let(:config) { Spotify::SessionConfig.new }
3
+
4
+ it "allows SessionCallbacks as a member value (by reference)" do
5
+ config[:callbacks] = Spotify::SessionCallbacks.new
6
+ config[:callbacks].should be_a Spotify::SessionCallbacks
7
+ end
8
+
9
+ it "automatically sets application key size when setting application key from string" do
10
+ config[:application_key] = "h\x00e\x00y"
11
+
12
+ config[:application_key_size].should eq 5
13
+ config[:application_key].read_string(5).should eq "h\x00e\x00y"
14
+ end
15
+
16
+ it "does not automatically set application key size when setting application key from pointer" do
17
+ expect { config[:application_key] = FFI::MemoryPointer.from_string("yay") }
18
+ .to_not change { config[:application_key_size] }
19
+ end
20
+ end
@@ -0,0 +1,34 @@
1
+ describe Spotify::Struct do
2
+ let(:klass) do
3
+ Class.new(Spotify::Struct) do
4
+ layout :api_version => :int,
5
+ :cache_location => Spotify::NULString,
6
+ :user_agent => Spotify::NULString,
7
+ :compress_playlists => :bool
8
+ end
9
+ end
10
+
11
+ let(:options) do
12
+ {
13
+ :api_version => 1,
14
+ :cache_location => "Yay",
15
+ :user_agent => nil,
16
+ :compress_playlists => false
17
+ }
18
+ end
19
+
20
+ it "allows initializing the struct with a hash" do
21
+ struct = klass.new(options)
22
+ options.each_pair { |key, value| struct[key].should eq value }
23
+ end
24
+
25
+ it "allows initializing the struct with a pointer" do
26
+ original = klass.new(options)
27
+ struct = klass.new(original.pointer)
28
+ options.each_pair { |key, value| struct[key].should eq value }
29
+ end
30
+
31
+ it "allows initializing the struct with nothing" do
32
+ expect { klass.new }.to_not raise_error
33
+ end
34
+ end
@@ -0,0 +1,31 @@
1
+ describe Spotify::Subscribers do
2
+ it "should create the subscribers array using count" do
3
+ # Memory looks like this:
4
+ #
5
+ # 00 00 00 00 <- count of subscribers
6
+ # 00 00 00 00 <- pointer to subscriber 1
7
+ # …… …… …… ……
8
+ # 00 00 00 00 <- pointer to subscriber n
9
+ real_struct = Spotify::Subscribers.new(2)
10
+ real_struct[:count] = 2
11
+ real_struct[:subscribers][0] = FFI::MemoryPointer.from_string("a")
12
+ real_struct[:subscribers][1] = FFI::MemoryPointer.from_string("bb")
13
+
14
+ struct = Spotify::Subscribers.new(real_struct.pointer)
15
+ struct[:count].should eq 2
16
+ struct[:subscribers].size.should eq 2
17
+ struct[:subscribers][0].read_string.should eq "a"
18
+ struct[:subscribers][1].read_string.should eq "bb"
19
+ expect { struct[:subscribers][2] }.to raise_error(IndexError)
20
+ end
21
+
22
+
23
+ it "should not fail given an empty subscribers struct" do
24
+ subscribers = FFI::MemoryPointer.new(:uint)
25
+ subscribers.write_uint(0)
26
+
27
+ subject = Spotify::Subscribers.new(subscribers)
28
+ subject[:count].should eq 0
29
+ expect { subject[:subscribers] }.to raise_error(ArgumentError)
30
+ end
31
+ end
@@ -0,0 +1,19 @@
1
+ describe "Spotify structs" do
2
+ API_H_XML.structs.each do |struct|
3
+ next if struct["incomplete"]
4
+
5
+ attached_struct = Spotify.structs.find do |const|
6
+ struct["name"].delete('_').match(/#{const}/i)
7
+ end
8
+
9
+ attached_members = Spotify.const_get(attached_struct).members.map(&:to_s)
10
+
11
+ describe struct["name"] do
12
+ it "should contain the same attributes in the same order" do
13
+ struct.variables.map(&:name).each_with_index do |member, index|
14
+ attached_members[index].should eq member
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,44 @@
1
+ describe Spotify::ImageID do
2
+ let(:context) { nil }
3
+ let(:subject) { Spotify::API.find_type(Spotify::ImageID) }
4
+ let(:null_pointer) { FFI::Pointer::NULL }
5
+
6
+ let(:image_id_pointer) do
7
+ pointer = FFI::MemoryPointer.new(:char, 20)
8
+ pointer.write_string(image_id)
9
+ pointer
10
+ end
11
+
12
+ let(:image_id) do
13
+ # deliberate NULL in middle of string
14
+ image_id = ":\xD94#\xAD\xD9\x97f\xE0\x00V6\x05\xC6\xE7n\xD2\xB0\xE4P"
15
+ image_id.force_encoding("BINARY")
16
+ image_id
17
+ end
18
+
19
+ describe "from_native" do
20
+ it "should be nil given a null pointer" do
21
+ subject.from_native(null_pointer, context).should be_nil
22
+ end
23
+
24
+ it "should be an image id given a non-null pointer" do
25
+ subject.from_native(image_id_pointer, context).should eq image_id
26
+ end
27
+ end
28
+
29
+ describe "to_native" do
30
+ it "should be a null pointer given nil" do
31
+ subject.to_native(nil, context).should be_nil
32
+ end
33
+
34
+ it "should be a 20-byte C string given an actual string" do
35
+ pointer = subject.to_native(image_id, context)
36
+ pointer.read_string(20).should eq image_id_pointer.read_string(20)
37
+ end
38
+
39
+ it "should raise an error given more or less than a 20 byte string" do
40
+ expect { subject.to_native(image_id + image_id, context) }.to raise_error(ArgumentError)
41
+ expect { subject.to_native(image_id[0..10], context) }.to raise_error(ArgumentError)
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,31 @@
1
+ describe Spotify::NULString do
2
+ describe ".to_native" do
3
+ it "returns a memory pointer containing the given string" do
4
+ pointer = Spotify::NULString.to_native("coolio", nil)
5
+ pointer.read_string.should eq "coolio"
6
+ end
7
+
8
+ it "returns a null pointer when given nil" do
9
+ pointer = Spotify::NULString.to_native(nil, nil)
10
+ pointer.should be_null
11
+ end
12
+
13
+ it "raises an error when given a non-string" do
14
+ expect { Spotify::NULString.to_native({}, nil) }
15
+ .to raise_error(TypeError)
16
+ end
17
+ end
18
+
19
+ describe ".from_native" do
20
+ it "returns an empty string if given a null pointer" do
21
+ value = Spotify::NULString.from_native(FFI::Pointer::NULL, nil)
22
+ value.should be_nil
23
+ end
24
+
25
+ it "returns the string value of the string pointer" do
26
+ pointer = FFI::MemoryPointer.from_string("hey")
27
+ value = Spotify::NULString.from_native(pointer, nil)
28
+ value.should eq "hey"
29
+ end
30
+ end
31
+ end