hallon 0.0.0 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. data/.autotest +6 -0
  2. data/.gemtest +0 -0
  3. data/.gitignore +29 -0
  4. data/.rspec +7 -0
  5. data/.yardopts +8 -0
  6. data/CHANGELOG +20 -0
  7. data/Gemfile +2 -0
  8. data/LICENSE.txt +21 -0
  9. data/QUIRKS +11 -0
  10. data/README.markdown +58 -0
  11. data/Rakefile +75 -0
  12. data/Termfile +7 -0
  13. data/examples/logging_in.rb +26 -0
  14. data/examples/printing_link_information.rb +27 -0
  15. data/hallon.gemspec +31 -0
  16. data/lib/hallon.rb +34 -0
  17. data/lib/hallon/error.rb +54 -0
  18. data/lib/hallon/ext/ffi.rb +26 -0
  19. data/lib/hallon/ext/spotify.rb +101 -0
  20. data/lib/hallon/image.rb +70 -0
  21. data/lib/hallon/link.rb +101 -0
  22. data/lib/hallon/linkable.rb +50 -0
  23. data/lib/hallon/observable.rb +91 -0
  24. data/lib/hallon/session.rb +189 -0
  25. data/lib/hallon/synchronizable.rb +32 -0
  26. data/lib/hallon/user.rb +69 -0
  27. data/lib/hallon/version.rb +7 -0
  28. data/spec/fixtures/example_uris.rb +11 -0
  29. data/spec/fixtures/pink_cover.jpg +0 -0
  30. data/spec/hallon/error_spec.rb +30 -0
  31. data/spec/hallon/ffi_spec.rb +5 -0
  32. data/spec/hallon/hallon_spec.rb +16 -0
  33. data/spec/hallon/image_spec.rb +41 -0
  34. data/spec/hallon/link_spec.rb +84 -0
  35. data/spec/hallon/linkable_spec.rb +43 -0
  36. data/spec/hallon/observable_spec.rb +103 -0
  37. data/spec/hallon/session_spec.rb +61 -0
  38. data/spec/hallon/synchronizable_spec.rb +19 -0
  39. data/spec/hallon/user_spec.rb +73 -0
  40. data/spec/spec_helper.rb +71 -0
  41. data/spec/support/.gitkeep +0 -0
  42. data/spec/support/context_initialized_session.rb +3 -0
  43. data/spec/support/context_logged_in.rb +16 -0
  44. data/spec/support/cover_me.rb +5 -0
  45. data/spec/support/shared_for_loadable_objects.rb +7 -0
  46. metadata +271 -96
