spotify 12.4.0 → 12.5.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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: bf64e70f3fd55a846fc2a016a2948cf547e92f54
4
+ data.tar.gz: 1012f48eb4fcb112b5866d63defe433a0974e2bb
5
+ SHA512:
6
+ metadata.gz: c6b782905e37db551e1291e1769508d5f0fa0d6f1c6932700b9c0bace51433228e558ea607d99937a6e2f5b26c2a0066e93e069c74a7b2878cd5f315f6c5d376
7
+ data.tar.gz: b402e97d8772d953ae453b7b1d61a3cd73ad9346f970b739adb23d97cc3740a0677c4c1c14845af482ace77434adef085333bb9b491ba70ee53e3dcb5a29d9f3
@@ -1,3 +1,6 @@
1
1
  rvm:
2
2
  - 1.9.2
3
3
  - 1.9.3
4
+ - 2.0.0
5
+ - rbx-19mode
6
+ - jruby-19mode
@@ -1,5 +1,26 @@
1
1
  [HEAD][]
2
2
  -----------
3
+
4
+ [v12.5.0][]
5
+ -----------
6
+ This release *also* breaks backwards-compatibilty, now all functions
7
+ accepting structs also have type-safety protection, similar to what
8
+ happens for pointers in v12.4.0.
9
+
10
+ Additionally, gem now supports JRuby and Rubinius explicitly!
11
+
12
+ - [a450bf27b9] implement type safety for all struct arguments
13
+ - [44b35d6432] automatic release for Subscribers struct
14
+ - [1c88809139] force application_key to be assigned as a string in SessionConfig
15
+ - [c9bc974d44] Subscribers now always have a :subscribers member
16
+ - [05e691c2aa] allow instantiating Subscribers from NULL
17
+ - [c83c9feb1c] Enable rbx-19mode on travis-ci.org
18
+ - [3e153a48f4] Enable jruby-19mode on travis-ci.org
19
+ - [72aadf4906] Use UTF8 everywhere, even in struct fields
20
+ - [e05b286118] Make Subscribers enumerable
21
+
22
+ [v12.4.0][]
23
+ -----------
3
24
  This release breaks backwards-compatibility, as functions will no
4
25
  longer accept pointers of any other type of what they expect. This
5
26
  means that you must wrap any pointers in a Spotify::ManagedPointer
@@ -164,8 +185,10 @@ v0.0.0
164
185
  ------
165
186
  - release to register rubygems.org name
166
187
 
167
- [HEAD]: https://github.com/Burgestrand/spotify/compare/v12.3.0...HEAD
188
+ [HEAD]: https://github.com/Burgestrand/spotify/compare/v12.5.0...HEAD
168
189
 
190
+ [v12.5.0]: https://github.com/Burgestrand/spotify/compare/v12.4.0...v12.5.0
191
+ [v12.4.0]: https://github.com/Burgestrand/spotify/compare/v12.3.0...v12.4.0
169
192
  [v12.3.0]: https://github.com/Burgestrand/spotify/compare/v12.2.0...v12.3.0
170
193
  [v12.2.0]: https://github.com/Burgestrand/spotify/compare/v12.0.3...v12.2.0
171
194
  [v12.0.3]: https://github.com/Burgestrand/spotify/compare/v12.0.2...v12.0.3
data/Gemfile CHANGED
@@ -3,5 +3,5 @@ gemspec
3
3
 
4
4
  gem 'pry'
5
5
  gem 'yard'
6
- gem 'redcarpet'
7
- gem "plaything", "~> 1.0"
6
+ gem 'maruku'
7
+ gem "plaything", "~> 1.1"
@@ -18,11 +18,13 @@ The Spotify gem has:
18
18
  - [Automatic garbage collection][]. Piggybacking on Ruby’s GC to manage pointer lifecycle.
19
19
  - [Parallell function call protection][]. libspotify is not thread-safe, but Spotify protects you.
20
20
  - [Type conversion and type safety][]. Special pointers for every Spotify type, protecting you from accidental mix-ups.
21
+ - [Support for JRuby and Rubinius][]. Thanks to FFI, the gem runs fine on the main three Ruby implementations!
21
22
 
22
23
  [100% API coverage]: http://rdoc.info/github/Burgestrand/spotify/master/Spotify/API
23
24
  [Automatic garbage collection]: http://rdoc.info/github/Burgestrand/spotify/master/Spotify/ManagedPointer
24
25
  [Parallell function call protection]: http://rdoc.info/github/Burgestrand/spotify/master/Spotify#method_missing-class_method
25
26
  [Type conversion and type safety]: http://rdoc.info/github/Burgestrand/spotify/master/Spotify/ManagedPointer
27
+ [Support for JRuby and Rubinius]: https://github.com/Burgestrand/spotify/blob/master/.travis.yml
26
28
 
27
29
  The Spotify gem is aimed at experienced developers
28
30
  --------------------------------------------------
@@ -50,8 +50,7 @@ class FrameReader
50
50
  @channels = channels
51
51
  @sample_type = sample_type
52
52
  @size = frames_count * @channels
53
- # strip type information, we work with bytes
54
- @pointer = FFI::Pointer.new(frames_ptr)
53
+ @pointer = FFI::Pointer.new(@sample_type, frames_ptr)
55
54
  end
56
55
 
57
56
  attr_reader :size
@@ -59,11 +58,10 @@ class FrameReader
59
58
  def each
60
59
  return enum_for(__method__) unless block_given?
61
60
 
62
- ffi_read = :"get_#{@sample_type}"
63
- ffi_size = FFI.type_size(@sample_type)
61
+ ffi_read = :"read_#{@sample_type}"
64
62
 
