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
@@ -1,17 +1,88 @@
1
1
  module Spotify
2
2
  class API
3
3
  # @!group ToplistBrowse
4
+
5
+ # Initiate a request for browsing a toplist.
6
+ #
7
+ # @example
8
+ # callback = proc { |toplist_browse| puts "Toplist query finished!" }
9
+ # toplist_browse = Spotify.toplistbrowse_create(session, :tracks, :everywhere, nil, callback, nil)
10
+ #
11
+ # @example for sweden
12
+ # callback = proc { |toplist_browse| puts "Toplist query finished!" }
13
+ # toplist_browse = Spotify.toplistbrowse_create(session, :tracks, Spotify::CountryCode.to_native("SE", nil), nil, callback, nil)
14
+ #
15
+ # @note it is *very* important that the callback is not garbage collected before it is called!
16
+ # @param [Session] session
17
+ # @param [Symbol] type one of :artists, :albums, :tracks
18
+ # @param [Symbol] region :everywhere, :user, or a CountryCode
19
+ # @param [String, nil] username if region is :user this is the user to get toplists for, use nil for currently logged in user
20
+ # @param [Proc<ToplistBrowse, FFI::Pointer>] callback
21
+ # @param [FFI::Pointer] userdata
22
+ # @method toplistbrowse_create(session, type, region, username, callback, userdata)
4
23
  attach_function :toplistbrowse_create, [ Session, :toplisttype, :toplistregion, UTF8String, :toplistbrowse_complete_cb, :userdata ], ToplistBrowse
24
+
25
+ # @param [ToplistBrowse] toplist_browse
26
+ # @return [Boolean] true if toplist request has finished loading
27
+ # @method toplistbrowse_is_loaded(toplist_browse)
5
28
  attach_function :toplistbrowse_is_loaded, [ ToplistBrowse ], :bool
6
- attach_function :toplistbrowse_error, [ ToplistBrowse ], :error
29
+
30
+ # @param [ToplistBrowse] toplist_browse
31
+ # @return [Symbol] error code
32
+ # @method toplistbrowse_error(toplist_browse)
33
+ attach_function :toplistbrowse_error, [ ToplistBrowse ], APIError
34
+
35
+ # @see #toplistbrowse_is_loaded
36
+ # @note if the toplist is not loaded, the function always return 0.
37
+ # @param [ToplistBrowse] toplist_browse
38
+ # @return [Integer] number of artists in the toplist result
39
+ # @method toplistbrowse_num_artists(toplist_browse)
7
40
  attach_function :toplistbrowse_num_artists, [ ToplistBrowse ], :int
41
+
42
+ # @see #toplistbrowse_num_artists
43
+ # @note if index is out of range, the function always return nil.
44
+ # @param [ToplistBrowse] toplist_browse
45
+ # @param [Integer] index number between 0...{#toplistbrowse_num_artists}
46
+ # @return [Artist, nil] artist at index
47
+ # @method toplistbrowse_artist(toplist_browse, :int)
8
48
  attach_function :toplistbrowse_artist, [ ToplistBrowse, :int ], Artist
49
+
50
+ # @see #toplistbrowse_is_loaded
51
+ # @note if the toplist is not loaded, the function always return 0.
52
+ # @param [ToplistBrowse] toplist_browse
53
+ # @return [Integer] number of albums in the toplist result
54
+ # @method toplistbrowse_num_albums(toplist_browse)
9
55
  attach_function :toplistbrowse_num_albums, [ ToplistBrowse ], :int
56
+
57
+ # @see #toplistbrowse_num_albums
58
+ # @note if index is out of range, the function always return nil.
59
+ # @param [ToplistBrowse] toplist_browse
60
+ # @param [Integer] index number between 0...{#toplistbrowse_num_albums}
61
+ # @return [Album, nil] album at index
62
+ # @method toplistbrowse_album(toplist_browse, index)
10
63
  attach_function :toplistbrowse_album, [ ToplistBrowse, :int ], Album
64
+
65
+ # @see #toplistbrowse_is_loaded
66
+ # @note if the toplist is not loaded, the function always return 0.
67
+ # @param [ToplistBrowse] toplist_browse
68
+ # @return [Integer] number of tracks in the toplist result
69
+ # @method toplistbrowse_num_tracks(toplist_browse)
11
70
  attach_function :toplistbrowse_num_tracks, [ ToplistBrowse ], :int
