spotify 12.5.3 → 12.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (104) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -0
  3. data/.travis.yml +20 -5
  4. data/CHANGELOG.md +29 -1
  5. data/Gemfile +5 -0
  6. data/MIT-LICENSE +21 -0
  7. data/README.markdown +75 -50
  8. data/Rakefile +1 -1
  9. data/examples/example-audio_delivery_speed.rb +48 -0
  10. data/examples/{audio-stream_example.rb → example-audio_stream.rb} +14 -29
  11. data/examples/example-console.rb +9 -0
  12. data/examples/example-listing_playlists.rb +89 -0
  13. data/examples/example-loading_object.rb +25 -0
  14. data/examples/example-random_related_artists.rb +53 -0
  15. data/examples/support.rb +106 -0
  16. data/lib/spotify.rb +36 -55
  17. data/lib/spotify/api.rb +54 -26
  18. data/lib/spotify/api/album.rb +45 -2
  19. data/lib/spotify/api/album_browse.rb +81 -3
  20. data/lib/spotify/api/artist.rb +21 -2
  21. data/lib/spotify/api/artist_browse.rb +121 -3
  22. data/lib/spotify/api/error.rb +5 -1
  23. data/lib/spotify/api/image.rb +72 -6
  24. data/lib/spotify/api/inbox.rb +33 -4
  25. data/lib/spotify/api/link.rb +117 -4
  26. data/lib/spotify/api/miscellaneous.rb +12 -0
  27. data/lib/spotify/api/playlist.rb +321 -16
  28. data/lib/spotify/api/playlist_container.rb +168 -9
  29. data/lib/spotify/api/search.rb +156 -3
  30. data/lib/spotify/api/session.rb +390 -26
  31. data/lib/spotify/api/toplist_browse.rb +74 -3
  32. data/lib/spotify/api/track.rb +134 -4
  33. data/lib/spotify/api/user.rb +18 -2
  34. data/lib/spotify/api_helpers.rb +47 -0
  35. data/lib/spotify/data_converters.rb +7 -0
  36. data/lib/spotify/{types → data_converters}/best_effort_string.rb +1 -1
  37. data/lib/spotify/{types → data_converters}/byte_string.rb +0 -0
  38. data/lib/spotify/data_converters/country_code.rb +30 -0
  39. data/lib/spotify/{types → data_converters}/image_id.rb +1 -1
  40. data/lib/spotify/{type_safety.rb → data_converters/type_safety.rb} +0 -0
  41. data/lib/spotify/{types → data_converters}/utf8_string.rb +2 -2
  42. data/lib/spotify/{types → data_converters}/utf8_string_pointer.rb +2 -2
  43. data/lib/spotify/error.rb +180 -47
  44. data/lib/spotify/managed_pointer.rb +32 -12
  45. data/lib/spotify/monkey_patches/ffi_buffer.rb +11 -0
  46. data/lib/spotify/monkey_patches/ffi_enums.rb +4 -0
  47. data/lib/spotify/monkey_patches/ffi_pointer.rb +1 -0
  48. data/lib/spotify/structs.rb +4 -0
  49. data/lib/spotify/structs/session_callbacks.rb +97 -26
  50. data/lib/spotify/structs/session_config.rb +1 -1
  51. data/lib/spotify/structs/subscribers.rb +4 -3
  52. data/lib/spotify/types.rb +104 -5
  53. data/lib/spotify/{objects → types}/album.rb +0 -0
  54. data/lib/spotify/{objects → types}/album_browse.rb +0 -0
  55. data/lib/spotify/{objects → types}/artist.rb +0 -0
  56. data/lib/spotify/{objects → types}/artist_browse.rb +0 -0
  57. data/lib/spotify/{objects → types}/image.rb +0 -0
  58. data/lib/spotify/{objects → types}/inbox.rb +0 -0
  59. data/lib/spotify/{objects → types}/link.rb +0 -0
  60. data/lib/spotify/{objects → types}/playlist.rb +0 -0
  61. data/lib/spotify/{objects → types}/playlist_container.rb +0 -0
  62. data/lib/spotify/{objects → types}/search.rb +0 -0
  63. data/lib/spotify/{objects → types}/session.rb +0 -0
  64. data/lib/spotify/{objects → types}/toplist_browse.rb +0 -0
  65. data/lib/spotify/{objects → types}/track.rb +0 -0
  66. data/lib/spotify/{objects → types}/user.rb +0 -0
  67. data/lib/spotify/util.rb +38 -35
  68. data/lib/spotify/version.rb +1 -1
  69. data/spec/spec_helper.rb +24 -13
  70. data/spec/spotify/api/image_spec.rb +32 -0
  71. data/spec/spotify/api/inbox_spec.rb +34 -0
  72. data/spec/spotify/api/link_spec.rb +40 -0
  73. data/spec/spotify/api/playlist_spec.rb +99 -0
  74. data/spec/spotify/api/playlistcontainer_spec.rb +82 -0
  75. data/spec/spotify/api/session_spec.rb +97 -0
  76. data/spec/spotify/api/track_spec.rb +29 -0
  77. data/spec/spotify/api_error_spec.rb +55 -0
  78. data/spec/spotify/api_spec.rb +17 -11
  79. data/spec/spotify/{types → data_converters}/best_effort_string_spec.rb +4 -4
  80. data/spec/spotify/{types → data_converters}/byte_string_spec.rb +0 -0
  81. data/spec/spotify/data_converters/country_code_spec.rb +16 -0
  82. data/spec/spotify/{types → data_converters}/image_id_spec.rb +1 -1
  83. data/spec/spotify/{type_safety_spec.rb → data_converters/type_safety_spec.rb} +0 -0
  84. data/spec/spotify/{types → data_converters}/utf8_string_pointer_spec.rb +0 -0
  85. data/spec/spotify/{types → data_converters}/utf8_string_spec.rb +1 -1
  86. data/spec/spotify/{api/functions_spec.rb → functions_spec.rb} +2 -0
  87. data/spec/spotify/managed_pointer_spec.rb +13 -13
  88. data/spec/spotify/structs/subscribers_spec.rb +5 -3
  89. data/spec/spotify/{defines_spec.rb → types_spec.rb} +16 -3
  90. data/spec/spotify/util_spec.rb +24 -0
  91. data/spec/spotify_spec.rb +74 -0
  92. data/spec/support/spotify_util.rb +6 -2
  93. data/spec/support/spy_output.rb +16 -0
  94. data/spotify.gemspec +4 -2
  95. metadata +111 -71
  96. data/examples/README.md +0 -15
  97. data/examples/console_example.rb +0 -22
  98. data/examples/example_support.rb +0 -66
  99. data/examples/loading-object_example.rb +0 -43
  100. data/examples/logging-in_example.rb +0 -58
  101. data/lib/spotify/defines.rb +0 -109
  102. data/lib/spotify/objects.rb +0 -17
  103. data/spec/spotify/enums_spec.rb +0 -9
  104. data/spec/spotify/spotify_spec.rb +0 -69
@@ -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