spotify 12.5.3 → 12.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (104) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -0
  3. data/.travis.yml +20 -5
  4. data/CHANGELOG.md +29 -1
  5. data/Gemfile +5 -0
  6. data/MIT-LICENSE +21 -0
  7. data/README.markdown +75 -50
  8. data/Rakefile +1 -1
  9. data/examples/example-audio_delivery_speed.rb +48 -0
  10. data/examples/{audio-stream_example.rb → example-audio_stream.rb} +14 -29
  11. data/examples/example-console.rb +9 -0
  12. data/examples/example-listing_playlists.rb +89 -0
  13. data/examples/example-loading_object.rb +25 -0
  14. data/examples/example-random_related_artists.rb +53 -0
  15. data/examples/support.rb +106 -0
  16. data/lib/spotify.rb +36 -55
  17. data/lib/spotify/api.rb +54 -26
  18. data/lib/spotify/api/album.rb +45 -2
  19. data/lib/spotify/api/album_browse.rb +81 -3
  20. data/lib/spotify/api/artist.rb +21 -2
  21. data/lib/spotify/api/artist_browse.rb +121 -3
  22. data/lib/spotify/api/error.rb +5 -1
  23. data/lib/spotify/api/image.rb +72 -6
  24. data/lib/spotify/api/inbox.rb +33 -4
  25. data/lib/spotify/api/link.rb +117 -4
  26. data/lib/spotify/api/miscellaneous.rb +12 -0
  27. data/lib/spotify/api/playlist.rb +321 -16
  28. data/lib/spotify/api/playlist_container.rb +168 -9
  29. data/lib/spotify/api/search.rb +156 -3
  30. data/lib/spotify/api/session.rb +390 -26
  31. data/lib/spotify/api/toplist_browse.rb +74 -3
  32. data/lib/spotify/api/track.rb +134 -4
  33. data/lib/spotify/api/user.rb +18 -2
  34. data/lib/spotify/api_helpers.rb +47 -0
  35. data/lib/spotify/data_converters.rb +7 -0
  36. data/lib/spotify/{types → data_converters}/best_effort_string.rb +1 -1
  37. data/lib/spotify/{types → data_converters}/byte_string.rb +0 -0
  38. data/lib/spotify/data_converters/country_code.rb +30 -0
  39. data/lib/spotify/{types → data_converters}/image_id.rb +1 -1
  40. data/lib/spotify/{type_safety.rb → data_converters/type_safety.rb} +0 -0
  41. data/lib/spotify/{types → data_converters}/utf8_string.rb +2 -2
  42. data/lib/spotify/{types → data_converters}/utf8_string_pointer.rb +2 -2
  43. data/lib/spotify/error.rb +180 -47
  44. data/lib/spotify/managed_pointer.rb +32 -12
  45. data/lib/spotify/monkey_patches/ffi_buffer.rb +11 -0
  46. data/lib/spotify/monkey_patches/ffi_enums.rb +4 -0
  47. data/lib/spotify/monkey_patches/ffi_pointer.rb +1 -0
  48. data/lib/spotify/structs.rb +4 -0
  49. data/lib/spotify/structs/session_callbacks.rb +97 -26
  50. data/lib/spotify/structs/session_config.rb +1 -1
  51. data/lib/spotify/structs/subscribers.rb +4 -3
  52. data/lib/spotify/types.rb +104 -5
  53. data/lib/spotify/{objects → types}/album.rb +0 -0
  54. data/lib/spotify/{objects → types}/album_browse.rb +0 -0
  55. data/lib/spotify/{objects → types}/artist.rb +0 -0
  56. data/lib/spotify/{objects → types}/artist_browse.rb +0 -0
  57. data/lib/spotify/{objects → types}/image.rb +0 -0
  58. data/lib/spotify/{objects → types}/inbox.rb +0 -0
  59. data/lib/spotify/{objects → types}/link.rb +0 -0
  60. data/lib/spotify/{objects → types}/playlist.rb +0 -0
  61. data/lib/spotify/{objects → types}/playlist_container.rb +0 -0
  62. data/lib/spotify/{objects → types}/search.rb +0 -0
  63. data/lib/spotify/{objects → types}/session.rb +0 -0
  64. data/lib/spotify/{objects → types}/toplist_browse.rb +0 -0
  65. data/lib/spotify/{objects → types}/track.rb +0 -0
  66. data/lib/spotify/{objects → types}/user.rb +0 -0
  67. data/lib/spotify/util.rb +38 -35
  68. data/lib/spotify/version.rb +1 -1
  69. data/spec/spec_helper.rb +24 -13
  70. data/spec/spotify/api/image_spec.rb +32 -0
  71. data/spec/spotify/api/inbox_spec.rb +34 -0
  72. data/spec/spotify/api/link_spec.rb +40 -0
  73. data/spec/spotify/api/playlist_spec.rb +99 -0
  74. data/spec/spotify/api/playlistcontainer_spec.rb +82 -0
  75. data/spec/spotify/api/session_spec.rb +97 -0
  76. data/spec/spotify/api/track_spec.rb +29 -0
  77. data/spec/spotify/api_error_spec.rb +55 -0
  78. data/spec/spotify/api_spec.rb +17 -11
  79. data/spec/spotify/{types → data_converters}/best_effort_string_spec.rb +4 -4
  80. data/spec/spotify/{types → data_converters}/byte_string_spec.rb +0 -0
  81. data/spec/spotify/data_converters/country_code_spec.rb +16 -0
  82. data/spec/spotify/{types → data_converters}/image_id_spec.rb +1 -1
  83. data/spec/spotify/{type_safety_spec.rb → data_converters/type_safety_spec.rb} +0 -0
  84. data/spec/spotify/{types → data_converters}/utf8_string_pointer_spec.rb +0 -0
  85. data/spec/spotify/{types → data_converters}/utf8_string_spec.rb +1 -1
  86. data/spec/spotify/{api/functions_spec.rb → functions_spec.rb} +2 -0
  87. data/spec/spotify/managed_pointer_spec.rb +13 -13
  88. data/spec/spotify/structs/subscribers_spec.rb +5 -3
  89. data/spec/spotify/{defines_spec.rb → types_spec.rb} +16 -3
  90. data/spec/spotify/util_spec.rb +24 -0
  91. data/spec/spotify_spec.rb +74 -0
  92. data/spec/support/spotify_util.rb +6 -2
  93. data/spec/support/spy_output.rb +16 -0
  94. data/spotify.gemspec +4 -2
  95. metadata +111 -71
  96. data/examples/README.md +0 -15
  97. data/examples/console_example.rb +0 -22
  98. data/examples/example_support.rb +0 -66
  99. data/examples/loading-object_example.rb +0 -43
  100. data/examples/logging-in_example.rb +0 -58
  101. data/lib/spotify/defines.rb +0 -109
  102. data/lib/spotify/objects.rb +0 -17
  103. data/spec/spotify/enums_spec.rb +0 -9
  104. data/spec/spotify/spotify_spec.rb +0 -69