71
+
72
+ # @see #toplistbrowse_num_tracks
73
+ # @note if index is out of range, the function always return nil.
74
+ # @param [ToplistBrowse] toplist_browse
75
+ # @param [Integer] index number between 0...{#toplistbrowse_num_tracks}
76
+ # @return [Track, nil] track at index
77
+ # @method toplistbrowse_track(toplist_browse, index)
12
78
  attach_function :toplistbrowse_track, [ ToplistBrowse, :int ], Track
79
+
80
+ # @param [ToplistBrowse] toplist_browse
81
+ # @return [Integer] the time in ms that was spent waiting for Spotify backend to serve request, or -1 if served from cache
82
+ # @method toplistbrowse_backend_request_duration(toplist_browse)
13
83
  attach_function :toplistbrowse_backend_request_duration, [ ToplistBrowse ], :int
14
- attach_function :toplistbrowse_add_ref, [ ToplistBrowse ], :error
15
- attach_function :toplistbrowse_release, [ ToplistBrowse ], :error
84
+
85
+ attach_function :toplistbrowse_add_ref, [ ToplistBrowse ], APIError
86
+ attach_function :toplistbrowse_release, [ ToplistBrowse ], APIError
16
87
  end
17
88
  end
@@ -1,26 +1,156 @@
1
1
  module Spotify
2
2
  class API
3
3
  # @!group Track
4
+
5
+ # @param [Track] track
6
+ # @return [Boolean] true if track has finished loading
7
+ # @method track_is_loaded(track)
4
8
  attach_function :track_is_loaded, [ Track ], :bool
5
- attach_function :track_error, [ Track ], :error
9
+
10
+ # @param [Track] track
11
+ # @return [Symbol] error code
12
+ # @method track_error(track)
13
+ attach_function :track_error, [ Track ], APIError
14
+
15
+ # @see #track_is_loaded
16
+ # @note if the track is not loaded, the function always return :unavailable.
17
+ # @param [Session] session
18
+ # @param [Track] track
19
+ # @return [Symbol] track availability, one of :unavailable, :available, :not_streamable, :banned_by_artist
20
+ # @method track_get_availability(session, track)
6
21
  attach_function :track_get_availability, [ Session, Track ], :track_availability
22
+
23
+ # @note if the track is not loaded, the function always return false.
24
+ # @param [Session] session
25
+ # @param [Track] track
26
+ # @return [Boolean] true if the track is a local track
27
+ # @method track_is_local(session, track)
7
28
  attach_function :track_is_local, [ Session, Track ], :bool
29
+
30
+ # @note if the track is not loaded, the function always return false.
31
+ # @param [Session] session
32
+ # @param [Track] track
33
+ # @return [Boolean] true if the track is automatically linked to another playable version of the track
34
+ # @method track_is_autolinked(session, track)
8
35
  attach_function :track_is_autolinked, [ Session, Track ], :bool
36
+
37
+ # @note if the track is not loaded, the function always return false.
38
+ # @param [Session] session
39
+ # @param [Track] track
40
+ # @return [Boolean] true if the track is starred by the current user
41
+ # @method track_is_starred(session, track)
9
42
  attach_function :track_is_starred, [ Session, Track ], :bool
10
- attach_function :track_set_starred, [ Session, :array, :int, :bool ], :error
43
+
44
+ # Star or unstar a list of tracks.
45
+ #
46
+ # @example
47
+ # Spotify.track_set_starred(session, [track_a, track_b], true) => :ok
48
+ #
49
+ # @param [Session] session
50
+ # @param [Array<Track>] tracks
51
+ # @param [Boolean] starred true if tracks should be starred, false if unstarred
52
+ # @return [Symbol] error code
53
+ # @method track_set_starred(session, tracks, starred)
54
+ attach_function :track_set_starred, [ Session, :array, :int, :bool ], APIError do |session, tracks, starred|
55
+ tracks = Array(tracks)
56
+
57
+ with_buffer(Spotify::Track, size: tracks.length) do |tracks_buffer|
58
+ tracks_buffer.write_array_of_pointer(tracks)
59
+ sp_track_set_starred(session, tracks_buffer, tracks.length, starred)
60
+ end
61
+ end
62
+
63
+ # @note if the track is not loaded, the function always return 0.
64
+ # @param [Track] track
65
+ # @return [Integer] number of artists performing the track
66
+ # @method track_num_artists(track)
11
67
  attach_function :track_num_artists, [ Track ], :int
