hallon 0.14.0 → 0.15.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.
@@ -1,62 +1,34 @@
1
1
  # coding: utf-8
2
- #
3
- # DISCLAIMER:
4
- # This file was written without extensive testing, and is merely an
5
- # example. Before using this yourself, I advice you to look through
6
- # the code carefully.
7
- #
8
2
 
9
- $LOAD_PATH.unshift(File.expand_path('../lib', File.dirname(__FILE__)))
10
-
11
- require 'hallon'
12
- require './spec/support/config'
13
-
14
- # Utility
15
- def prompt(str)
16
- print str
17
- gets.chomp
18
- end
19
-
20
- session = Hallon::Session.initialize IO.read(ENV['HALLON_APPKEY']) do
21
- on(:log_message) do |message|
22
- puts "[LOG] #{message}"
23
- end
24
-
25
- on(:connection_error) do |error|
26
- Hallon::Error.maybe_raise(error)
27
- end
28
-
29
- on(:logged_out) do
30
- abort "[FAIL] Logged out!"
31
- end
32
- end
33
-
34
- session.login!(ENV['HALLON_USERNAME'], ENV['HALLON_PASSWORD'])
35
-
36
- puts "Successfully logged in!"
3
+ # Require support code, used by all the examples.
4
+ require_relative 'example_support'
5
+ session = Hallon::Session.instance
37
6
 
38
7
  while username = prompt("Enter a Spotify username: ")
39
8
  begin
40
- puts "Fetching container for #{username}..."
41
- published = Hallon::User.new(username).published
42
- session.wait_for { published.loaded? }
9
+ puts "Loading #{username}."
10
+ user = Hallon::User.new(username)
43
11
 
44
- puts "Listing #{published.size} playlists."
45
- published.contents.each do |playlist|
46
- next if playlist.nil? # folder or somesuch
12
+ puts "Fetching published playlists for #{username}..."
13
+ published = user.published.load
47
14
 
48
- session.wait_for { playlist.loaded? }
15
+ puts "Loading #{published.size} playlists."
16
+ all_playlists = published.contents.find_all do |playlist|
17
+ playlist.is_a?(Hallon::Playlist) # ignore folders
18
+ end
49
19
 
50
- puts
51
- puts playlist.name << ": "
20
+ all_playlists.each(&:load)
52
21
 
53
- playlist.tracks.each_with_index do |track, i|
54
- session.wait_for { track.loaded? }
22
+ all_playlists.each do |playlist|
23
+ puts
24
+ puts "Listing tracks for #{playlist.name} (#{playlist.to_str}):"
55
25
 
26
+ tracks = playlist.tracks.to_a.map(&:load)
27
+ tracks.each_with_index do |track, i|
56
28
  puts "\t (#{i+1}/#{playlist.size}) #{track.name}"
57
29
  end
58
30
  end
59
31
  rescue Interrupt
60
- # do nothing, continue with loop
32
+ puts "Interrupted!"
61
33
  end
62
34
  end
data/hallon.gemspec CHANGED
@@ -22,9 +22,10 @@ Gem::Specification.new do |gem|
22
22
  gem.required_ruby_version = '>= 1.9'
23
23
 
24
24
  gem.add_dependency 'ref', '~> 1.0'
25
- gem.add_dependency 'spotify', '~> 10.3.0'
25
+ gem.add_dependency 'spotify', '~> 11.0.2'
26
26
  gem.add_development_dependency 'rake', '~> 0.8'
27
27
  gem.add_development_dependency 'rspec', '~> 2'
28
28
  gem.add_development_dependency 'yard'
29
+ gem.add_development_dependency 'bundler'
29
30
  gem.add_development_dependency 'rdiscount'
30
31
  end
data/lib/hallon.rb CHANGED
@@ -59,14 +59,15 @@ module Hallon
59
59
  # @example
60
60
  # Hallon::URI === "spotify:user:burgestrand" # => true
