spotify 12.4.0 → 12.5.0

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