hallon 0.4.0 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitmodules +3 -0
- data/.travis.yml +2 -0
- data/CHANGELOG +30 -6
- data/README.markdown +7 -7
- data/Rakefile +70 -16
- data/examples/logging_in.rb +3 -3
- data/examples/printing_link_information.rb +1 -1
- data/examples/show_published_playlists_of_user.rb +92 -0
- data/hallon.gemspec +7 -4
- data/lib/hallon.rb +16 -4
- data/lib/hallon/album.rb +16 -6
- data/lib/hallon/album_browse.rb +78 -0
- data/lib/hallon/artist.rb +59 -0
- data/lib/hallon/artist_browse.rb +89 -0
- data/lib/hallon/base.rb +7 -0
- data/lib/hallon/enumerator.rb +64 -0
- data/lib/hallon/error.rb +8 -6
- data/lib/hallon/ext/spotify.rb +3 -3
- data/lib/hallon/image.rb +25 -12
- data/lib/hallon/link.rb +4 -4
- data/lib/hallon/linkable.rb +4 -2
- data/lib/hallon/observable.rb +1 -4
- data/lib/hallon/player.rb +130 -0
- data/lib/hallon/search.rb +128 -0
- data/lib/hallon/session.rb +226 -25
- data/lib/hallon/toplist.rb +83 -0
- data/lib/hallon/track.rb +62 -7
- data/lib/hallon/user.rb +6 -6
- data/lib/hallon/version.rb +1 -1
- data/spec/hallon/album_browse_spec.rb +20 -0
- data/spec/hallon/album_spec.rb +12 -7
- data/spec/hallon/artist_browse_spec.rb +29 -0
- data/spec/hallon/artist_spec.rb +32 -0
- data/spec/hallon/enumerator_spec.rb +106 -0
- data/spec/hallon/error_spec.rb +10 -0
- data/spec/hallon/hallon_spec.rb +5 -1
- data/spec/hallon/image_spec.rb +39 -25
- data/spec/hallon/linkable_spec.rb +12 -4
- data/spec/hallon/observable_spec.rb +5 -0
- data/spec/hallon/player_spec.rb +73 -0
- data/spec/hallon/search_spec.rb +80 -0
- data/spec/hallon/session_spec.rb +187 -6
- data/spec/hallon/toplist_spec.rb +40 -0
- data/spec/hallon/track_spec.rb +43 -8
- data/spec/mockspotify.rb +47 -0
- data/spec/mockspotify/.gitignore +5 -0
- data/spec/mockspotify/extconf.rb +5 -0
- data/spec/mockspotify/mockspotify_spec.rb +41 -0
- data/spec/spec_helper.rb +20 -0
- data/spec/support/common_objects.rb +84 -7
- metadata +72 -20
- data/lib/hallon/ext/object.rb +0 -16
@@ -0,0 +1,59 @@
|
|
1
|
+
module Hallon
|
2
|
+
# Artists in Hallon are the people behind the songs. Methods
|
3
|
+
# are defined for retrieving their names and loaded status.
|
4
|
+
#
|
5
|
+
# To retrieve more information about an artist, you can browse
|
6
|
+
# it. This will give access to more detailed data such as bio,
|
7
|
+
# portraits and more. Hallon does not support this as of yet,
|
8
|
+
# but you can use the underlying Spotify API for this, just like
|
9
|
+
# we have for {Album}s.
|
10
|
+
#
|
11
|
+
# Both Albums and Tracks can have more than one artist.
|
12
|
+
#
|
13
|
+
# @see http://developer.spotify.com/en/libspotify/docs/group__artist.html
|
14
|
+
class Artist < Base
|
15
|
+
extend Linkable
|
16
|
+
|
17
|
+
from_link :as_artist
|
18
|
+
to_link :from_artist
|
19
|
+
|
20
|
+
# Construct an artist given a link.
|
21
|
+
#
|
22
|
+
# @param [String, Link, FFI::Pointer] link
|
23
|
+
def initialize(link)
|
24
|
+
@pointer = Spotify::Pointer.new from_link(link), :artist, true
|
25
|
+
end
|
26
|
+
|
27
|
+
# Retrieve Artist name. Empty string if Artist is not loaded.
|
28
|
+
#
|
29
|
+
# @return [String]
|
30
|
+
def name
|
31
|
+
Spotify.artist_name(@pointer)
|
32
|
+
end
|
33
|
+
|
34
|
+
# True if the Artist is loaded.
|
35
|
+
#
|
36
|
+
# @return [Boolean]
|
37
|
+
def loaded?
|
38
|
+
Spotify.artist_is_loaded(@pointer)
|
39
|
+
end
|
40
|
+
|
41
|
+
# @param [Boolean] as_image true if you want it as an Image
|
42
|
+
# @return [Image, Link, nil] artist portrait, or the link to it, or nil
|
43
|
+
def portrait(as_image = true)
|
44
|
+
portrait = Spotify.link_create_from_artist_portrait(@pointer)
|
45
|
+
unless portrait.null?
|
46
|
+
klass = as_image ? Image : Link
|
47
|
+
klass.new(portrait)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# Browse the Artist, giving you the ability to explore its’
|
52
|
+
# portraits, biography and more.
|
53
|
+
#
|
54
|
+
# @return [ArtistBrowse] an artist browsing object
|
55
|
+
def browse
|
56
|
+
ArtistBrowse.new(pointer)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
module Hallon
|
2
|
+
# ArtistBrowse is like AlbumBrowse, only that it’s for {Track}s.
|
3
|
+
#
|
4
|
+
# When it loads, it triggers the load callback on itself, that
|
5
|
+
# can be utilized by giving {#on}(:load) a block to execute.
|
6
|
+
#
|
7
|
+
# @example
|
8
|
+
# browse = artist.browse # artist is a Hallon::Artist
|
9
|
+
# browse.on(:load) do
|
10
|
+
# puts "#{browse.artist.name} browser has been loaded!"
|
11
|
+
# end
|
12
|
+
# session.wait_for { browse.loaded? }
|
13
|
+
#
|
14
|
+
# @see Artist
|
15
|
+
# @see http://developer.spotify.com/en/libspotify/docs/group__artistbrowse.html
|
16
|
+
class ArtistBrowse < Base
|
17
|
+
include Observable
|
18
|
+
|
19
|
+
# Creates an ArtistBrowse instance from an Artist or an Artist pointer.
|
20
|
+
#
|
21
|
+
# @note Use {Artist#browse} to browse an Artist.
|
22
|
+
# @param [Artist, FFI::Pointer] artist
|
23
|
+
def initialize(artist)
|
24
|
+
artist = artist.pointer if artist.respond_to?(:pointer)
|
25
|
+
@callback = proc { trigger(:load) }
|
26
|
+
|
27
|
+
artistbrowse = Spotify.artistbrowse_create(session.pointer, artist, @callback, nil)
|
28
|
+
@pointer = Spotify::Pointer.new(artistbrowse, :artistbrowse, false)
|
29
|
+
end
|
30
|
+
|
31
|
+
# @return [Boolean] true if the album browser is loaded
|
32
|
+
def loaded?
|
33
|
+
Spotify.artistbrowse_is_loaded(@pointer)
|
34
|
+
end
|
35
|
+
|
36
|
+
# @see Error
|
37
|
+
# @return [Symbol] artist browser error status
|
38
|
+
def error
|
39
|
+
Spotify.artistbrowse_error(@pointer)
|
40
|
+
end
|
41
|
+
|
42
|
+
# @return [Artist, nil] artist this browser is browsing
|
43
|
+
def artist
|
44
|
+
artist = Spotify.artistbrowse_artist(@pointer)
|
45
|
+
Artist.new(artist) unless artist.null?
|
46
|
+
end
|
47
|
+
|
48
|
+
# @return [String] artist biography
|
49
|
+
def biography
|
50
|
+
Spotify.artistbrowse_biography(@pointer)
|
51
|
+
end
|
52
|
+
|
53
|
+
# @return [Enumerator<Image>] artist portraits
|
54
|
+
def portraits
|
55
|
+
size = Spotify.artistbrowse_num_portraits(@pointer)
|
56
|
+
Enumerator.new(size) do |i|
|
57
|
+
id = Spotify.artistbrowse_portrait(@pointer, i).read_string(20)
|
58
|
+
Image.new(id)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# @return [Enumerator<Track>] artist authored tracks
|
63
|
+
def tracks
|
64
|
+
size = Spotify.artistbrowse_num_tracks(@pointer)
|
65
|
+
Enumerator.new(size) do |i|
|
66
|
+
track = Spotify.artistbrowse_track(@pointer, i)
|
67
|
+
Track.new(track)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# @return [Enumerator<Album>] artist authored albums
|
72
|
+
def albums
|
73
|
+
size = Spotify.artistbrowse_num_albums(@pointer)
|
74
|
+
Enumerator.new(size) do |i|
|
75
|
+
album = Spotify.artistbrowse_album(@pointer, i)
|
76
|
+
Album.new(album)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
# @return [Enumartor<Artist>] similar artists to this artist
|
81
|
+
def similar_artists
|
82
|
+
size = Spotify.artistbrowse_num_similar_artists(@pointer)
|
83
|
+
Enumerator.new(size) do |i|
|
84
|
+
artist = Spotify.artistbrowse_similar_artist(@pointer, i)
|
85
|
+
Artist.new(artist)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
data/lib/hallon/base.rb
CHANGED
@@ -0,0 +1,64 @@
|
|
1
|
+
module Hallon
|
2
|
+
# Hallon::Enumerator is like a lazy Array.
|
3
|
+
#
|
4
|
+
# It provides methods from Enumerable to enumerate through its’ contents,
|
5
|
+
# size information and Array access methods. It’s used throughout Hallon
|
6
|
+
# for collections of items such as artist tracks, albums and so on.
|
7
|
+
class Enumerator
|
8
|
+
include Enumerable
|
9
|
+
|
10
|
+
# @return [Integer] number of items this enumerator can yield
|
11
|
+
attr_reader :size
|
12
|
+
|
13
|
+
# Construct an enumerator of `size` elements.
|
14
|
+
#
|
15
|
+
# @param [Integer] size
|
16
|
+
# @yield to the given block when an item is requested (through #each, #[] etc)
|
17
|
+
# @yieldparam [Integer] index item to retrieve
|
18
|
+
def initialize(size, &yielder)
|
19
|
+
@size = size
|
20
|
+
@items = Array.new(size) do |i|
|
21
|
+
lambda { yielder[i] }
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# Yield each item out of the enumerator.
|
26
|
+
#
|
27
|
+
# @yield obj
|
28
|
+
# @return [Enumerator]
|
29
|
+
def each
|
30
|
+
tap do
|
31
|
+
size.times { |i| yield(self[i]) }
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# @overload [](index)
|
36
|
+
# @return [Object, nil]
|
37
|
+
#
|
38
|
+
# @overload [](start, length)
|
39
|
+
# @return [Array, nil]
|
40
|
+
#
|
41
|
+
# @overload [](range)
|
42
|
+
# @return [Array, nil]
|
43
|
+
#
|
44
|
+
# Works exactly the same as Array#[], including the special cases.
|
45
|
+
#
|
46
|
+
# @see http://rdoc.info/stdlib/core/1.9.2/Array:[]
|
47
|
+
def [](*args)
|
48
|
+
result = @items[*args]
|
49
|
+
|
50
|
+
if result.nil?
|
51
|
+
nil
|
52
|
+
elsif result.respond_to?(:map)
|
53
|
+
result.map(&:call)
|
54
|
+
else
|
55
|
+
result.call
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# @return [String] String representation of the Enumerator.
|
60
|
+
def to_s
|
61
|
+
"<#{self.class.name}:0x#{object_id.to_s(16)} @size=#{size}>"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
data/lib/hallon/error.rb
CHANGED
@@ -9,7 +9,7 @@ module Hallon
|
|
9
9
|
#
|
10
10
|
# @return [Hash<Symbol, Integer>]
|
11
11
|
def table
|
12
|
-
Spotify
|
12
|
+
Spotify.enum_type(:error).to_hash
|
13
13
|
end
|
14
14
|
|
15
15
|
# Given a number or a symbol, find both the symbol and the error
|
@@ -18,7 +18,7 @@ module Hallon
|
|
18
18
|
# @param [Symbol, Fixnum] error
|
19
19
|
# @return [[Fixnum, Symbol]] (error code, error symbol)
|
20
20
|
def disambiguate(error)
|
21
|
-
@enum ||= Spotify
|
21
|
+
@enum ||= Spotify.enum_type(:error)
|
22
22
|
|
23
23
|
if error.is_a? Symbol
|
24
24
|
error = @enum[symbol = error]
|
@@ -38,15 +38,17 @@ module Hallon
|
|
38
38
|
# @param [Fixnum, Symbol]
|
39
39
|
# @return [String]
|
40
40
|
def explain(error)
|
41
|
-
Spotify
|
41
|
+
Spotify.error_message disambiguate(error)[0]
|
42
42
|
end
|
43
43
|
|
44
|
-
# Raise an {Error} with the given errno, unless it is `0` or `:ok`.
|
44
|
+
# Raise an {Error} with the given errno, unless it is `nil`, `:timeout`, `0` or `:ok`.
|
45
45
|
#
|
46
46
|
# @param [Fixnum, Symbol] error
|
47
47
|
# @return [nil]
|
48
|
-
def maybe_raise(
|
49
|
-
|
48
|
+
def maybe_raise(x)
|
49
|
+
return nil if [nil, :timeout].include?(x)
|
50
|
+
|
51
|
+
error, symbol = disambiguate(x)
|
50
52
|
return symbol if symbol == :ok
|
51
53
|
|
52
54
|
message = []
|
data/lib/hallon/ext/spotify.rb
CHANGED
@@ -15,11 +15,11 @@ module Spotify
|
|
15
15
|
#
|
16
16
|
# @param [FFI::Pointer] ptr
|
17
17
|
# @param [Symbol] type session, link, etc
|
18
|
-
# @param [Boolean
|
18
|
+
# @param [Boolean] add_ref increase reference count
|
19
19
|
# @return [FFI::AutoPointer]
|
20
20
|
def initialize(ptr, type, add_ref = false)
|
21
21
|
super ptr, releaser_for(@type = type)
|
22
|
-
Spotify
|
22
|
+
Spotify.send(:"#{type}_add_ref", ptr) if add_ref
|
23
23
|
end
|
24
24
|
|
25
25
|
# Create a proc that will accept a pointer of a given type and
|
@@ -31,7 +31,7 @@ module Spotify
|
|
31
31
|
lambda do |ptr|
|
32
32
|
unless ptr.null?
|
33
33
|
$stdout.puts "Spotify::#{type}_release(#{ptr})" if $DEBUG
|
34
|
-
Spotify
|
34
|
+
Spotify.send(:"#{type}_release", ptr)
|
35
35
|
end
|
36
36
|
end
|
37
37
|
end
|
data/lib/hallon/image.rb
CHANGED
@@ -7,21 +7,21 @@ module Hallon
|
|
7
7
|
extend Linkable
|
8
8
|
|
9
9
|
from_link :as_image do |link, session|
|
10
|
-
Spotify
|
10
|
+
Spotify.image_create_from_link(session, link)
|
11
11
|
end
|
12
12
|
|
13
13
|
to_link :from_image
|
14
14
|
|
15
15
|
# Image triggers `:load` when loaded
|
16
|
-
include
|
16
|
+
include Observable
|
17
17
|
|
18
18
|
# Create a new instance of an Image.
|
19
19
|
#
|
20
20
|
# @param [String, Link, FFI::Pointer] link link or image id
|
21
21
|
# @param [Hallon::Session] session
|
22
|
-
def initialize(link
|
22
|
+
def initialize(link)
|
23
23
|
if link.is_a?(String)
|
24
|
-
link = to_id($1) if link =~ %r|image[:/](
|
24
|
+
link = to_id($1) if link =~ %r|image[:/]([a-fA-F0-9]{40})|
|
25
25
|
|
26
26
|
FFI::MemoryPointer.new(:char, 20) do |ptr|
|
27
27
|
ptr.write_bytes link
|
@@ -33,11 +33,14 @@ module Hallon
|
|
33
33
|
|
34
34
|
@pointer = Spotify::Pointer.new link, :image
|
35
35
|
|
36
|
-
@callback = proc { trigger
|
37
|
-
Spotify
|
36
|
+
@callback = proc { trigger :load }
|
37
|
+
Spotify.image_add_load_callback(@pointer, @callback, nil)
|
38
38
|
|
39
39
|
# TODO: remove load_callback when @pointer is released
|
40
|
-
#
|
40
|
+
# NOTE: on(:load) will trigger while load callback is still executing,
|
41
|
+
# and removing the load callback from within the load callback
|
42
|
+
# does not make libspotify happy, and thus segfaults D:
|
43
|
+
#
|
41
44
|
# on(:load) { Spotify::image_remove_load_callback(@pointer, @callback, nil) }
|
42
45
|
end
|
43
46
|
|
@@ -45,21 +48,21 @@ module Hallon
|
|
45
48
|
#
|
46
49
|
# @return [Boolean]
|
47
50
|
def loaded?
|
48
|
-
Spotify
|
51
|
+
Spotify.image_is_loaded(@pointer)
|
49
52
|
end
|
50
53
|
|
51
54
|
# Retrieve the current error status.
|
52
55
|
#
|
53
56
|
# @return [Symbol] error
|
54
57
|
def status
|
55
|
-
Spotify
|
58
|
+
Spotify.image_error(@pointer)
|
56
59
|
end
|
57
60
|
|
58
61
|
# Retrieve image format.
|
59
62
|
#
|
60
63
|
# @return [Symbol] `:jpeg` or `:unknown`
|
61
64
|
def format
|
62
|
-
Spotify
|
65
|
+
Spotify.image_format(@pointer)
|
63
66
|
end
|
64
67
|
|
65
68
|
# Retrieve image ID as a string.
|
@@ -67,7 +70,7 @@ module Hallon
|
|
67
70
|
# @param [Boolean] raw true if you want the image id as a hexadecimal string
|
68
71
|
# @return [String]
|
69
72
|
def id(raw = false)
|
70
|
-
id = Spotify
|
73
|
+
id = Spotify.image_image_id(@pointer).read_string(20)
|
71
74
|
raw ? id : to_hex(id)
|
72
75
|
end
|
73
76
|
|
@@ -76,11 +79,21 @@ module Hallon
|
|
76
79
|
# @return [String]
|
77
80
|
def data
|
78
81
|
FFI::MemoryPointer.new(:size_t) do |size|
|
79
|
-
data = Spotify
|
82
|
+
data = Spotify.image_data(@pointer, size)
|
80
83
|
return data.read_bytes(size.read_size_t)
|
81
84
|
end
|
82
85
|
end
|
83
86
|
|
87
|
+
# True if the images both have the same ID, or if their
|
88
|
+
# pointers are the same.
|
89
|
+
#
|
90
|
+
# @see Base#==
|
91
|
+
def ==(other)
|
92
|
+
super or id(true) == other.id(true)
|
93
|
+
rescue NoMethodError, ArgumentError
|
94
|
+
false
|
95
|
+
end
|
96
|
+
|
84
97
|
protected
|
85
98
|
# @param [String]
|
86
99
|
# @return [String]
|
data/lib/hallon/link.rb
CHANGED
@@ -28,7 +28,7 @@ module Hallon
|
|
28
28
|
# @raise [ArgumentError] link could not be parsed
|
29
29
|
def initialize(uri)
|
30
30
|
if (link = uri).respond_to? :to_str
|
31
|
-
link = Spotify
|
31
|
+
link = Spotify.link_create_from_string(link.to_str)
|
32
32
|
end
|
33
33
|
|
34
34
|
@pointer = Spotify::Pointer.new(link, :link)
|
@@ -40,14 +40,14 @@ module Hallon
|
|
40
40
|
#
|
41
41
|
# @return [Symbol]
|
42
42
|
def type
|
43
|
-
Spotify
|
43
|
+
Spotify.link_type(@pointer)
|
44
44
|
end
|
45
45
|
|
46
46
|
# Spotify URI length.
|
47
47
|
#
|
48
48
|
# @return [Fixnum]
|
49
49
|
def length
|
50
|
-
Spotify
|
50
|
+
Spotify.link_as_string(@pointer, nil, 0)
|
51
51
|
end
|
52
52
|
|
53
53
|
# Get the Spotify URI this Link represents.
|
@@ -57,7 +57,7 @@ module Hallon
|
|
57
57
|
# @return [String]
|
58
58
|
def to_str(length = length)
|
59
59
|
FFI::Buffer.alloc_out(length + 1) do |b|
|
60
|
-
Spotify
|
60
|
+
Spotify.link_as_string(@pointer, b, b.size)
|
61
61
|
return b.get_string(0)
|
62
62
|
end
|
63
63
|
end
|
data/lib/hallon/linkable.rb
CHANGED
@@ -35,9 +35,11 @@ module Hallon
|
|
35
35
|
type = as_object.to_s[/^(as_)?([^_]+)/, 2].to_sym
|
36
36
|
|
37
37
|
define_method(:from_link) do |link, *args|
|
38
|
-
if link.is_a? FFI::Pointer then link else
|
38
|
+
link = if link.is_a? FFI::Pointer then link else
|
39
39
|
block.call Link.new(link).pointer(type), *args
|
40
40
|
end
|
41
|
+
|
42
|
+
link.tap { raise Hallon::Error, "invalid link" if link.null? }
|
41
43
|
end
|
42
44
|
end
|
43
45
|
|
@@ -51,7 +53,7 @@ module Hallon
|
|
51
53
|
def to_link(cmethod)
|
52
54
|
define_method(:to_link) do |*args|
|
53
55
|
link = Spotify.__send__(:"link_create_#{cmethod}", @pointer, *args)
|
54
|
-
|
56
|
+
Link.new(link)
|
55
57
|
end
|
56
58
|
end
|
57
59
|
end
|
data/lib/hallon/observable.rb
CHANGED
@@ -4,9 +4,6 @@ module Hallon
|
|
4
4
|
#
|
5
5
|
# @private
|
6
6
|
module Observable
|
7
|
-
# Required for maintaining thread-safety around #handlers
|
8
|
-
include Hallon::Synchronizable
|
9
|
-
|
10
7
|
# Defines a handler for the given event.
|
11
8
|
#
|
12
9
|
# @example defining a handler and triggering it
|
@@ -33,7 +30,7 @@ module Hallon
|
|
33
30
|
def on(*events, &block)
|
34
31
|
raise ArgumentError, "no block given" unless block
|
35
32
|
wrap = events.length > 1
|
36
|
-
events.each do |event|
|
33
|
+
events.map(&:to_sym).each do |event|
|
37
34
|
block = proc { |*args| yield(event, *args) } if wrap
|
38
35
|
__handlers[event] = [] unless __handlers.has_key?(event)
|
39
36
|
__handlers[event] << block
|