61
61
  URI = /(spotify:(?:
62
- (?:artist|album|track|user:[^:]+:playlist):[a-fA-F0-9]+
63
- |user:[^:]+
62
+ (?:artist|album|user:[^:]+:playlist):[a-zA-Z0-9]{22}
63
+ |track:[a-zA-Z0-9]{22}(?:\#\d{1,2}:\d{1,2})?
64
+ |user:[^:]+(?::starred)?
64
65
  |search:(?:[-\w$\.+!*'(),]+|%[a-fA-F0-9]{2})+
65
66
  |image:[a-fA-F0-9]{40}
66
67
  ))
67
68
  /x
68
69
 
69
- # Thrown by {Loadable#load} on failure.
70
+ # Thrown by {Loadable#load} and {Playlist#update} on failure.
70
71
  TimeoutError = Class.new(Hallon::Error)
71
72
 
72
73
  class << self
@@ -83,5 +84,5 @@ module Hallon
83
84
  end
84
85
  end
85
86
 
86
- self.load_timeout = 5 # seconds
87
+ self.load_timeout = 31.4159 # seconds
87
88
  end
data/lib/hallon/album.rb CHANGED
@@ -23,7 +23,7 @@ module Hallon
23
23
  Spotify.enum_type(:albumtype).symbols
24
24
  end
25
25
 
26
- extend Linkable
26
+ include Linkable
27
27
  include Loadable
28
28
 
29
29
  to_link :from_album
data/lib/hallon/artist.rb CHANGED
@@ -11,7 +11,7 @@ module Hallon
11
11
  #
12
12
  # @see http://developer.spotify.com/en/libspotify/docs/group__artist.html
13
13
  class Artist < Base
14
- extend Linkable
14
+ include Linkable
15
15
  include Loadable
16
16
 
17
17
  from_link :as_artist
data/lib/hallon/base.rb CHANGED
@@ -1,6 +1,4 @@
1
1
  # coding: utf-8
2
- require 'timeout'
3
-
4
2
  module Hallon
5
3
  # All objects in Hallon are mere representations of Spotify objects.
6
4
  # Hallon::Base covers basic functionality shared by all of these.
@@ -41,6 +39,7 @@ module Hallon
41
39
  end
42
40
 
43
41
  private
42
+
44
43
  # @macro [attach] to_link
45
44
  # @method to_link
46
45
  # @scope instance
@@ -72,7 +71,8 @@ module Hallon
72
71
  #
73
72
  # @param [Spotify::Pointer, String, Link] resource
74
73
  # @return [Spotify::Pointer]
75
- # @raise [TypeError] when pointer could not be created, or null
74
+ # @raise [TypeError] when the pointer is of the wrong type
75
+ # @raise [ArgumentError] when pointer could not be created, or null
76
76
  def to_pointer(resource, type, *args)
77
77
  if resource.is_a?(FFI::Pointer) and not resource.is_a?(Spotify::Pointer)
78
78
  raise TypeError, "Hallon does not support raw FFI::Pointers, wrap it in a Spotify::Pointer"
@@ -23,6 +23,7 @@ module Spotify
23
23
  # @raise [NoMethodError] if `function` is not defined
24
24
  # @see Spotify::Pointer
25
25
  def self.wrap_function(function, return_type)
26
+ method(function)
26
27
  define_singleton_method("#{function}!") do |*args|
27
28
  pointer = public_send(function, *args)
28
29
  Spotify::Pointer.new(pointer, return_type, function !~ /create/)
@@ -81,7 +82,6 @@ module Spotify
81
82
  wrap_function :link_create_from_user, :link
82
83
 
83
84
  wrap_function :search_create, :search
84
- wrap_function :radio_search_create, :search
85
85
  wrap_function :search_track, :track
86
86
  wrap_function :search_album, :album
87
87
  wrap_function :search_artist, :artist
data/lib/hallon/image.rb CHANGED
@@ -4,7 +4,7 @@ module Hallon
4
4
  #
5
5
  # @see http://developer.spotify.com/en/libspotify/docs/group__image.html
6
6
  class Image < Base
7
- extend Linkable
7
+ include Linkable
8
8
 
9
9
  from_link :as_image do |link|
10
10
  Spotify.image_create_from_link!(session.pointer, link)
data/lib/hallon/link.rb CHANGED
@@ -11,6 +11,8 @@ module Hallon
11
11
  def self.valid?(spotify_uri)
12
12
  if spotify_uri.is_a?(Link)
13
13
  return true
14
+ elsif spotify_uri.to_s["\x00"] # image ids
15
+ return false
14
16
  end
15
17
 
16
18
  link = Spotify.link_create_from_string!(spotify_uri.to_s)
@@ -54,7 +56,7 @@ module Hallon
54
56
  def to_str(length = length)
55
57
  FFI::Buffer.alloc_out(length + 1) do |b|
56
58
  Spotify.link_as_string(pointer, b, b.size)
57
- return b.get_string(0)
59
+ return b.get_string(0).force_encoding("UTF-8")
58
60
  end
59
61
  end
60
62
 
@@ -8,123 +8,120 @@ module Hallon
8
8
  #
9
9
  # @private
10
10
  module Linkable
11
- # Defines `#from_link`, used in converting a link to a pointer. You
12
- # can either pass it a `method_name`, or a `type` and a block.
13
- #
14
- # @overload from_link(method_name)
15
- # Define `#from_link` simply by giving the name of the method,
16
- # minus the `link_` prefix.
17
- #
18
- # @example
19
- # class Album
20
- # extend Linkable
21
- #
22
- # from_link :as_album # => Spotify.link_as_album(pointer, *args)
23
- # # ^ is roughly equivalent to:
24
- # def from_link(link, *args)
25
- # unless Spotify::Pointer.typechecks?(link, :link)
26
- # link = Link.new(link).pointer(:album)
27
- # end
28
- #
29
- # Spotify.link_as_album!(link)
30
- # end
31
- # end
32
- #
33
- # @param [Symbol] method_name
34
- #
35
- # @overload from_link(type) { |*args| … }
36
- # Define `#from_link` to use the given block to convert an object
37
- # from a link. The link is converted to a pointer and typechecked
38
- # to be of the same type as `type` before given to the block.
39
- #
40
- # @example
41
- # class User
42
- # extend Linkable
43
- #
44
- # from_link :profile do |pointer|
45
- # Spotify.link_as_user!(pointer)
46
- # end
47
- # # ^ is roughly equivalent to:
48
- # def from_link(link, *args)
49
- # unless Spotify::Pointer.typechecks?(link, :link)
50
- # link = Link.new(link).pointer(:profile)
51
- # end
52
- #
53
- # Spotify.link_as_user!(link)
54
- # end
55
- # end
56
- #
57
- # @param [#to_s] type link type
58
- # @yield [link, *args] called when conversion is needed from Link pointer
59
- # @yieldparam [Spotify::Pointer] link
60
- # @yieldparam *args any extra arguments given to `#from_link`
61
- #
62
- # @note Private API. You probably do not need to care about this method.
63
- def from_link(as_object, &block)
64
- block ||= Spotify.method(:"link_#{as_object}!")
65
- type = as_object.to_s[/^(as_)?([^_]+)/, 2].to_sym
11
+ module ClassMethods
12
+ # Defines `#from_link`, used in converting a link to a pointer. You
13
+ # can either pass it a `method_name`, or a `type` and a block.
14
+ #
15
+ # @overload from_link(method_name)
16
+ # Define `#from_link` simply by giving the name of the method,
17
+ # minus the `link_` prefix.
18
+ #
19
+ # @example
20
+ # class Album
21
+ # include Linkable
22
+ #
23
+ # from_link :as_album # => Spotify.link_as_album(pointer, *args)
24
+ # # ^ is roughly equivalent to:
25
+ # def from_link(link, *args)
26
+ # unless Spotify::Pointer.typechecks?(link, :link)
27
+ # link = Link.new(link).pointer(:album)
28
+ # end
29
+ #
30
+ # Spotify.link_as_album!(link)
31
+ # end
32
+ # end
33
+ #
34
+ # @param [Symbol] method_name
35
+ #
36
+ # @overload from_link(type) { |*args| }
37
+ # Define `#from_link` to use the given block to convert an object
38
+ # from a link. The link is converted to a pointer and typechecked
39
+ # to be of the same type as `type` before given to the block.
40
+ #
41
+ # @example
42
+ # class User
43
+ # include Linkable
44
+ #
45
+ # from_link :profile do |pointer|
46
+ # Spotify.link_as_user!(pointer)
47
+ # end
48
+ # # ^ is roughly equivalent to:
49
+ # def from_link(link, *args)
50
+ # unless Spotify::Pointer.typechecks?(link, :link)
51
+ # link = Link.new(link).pointer(:profile)
52
+ # end
53
+ #
54
+ # Spotify.link_as_user!(link)
55
+ # end
56
+ # end
57
+ #
58
+ # @param [#to_s] type link type
59
+ # @yield [link, *args] called when conversion is needed from Link pointer
60
+ # @yieldparam [Spotify::Pointer] link
61
+ # @yieldparam *args any extra arguments given to `#from_link`
62
+ #
63
+ # @note Private API. You probably do not need to care about this method.
64
+ def from_link(as_object, &block)
65
+ block ||= Spotify.method(:"link_#{as_object}!")
66
+ type = as_object.to_s[/^(as_)?([^_]+)/, 2].to_sym
67
+
68
+ define_method(:from_link) do |link, *args|
69
+ unless Spotify::Pointer.typechecks?(link, :link)
70
+ link = Link.new(link).pointer(type)
71
+ end
66
72
 
67
- define_method(:from_link) do |link, *args|
68
- unless Spotify::Pointer.typechecks?(link, :link)
69
- link = Link.new(link).pointer(type)
73
+ instance_exec(link, *args, &block)
70
74
  end
71
75
 
72
- instance_exec(link, *args, &block)
76
+ private :from_link
73
77
  end
74
78
 
75
- private :from_link
76
- end
77
-
78
- # Defines `#to_link` method, used in converting the object to a {Link}.
79
- #
80
- # @example
81
- # class Artist
82
- # extend Linkable
83
- #
84
- # to_link :from_artist
85
- # # ^ is the same as:
86
- # def to_link(*args)
87
- # link = Spotify.link_create_from_artist!(pointer, *args)
88
- # Link.new(link)
89
- # end
90
- # end
91
- #
92
- # @param [Symbol] cmethod name of the C method, say `from_artist` in `Spotify.link_create_from_artist`.
93
- # @return [Link, nil]
94
- def to_link(cmethod)
95
- define_method(:to_link) do |*args|
96
- link = Spotify.__send__(:"link_create_#{cmethod}!", pointer, *args)
97
- Link.from(link)
79
+ # Defines `#to_link` method, used in converting the object to a {Link}.
80
+ #
81
+ # @example
82
+ # class Artist
83
+ # include Linkable
84
+ #
85
+ # to_link :from_artist
86
+ # # ^ is the same as:
87
+ # def to_link(*args)
88
+ # link = Spotify.link_create_from_artist!(pointer, *args)
89
+ # Link.new(link)
90
+ # end
91
+ # end
92
+ #
93
+ # @param [Symbol] cmethod name of the C method, say `from_artist` in `Spotify.link_create_from_artist`.
94
+ # @return [Link, nil]
95
+ def to_link(cmethod)
96
+ define_method(:to_link) do |*args|
97
+ link = Spotify.__send__(:"link_create_#{cmethod}!", pointer, *args)
98
+ Link.from(link)
99
+ end
98
100
  end
99
101
  end
100
102
 
101
- private :from_link
102
- private :to_link
103
-
104
- def self.extended(other)
105
- other.send(:include, InstanceMethods)
103
+ def self.included(other)
104
+ other.extend ClassMethods
106
105
  end
107
106
 
108
- module InstanceMethods
109
- # Converts the Linkable first to a Link, and then that link to a String.
110
- #
111
- # @note Returns an empty string if the #to_link call fails.
112
- # @return [String]
113
- def to_str
114
- link = to_link
115
- link &&= link.to_str
116
- link.to_s
117
- end
107
+ # Converts the Linkable first to a Link, and then that link to a String.
108
+ #
109
+ # @note Returns an empty string if the #to_link call fails.
110
+ # @return [String]
111
+ def to_str
112
+ link = to_link
113
+ link &&= link.to_str
114
+ link.to_s
115
+ end
118
116
 
119
- # Compare the Linkable to other. If other is a Linkable, also
120
- # compare their `to_link` if necessary.
121
- #
122
- # @param [Object] other
123
- # @return [Boolean]
124
- def ===(other)
125
- super or if other.respond_to?(:to_link)
126
- to_link == other.to_link
127
- end
117
+ # Compare the Linkable to other. If other is a Linkable, also
118
+ # compare their `to_link` if necessary.
119
+ #
120
+ # @param [Object] other
121
+ # @return [Boolean]
122
+ def ===(other)
123
+ super or if other.respond_to?(:to_link)
124
+ to_link == other.to_link
128
125
  end
129
126
  end
130
127
  end
@@ -1,4 +1,5 @@
1
1
  # coding: utf-8
2
+ require 'timeout'
2
3
 
3
4
  module Hallon
4
5
  module Loadable
@@ -101,12 +101,14 @@ module Hallon
101
101
  # Defines a handler for the given event.
102
102
  #
103
103
  # @param [#to_s] event name of event to handle
104
- # @return [Proc] the given block
104
+ # @return [Proc] the previous handler
105
105
  # @yield (*args) event handler block
106
106
  def on(event, &block)
107
107
  raise ArgumentError, "no block given" unless block
108
108
  raise NameError, "no such callback: #{event}" unless has_callback?(event)
109
- handlers[event.to_s] = block
109
+ handlers[event.to_s].tap do
110
+ handlers[event.to_s] = block
111
+ end
110
112
  end
111
113
 
112
114
  # @param [#to_s] name