@@ -0,0 +1,6 @@
1
+ require 'autotest/rspec2'
2
+ require 'autotest/growl'
3
+ require 'autotest/fsevent' rescue nil
4
+
5
+ Autotest::add_discovery { 'rspec2' }
6
+ Autotest::Growl::image_dir = 'ampelmaennchen'
File without changes
@@ -0,0 +1,29 @@
1
+ # Private files
2
+ *.key
3
+ spec/support/config.rb
4
+ tmp/
5
+ libspotify-*
6
+ core.*
7
+ Gemfile.lock
8
+
9
+ # Extension files
10
+ *.bundle
11
+
12
+ # Generated by:
13
+ ## rcov/cover_me
14
+ coverage/
15
+ coverage.data
16
+
17
+ ## yardoc
18
+ doc/
19
+ .yardoc
20
+
21
+ ## bundler
22
+ .bundle
23
+
24
+ ## jeweler
25
+ pkg
26
+ *.gem
27
+
28
+ ## Mac OS
29
+ .DS_Store
data/.rspec ADDED
@@ -0,0 +1,7 @@
1
+ --color
2
+ -fs
3
+ -Ilib
4
+ -Ispec
5
+ --fail-fast
6
+ --require cover_me
7
+ --require spec_helper
@@ -0,0 +1,8 @@
1
+ --title "Hallon"
2
+ --list-undoc
3
+ --charset UTF-8
4
+ --readme README.markdown
5
+ --markup markdown
6
+ --private
7
+ lib/**/*.rb
8
+ ext/**/*.c
@@ -0,0 +1,20 @@
1
+ Hallon’s Changelog
2
+ ==================
3
+
4
+ v0.1.0
5
+ ------------------
6
+ Initial, first, release! This version is merely made to
7
+ have a starting point, a point of reference, for future
8
+ releases soon to come.
9
+
10
+ - Error subsystem is covered (`sp_error_message(error_code)`)
11
+ - Image subsystem is complete, however you can only create images
12
+ from links at this moment.
13
+ - Session API is partial. Currently you can login, logout, retrieve
14
+ the logged in user and query user relations.
15
+ - User API is complete, but you can only create users from your
16
+ currently logged in user (through Session) or from links.
17
+
18
+ The API is still very young, and I expect a lot of changes to
19
+ happen to it, to make the asynchronous nature of libspotify
20
+ easier to handle.
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source :rubygems
2
+ gemspec
@@ -0,0 +1,21 @@
1
+ Copyright 2011 Kim Burgestrand. All rights reserved.
2
+
3
+ Redistribution and use in source and binary forms, with or without modification, are
4
+ permitted provided that the following conditions are met:
5
+
6
+ 1. Redistributions of source code must retain the above copyright notice, this list of
7
+ conditions and the following disclaimer.
8
+
9
+ 2. Redistributions in binary form must reproduce the above copyright notice, this list
10
+ of conditions and the following disclaimer in the documentation and/or other materials
11
+ provided with the distribution.
12
+
13
+ THIS SOFTWARE IS PROVIDED BY KIM BURGESTRAND ``AS IS'' AND ANY EXPRESS OR IMPLIED
14
+ WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
15
+ FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL KIM BURGESTRAND OR
16
+ CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
17
+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
18
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
19
+ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
20
+ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
21
+ ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
data/QUIRKS ADDED
@@ -0,0 +1,11 @@
1
+ # Quirks, oddities and general notes about libspotify
2
+
3
+ ## Session
4
+ - `sp_session_release` will segfault (rare)
5
+ - `login` callback fires even if the login fails; instead,
6
+ `connection_error` will fire a few moments later
7
+ - creating a session, releasing it, and then creating one again
8
+ will segfault
9
+
10
+ ## Link
11
+ - segfaults if created before a session
@@ -0,0 +1,58 @@
1
+ What is Hallon?
2
+ ===============
3
+ We rubyists have this awesome [spotify gem][] allowing us to use [libspotify][] from within Ruby, but it has a significant drawback: the `libspotify` API is very hard to use. Now, we can’t have that, so what do we do? We make Hallon!
4
+
5
+ Hallon is Swedish for “Raspberry”, and has been written to satisfy my needs for API simplicity. It provides you with a wrapper around the spotify gem, making the experience of using `libspotify` from Ruby much more enjoyable.
6
+
7
+ Hallon would not have been possible if not for these people:
8
+
9
+ - Per Reimers, cracking synchronization bugs with me in the deep night (4 AM) and correcting me when I didn’t know better
10
+ - [Spotify](http://www.spotify.com/), providing a service worth attention (and my money!)
11
+ - [Linus Oleander](https://github.com/oleander), involving me with the `radiofy.se` project, ultimately spawning the necessity of Hallon
12
+ - [Jesper Särnesjö][], creator of [Greenstripes][], making me think of Hallon as an achievable goal
13
+
14
+ Code samples can be found under `examples/` directory.
15
+
16
+ This is awesome! I want to help!
17
+ --------------------------------
18
+ Sweet! You contribute in more than one way!
19
+
20
+ ### Write code!
21
+ [Fork](http://help.github.com/forking/) Hallon, [write tests for everything](http://relishapp.com/rspec) you do (so I don’t break your stuff during my own development) and send a pull request. If you modify existing files, please adhere to the coding standard surrounding your code!
22
+
23
+ ### [Send me feedback and requests](http://github.com/Burgestrand/Hallon/issues)
24
+ Really, I ❤ feedback! Suggestions on how to improve the API, tell me what is delicious about Hallon, tell me what is yucky about Hallon… anything! All feedback is useful in one way or another.
25
+
26
+ You have any questions?
27
+ -----------------------
28
+ If you need to discuss issues or feature requests you can use [Hallons issue tracker](http://github.com/Burgestrand/Hallon/issues). For *anything* else you have to say or ask I can also be reached via [email (found on GitHub profile)](http://github.com/Burgestrand) or [@burgestrand on twitter](http://twitter.com/Burgestrand).
29
+
30
+ In fact, you can contact me via email or twitter even if it’s about features or issues. I’ll probably put them in the issue tracker myself after the discussion ;)
31
+
32
+ What’s the catch?
33
+ -----------------
34
+ There are several!
35
+
36
+ ### Hallon is unstable
37
+ The API is unstable, my code is likely unstable. Everything should be considered unstable!
38
+
39
+ ### Hallon only supports one session per process
40
+ You can only keep one session with Spotify alive at a time in the same process, due to a limitation of `libspotify`.
41
+
42
+ ### You still have to worry about threads
43
+ I have been doing my best at hiding the complexity in `libspotify`, but it’s still a work in progress. Despite my efforts, you’ll need to be familiar with concurrent programming to use Hallon properly.
44
+
45
+ Versioning policy
46
+ -----------------
47
+ Hallon uses [semantic versioning](http://semver.org) as of v0.0.0. As long
48
+ as Hallon stays at major version 0, no guarantees of backwards-compatibility
49
+ are made. CHANGELOG will be kept up to date with the different versions.
50
+
51
+ License
52
+ -------
53
+ Hallon is licensed under a 2-clause (Simplified) BSD license. More information can be found in the `LICENSE.txt` file.
54
+
55
+ [spotify gem]: https://rubygems.org/gems/spotify
56
+ [libspotify]: http://developer.spotify.com/en/libspotify/overview/
57
+ [Greenstripes]: http://github.com/sarnesjo/greenstripes
58
+ [Jesper Särnesjö]: http://jesper.sarnesjo.org/
@@ -0,0 +1,75 @@
1
+ # coding: utf-8
2
+ require 'rake'
3
+
4
+ require 'bundler'
5
+ Bundler::GemHelper.install_tasks
6
+
7
+ require 'yard'
8
+ YARD::Rake::YardocTask.new
9
+
10
+ require 'rspec/core/rake_task'
11
+
12
+ desc "Run all specs (even those requiring logging in to Spotify)"
13
+ RSpec::Core::RakeTask.new('spec:full')
14
+
15
+ desc "Run all specs like spec:full, but with debug mode and full warnings enabled"
16
+ RSpec::Core::RakeTask.new('spec:debug') do |task|
17
+ task.skip_bundler = true
18
+ task.ruby_opts = '-d -W2'
19
+ end
20
+
21
+ desc "Run all offline specs"
22
+ RSpec::Core::RakeTask.new('spec') do |task|
23
+ task.pattern = 'spec/hallon/*_spec.rb'
24
+ task.rspec_opts = '--tag ~logged_in'
25
+ end
26
+
27
+ desc "Run the full test suite and generate a coverage report"
28
+ task 'spec:cov' => ['clean', 'spec:full'] do
29
+ require 'cover_me'
30
+ require './spec/support/cover_me'
31
+
32
+ CoverMe.config.at_exit = proc { `open coverage/index.html` }
33
+ CoverMe.complete!
34
+ end
35
+
36
+ desc "Process the Hallon codebase, finding out which Spotify methods are being used"
37
+ task 'spotify:coverage' do
38
+ require 'set'
39
+ require 'spotify'
40
+
41
+ methods = Spotify.methods(false).map(&:to_s)
42
+ covered = Set.new(methods)
43
+ matcher = /Spotify::([\w_]+)[ \(]/
44
+
45
+ FileList['lib/**/*.rb'].each do |file|
46
+ File.read(file).scan(matcher) { |method, _| covered.delete(method) }
47
+ end
48
+
49
+ covered.group_by { |m| m[/[^_]+/] }.each_pair do |group, methods|
50
+ puts "#{group.capitalize}:"
51
+ methods.each do |m|
52
+ puts " #{m}"
53
+ end
54
+ puts
55
+ end
56
+
57
+ puts "Coverage: %.02f%%" % (100 * (1 - covered.size.fdiv(methods.size)))
58
+ end
59
+
60
+ task :test => :spec
61
+
62
+ #
63
+ # Custom tasks
64
+ #
65
+ desc "Generates YARD documentation and open it."
66
+ task :doc => :yard do
67
+ sh 'open doc/index.html'
68
+ end
69
+
70
+ desc "Remove generated files"
71
+ task :clean do
72
+ sh 'git clean -fdx --exclude Gemfile.lock --exclude spec/support/config.rb'
73
+ end
74
+
75
+ task :default => [:spec]
@@ -0,0 +1,7 @@
1
+ # You wonder what this is?
2
+ #
3
+ # This is a Termfile, and is used by a gem named “terminitor” that
4
+ # allows me to set up the work environment for Hallon much easier.
5
+
6
+ run 'bundle exec autotest'
7
+ tab '$EDITOR .'
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+ require 'hallon'
3
+ require './spec/support/config'
4
+
5
+ session = Hallon::Session.instance IO.read(ENV['HALLON_APPKEY']) do
6
+ on(:log_message) do |message|
7
+ puts "[LOG] #{message}"
8
+ end
9
+ end
10
+
11
+ session.login ENV['HALLON_USERNAME'], ENV['HALLON_PASSWORD']
12
+ logged_in = session.process_events_on(:logged_in) { |error| error }
13
+
14
+ unless logged_in == :ok
15
+ abort "[ERROR] (:logged_in) #{Hallon::Error.explain(logged_in)}"
16
+ end
17
+
18
+ conn_error = session.process_events_on(:connection_error) do |error|
19
+ session.logged_in? or error
20
+ end
21
+
22
+ unless conn_error == true
23
+ abort "[ERROR] (:connection_error) #{Hallon::Error.explain(conn_error)}"
24
+ end
25
+
26
+ puts "Successfully logged in!"
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+ require 'hallon'
3
+ require './spec/support/config'
4
+
5
+ # Utility
6
+ def prompt(str)
7
+ print str
8
+ gets.chomp
9
+ end
10
+
11
+ # Hallon
12
+ session = Hallon::Session.instance IO.read(ENV['HALLON_APPKEY']) do
13
+ on(:log_message) do |message|
14
+ $stderr.puts "[LOG] #{message}"
15
+ end
16
+ end
17
+
18
+ while url = prompt("Enter a Spotify URI: ")
19
+ begin
20
+ p (link = Hallon::Link.new(url))
21
+ puts "\tHTTP URL: #{link.to_url}"
22
+ puts "\tSpotify URI: #{link.to_str}"
23
+ puts "\tLink type: #{link.type}"
24
+ rescue ArgumentError => e
25
+ puts e
26
+ end
27
+ end
@@ -0,0 +1,31 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require './lib/hallon/version'
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.name = "hallon"
6
+ gem.summary = %Q{Delicious Ruby bindings to the official Spotify API}
7
+ gem.homepage = "http://github.com/Burgestrand/Hallon"
8
+ gem.authors = ["Kim Burgestrand"]
9
+ gem.email = 'kim@burgestrand.se'
10
+ gem.license = 'GNU AGPL'
11
+
12
+ gem.description = IO.read('./README.markdown', encoding: 'utf-8')
13
+
14
+ gem.files = `git ls-files`.split("\n")
15
+ gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
16
+ gem.executables = []
17
+ gem.require_paths = ["lib"]
18
+
19
+ gem.version = Hallon::VERSION
20
+ gem.platform = Gem::Platform::RUBY
21
+ gem.required_ruby_version = '~> 1.9'
22
+
23
+ gem.add_dependency 'spotify', '~> 8.0.5'
24
+ gem.add_development_dependency 'rake', '~> 0.8'
25
+ gem.add_development_dependency 'rspec', '~> 2'
26
+ gem.add_development_dependency 'autotest-standalone'
27
+ gem.add_development_dependency 'autotest-growl'
28
+ gem.add_development_dependency 'cover_me'
29
+ gem.add_development_dependency 'yard'
30
+ gem.add_development_dependency 'rdiscount'
31
+ end
@@ -0,0 +1,34 @@
1
+ # coding: utf-8
2
+ require 'spotify'
3
+ require 'hallon/ext/spotify'
4
+ require 'hallon/ext/ffi'
5
+
6
+ require 'hallon/synchronizable'
7
+ require 'hallon/observable'
8
+ require 'hallon/linkable'
9
+
10
+ require 'hallon/version'
11
+ require 'hallon/error'
12
+ require 'hallon/session'
13
+ require 'hallon/link'
14
+ require 'hallon/user'
15
+ require 'hallon/image'
16
+
17
+ # The Hallon module wraps around all Hallon objects to avoid polluting
18
+ # the global namespace. To start using Hallon, you most likely want to
19
+ # be looking for the documentation on {Hallon::Session}.
20
+ module Hallon
21
+ # @see Spotify::API_VERSION
22
+ API_VERSION = Spotify::API_VERSION
23
+
24
+ # A regex that matches all Spotify URIs
25
+ #
26
+ # @example
27
+ # Hallon::URI === "spotify:user:burgestrand" # => true
28
+ URI = /(spotify:(?:
29
+ (?:artist|album|track|user:[^:]+:playlist):[a-zA-Z0-9]+
30
+ |user:[^:]+
31
+ |search:(?:[-\w$\.+!*'(),]+|%[a-fA-F0-9]{2})+
32
+ ))
33
+ /x
34
+ end
@@ -0,0 +1,54 @@
1
+ # coding: utf-8
2
+ module Hallon
3
+ # Thrown by Hallon on libspotify errors.
4
+ #
5
+ # @see http://developer.spotify.com/en/libspotify/docs/group__error.html
6
+ class Error < RuntimeError
7
+ class << self
8
+ # Given a number or a symbol, find both the symbol and the error
9
+ # number it represents.
10
+ #
11
+ # @param [Symbol, Fixnum] error
12
+ # @return [[Fixnum, Symbol]] (error code, error symbol)
13
+ def disambiguate(error)
14
+ @enum ||= Spotify::enum_type(:error)
15
+
16
+ if error.is_a? Symbol
17
+ error = @enum[symbol = error]
18
+ else
19
+ symbol = @enum[error]
20
+ end
21
+
22
+ if error.nil? || symbol.nil?
23
+ [-1, nil]
24
+ else
25
+ [error, symbol]
26
+ end
27
+ end
28
+
29
+ # Explain a Spotify error with a string message.
30
+ #
31
+ # @param [Fixnum, Symbol]
32
+ # @return [String]
33
+ def explain(error)
34
+ Spotify::error_message disambiguate(error)[0]
35
+ end
36
+
37
+ # Raise an {Error} with the given errno, unless it is `0` or `:ok`.
38
+ #
39
+ # @param [Fixnum, Symbol] error
40
+ # @return [nil]
41
+ def maybe_raise(error)
42
+ error, symbol = disambiguate(error)
43
+
44
+ unless symbol == :ok
45
+ message = []
46
+ message << "[#{symbol.upcase}]"
47
+ message << explain(error)
48
+ message << "(#{error})"
49
+ raise Hallon::Error, message.join(' ')
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,26 @@
1
+ # Custom extensions to the FFI gem.
2
+ #
3
+ # @see https://github.com/ffi/ffi
4
+ module FFI
5
+ # FFI::Pointer is the underlying class used to read
6
+ # and write data to pointers. For more information
7
+ # see the FFI gem.
8
+ class Pointer
9
+ type, _ = begin
10
+ type = FFI.find_type(:size_t)
11
+ FFI::TypeDefs.find do |(name, t)|
12
+ method_defined? "read_#{name}" if t == type
13
+ end
14
+ end
15
+
16
+ unless type.nil?
17
+ # Read N `size_t` from the start of the pointer.
18
+ #
19
+ # @param [Integer] count how many to read
20
+ # @return a type of appropriate size
21
+ define_method(:read_size_t) do |*args|
22
+ public_send("read_#{type}", *args)
23
+ end
24
+ end
25
+ end
26
+ end