68
+
69
+ # @see #track_num_artists
70
+ # @note if index is out of range, the function always return nil.
71
+ # @param [Track] track
72
+ # @param [Integer] index number between 0...{#track_num_artists}
73
+ # @return [Artist, nil] artist at index
74
+ # @method track_artist(track, index)
12
75
  attach_function :track_artist, [ Track, :int ], Artist
76
+
77
+ # @note if the track is not loaded, the function always return nil.
78
+ # @param [Track] track
79
+ # @return [Album, nil] album of the track
80
+ # @method track_album(track)
13
81
  attach_function :track_album, [ Track ], Album
82
+
83
+ # @note if the track is not loaded, the function always return an empty string.
84
+ # @param [Track] track
85
+ # @return [String] track name
86
+ # @method track_name(track)
14
87
  attach_function :track_name, [ Track ], UTF8String
88
+
89
+ # @note if the track is not loaded, the function always return 0.
90
+ # @param [Track] track
91
+ # @return [Integer] duration of the track in milliseconds
92
+ # @method track_duration(track)
15
93
  attach_function :track_duration, [ Track ], :int
94
+
95
+ # @note if the track is not loaded, the function always return 0.
96
+ # @param [Track] track
97
+ # @return [Integer] popularity of the track, 0..100
98
+ # @method track_popularity(track)
16
99
  attach_function :track_popularity, [ Track ], :int
100
+
101
+ # @note The function only returns valid data for tracks appearing in an ArtistBrowse or AlbumBrowse result.
102
+ # @note if the disc is not available, the function always return 0.
103
+ # @param [Track] track
104
+ # @return [Integer] disc number for the track, 1..(total number of discs on album)
105
+ # @method track_disc(track)
17
106
  attach_function :track_disc, [ Track ], :int
107
+
108
+ # @note The function only returns valid data for tracks appearing in an ArtistBrowse or AlbumBrowse result.
109
+ # @note if the disc is not available, the function always return 0.
110
+ # @param [Track] track
111
+ # @return [Integer] position of track on it's disc, starts at 1
112
+ # @method track_index(track)
18
113
  attach_function :track_index, [ Track ], :int
114
+
115
+ # Placeholder tracks are a way to store non-track items in a playlist.
116
+ #
117
+ # This is used when sending playlists to users, for example.
118
+ #
119
+ # @see #link_create_from_track
120
+ # @note the track does not need to be loaded for this function to return a correct value.
121
+ # @note use {#link_create_from_track} to get a link to the wrapped item.
122
+ # @param [Track] track
123
+ # @return [Boolean] true if track is a placeholder
124
+ # @method track_is_placeholder(track)
19
125
  attach_function :track_is_placeholder, [ Track ], :bool
126
+
127
+ # @note If the track has been autolinked, the returned track will not be the same track.
128
+ # @param [Session] session
129
+ # @param [Track] track
130
+ # @return [Track] the actual track that will be played if track is scheduled for playback
131
+ # @method track_get_playable(session, track)
20
132
  attach_function :track_get_playable, [ Session, Track ], Track
133
+
134
+ # @param [Track] track
135
+ # @return [Symbol] offline status of track, one of :no, :waiting, :downloading, :done, :error, :done_expired, :limit_exceeded, :done_resync
136
+ # @method track_offline_get_status(track)
21
137
  attach_function :track_offline_get_status, [ Track ], :track_offline_status
138
+
139
+ # Create a local Track reference, whatever that means.
140
+ #
141
+ # @example
142
+ # track = Spotify.localtrack_create("Indianer", "The Best Of Indianer", "Armonia", 343_000)
143
+ # # track spotify URI is: spotify:local:Indianer:Armonia:The+Best+Of+Indianer:34
144
+ #
145
+ # @param [String] artist
146
+ # @param [String] title
147
+ # @param [String] album
148
+ # @param [Integer] duration in milliseconds, or -1 if not available
149
+ # @return [Track]
150
+ # @method localtrack_create(artist, title, album, duration)
22
151
  attach_function :localtrack_create, [ UTF8String, UTF8String, UTF8String, :int ], Track
23
- attach_function :track_add_ref, [ Track ], :error
24
- attach_function :track_release, [ Track ], :error
152
+
153
+ attach_function :track_add_ref, [ Track ], APIError
154
+ attach_function :track_release, [ Track ], APIError
25
155
  end