@@ -0,0 +1,29 @@
1
+ describe "Spotify::API" do
2
+ describe "#track_set_starred" do
3
+ let(:session) { double }
4
+ let(:track_a) { FFI::MemoryPointer.new(:pointer) }
5
+ let(:track_b) { FFI::MemoryPointer.new(:pointer) }
6
+ let(:tracks) { [track_a, track_b] }
7
+
8
+ it "changes the starred state of the given tracks" do
9
+ api.should_receive(:sp_track_set_starred) do |ptr, buffer, buffer_size, starred|
10
+ ptr.should eq(session)
11
+ buffer.read_array_of_pointer(buffer_size).should eq(tracks)
12
+ starred.should eq(true)
13
+ :ok
14
+ end
15
+
16
+ api.track_set_starred(session, tracks, true).should eq(:ok)
17
+ end
18
+
19
+ it "automatically casts the second parameter to an array" do
20
+ api.should_receive(:sp_track_set_starred) do |ptr, buffer, buffer_size, starred|
21
+ buffer.read_array_of_pointer(1).should eq([track_a])
22
+ buffer_size.should eq(1)
23
+ :ok
24
+ end
25
+
26
+ api.track_set_starred(session, track_a, true).should eq(:ok)
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,55 @@
1
+ describe Spotify::APIError do
2
+ let(:context) { nil }
3
+
4
+ describe ".from_native" do
5
+ it "returns an error of the correct class" do
6
+ error = Spotify::APIError.from_native(15, context)
7
+ error.should be_a Spotify::UserNeedsPremiumError
8
+ end
9
+
10
+ it "returns nil if not an error" do
11
+ Spotify::APIError.from_native(0, context).should be_nil
12
+ end
13
+
14
+ it "raises an error if not a valid error code" do
15
+ expect { Spotify::APIError.from_native(1337, context) }
16
+ .to raise_error(ArgumentError, /unknown error code/)
17
+ end
18
+ end
19
+
20
+ describe ".to_i" do
21
+ it "returns the error code" do
22
+ Spotify::APIError.to_i.should be_nil
23
+ Spotify::UserNeedsPremiumError.to_i.should eq(15)
24
+ end
25
+ end
26
+
27
+ describe "#message" do
28
+ it "is a formatted message with an explanation of the error" do
29
+ error = Spotify::UserNeedsPremiumError.new
30
+ error.message.should match /Needs a premium account/
31
+ error.message.should match /15/
32
+ end
33
+
34
+ it "can be overridden if necessary" do
35
+ error = Spotify::UserNeedsPremiumError.new("hoola hoop")
36
+ error.message.should eq "hoola hoop"
37
+ end
38
+ end
39
+
40
+ describe "API mapping" do
41
+ sp_error = API_H_XML.enumerations.each { |enum| break enum if enum.name == "sp_error" }
42
+ sp_error.values.each do |value|
43
+ next if value.name =~ /sp_error_ok/i
44
+ class_name = value.name.downcase.sub(/^sp_error/, "").gsub(/(_|^)(\w)/) { $2.upcase }
45
+ class_name << "Error" unless class_name =~ /Error/
46
+
47
+ describe class_name do
48
+ it "should have the error code number" do
49
+ constant = Spotify.constants.grep(/#{class_name}/i)[0]
50
+ Spotify.const_get(constant).to_i.should eq(value["init"].to_i)
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -1,18 +1,24 @@
1
1
  describe Spotify::API do
2
- describe ".platform" do
3
- it "prints a warning containing the OS if platform unknown" do
4
- old_platform = FFI::Platform::OS.dup
5
- $stderr, old_stderr = StringIO.new, $stderr
2
+ describe ".attach_function" do
3
+ it "is a retaining class if the method is not creating" do
4
+ begin
5
+ Spotify::API.attach_function :whatever, [], Spotify::User
6
+ rescue FFI::NotFoundError
7
+ # expected, this method does not exist
8
+ end
6
9
 
10
+ $attached_methods["whatever"][:returns].should eq Spotify::User.retaining_class
11
+ end
12
+
13
+ it "is a non-retaining class if the method is creating" do
7
14
  begin
8
- FFI::Platform::OS.replace "LAWL"
9
- Spotify.platform
10
- $stderr.rewind
11
- $stderr.read.should include "LAWL"
12
- ensure
13
- $stderr = old_stderr
14
- FFI::Platform::OS.replace(old_platform)
15
+ Spotify::API.attach_function :whatever_create, [], Spotify::User
16
+ rescue FFI::NotFoundError
17
+ # expected, this method does not exist
15
18
  end
19
+
20
+ $attached_methods["whatever_create"][:returns].should be Spotify::User
21
+ $attached_methods["whatever_create"][:returns].should_not be Spotify::User.retaining_class
16
22
  end
17
23
  end
18
24
  end
@@ -4,19 +4,19 @@ describe Spotify::BestEffortString do
4
4
  it "simply reads a null-terminated string" do
5
5
  source = "hello wörld"
6
6
  string = Spotify::BestEffortString.to_native(source, nil)
7
- string.should eq "hello wörld".force_encoding("BINARY")
7
+ string.should eq "hello wörld".force_encoding(Encoding::BINARY)
8
8
  end
9
9
 
10
10
  it "cuts the string at the first encountered NULL-byte" do
11
- source = "hello\x00wörld".force_encoding("BINARY")
11
+ source = "hello\x00wörld".force_encoding(Encoding::BINARY)
12
12
  string = Spotify::BestEffortString.to_native(source, nil)
13
- string.should eq "hello".force_encoding("BINARY")
13
+ string.should eq "hello".force_encoding(Encoding::BINARY)
14
14
  end
15
15
 
16
16
  it "treats empty string as empty string, and not null" do
17
17
  source = ""
18
18
  string = Spotify::BestEffortString.to_native(source, nil)
19
- string.should eq "".force_encoding("BINARY")
19
+ string.should eq "".force_encoding(Encoding::BINARY)
20
20
  end
21
21
  end
22
22
  end
@@ -0,0 +1,16 @@
1
+ describe Spotify::CountryCode do
2
+ let(:context) { nil }
3
+ subject(:type) { Spotify::API.find_type(Spotify::CountryCode) }
4
+
5
+ describe ".from_native" do
6
+ it "decodes a country code" do
7
+ type.from_native(21317, context).should eq "SE"
8
+ end
9
+ end
10
+
11
+ describe ".to_native" do
12
+ it "encodes a country code" do
13
+ type.to_native("SE", context).should eq 21317
14
+ end
15
+ end
16
+ end
@@ -12,7 +12,7 @@ describe Spotify::ImageID do
12
12
  let(:image_id) do
13
13
  # deliberate NULL in middle of string
14
14
  image_id = ":\xD94#\xAD\xD9\x97f\xE0\x00V6\x05\xC6\xE7n\xD2\xB0\xE4P"
15
- image_id.force_encoding("BINARY")
15
+ image_id.force_encoding(Encoding::BINARY)
16
16
  image_id
17
17
  end
18
18
 
@@ -9,7 +9,7 @@ describe Spotify::UTF8String do
9
9
 
10
10
  let(:char) do
11
11
  char = "\xC4"
12
- char.force_encoding('ISO-8859-1')
12
+ char.force_encoding(Encoding::ISO_8859_1)
13
13
  char
14
14
  end
15
15
 
@@ -20,6 +20,8 @@ describe "Spotify functions" do
20
20
  case type["name"]
21
21
  when "unsigned int"
22
22
  :uint
23
+ when "sp_error"
24
+ Spotify::APIError
23
25
  else
24
26
  type["name"].sub(/\Asp_/, '').to_sym
25
27
  end
@@ -1,6 +1,5 @@
1
1
  # coding: utf-8
2
2
  describe Spotify::ManagedPointer do
3
- let(:klass) { described_class }
4
3
  let(:null) { FFI::Pointer::NULL }
5
4
  let(:pointer) { FFI::Pointer.new(1) }
6
5
  let(:klass) { Spotify::User }
@@ -17,19 +16,17 @@ describe Spotify::ManagedPointer do
17
16
  api.should_not_receive(:user_release)
18
17
 
19
18
  ptr = retaining_klass.new(FFI::Pointer::NULL)
20
- ptr.autorelease = false
21
19
  ptr.free
22
20
  end
23
21
 
24
22
  describe "#release" do
25
23
  it "wraps the release pointer properly to avoid type-failures" do
26
- api.should_receive(:user_release).and_return do |pointer|
24
+ api.should_receive(:user_release) do |pointer|
27
25
  pointer.should be_instance_of(klass)
28
26
  pointer.should_not be_autorelease # autorelease should be off
29
27
  end
30
28
 
31
29
  ptr = klass.new(FFI::Pointer.new(1))
32
- ptr.autorelease = false
33
30
  ptr.free
34
31
  end
35
32
  end
@@ -61,6 +58,13 @@ describe Spotify::ManagedPointer do
61
58
  end
62
59
  end
63
60
 
61
+ describe ".from_native" do
62
+ it "returns nil if pointer is null" do
63
+ native = FFI::Pointer::NULL
64
+ klass.from_native(native, nil).should be_nil
65
+ end
66
+ end
67
+
64
68
  describe ".size" do
65
69
  it "returns the size of a pointer" do
66
70
  Spotify::ManagedPointer.size.should eq FFI.type_size(:pointer)
@@ -106,21 +110,17 @@ describe Spotify::ManagedPointer do
106
110
  next unless klass < Spotify::ManagedPointer
107
111
 
108
112
  it "#{klass.name} has a valid retain method" do
109
- Spotify.should_receive(:public_send).and_return do |method, *args|
110
- Spotify.should respond_to(method)
111
- method.should match(/_add_ref$/)
112
- end
113
+ Spotify.should_receive(:"#{klass.type}_add_ref")
114
+ Spotify.should respond_to(:"#{klass.type}_add_ref")
113
115
 
114
116
  klass.retain(FFI::Pointer.new(1))
115
117
  end unless klass == Spotify::Session # has no valid retain
116
118
 
117
119
  it "#{klass.name} has a valid release method" do
118
- Spotify.should_receive(:public_send).and_return do |method, *args|
119
- Spotify.should respond_to(method)
120
- method.should match(/_release$/)
121
- end
120
+ Spotify.should_receive(:"#{klass.type}_release")
121
+ Spotify.should respond_to(:"#{klass.type}_release")
122
122
 
123
- klass.release(FFI::Pointer.new(1))
123
+ klass.release(FFI::Pointer.new(1)).value
124
124
  end
125
125
  end
126
126
  end
@@ -100,10 +100,12 @@ describe Spotify::Subscribers do
100
100
  end
101
101
  end
102
102
 
103
+ it "returns an enumerator when not given a block" do
104
+ subscribers.each.should be_a Enumerator
105
+ end
106
+
103
107
  it "returns an enumerator with a defined size when not given a block", :ruby_version => ">= 2.0.0" do
104
- enumerator = subscribers.each
105
- enumerator.should be_a Enumerator
106
- enumerator.size.should eq 3
108
+ subscribers.each.size.should eq 3
107
109
  end
108
110
 
109
111
  it "yields every subscriber as an UTF-8 encoded string" do
@@ -1,6 +1,20 @@
1
- describe "Spotify enums" do
1
+ describe "Spotify types" do
2
+ describe "audio sample types" do
3
+ Spotify::API.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
+
2
10
  API_H_XML.enumerations.each do |enum|
3
- attached_enum = Spotify.enum_type enum["name"].sub(/\Asp_/, '').to_sym
11
+ next if enum["name"] == "sp_error"
12
+
13
+ attached_tag = enum["name"].sub(/\Asp_/, '').to_sym
14
+ attached_enum = Spotify.enum_type(attached_tag)
15
+ unless attached_enum.tag == attached_tag
16
+ raise "Cannot find attached enum for #{enum["name"]}"
17
+ end
4
18
  original_enum = enum.values.map { |v| [v["name"].downcase, v["init"]] }
5
19
 
6
20
  describe enum["name"] do
@@ -19,4 +33,3 @@ describe "Spotify enums" do
19
33
  end
20
34
  end
21
35
  end
22
-
@@ -0,0 +1,24 @@
1
+ describe "Spotify::Util" do
2
+ describe ".enum_value!" do
3
+ it "raises an error if given an invalid enum value" do
4
+ expect { Spotify::Util.enum_value!(:moo, "error value") }.to raise_error(ArgumentError)
5
+ end
6
+
7
+ it "gives back the enum value for that enum" do
8
+ Spotify::Util.enum_value!(:no_tracks, "search browse").should eq 1
9
+ end
10
+ end
11
+
12
+ describe ".platform" do
13
+ it "prints a warning containing the OS if platform unknown" do
14
+ suppress = true
15
+ _, err = spy_output(suppress) do
16
+ stub_const("FFI::Platform::OS", "LAWL")
17
+ Spotify::Util.platform
18
+ end
19
+
20
+ err.should match "unknown platform"
21
+ err.should match "LAWL"
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,74 @@
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
+
18
+ it "allows creating proxy methods" do
19
+ api.should_receive(:error_message).and_return("Some error")
20
+ Spotify.method(:error_message).call.should eq "Some error"
21
+ end
22
+ end
23
+
24
+ describe ".log" do
25
+ it "print nothing if not debugging" do
26
+ out, err = spy_output do
27
+ old_debug, $DEBUG = $DEBUG, false
28
+ Spotify.log "They see me loggin'"
29
+ $DEBUG = old_debug
30
+ end
31
+ out.should be_empty
32
+ err.should be_empty
33
+ end
34
+
35
+ it "prints output and path if debugging" do
36
+ suppress = true
37
+ out, err = spy_output(suppress) do
38
+ old_debug, $DEBUG = $DEBUG, true
39
+ Spotify.log "Testin' Spotify log"
40
+ $DEBUG = old_debug
41
+ end
42
+
43
+ out.should match "Testin' Spotify log"
44
+ out.should match "spec/spotify_spec.rb"
45
+
46
+ err.should be_empty
47
+ end
48
+ end
49
+
50
+ describe ".try" do
51
+ it "raises an error when the result is not OK" do
52
+ api.should_receive(:error_example).and_return(Spotify::APIError.from_native(5, nil))
53
+ expect { Spotify.try(:error_example) }.to raise_error(Spotify::BadApplicationKeyError, /Invalid application key/)
54
+ end
55
+
56
+ it "does not raise an error when the result is OK" do
57
+ api.should_receive(:error_example).and_return(nil)
58
+ Spotify.try(:error_example).should eq nil
59
+ end
60
+
61
+ it "does not raise an error when the result is not an error-type" do
62
+ result = Object.new
63
+ api.should_receive(:error_example).and_return(result)
64
+ Spotify.try(:error_example).should eq result
65
+ end
66
+
67
+ it "does not raise an error when the resource is loading" do
68
+ api.should_receive(:error_example).and_return(Spotify::APIError.from_native(17, nil))
69
+ error = Spotify.try(:error_example)
70
+ error.should be_a(Spotify::IsLoadingError)
71
+ error.message.should match /Resource not loaded yet/
72
+ end
73
+ end
74
+ end
@@ -1,15 +1,19 @@
1
1
  module Spotify
2
2
  # used to find the actual type of a thing
3
3
  def self.resolve_type(type)
4
- if type.is_a?(Class) and type <= Spotify::ManagedPointer
4
+ found = if type.is_a?(Class) and type <= Spotify::ManagedPointer
5
+ type
6
+ elsif type == Spotify::APIError
5
7
  type
6
8
  elsif type.respond_to?(:native_type)
7
9
  type.native_type
8
10
  else
9
11
  type = Spotify::API.find_type(type)
10
- type = type.type if type.respond_to?(:type)
11
12
  type
12
13
  end
14
+
15
+ found = found.type while found.respond_to?(:type)
16
+ found
13
17
  end
14
18
 
15
19
  # @return [Array<FFI::Struct>] all structs in Spotify namespace
@@ -0,0 +1,16 @@
1
+ require "stringio"
2
+
3
+ def spy_output(suppress = false)
4
+ old_out, $stdout = $stdout, StringIO.new
5
+ old_err, $stderr = $stderr, StringIO.new
6
+ yield
7
+ out = $stdout.tap(&:rewind).read
8
+ err = $stderr.tap(&:rewind).read
9
+ [out, err]
10
+ ensure
11
+ old_out.write(out) if not suppress and out
12
+ old_err.write(err) if not suppress and err
13
+
14
+ $stderr = old_err
15
+ $stdout = old_out
16
+ end