hallon 0.4.0 → 0.8.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.
- 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,83 @@
|
|
1
|
+
module Hallon
|
2
|
+
# Toplists are what they sound like. They’re collections of
|
3
|
+
# artists, albums or tracks popular in a certain area either
|
4
|
+
# by country, user or everywhere.
|
5
|
+
#
|
6
|
+
# @see http://developer.spotify.com/en/libspotify/docs/group__toplist.html
|
7
|
+
class Toplist < Base
|
8
|
+
include Observable
|
9
|
+
|
10
|
+
# Create a Toplist browsing object.
|
11
|
+
#
|
12
|
+
# @overload initialize(type, username)
|
13
|
+
# @overload initialize(type, country)
|
14
|
+
# @overload initialize(type)
|
15
|
+
#
|
16
|
+
# @param [Symbol] type one of :artists, :albums or :tracks
|
17
|
+
# @param [String, Symbol, nil] region username, 2-letter country code or nil
|
18
|
+
def initialize(type, region = nil)
|
19
|
+
case region
|
20
|
+
when String
|
21
|
+
user = region
|
22
|
+
region = :user
|
23
|
+
when NilClass
|
24
|
+
region = :anywhere
|
25
|
+
when Symbol
|
26
|
+
region = to_country(region)
|
27
|
+
end
|
28
|
+
|
29
|
+
@callback = proc { trigger(:load) }
|
30
|
+
pointer = Spotify.toplistbrowse_create(session.pointer, type, region, user, @callback, nil)
|
31
|
+
@pointer = Spotify::Pointer.new(pointer, :toplistbrowse, false)
|
32
|
+
end
|
33
|
+
|
34
|
+
# @return [Boolean] true if the toplist is loaded
|
35
|
+
def loaded?
|
36
|
+
Spotify.toplistbrowse_is_loaded(@pointer)
|
37
|
+
end
|
38
|
+
|
39
|
+
# @return [Symbol] toplist error status
|
40
|
+
def error
|
41
|
+
Spotify.toplistbrowse_error(@pointer)
|
42
|
+
end
|
43
|
+
|
44
|
+
# @return [Enumerator<Artist>]
|
45
|
+
def artists
|
46
|
+
size = Spotify.toplistbrowse_num_artists(@pointer)
|
47
|
+
Enumerator.new(size) do |i|
|
48
|
+
artist = Spotify.toplistbrowse_artist(@pointer, i)
|
49
|
+
Artist.new(artist)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# @return [Enumerator<Album>]
|
54
|
+
def albums
|
55
|
+
size = Spotify.toplistbrowse_num_albums(@pointer)
|
56
|
+
Enumerator.new(size) do |i|
|
57
|
+
album = Spotify.toplistbrowse_album(@pointer, i)
|
58
|
+
Artist.new(album)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# @return [Enumerator<Track>]
|
63
|
+
def tracks
|
64
|
+
size = Spotify.toplistbrowse_num_tracks(@pointer)
|
65
|
+
Enumerator.new(size) do |i|
|
66
|
+
track = Spotify.toplistbrowse_track(@pointer, i)
|
67
|
+
Artist.new(track)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
# Convert a given two-character region to a Spotify
|
73
|
+
# compliant region (encoded in a 16bit integer).
|
74
|
+
#
|
75
|
+
# @param [#to_s]
|
76
|
+
# @return [Integer]
|
77
|
+
def to_country(region)
|
78
|
+
code = region.to_s.upcase
|
79
|
+
high, low = code.bytes.take(2)
|
80
|
+
(high << 8) | low
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
data/lib/hallon/track.rb
CHANGED
@@ -104,14 +104,69 @@ module Hallon
|
|
104
104
|
# @return [Hallon::Album]
|
105
105
|
def album
|
106
106
|
album = Spotify.track_album(@pointer)
|
107
|
-
|
107
|
+
Album.new(album) unless album.null?
|
108
108
|
end
|
109
109
|
|
110
|
-
#
|
111
|
-
#
|
112
|
-
#
|
113
|
-
#
|
114
|
-
#
|
115
|
-
|
110
|
+
# Artist who performed this Track.
|
111
|
+
#
|
112
|
+
# @note There may be more than one artist, see {#artists} for retrieving them all!
|
113
|
+
# @see #artists
|
114
|
+
# @return [Hallon::Artist, nil]
|
115
|
+
def artist
|
116
|
+
artists.first
|
117
|
+
end
|
118
|
+
|
119
|
+
# All {Artist}s who performed this Track.
|
120
|
+
#
|
121
|
+
# @note Track must be loaded, or you’ll get zero artists.
|
122
|
+
# @return [Hallon::Enumerator<Artist>]
|
123
|
+
def artists
|
124
|
+
size = Spotify.track_num_artists(@pointer)
|
125
|
+
Enumerator.new(size) do |i|
|
126
|
+
artist = Spotify.track_artist(@pointer, i)
|
127
|
+
Artist.new(artist) unless artist.null?
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
# True if the Track is available.
|
132
|
+
#
|
133
|
+
# @note This’ll always return false unless the track is loaded.
|
134
|
+
# @return [Boolean]
|
135
|
+
def available?
|
136
|
+
Spotify.track_is_available(session.pointer, @pointer)
|
137
|
+
end
|
138
|
+
|
139
|
+
# True if the Track is a local track.
|
140
|
+
#
|
141
|
+
# @note This’ll always return false unless the track is loaded.
|
142
|
+
# @return [Boolean]
|
143
|
+
def local?
|
144
|
+
Spotify.track_is_local(session.pointer, @pointer)
|
145
|
+
end
|
146
|
+
|
147
|
+
# True if the Track is autolinked.
|
148
|
+
#
|
149
|
+
# @note This’ll always return false unless the track is loaded.
|
150
|
+
# @return [Boolean]
|
151
|
+
def autolinked?
|
152
|
+
Spotify.track_is_autolinked(session.pointer, @pointer)
|
153
|
+
end
|
154
|
+
|
155
|
+
# True if the track is starred.
|
156
|
+
#
|
157
|
+
# @note This’ll always return false unless the track is loaded.
|
158
|
+
# @return [Boolean]
|
159
|
+
def starred?
|
160
|
+
Spotify.track_is_starred(session.pointer, @pointer)
|
161
|
+
end
|
162
|
+
|
163
|
+
# Set {#starred?} status of current track.
|
164
|
+
#
|
165
|
+
# @note It’ll set the starred status for the current Session.instance.
|
166
|
+
# @param [Boolean] starred
|
167
|
+
# @return [Boolean]
|
168
|
+
def starred=(starred)
|
169
|
+
starred ? session.star(self) : session.unstar(self)
|
170
|
+
end
|
116
171
|
end
|
117
172
|
end
|
data/lib/hallon/user.rb
CHANGED
@@ -17,7 +17,7 @@ module Hallon
|
|
17
17
|
# @param [String, Hallon::Link, FFI::Pointer] link
|
18
18
|
# @return [FFI::Pointer]
|
19
19
|
from_link :profile do |link|
|
20
|
-
Spotify
|
20
|
+
Spotify.link_as_user(link)
|
21
21
|
end
|
22
22
|
|
23
23
|
# @macro [attach] to_link
|
@@ -37,7 +37,7 @@ module Hallon
|
|
37
37
|
|
38
38
|
# @return [Boolean] true if the user is loaded
|
39
39
|
def loaded?
|
40
|
-
Spotify
|
40
|
+
Spotify.user_is_loaded(@pointer)
|
41
41
|
end
|
42
42
|
|
43
43
|
# Retrieve the name of the current user.
|
@@ -48,11 +48,11 @@ module Hallon
|
|
48
48
|
def name(type = :canonical)
|
49
49
|
case type
|
50
50
|
when :display
|
51
|
-
Spotify
|
51
|
+
Spotify.user_display_name(@pointer)
|
52
52
|
when :full
|
53
|
-
Spotify
|
53
|
+
Spotify.user_full_name(@pointer)
|
54
54
|
when :canonical
|
55
|
-
Spotify
|
55
|
+
Spotify.user_canonical_name(@pointer)
|
56
56
|
else
|
57
57
|
raise ArgumentError, "expected type to be :display, :full or :canonical, but was #{type}"
|
58
58
|
end.to_s
|
@@ -62,7 +62,7 @@ module Hallon
|
|
62
62
|
#
|
63
63
|
# @return [String]
|
64
64
|
def picture
|
65
|
-
Spotify
|
65
|
+
Spotify.user_picture(@pointer).to_s
|
66
66
|
end
|
67
67
|
end
|
68
68
|
end
|
data/lib/hallon/version.rb
CHANGED
@@ -0,0 +1,20 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
describe Hallon::AlbumBrowse do
|
3
|
+
subject do
|
4
|
+
mock_session do
|
5
|
+
album = Hallon::Album.new(mock_album)
|
6
|
+
Spotify.should_receive(:albumbrowse_create).and_return(mock_albumbrowse)
|
7
|
+
Hallon::AlbumBrowse.new(album)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
it { should be_loaded }
|
12
|
+
its(:error) { should eq :ok }
|
13
|
+
its(:album) { should eq Hallon::Album.new(mock_album) }
|
14
|
+
its(:artist) { should eq Hallon::Artist.new(mock_artist) }
|
15
|
+
its('copyrights.size') { should eq 2 }
|
16
|
+
its('copyrights.to_a') { should eq %w[Kim Elin] }
|
17
|
+
its('tracks.size') { should eq 2 }
|
18
|
+
its('tracks.to_a') { should eq [mock_track, mock_track_two].map{ |p| Hallon::Track.new(p) } }
|
19
|
+
its(:review) { should eq "This album is AWESOME" }
|
20
|
+
end
|
data/spec/hallon/album_spec.rb
CHANGED
@@ -6,6 +6,13 @@ describe Hallon::Album do
|
|
6
6
|
its(:year) { should be 2004 }
|
7
7
|
its(:type) { should be :single }
|
8
8
|
|
9
|
+
its(:browse) do
|
10
|
+
mock_session do
|
11
|
+
Spotify.should_receive(:albumbrowse_create).exactly(2).times.and_return(mock_albumbrowse)
|
12
|
+
should eq Hallon::AlbumBrowse.new(mock_album)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
9
16
|
it { should be_available }
|
10
17
|
it { should be_loaded }
|
11
18
|
|
@@ -15,12 +22,12 @@ describe Hallon::Album do
|
|
15
22
|
subject.artist.should be_nil
|
16
23
|
end
|
17
24
|
|
18
|
-
it "should be an artist if it exists"
|
25
|
+
it "should be an artist if it exists" do
|
26
|
+
subject.artist.should be_a Hallon::Artist
|
27
|
+
end
|
19
28
|
end
|
20
29
|
|
21
30
|
describe "cover" do
|
22
|
-
before { Hallon::Session.should_receive(:instance).and_return(session) }
|
23
|
-
|
24
31
|
it "should be nil if there is no image" do
|
25
32
|
Spotify.should_receive(:album_cover).and_return(null_pointer)
|
26
33
|
subject.cover.should be_nil
|
@@ -31,14 +38,12 @@ describe Hallon::Album do
|
|
31
38
|
ptr.write_string(mock_image_id)
|
32
39
|
|
33
40
|
Spotify.should_receive(:album_cover).and_return(ptr)
|
34
|
-
subject.cover.id.should eq mock_image_hex
|
41
|
+
mock_session { subject.cover.id.should eq mock_image_hex }
|
35
42
|
end
|
36
43
|
end
|
37
44
|
end
|
38
45
|
|
39
46
|
describe ".types" do
|
40
|
-
|
41
|
-
Hallon::Album.types.should_not be_empty
|
42
|
-
end
|
47
|
+
specify { Hallon::Album.types.should_not be_empty }
|
43
48
|
end
|
44
49
|
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
describe Hallon::ArtistBrowse do
|
3
|
+
subject do
|
4
|
+
mock_session do
|
5
|
+
artist = Hallon::Artist.new(mock_artist)
|
6
|
+
Spotify.should_receive(:artistbrowse_create).and_return(mock_artistbrowse)
|
7
|
+
Hallon::ArtistBrowse.new(artist)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
it { should be_loaded }
|
12
|
+
its(:error) { should eq :ok }
|
13
|
+
its(:artist) { should eq Hallon::Artist.new(mock_artist) }
|
14
|
+
|
15
|
+
its('portraits.size') { should eq 2 }
|
16
|
+
its('portraits.to_a') do
|
17
|
+
Hallon::Session.should_receive(:instance).exactly(2).times.and_return(session)
|
18
|
+
|
19
|
+
subject.map{ |img| img.id(true) }.should eq [mock_image_id, mock_image_id]
|
20
|
+
end
|
21
|
+
|
22
|
+
its('tracks.size') { should eq 2 }
|
23
|
+
its('tracks.to_a') { should eq [mock_track, mock_track_two].map{ |p| Hallon::Track.new(p) } }
|
24
|
+
its('albums.size') { should eq 1 }
|
25
|
+
its('albums.to_a') { should eq [Hallon::Album.new(mock_album)] }
|
26
|
+
its('similar_artists.size') { should eq 2 }
|
27
|
+
its('similar_artists.to_a') { should eq [mock_artist, mock_artist_two].map{ |p| Hallon::Artist.new(p) } }
|
28
|
+
its(:biography) { should eq 'grew up in DA BLOCK' }
|
29
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
describe Hallon::Artist do
|
3
|
+
subject { Hallon::Artist.new(mock_artist) }
|
4
|
+
|
5
|
+
it { should be_loaded }
|
6
|
+
its(:name) { should eq "Jem" }
|
7
|
+
its(:browse) do
|
8
|
+
Hallon::Session.should_receive(:instance).exactly(2).times.and_return(session)
|
9
|
+
Spotify.should_receive(:artistbrowse_create).exactly(2).times.and_return(mock_artistbrowse)
|
10
|
+
|
11
|
+
should eq Hallon::ArtistBrowse.new(mock_artist)
|
12
|
+
end
|
13
|
+
|
14
|
+
describe "#portrait" do
|
15
|
+
let(:link) { Hallon::Link.new("spotify:image:c78f091482e555bd2ffacfcd9cbdc0714b221663") }
|
16
|
+
let(:link_pointer) { FFI::Pointer.new(link.pointer.address) }
|
17
|
+
|
18
|
+
before do
|
19
|
+
Hallon::Link.new(link_pointer).should eq link
|
20
|
+
Spotify.should_receive(:link_create_from_artist_portrait).with(subject.pointer).and_return(link_pointer)
|
21
|
+
end
|
22
|
+
|
23
|
+
specify "as an image" do
|
24
|
+
Hallon::Session.should_receive(:instance).twice.and_return(session)
|
25
|
+
subject.portrait.should eq Hallon::Image.new(link_pointer)
|
26
|
+
end
|
27
|
+
|
28
|
+
specify "as a link" do
|
29
|
+
subject.portrait(false).should eq link
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
describe Hallon::Enumerator do
|
2
|
+
let(:item) do
|
3
|
+
mock.tap { |x| x.stub(:get).and_return(&alphabet) }
|
4
|
+
end
|
5
|
+
|
6
|
+
let(:enum) do
|
7
|
+
Hallon::Enumerator.new(5) { |i| item.get(i) }
|
8
|
+
end
|
9
|
+
|
10
|
+
let(:alphabet) do
|
11
|
+
proc { |x| %w[a b c d e][x] }
|
12
|
+
end
|
13
|
+
|
14
|
+
|
15
|
+
it "should be an enumerable" do
|
16
|
+
enum.should respond_to :each
|
17
|
+
enum.should be_an Enumerable
|
18
|
+
end
|
19
|
+
|
20
|
+
describe "#each" do
|
21
|
+
it "should call the containing block" do
|
22
|
+
calls = []
|
23
|
+
enum = Hallon::Enumerator.new(4) { |i| item.get(i) }
|
24
|
+
enum.each_with_index { |x, i| x.should eq alphabet[i] }
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe "#size" do
|
29
|
+
it "should return the given size" do
|
30
|
+
Hallon::Enumerator.new(4).size.should eq 4
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe "#[]" do
|
35
|
+
it "should support #[x] within range" do
|
36
|
+
item.should_receive(:get).with(1).and_return(&alphabet)
|
37
|
+
|
38
|
+
enum[1].should eq "b"
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should support negative #[x] within range" do
|
42
|
+
item.should_receive(:get).with(4).and_return(&alphabet)
|
43
|
+
|
44
|
+
enum[-1].should eq "e"
|
45
|
+
end
|
46
|
+
|
47
|
+
it "should return nil for #[x] outside range" do
|
48
|
+
item.should_not_receive(:get)
|
49
|
+
|
50
|
+
enum[6].should be_nil
|
51
|
+
end
|
52
|
+
|
53
|
+
it "should return nil for #[-x] outside range" do
|
54
|
+
item.should_not_receive(:get)
|
55
|
+
|
56
|
+
enum[-6].should be_nil
|
57
|
+
end
|
58
|
+
|
59
|
+
it "should return a slice of elements for #[x, y]" do
|
60
|
+
item.should_receive(:get).with(1).and_return(&alphabet)
|
61
|
+
item.should_receive(:get).with(2).and_return(&alphabet)
|
62
|
+
|
63
|
+
enum[1, 2].should eq %w[b c]
|
64
|
+
end
|
65
|
+
|
66
|
+
it "should return elements for an inclusive range of #[x..y]" do
|
67
|
+
item.should_receive(:get).with(1).and_return(&alphabet)
|
68
|
+
item.should_receive(:get).with(2).and_return(&alphabet)
|
69
|
+
item.should_receive(:get).with(3).and_return(&alphabet)
|
70
|
+
|
71
|
+
enum[1..3].should eq %w[b c d]
|
72
|
+
end
|
73
|
+
|
74
|
+
it "should return return only existing elements for partly inclusive range of #[x..y]" do
|
75
|
+
item.should_receive(:get).with(4).and_return(&alphabet)
|
76
|
+
|
77
|
+
enum[4..7].should eq %w[e]
|
78
|
+
end
|
79
|
+
|
80
|
+
it "should return nil for a completely outside range of #[x..y]" do
|
81
|
+
item.should_not_receive(:get)
|
82
|
+
|
83
|
+
enum[6..10].should eq nil
|
84
|
+
end
|
85
|
+
|
86
|
+
it "should return the items for #[-x, y]" do
|
87
|
+
item.should_receive(:get).with(2).and_return(&alphabet)
|
88
|
+
item.should_receive(:get).with(3).and_return(&alphabet)
|
89
|
+
item.should_receive(:get).with(4).and_return(&alphabet)
|
90
|
+
|
91
|
+
enum[-3, 3].should eq %w[c d e]
|
92
|
+
end
|
93
|
+
|
94
|
+
it "should slice between items by #[x, y]" do
|
95
|
+
item.should_not_receive(:get)
|
96
|
+
|
97
|
+
enum[5, 1].should eq []
|
98
|
+
end
|
99
|
+
|
100
|
+
it "should slice between items by #[x..y]" do
|
101
|
+
item.should_not_receive(:get)
|
102
|
+
|
103
|
+
enum[5..10].should eq []
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|