26
156
  end
@@ -1,10 +1,26 @@
1
1
  module Spotify
2
2
  class API
3
3
  # @!group User
4
+
5
+ # @param [User] user
6
+ # @return [String] canonical username of user, the one used by Spotify in just about all places
7
+ # @method user_canonical_name(user)
4
8
  attach_function :user_canonical_name, [ User ], UTF8String
9
+
10
+ # @note #user_is_loaded can return true even if libspotify does not know the display name yet.
11
+ # @note if libspotify does not have a display name, the function will return the canonical name of the user.
12
+ # @param [User] user
13
+ # @return [String] display name of user
14
+ # @method user_display_name(user)
5
15
  attach_function :user_display_name, [ User ], UTF8String
16
+
17
+ # @note the function may return true, even if display name is not yet known, make sure to {#session_process_events}!
18
+ # @param [Track] track
19
+ # @return [Boolean] true if user has finished loading
20
+ # @method user_is_loaded(user)
6
21
  attach_function :user_is_loaded, [ User ], :bool
7
- attach_function :user_add_ref, [ User ], :error
8
- attach_function :user_release, [ User ], :error
22
+
23
+ attach_function :user_add_ref, [ User ], APIError
24
+ attach_function :user_release, [ User ], APIError
9
25
  end
10
26
  end
@@ -0,0 +1,47 @@
1
+ module Spotify
2
+ # Some methods used in the implementation that makes the C calls
3
+ # less insane to make.
4
+ #
5
+ # @private
6
+ module APIHelpers
7
+ module_function
8
+
9
+ # Allocate some memory and yield it to the given block.
10
+ #
11
+ # @param type
12
+ # @param [Hash] options
13
+ # @option options [Integer] :size (1)
14
+ # @option options [Boolean] :clear (false)
15
+ def with_buffer(type, options = {})
16
+ size = options.fetch(:size, 1)
17
+ clear = options.fetch(:clear, false)
18
+
19
+ if size > 0
20
+ FFI::MemoryPointer.new(type, size, clear) do |buffer|
21
+ return yield buffer, buffer.size
22
+ end
23
+ end
24
+ end
25
+
26
+ # Allocate some memory, specifically for a string, and yield it to the given block.
27
+ #
28
+ # @param [Integer] length
29
+ # @param [Hash] options
30
+ # @option options [Boolean] :clear (false)
31
+ def with_string_buffer(length, *args)
32
+ if length > 0
33
+ with_buffer(:char, size: length + 1) do |buffer, size|
34
+ error = yield buffer, size
35
+
36
+ if error.is_a?(Symbol) and error != :ok
37
+ ""
38
+ else
39
+ buffer.get_string(0, length).force_encoding(Encoding::UTF_8)
40
+ end
41
+ end
42
+ else
43
+ ""
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,7 @@
1
+ require 'spotify/data_converters/utf8_string'
2
+ require 'spotify/data_converters/utf8_string_pointer'
3
+ require 'spotify/data_converters/image_id'
4
+ require 'spotify/data_converters/byte_string'
5
+ require 'spotify/data_converters/best_effort_string'
6
+ require 'spotify/data_converters/country_code'
7
+ require 'spotify/data_converters/type_safety'
@@ -14,7 +14,7 @@ module Spotify
14
14
  # @param ctx
15
15
  # @return [String] value, up until the first NULL byte
16
16
  def to_native(value, ctx)
17
- value && value.dup.force_encoding("BINARY")[/[^\x00]*/n]
17
+ value && value.dup.force_encoding(Encoding::BINARY)[/[^\x00]*/n]
18
18
  end
19
19
  end
20
20
  end
@@ -0,0 +1,30 @@
1
+ module Spotify
2
+ # A type for converting a country code to an int, and vice versa.
3
+ module CountryCode
4
+ extend FFI::DataConverter
5
+ native_type FFI::Type::INT
6
+
7
+ # String#pack format used by libspotify for storing country code.
8
+ PACK_FORMAT = "s>"
9
+
10
+ class << self
11
+ # Given a two-char country code, encodes it to an integer.
12
+ #
13
+ # @param [String, nil] country_code
14
+ # @param ctx
15
+ # @return [Integer] country code as int
16
+ def to_native(country_code, ctx)
17
+ country_code.unpack(PACK_FORMAT)[0]
18
+ end
19
+
20
+ # Given a two-char country code as int, decodes it to a string.
21
+ #
22
+ # @param [Integer] country_code
23
+ # @param ctx
24
+ # @return [String] country code as string
25
+ def from_native(country_code, ctx)
26
+ [country_code].pack(PACK_FORMAT)
27
+ end
28
+ end
29
+ end
30
+ end
@@ -38,7 +38,7 @@ module Spotify
38
38
  # @param ctx
