spotify 12.2.0 → 12.3.0

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