spotify 12.5.3 → 12.6.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 (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