39
39
  # @return [String, nil] the image ID as a string, or nil
40
40
  def from_native(value, ctx)
41
- value.read_string(size) unless value.null?
41
+ value.get_bytes(0, size) unless value.null?
42
42
  end
43
43
 
44
44
  # @see NulString.reference_required?
@@ -18,7 +18,7 @@ module Spotify
18
18
  # @param ctx
19
19
  # @return [String] value, but in UTF-8 if it wasn’t already
20
20
  def to_native(value, ctx)
21
- value && value.encode('UTF-8')
21
+ value && value.encode(Encoding::UTF_8)
22
22
  end
23
23
 
24
24
  # Given an original string, assume it is in UTF-8.
@@ -28,7 +28,7 @@ module Spotify
28
28
  # @param ctx
29
29
  # @return [String] value, but with UTF-8 encoding
30
30
  def from_native(value, ctx)
31
- value && value.force_encoding('UTF-8')
31
+ value && value.force_encoding(Encoding::UTF_8)
32
32
  end
33
33
  end
34
34
  end
@@ -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.encode("UTF-8"))
19
+ value && FFI::MemoryPointer.from_string(value.to_str.encode(Encoding::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.force_encoding("UTF-8") unless value.null?
28
+ value.read_string.force_encoding(Encoding::UTF_8) unless value.null?
29
29
  end
30
30
 
31
31
  # Used by FFI::StructLayoutField to know if this field
@@ -1,65 +1,198 @@
1
1
  module Spotify
2
2
  # A generic error class for Spotify errors.
3
3
  class Error < StandardError
4
- class << self
5
- # Explain a Spotify error with a descriptive message.
6
- #
7
- # @note this method calls the API directly, since the
8
- # underlying API call is considered thread-safe.
9
- #
10
- # @param [Symbol, Integer] error
11
- # @return [String] a decriptive string of the error
12
- def explain(error)
13
- error, symbol = disambiguate(error)
4
+ end
14
5
 
15
- message = []
16
- message << "[#{symbol.to_s.upcase}]"
17
- message << Spotify::API.error_message(error)
18
- message << "(#{error})"
6
+ # @abstract Generic error class, extended by all libspotify errors.
7
+ class APIError < Spotify::Error
8
+ extend FFI::DataConverter
9
+ native_type :int
19
10
 
20
- message.join(' ')
21
- end
11
+ @@code_to_class = { 0 => nil }
22
12
 
23
- # Given a number or a symbol, find both the symbol and the error
24
- # number it represents.
25
- #
26
- # @example given an integer
27
- # Spotify::Error.disambiguate(0) # => [0, :ok]
28
- #
29
- # @example given a symbol
30
- # Spotify::Error.disambiguate(:ok) # => [0, :ok]
31
- #
32
- # @example given bogus
33
- # Spotify::Error.disambiguate(:bogus) # => [-1, nil]
13
+ class << self
14
+ # Returns an error if applicable.
34
15
  #
35
- # @param [Symbol, Fixnum] error
36
- # @return [[Fixnum, Symbol]] (error code, error symbol)
37
- def disambiguate(error)
38
- @enum ||= Spotify.enum_type(:error)
39
-
40
- if error.is_a? Symbol
41
- error = @enum[symbol = error]
42
- else
43
- symbol = @enum[error]
16
+ # @param [Integer] error
17
+ # @return [Error, nil] an error, unless error symbol was OK
18
+ def from_native(error, context)
19
+ error_class = @@code_to_class.fetch(error) do
20
+ raise ArgumentError, "unknown error code: #{error}"
44
21
  end
22
+ error_class.new if error_class
23
+ end
45
24
 
46
- if error.nil? || symbol.nil?
47
- [-1, nil]
25
+ # From an error, retrieve it's native value.
26
+ #
27
+ # @param [Error] error
28
+ # @return [Symbol]
29
+ def to_native(error, context)
30
+ if error
31
+ error.to_i
48
32
  else
49
- [error, symbol]
33
+ 0
50
34
  end
51
35
  end
52
- end
53
36
 
54
- # Overridden to allow raising errors with just an error code.
55
- #
56
- # @param [Integer, String] code_or_message spotify error code, or string message.
57
- def initialize(code_or_message = nil)
58
- if code_or_message.is_a?(Integer) or code_or_message.is_a?(Symbol)
59
- code_or_message &&= self.class.explain(code_or_message)
37
+ # @return [Integer] error code
38
+ attr_reader :to_i
39
+
40
+ private
41
+
42
+ def error_code(number)
43
+ @to_i = number
44
+ @@code_to_class[number] = self
60
45
  end
46
+ end
61
47
 
62
- super(code_or_message)
48
+ # @param [String] message only to be supplied if overridden
49
+ def initialize(message = "#{Spotify::API.error_message(self)} (#{to_i})")
50
+ super
63
51
  end
52
+
53
+ # @return (see .to_i)
54
+ def to_i
55
+ self.class.to_i
56
+ end
57
+ end
58
+
59
+ class BadAPIVersionError < APIError
60
+ error_code 1
61
+ end
62
+
63
+ class APIInitializationFailedError < APIError
64
+ error_code 2
65
+ end
66
+
67
+ class TrackNotPlayableError < APIError
68
+ error_code 3
69
+ end
70
+
71
+ class BadApplicationKeyError < APIError
72
+ error_code 5
73
+ end
74
+
75
+ class BadUsernameOrPasswordError < APIError
76
+ error_code 6
77
+ end
78
+
79
+ class UserBannedError < APIError
80
+ error_code 7
81
+ end
82
+
83
+ class UnableToContactServerError < APIError
84
+ error_code 8
85
+ end
86
+
87
+ class ClientTooOldError < APIError
88
+ error_code 9
89
+ end
90
+
91
+ class OtherPermanentError < APIError
92
+ error_code 10
93
+ end
94
+
95
+ class BadUserAgentError < APIError
96
+ error_code 11
97
+ end
98
+
99
+ class MissingCallbackError < APIError
100
+ error_code 12
101
+ end
102
+
103
+ class InvalidIndataError < APIError
104
+ error_code 13
105
+ end
106
+
107
+ class IndexOutOfRangeError < APIError
108
+ error_code 14
109
+ end
110
+
111
+ class UserNeedsPremiumError < APIError
112
+ error_code 15
113
+ end
114
+
115
+ class OtherTransientError < APIError
116
+ error_code 16
117
+ end
118
+
119
+ class IsLoadingError < APIError
120
+ error_code 17
121
+ end
122
+
123
+ class NoStreamAvailableError < APIError
124
+ error_code 18
125
+ end
126
+
127
+ class PermissionDeniedError < APIError
128
+ error_code 19
129
+ end
130
+
131
+ class InboxIsFullError < APIError
132
+ error_code 20
133
+ end
134
+
135
+ class NoCacheError < APIError
136
+ error_code 21
137
+ end
138
+
139
+ class NoSuchUserError < APIError
140
+ error_code 22
141
+ end
142
+
143
+ class NoCredentialsError < APIError
144
+ error_code 23
145
+ end
146
+
147
+ class NetworkDisabledError < APIError
148
+ error_code 24
149
+ end
150
+
151
+ class InvalidDeviceIdError < APIError
152
+ error_code 25
153
+ end
154
+
155
+ class CantOpenTraceFileError < APIError
156
+ error_code 26
157
+ end
158
+
159
+ class ApplicationBannedError < APIError
160
+ error_code 27
161
+ end
162
+
163
+ class OfflineTooManyTracksError < APIError
164
+ error_code 31
165
+ end
166
+
167
+ class OfflineDiskCacheError < APIError
168
+ error_code 32
169
+ end
170
+
171
+ class OfflineExpiredError < APIError
172
+ error_code 33
173
+ end
174
+
175
+ class OfflineNotAllowedError < APIError
176
+ error_code 34
177
+ end
178
+
179
+ class OfflineLicenseLostError < APIError
180
+ error_code 35
181
+ end
182
+
183
+ class OfflineLicenseError < APIError
184
+ error_code 36
185
+ end
186
+
187
+ class LastfmAuthError < APIError
188
+ error_code 39
189
+ end
190
+
191
+ class InvalidArgumentError < APIError
192
+ error_code 40
193
+ end
194
+
195
+ class SystemFailureError < APIError
196
+ error_code 41
64
197
  end
65
198
  end