65
63
  (0...size).each do |index|
66
- yield @pointer.public_send(ffi_read, index * ffi_size)
64
+ yield @pointer[index].public_send(ffi_read)
67
65
  end
68
66
  end
69
67
  end
@@ -118,8 +116,7 @@ $session_callbacks = {
118
116
  $logger.debug("session (player)") { "music delivery audio discontuity" }
119
117
  else
120
118
  frames = FrameReader.new(format[:channels], format[:sample_type], num_frames, frames)
121
- consumed_samples = plaything << frames
122
- consumed_frames = consumed_samples / format[:channels]
119
+ consumed_frames = plaything.stream(frames, format.to_h)
123
120
  $logger.debug("session (player)") { "music delivery #{consumed_frames} of #{num_frames}" }
124
121
  consumed_frames
125
122
  end
@@ -141,7 +138,7 @@ $session_callbacks = {
141
138
  # https://developer.spotify.com/technologies/libspotify/docs/12.1.45/structsp__session__config.html
142
139
  config = Spotify::SessionConfig.new({
143
140
  api_version: Spotify::API_VERSION.to_i,
144
- application_key: IO.read("./spotify_appkey.key"),
141
+ application_key: IO.read("./spotify_appkey.key", encoding: "BINARY"),
145
142
  cache_location: ".spotify/",
146
143
  settings_location: ".spotify/",
147
144
  tracefile: "spotify_tracefile.txt",
@@ -1,8 +1,9 @@
1
1
  #!/usr/bin/env ruby
2
2
  # encoding: utf-8
3
3
 
4
- require 'spotify'
5
- require 'logger'
4
+ require "bundler/setup"
5
+ require "spotify"
6
+ require "logger"
6
7
 
7
8
  # We use a logger to print some information on when things are happening.
8
9
  $logger = Logger.new($stderr)
@@ -11,21 +12,14 @@ $logger = Logger.new($stderr)
11
12
  # Some utility.
12
13
  #
13
14
 
14
- # This method is just for convenience. Calling the process_events function
15
- # is slightly cumbersome.
16
- def process_events(session)
17
- FFI::MemoryPointer.new(:int) do |ptr|
18
- Spotify.session_process_events(session, ptr)
19
- return Rational(ptr.read_int, 1000)
20
- end
21
- end
22
-
23
15
  # libspotify supports callbacks, but they are not useful for waiting on
24
16
  # operations (how they fire can be strange at times, and sometimes they
25
17
  # might not fire at all). As a result, polling is the way to go.
26
18
  def poll(session)
27
19
  until yield
28
- process_events(session)
20
+ FFI::MemoryPointer.new(:int) do |ptr|
21
+ Spotify.session_process_events(session, ptr)
22
+ end
29
23
  sleep(0.01)
30
24
  end
31
25
  end
@@ -1,7 +1,7 @@
1
1
  module Spotify
2
2
  class API
3
3
  # @!group Link
4
- attach_function :link_create_from_string, [ :string ], Link
4
+ attach_function :link_create_from_string, [ UTF8String ], Link
5
5
  attach_function :link_create_from_track, [ Track, :int ], Link
6
6
  attach_function :link_create_from_album, [ Album ], Link
7
7
  attach_function :link_create_from_artist, [ Artist ], Link
@@ -2,8 +2,8 @@ module Spotify
2
2
  class API
3
3
  # @!group Playlist
4
4
  attach_function :playlist_is_loaded, [ Playlist ], :bool
5
- attach_function :playlist_add_callbacks, [ Playlist, PlaylistCallbacks, :userdata ], :error
6
- attach_function :playlist_remove_callbacks, [ Playlist, PlaylistCallbacks, :userdata ], :error
5
+ attach_function :playlist_add_callbacks, [ Playlist, PlaylistCallbacks.by_ref, :userdata ], :error
6
+ attach_function :playlist_remove_callbacks, [ Playlist, PlaylistCallbacks.by_ref, :userdata ], :error
7
7
  attach_function :playlist_num_tracks, [ Playlist ], :int
8
8
  attach_function :playlist_track, [ Playlist, :int ], Track
9
9
  attach_function :playlist_track_create_time, [ Playlist, :int ], :int
@@ -24,8 +24,8 @@ module Spotify
24
24
  attach_function :playlist_remove_tracks, [ Playlist, :array, :int ], :error
25
25
  attach_function :playlist_reorder_tracks, [ Playlist, :array, :int, :int ], :error
26
26
  attach_function :playlist_num_subscribers, [ Playlist ], :uint
27
- attach_function :playlist_subscribers, [ Playlist ], Subscribers
28
- attach_function :playlist_subscribers_free, [ Subscribers ], :error
27
+ attach_function :playlist_subscribers, [ Playlist ], Subscribers.auto_ptr
28
+ attach_function :playlist_subscribers_free, [ Subscribers.by_ref ], :error
29
29
  attach_function :playlist_update_subscribers, [ Session, Playlist ], :error
30
30
  attach_function :playlist_is_in_ram, [ Session, Playlist ], :bool
31
31
  attach_function :playlist_set_in_ram, [ Session, Playlist, :bool ], :error
@@ -1,8 +1,8 @@
1
1
  module Spotify
2
2
  class API
3
3
  # @!group PlaylistContainer
4
- attach_function :playlistcontainer_add_callbacks, [ PlaylistContainer, PlaylistContainerCallbacks, :userdata ], :error
5
- attach_function :playlistcontainer_remove_callbacks, [ PlaylistContainer, PlaylistContainerCallbacks, :userdata ], :error
4
+ attach_function :playlistcontainer_add_callbacks, [ PlaylistContainer, PlaylistContainerCallbacks.by_ref, :userdata ], :error
5
+ attach_function :playlistcontainer_remove_callbacks, [ PlaylistContainer, PlaylistContainerCallbacks.by_ref, :userdata ], :error
6
6
  attach_function :playlistcontainer_num_playlists, [ PlaylistContainer ], :int
7
7
  attach_function :playlistcontainer_playlist, [ PlaylistContainer, :int ], Playlist
8
8
  attach_function :playlistcontainer_playlist_type, [ PlaylistContainer, :int ], :playlist_type
@@ -1,10 +1,10 @@
1
1
  module Spotify
2
2
  class API
3
3
  # @!group Session
4
- attach_function :session_create, [ SessionConfig, :buffer_out ], :error
4
+ attach_function :session_create, [ SessionConfig.by_ref, :buffer_out ], :error
5
5
  attach_function :session_release, [ Session ], :error
6
6
  attach_function :session_process_events, [ Session, :buffer_out ], :error
7
- attach_function :session_login, [ Session, UTF8String, :string, :bool, :string ], :error
7
+ attach_function :session_login, [ Session, UTF8String, UTF8String, :bool, UTF8String ], :error
8
8
  attach_function :session_relogin, [ Session ], :error
9
9
  attach_function :session_forget_me, [ Session ], :error
10
10
  attach_function :session_remembered_user, [ Session, :buffer_out, :size_t ], :int
@@ -28,19 +28,19 @@ module Spotify
28
28
  attach_function :session_set_connection_rules, [ Session, :connection_rules ], :error
29
29
  attach_function :offline_tracks_to_sync, [ Session ], :int
30
30
  attach_function :offline_num_playlists, [ Session ], :int
31
- attach_function :offline_sync_get_status, [ Session, OfflineSyncStatus ], :bool
31
+ attach_function :offline_sync_get_status, [ Session, OfflineSyncStatus.by_ref ], :bool
32
32
  attach_function :offline_time_left, [ Session ], :int
33
33
  attach_function :session_user_country, [ Session ], :int
34
34
  attach_function :session_preferred_offline_bitrate, [ Session, :bitrate, :bool ], :error
35
35
  attach_function :session_set_volume_normalization, [ Session, :bool ], :error
36
36
  attach_function :session_get_volume_normalization, [ Session ], :bool
37
37
  attach_function :session_flush_caches, [ Session ], :error
38
- attach_function :session_user_name, [ Session ], :string
38
+ attach_function :session_user_name, [ Session ], UTF8String
39
39
  attach_function :session_set_private_session, [ Session, :bool ], :error
40
40
  attach_function :session_is_private_session, [ Session ], :bool
41
41
  attach_function :session_set_scrobbling, [ Session, :social_provider, :scrobbling_state ], :error
42
42
  attach_function :session_is_scrobbling, [ Session, :social_provider, :buffer_out ], :error
43
43
  attach_function :session_is_scrobbling_possible, [ Session, :social_provider, :buffer_out ], :error
44
- attach_function :session_set_social_credentials, [ Session, :social_provider, UTF8String, :string ], :error
44
+ attach_function :session_set_social_credentials, [ Session, :social_provider, UTF8String, UTF8String ], :error
45
45
  end
46
46
  end
@@ -15,6 +15,8 @@ module Spotify
15
15
  #
16
16
  # @api private
17
17
  class ManagedPointer < FFI::AutoPointer
18
+ extend Spotify::TypeSafety
19
+
18
20
  class << self
19
21
  # Releases the given pointer if it is not null.
20
22
  #
@@ -24,7 +26,7 @@ module Spotify
24
26
  def release(pointer)
25
27
  unless pointer.null?
26
28
  # this is to circumvent the type protection
27
- pointer = base_class.new(pointer)
29
+ pointer = type_class.new(pointer)
28
30
  pointer.autorelease = false
29
31
 
30
32
  $stderr.puts "Spotify.#{type}_release(#{pointer.inspect})" if $DEBUG
@@ -36,7 +38,7 @@ module Spotify
36
38
  #
37
39
  # This method derives the retain method from the class name.
38
40
  #
39
- # @param [self] pointer must be an instance of {#base_class}
41
+ # @param [self] pointer must be an instance of {#type_class}
40
42
  def retain(pointer)
41
43
  unless pointer.null?
42
44
  $stderr.puts "Spotify.#{type}_add_ref(#{pointer.inspect})" if $DEBUG
@@ -49,13 +51,17 @@ module Spotify
49
51
  def to_native(value, ctx)
50
52
  if value.nil? or value.null?
51
53
  raise TypeError, "#{name} pointers cannot be null, was #{value.inspect}"
52
- elsif value.kind_of?(base_class)
53
- super
54
54
  else
55
- raise TypeError, "expected a kind of #{name}, was #{value.class}"
55
+ super
56
56
  end
57
57
  end
58
58
 
59
+ # @see https://github.com/jruby/jruby/issues/607
60
+ # @return [Integer] size of the native type, defined for JRuby.
61
+ def size
62
+ FFI.type_size(:pointer)
63
+ end
64
+
59
65
  # Retaining class is needed for the functions that return a pointer that
60
66
  # does not have its reference count increased. This class is a subclass
61
67
  # of the ManagedPointer, and should behave the same in all circumstances
@@ -79,7 +85,7 @@ module Spotify
79
85
 
80
86
  protected
81
87
 
82
- def base_class
88
+ def type_class
83
89
  superclass
84
90
  end
85
91
  end
@@ -101,13 +107,6 @@ module Spotify
101
107
  def type
102
108
  name.split('::')[-1].downcase
103
109
  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
111
110
  end
112
111
 
113
112
  # @return [String] string representation of self.
@@ -1,3 +1,4 @@
1
+ require 'spotify/type_safety'
1
2
  require 'spotify/managed_pointer'
2
3
 
3
4
  require 'spotify/objects/album'
@@ -3,6 +3,8 @@ module Spotify
3
3
  # checking that happens in the Spotify::API namespace, and
4
4
  # it also allows you to initialize structs with a hash.
5
5
  class Struct < FFI::Struct
6
+ extend Spotify::TypeSafety
7
+
6
8
  # This is used by FFI to do type lookups when creating the
7
9
  # struct layout. By overriding this we can trick FFI into
8
10
  # looking up types in the right location.
@@ -20,22 +20,22 @@ module Spotify
20
20
  class SessionConfig < Spotify::Struct
21
21
  it = {}
22
22
  it[:api_version] = :int
23
- it[:cache_location] = NULString
24
- it[:settings_location] = NULString
25
- it[:application_key] = :pointer
23
+ it[:cache_location] = UTF8StringPointer
24
+ it[:settings_location] = UTF8StringPointer
25
+ it[:application_key] = ByteString
26
26
  it[:application_key_size] = :size_t
27
- it[:user_agent] = NULString
27
+ it[:user_agent] = UTF8StringPointer
28
28
  it[:callbacks] = SessionCallbacks.by_ref
29
29
  it[:userdata] = :userdata
30
30
  it[:compress_playlists] = :bool
31
31
  it[:dont_save_metadata_for_playlists] = :bool
32
32
  it[:initially_unload_playlists] = :bool
33
- it[:device_id] = NULString
34
- it[:proxy] = NULString
35
- it[:proxy_username] = NULString
36
- it[:proxy_password] = NULString
37
- it[:ca_certs_filename] = NULString if Spotify::API.linux?
38
- it[:tracefile] = NULString
33
+ it[:device_id] = UTF8StringPointer
34
+ it[:proxy] = UTF8StringPointer
35
+ it[:proxy_username] = UTF8StringPointer
36
+ it[:proxy_password] = UTF8StringPointer
37
+ it[:ca_certs_filename] = UTF8StringPointer if Spotify::API.linux?
38
+ it[:tracefile] = UTF8StringPointer
39
39
  layout(it)
40
40
 
41
41
  # Overridden for some keys for convenience.
@@ -49,14 +49,8 @@ module Spotify
49
49
  def []=(key, value)
50
50
  case key
51
51
  when :application_key
52
- if value.is_a?(String)
53
- pointer = FFI::MemoryPointer.new(:char, value.bytesize)
54
- pointer.write_string(value)
55
- super(key, pointer)
56
- self[:application_key_size] = pointer.size
57
- else
58
- super
59
- end
52
+ super(key, value)
53
+ self[:application_key_size] = value.bytesize if value
60
54
  else super
61
55
  end
62
56
  end
@@ -1,11 +1,29 @@
1
1
  module Spotify
2
2
  # Spotify::Struct for Subscribers of a Playlist.
3
3
  #
4
+ # Memory looks like this:
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
+ #
4
10
  # @attr [Fixnum] count
5
11
  # @attr [Array<Pointer<String>>] subscribers
6
12
  class Subscribers < Spotify::Struct
13
+ include Enumerable
14
+
15
+ class << self
16
+ def release(pointer)
17
+ unless pointer.null?
18
+ pointer = type_class.new(pointer)
19
+ $stderr.puts "Spotify.playlist_subscribers_free(#{pointer.inspect})" if $DEBUG
20
+ Spotify.playlist_subscribers_free(pointer)
21
+ end
22
+ end
23
+ end
24
+
7
25
  layout :count => :uint,
8
- :subscribers => [:pointer, 1] # array of pointers to strings
26
+ :subscribers => [UTF8StringPointer, 0] # array of pointers to strings
9
27
 
10
28
  # Redefined, as the layout of the Struct can only be determined
11
29
  # at run-time.
@@ -13,18 +31,43 @@ module Spotify
13
31
  # @param [FFI::Pointer, Integer] pointer_or_count
14
32
  def initialize(pointer_or_count)
15
33
  count = if pointer_or_count.is_a?(FFI::Pointer)
16
- pointer_or_count.read_uint
34
+ if pointer_or_count.null?
35
+ 0
36
+ else
37
+ pointer_or_count.read_uint
38
+ end
17
39
  else
18
40
  pointer_or_count
19
41
  end
20
42
 
21
43
  layout = [:count, :uint]
22
- layout += [:subscribers, [:pointer, count]] if count > 0
44
+ layout += [:subscribers, [UTF8StringPointer, count]]
23
45
 
24
46
  if pointer_or_count.is_a?(FFI::Pointer)
25
47
  super(pointer_or_count, *layout)
26
48
  else
27
49
  super(nil, *layout)
50
+ self[:count] = count
51
+ end
52
+ end
53
+
54
+ # Yields every subscriber as a UTF8-encoded string.
55
+ #
56
+ # @yield [subscriber]
57
+ # @yieldparam [String] subscriber
58
+ def each
59
+ return enum_for(__method__) { count } unless block_given?
60
+ count.times { |index| yield self[:subscribers][index] }
61
+ end
62
+
63
+ private
64
+
65
+ # @return [Integer] number of subscribers in the struct.
66
+ def count
67
+ if null?
68
+ 0
69
+ else
70
+ self[:count]
28
71
  end
29
72
  end
30
73
  end
@@ -0,0 +1,25 @@
1
+ module Spotify
2
+ module TypeSafety
3
+ # Convert given value to native value, with type checking.
4
+ #
5
+ # @note Calls super-implementation if type is safe.
6
+ #
7
+ # @param value
8
+ # @param ctx
9
+ # @raise [TypeError] if value is not of the same kind as {#type_class}.
10
+ def to_native(value, ctx)
11
+ if value.kind_of?(type_class)
12
+ super
13
+ else
14
+ raise TypeError, "expected a kind of #{name}, was #{value.class}"
15
+ end
16
+ end
17
+
18
+ # Retrieve the type that all objects going into to_native must be of.
19
+ #
20
+ # @return self by default
21
+ def type_class
22
+ self
23
+ end
24
+ end
25
+ end
@@ -1,3 +1,4 @@
1
1
  require 'spotify/types/utf8_string'
2
+ require 'spotify/types/utf8_string_pointer'
2
3
  require 'spotify/types/image_id'
3
- require 'spotify/types/nul_string'
4
+ require 'spotify/types/byte_string'
@@ -0,0 +1,28 @@
1
+ module Spotify
2
+ module ByteString
3
+ extend FFI::DataConverter
4
+ native_type FFI::Type::POINTER
5
+
6
+ class << self
7
+ # Given either a String or nil, make an actual FFI::Pointer
8
+ # of that value, without an ending NULL-byte.
9
+ #
10
+ # @param [#to_str, nil] value
11
+ # @param ctx
12
+ # @return [FFI::Pointer]
13
+ def to_native(value, ctx)
14
+ value && begin
15
+ value = value.to_str
16
+
17
+ pointer = FFI::MemoryPointer.new(:char, value.bytesize)
18
+ pointer.write_string(value)
19
+ end
20
+ end
21
+
22
+ # @see NulString.reference_required?
23
+ def reference_required?
24
+ true
25
+ end
26
+ end
27
+ end
28
+ end
@@ -4,7 +4,7 @@ module Spotify
4
4
  #
5
5
  # Keep in mind this implementation is unsafe to use on Rubinius
6
6
  # as long as it ignores the .reference_required? indication.
7
- module NULString
7
+ module UTF8StringPointer
8
8
  extend FFI::DataConverter
9
9
  native_type FFI::Type::POINTER
10
10
 
@@ -16,7 +16,7 @@ module Spotify
16
16
  # @param ctx
17
17
  # @return [FFI::Pointer]
18
18
  def to_native(value, ctx)
19
- value && FFI::MemoryPointer.from_string(value.to_str)
19
+ value && FFI::MemoryPointer.from_string(value.to_str.encode("UTF-8"))
20
20
  end
21
21
 
22
22
  # Given a pointer, read out it’s string.
@@ -25,7 +25,7 @@ module Spotify
25
25
  # @param ctx
26
26
  # @return [String, nil]
27
27
  def from_native(value, ctx)
28
- value.read_string unless value.null?
28
+ value.read_string.force_encoding("UTF-8") unless value.null?
29
29
  end
30
30
 
31
31
  # Used by FFI::StructLayoutField to know if this field
@@ -1,7 +1,7 @@
1
1
  module Spotify
2
2
  # @note See README for versioning policy.
3
3
  # @return [String] Spotify gem version.
4
- VERSION = '12.4.0'
4
+ VERSION = '12.5.0'
5
5
 
6
6
  # @return [String] Compatible libspotify API version.
7
7
  API_VERSION = '12.1.51'
@@ -1,10 +1,10 @@
1
1
  # encoding: utf-8
2
- require 'rbgccxml'
3
- require 'rspec'
4
- require 'pry'
2
+ require "rbgccxml"
3
+ require "rspec"
4
+ require "pry"
5
5
 
6
- require 'spec/support/hook_spotify'
7
- require 'spec/support/spotify_util'
6
+ require "spec/support/hook_spotify"
7
+ require "spec/support/spotify_util"
8
8
 
9
9
  # You can pregenerate new XML files through:
10
10
  # gccxml spec/api-mac.h -fxml=spec/api-mac.xml
@@ -17,4 +17,14 @@ RSpec.configure do |config|
17
17
  def api
18
18
  Spotify::API
19
19
  end
20
+
21
+ config.filter_run_excluding(engine: ->(engine) do
22
+ ! Array(engine).include?(RUBY_ENGINE)
23
+ end)
24
+
25
+ config.filter_run_excluding(ruby_version: ->(requirement) do
26
+ ruby_version = Gem::Version.new(RUBY_VERSION)
27
+ required_version = Gem::Requirement.new(requirement)
28
+ ! required_version.satisfied_by?(ruby_version)
29
+ end)
20
30
  end
@@ -68,7 +68,18 @@ describe Spotify::ManagedPointer do
68
68
  end
69
69
  end
70
70
 
71
- describe "garbage collection" do
71
+ describe ".size" do
72
+ it "returns the size of a pointer" do
73
+ Spotify::ManagedPointer.size.should eq FFI.type_size(:pointer)
74
+ end
75
+ end
76
+
77
+ # We only test this on MRI, since it does not work in quite
78
+ # the same way on JRuby nor Rubinius with regards to the GC.
79
+ #
80
+ # Luckily, if it works on MRI, we should be able to assume
81
+ # that it works on JRuby and Rubinius too.
82
+ describe "garbage collection", :engine => "ruby" do
72
83
  module Spotify
73
84
  class << API
74
85
  def bogus_add_ref(pointer)
@@ -13,8 +13,8 @@ describe Spotify::SessionConfig do
13
13
  config[:application_key].read_string(5).should eq "h\x00e\x00y"
14
14
  end
15
15
 
16
- it "does not automatically set application key size when setting application key from pointer" do
16
+ it "does not support setting application key with a pointer" do
17
17
  expect { config[:application_key] = FFI::MemoryPointer.from_string("yay") }
18
- .to_not change { config[:application_key_size] }
18
+ .to raise_error(NoMethodError, /to_str/)
19
19
  end
20
20
  end
@@ -2,8 +2,8 @@ describe Spotify::Struct do
2
2
  let(:klass) do
3
3
  Class.new(Spotify::Struct) do
4
4
  layout :api_version => :int,
5
- :cache_location => Spotify::NULString,
6
- :user_agent => Spotify::NULString,
5
+ :cache_location => Spotify::UTF8StringPointer,
6
+ :user_agent => Spotify::UTF8StringPointer,
7
7
  :compress_playlists => :bool
8
8
  end
9
9
  end
@@ -1,31 +1,115 @@
1
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)
2
+ describe "#initialize" do
3
+ context "given a null pointer" do
4
+ subject(:subscribers) do
5
+ Spotify::Subscribers.new(FFI::Pointer::NULL)
6
+ end
7
+
8
+ it "is null" do
9
+ should be_null
10
+ end
11
+
12
+ it "protects against referencing non-allocated memory" do
13
+ expect { subscribers[:count] }.to raise_error(FFI::NullPointerError)
14
+ end
15
+
16
+ it "is empty" do
17
+ subscribers.to_a.should be_empty
18
+ end
19
+ end
20
+
21
+ context "given an empty struct pointer" do
22
+ let(:memory) do
23
+ pointer = FFI::MemoryPointer.new(:uint)
24
+ pointer.write_uint(0)
25
+ pointer
26
+ end
27
+
28
+ subject(:subscribers) do
29
+ pointer = FFI::Pointer.new(memory.address)
30
+ Spotify::Subscribers.new(pointer)
31
+ end
32
+
33
+ specify do
34
+ subscribers[:count].should be_zero
35
+ end
36
+
37
+ it "is empty" do
38
+ subscribers.to_a.should be_empty
39
+ end
40
+ end
41
+
42
+ context "given a filled struct pointer" do
43
+ let(:memory) do
44
+ klass = Class.new(Spotify::Struct) do
45
+ layout count: :uint,
46
+ a: Spotify::UTF8StringPointer,
47
+ b: Spotify::UTF8StringPointer
48
+ end
49
+
50
+ klass.new.tap do |struct|
51
+ struct[:count] = 2
52
+ struct[:a] = "Alpha"
53
+ struct[:b] = "Beta"
54
+ end
55
+ end
56
+
57
+ subject(:subscribers) do
58
+ Spotify::Subscribers.new(memory.pointer)
59
+ end
60
+
61
+ it "has the correct count" do
62
+ subscribers[:count].should eq 2
63
+ end
64
+
65
+ it "contains the subscribers" do
66
+ subscribers[:subscribers][0].should eq "Alpha"
67
+ subscribers[:subscribers][1].should eq "Beta"
68
+ end
69
+ end
70
+
71
+ context "given an integer count" do
72
+ subject(:subscribers) do
73
+ Spotify::Subscribers.new(2)
74
+ end
75
+
76
+ it "allocates memory for a `count` sized subscribers" do
77
+ subscribers[:subscribers].size.should eq 2
78
+ end
79
+
80
+ it "assigns count to the correct count" do
81
+ subscribers[:count].should eq 2
82
+ end
83
+ end
84
+ end
85
+
86
+ describe "#[:subscribers]" do
87
+ it "raises an error when referencing a value out of bounds" do
88
+ subscribers = Spotify::Subscribers.new(2)
89
+ expect { subscribers[:subscribers][2] }.to raise_error(IndexError)
90
+ end
20
91
  end
21
92
 
93
+ describe "#each" do
94
+ subject(:subscribers) do
95
+ Spotify::Subscribers.new(3).tap do |struct|
96
+ struct[:count] = 3
97
+ struct[:subscribers][0] = "Alpha"
98
+ struct[:subscribers][1] = "Beta"
99
+ struct[:subscribers][2] = "Gamma"
100
+ end
101
+ end
22
102
 
23
- it "should not fail given an empty subscribers struct" do
24
- subscribers = FFI::MemoryPointer.new(:uint)
25
- subscribers.write_uint(0)
103
+ 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
107
+ end
26
108
 
27
- subject = Spotify::Subscribers.new(subscribers)
28
- subject[:count].should eq 0
29
- expect { subject[:subscribers] }.to raise_error(ArgumentError)
109
+ it "yields every subscriber as an UTF-8 encoded string" do
110
+ strings = subscribers.to_a
111
+ strings.should eq %w[Alpha Beta Gamma]
112
+ strings.map(&:encoding).uniq.should eq [Encoding::UTF_8]
113
+ end
30
114
  end
31
115
  end
@@ -0,0 +1,36 @@
1
+ describe Spotify::TypeSafety do
2
+ let(:superklass) do
3
+ Class.new do
4
+ def self.to_native(value, ctx)
5
+ value
6
+ end
7
+ end
8
+ end
9
+
10
+ let(:klass) do
11
+ Class.new(superklass) do
12
+ extend Spotify::TypeSafety
13
+
14
+ def self.type_class
15
+ self
16
+ end
17
+ end
18
+ end
19
+
20
+ describe "#to_native" do
21
+ it "calls to the superclass if value is accepted" do
22
+ value = klass.new
23
+ klass.to_native(value, nil).should eq value
24
+ end
25
+
26
+ it "raises a type error if the value is of the wrong type" do
27
+ expect { klass.to_native(Object.new, nil) }.to raise_error(TypeError, /expected a kind of/)
28
+ end
29
+ end
30
+
31
+ describe "#type_class" do
32
+ it "defaults to the class itself" do
33
+ klass.type_class.should eq klass
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,26 @@
1
+ describe Spotify::ByteString do
2
+ describe ".to_native" do
3
+ it "returns a memory pointer containing the given string, without ending NULL byte" do
4
+ pointer = Spotify::ByteString.to_native("coolio", nil)
5
+ pointer.size.should eq 6
6
+ end
7
+
8
+ it "returns nil when given nil" do
9
+ pointer = Spotify::ByteString.to_native(nil, nil)
10
+ pointer.should be_nil
11
+ end
12
+
13
+ it "raises an error when given a non-string" do
14
+ expect { Spotify::ByteString.to_native({}, nil) }
15
+ .to raise_error(NoMethodError, /to_str/)
16
+ end
17
+ end
18
+
19
+ describe ".from_native" do
20
+ it "returns the value, there is no size information" do
21
+ pointer = FFI::MemoryPointer.from_string("hey")
22
+ value = Spotify::ByteString.from_native(pointer, nil)
23
+ value.should eql pointer
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,49 @@
1
+ # encoding: utf-8
2
+ describe Spotify::UTF8StringPointer do
3
+ describe ".to_native" do
4
+ it "returns a memory pointer containing the given string" do
5
+ pointer = Spotify::UTF8StringPointer.to_native("coolio", nil)
6
+ pointer.read_string.should eq "coolio"
7
+ end
8
+
9
+ it "returns nil when given nil" do
10
+ pointer = Spotify::UTF8StringPointer.to_native(nil, nil)
11
+ pointer.should be_nil
12
+ end
13
+
14
+ it "converts strings to UTF-8" do
15
+ string = "åäö".encode("ISO-8859-1")
16
+ pointer = Spotify::UTF8StringPointer.to_native(string, nil)
17
+
18
+ string.bytesize.should eq 3
19
+ pointer.read_string.bytesize.should eq 6
20
+ end
21
+
22
+ it "raises an error when given a non-string" do
23
+ expect { Spotify::UTF8StringPointer.to_native({}, nil) }
24
+ .to raise_error(NoMethodError, /to_str/)
25
+ end
26
+ end
27
+
28
+ describe ".from_native" do
29
+ it "returns an empty string if given a null pointer" do
30
+ value = Spotify::UTF8StringPointer.from_native(FFI::Pointer::NULL, nil)
31
+ value.should be_nil
32
+ end
33
+
34
+ it "returns the string value of the string pointer" do
35
+ pointer = FFI::MemoryPointer.from_string("hey")
36
+ value = Spotify::UTF8StringPointer.from_native(pointer, nil)
37
+ value.should eq "hey"
38
+ end
39
+
40
+ it "forces the encoding to UTF-8" do
41
+ string = FFI::MemoryPointer.from_string("åäö".encode("ISO-8859-1"))
42
+
43
+ value = Spotify::UTF8StringPointer.from_native(string, nil)
44
+ value.bytesize.should eq 3
45
+ value.encoding.should eq Encoding::UTF_8
46
+ value.should_not be_valid_encoding
47
+ end
48
+ end
49
+ end
@@ -3,6 +3,8 @@ module Spotify
3
3
  def self.resolve_type(type)
4
4
  if type.is_a?(Class) and type <= Spotify::ManagedPointer
5
5
  type
6
+ elsif type.respond_to?(:native_type)
7
+ type.native_type
6
8
  else
7
9
  type = Spotify::API.find_type(type)
8
10
  type = type.type if type.respond_to?(:type)
metadata CHANGED
@@ -1,102 +1,91 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: spotify
3
3
  version: !ruby/object:Gem::Version
4
- prerelease:
5
- version: 12.4.0
4
+ version: 12.5.0
6
5
  platform: ruby
7
6
  authors:
8
7
  - Kim Burgestrand
9
8
  autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
- date: 2013-03-26 00:00:00.000000000 Z
11
+ date: 2013-04-24 00:00:00.000000000 Z
13
12
  dependencies:
14
13
  - !ruby/object:Gem::Dependency
15
- version_requirements: !ruby/object:Gem::Requirement
14
+ name: ffi
15
+ requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - ~>
18
18
  - !ruby/object:Gem::Version
19
19
  version: '1.0'
20
- - - ! '>='
20
+ - - '>='
21
21
  - !ruby/object:Gem::Version
22
22
  version: 1.0.11
23
- none: false
23
+ type: :runtime
24
24
  prerelease: false
25
- name: ffi
26
- requirement: !ruby/object:Gem::Requirement
25
+ version_requirements: !ruby/object:Gem::Requirement
27
26
  requirements:
28
27
  - - ~>
29
28
  - !ruby/object:Gem::Version
30
29
  version: '1.0'
31
- - - ! '>='
30
+ - - '>='
32
31
  - !ruby/object:Gem::Version
33
32
  version: 1.0.11
34
- none: false
35
- type: :runtime
36
33
  - !ruby/object:Gem::Dependency
37
- version_requirements: !ruby/object:Gem::Requirement
38
- requirements:
39
- - - ~>
40
- - !ruby/object:Gem::Version
41
- version: 12.1.51
42
- none: false
43
- prerelease: false
44
34
  name: libspotify
45
35
  requirement: !ruby/object:Gem::Requirement
46
36
  requirements:
47
37
  - - ~>
48
38
  - !ruby/object:Gem::Version
49
39
  version: 12.1.51
50
- none: false
51
40
  type: :runtime
52
- - !ruby/object:Gem::Dependency
41
+ prerelease: false
53
42
  version_requirements: !ruby/object:Gem::Requirement
54
43
  requirements:
55
- - - ! '>='
44
+ - - ~>
56
45
  - !ruby/object:Gem::Version
57
- version: '0'
58
- none: false
59
- prerelease: false
46
+ version: 12.1.51
47
+ - !ruby/object:Gem::Dependency
60
48
  name: rake
61
49
  requirement: !ruby/object:Gem::Requirement
62
50
  requirements:
63
- - - ! '>='
51
+ - - '>='
64
52
  - !ruby/object:Gem::Version
65
53
  version: '0'
66
- none: false
67
54
  type: :development
68
- - !ruby/object:Gem::Dependency
55
+ prerelease: false
69
56
  version_requirements: !ruby/object:Gem::Requirement
70
57
  requirements:
71
- - - ! '>='
58
+ - - '>='
72
59
  - !ruby/object:Gem::Version
73
60
  version: '0'
74
- none: false
75
- prerelease: false
61
+ - !ruby/object:Gem::Dependency
76
62
  name: rbgccxml
77
63
  requirement: !ruby/object:Gem::Requirement
78
64
  requirements:
79
- - - ! '>='
65
+ - - '>='
80
66
  - !ruby/object:Gem::Version
81
67
  version: '0'
82
- none: false
83
68
  type: :development
84
- - !ruby/object:Gem::Dependency
69
+ prerelease: false
85
70
  version_requirements: !ruby/object:Gem::Requirement
86
71
  requirements:
87
- - - ! '>='
72
+ - - '>='
88
73
  - !ruby/object:Gem::Version
89
74
  version: '0'
90
- none: false
91
- prerelease: false
75
+ - !ruby/object:Gem::Dependency
92
76
  name: rspec
93
77
  requirement: !ruby/object:Gem::Requirement
94
78
  requirements:
95
- - - ! '>='
79
+ - - '>='
96
80
  - !ruby/object:Gem::Version
97
81
  version: '0'
98
- none: false
99
82
  type: :development
83
+ prerelease: false
84
+ version_requirements: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - '>='
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
100
89
  description:
101
90
  email:
102
91
  - kim@burgestrand.se
@@ -161,10 +150,12 @@ files:
161
150
  - lib/spotify/structs/session_callbacks.rb
162
151
  - lib/spotify/structs/session_config.rb
163
152
  - lib/spotify/structs/subscribers.rb
153
+ - lib/spotify/type_safety.rb
164
154
  - lib/spotify/types.rb
155
+ - lib/spotify/types/byte_string.rb
165
156
  - lib/spotify/types/image_id.rb
166
- - lib/spotify/types/nul_string.rb
167
157
  - lib/spotify/types/utf8_string.rb
158
+ - lib/spotify/types/utf8_string_pointer.rb
168
159
  - lib/spotify/util.rb
169
160
  - lib/spotify/version.rb
170
161
  - spec/bench_helper.rb
@@ -181,8 +172,10 @@ files:
181
172
  - spec/spotify/structs/struct_spec.rb
182
173
  - spec/spotify/structs/subscribers_spec.rb
183
174
  - spec/spotify/structs_spec.rb
175
+ - spec/spotify/type_safety_spec.rb
176
+ - spec/spotify/types/byte_string_spec.rb
184
177
  - spec/spotify/types/image_id_spec.rb
185
- - spec/spotify/types/nul_string_spec.rb
178
+ - spec/spotify/types/utf8_string_pointer_spec.rb
186
179
  - spec/spotify/types/utf8_string_spec.rb
187
180
  - spec/support/api-linux.h
188
181
  - spec/support/api-linux.xml
@@ -196,30 +189,26 @@ files:
196
189
  homepage: https://github.com/Burgestrand/spotify
197
190
  licenses:
198
191
  - X11 License
192
+ metadata: {}
199
193
  post_install_message:
200
194
  rdoc_options: []
201
195
  require_paths:
202
196
  - lib
203
197
  required_ruby_version: !ruby/object:Gem::Requirement
204
198
  requirements:
205
- - - ! '>='
199
+ - - '>='
206
200
  - !ruby/object:Gem::Version
207
201
  version: '1.9'
208
- none: false
209
202
  required_rubygems_version: !ruby/object:Gem::Requirement
210
203
  requirements:
211
- - - ! '>='
204
+ - - '>='
212
205
  - !ruby/object:Gem::Version
213
206
  version: '0'
214
- segments:
215
- - 0
216
- hash: -321866255872055088
217
- none: false
218
207
  requirements: []
219
208
  rubyforge_project:
220
- rubygems_version: 1.8.24
209
+ rubygems_version: 2.0.3
221
210
  signing_key:
222
- specification_version: 3
211
+ specification_version: 4
223
212
  summary: Low-level Ruby bindings for libspotify
224
213
  test_files:
225
214
  - spec/bench_helper.rb
@@ -236,8 +225,10 @@ test_files:
236
225
  - spec/spotify/structs/struct_spec.rb
237
226
  - spec/spotify/structs/subscribers_spec.rb
238
227
  - spec/spotify/structs_spec.rb
228
+ - spec/spotify/type_safety_spec.rb
229
+ - spec/spotify/types/byte_string_spec.rb
239
230
  - spec/spotify/types/image_id_spec.rb
240
- - spec/spotify/types/nul_string_spec.rb
231
+ - spec/spotify/types/utf8_string_pointer_spec.rb
241
232
  - spec/spotify/types/utf8_string_spec.rb
242
233
  - spec/support/api-linux.h
243
234
  - spec/support/api-linux.xml
@@ -1,31 +0,0 @@
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 nil when given nil" do
9
- pointer = Spotify::NULString.to_native(nil, nil)
10
- pointer.should be_nil
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(NoMethodError, /to_str/)
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