Giter VIP home page Giter VIP logo

hallon's Introduction

Hallon (Swedish for “Raspberry”) is the ruby gem for interacting with the official Spotify C API. It is the only ruby gem for libspotify that is up-to-date and usable. My goal with Hallon is to make libspotify a joy to use.

Code samples can be found under the examples/ directory. An explanation on how to run them can be found on the Hallon wiki on GitHub.

Important to consider before using Hallon

First off, Hallon is no longer being maintained.

Hallon’s model was from the start something along the lines of a synchronous Ruby binding to libspotify, and I no longer believe the way Hallon achieve this to be a good way of doing things. Because of this, development of Hallon has been stagnant for a long time. An additional reason is that I am no longer personally using Hallon for anything myself.

Most of my effort towards libspotify and Ruby have been focused on the spotify gem that powers Hallon: https://github.com/Burgestrand/spotify. For new projects, and possibly even existing projects, I would strongly suggest using the spotify gem instead of using Hallon, because:

  • Less of an abstraction around libspotify.
  • Less bugs, because of 1.
  • Less risk of e.g. deadlocks in the library because of 1.
  • Less confusing, because of 1.
  • Better than C (it’s in ruby, has automatic garbage collection, thread safe).
  • It’s still seeing new features and improvements, Hallon currently isn’t.

However, the spotify gem also has a few cons in comparison to Hallon:

  • Less of an abstraction around libspotify.
  • Requires (even if rudimentary) knowledge of FFI.
  • Some things are still annoying to do (e.g. calling certain functions, and callbacks) because of 2 and 1.

Please consider the above points before using Hallon.

Contact details

If you for some reason cannot use the mailing list or GitHub issue tracker you may contact me directly. My email is found on my GitHub profile.

Prerequisites

Before you start using Hallon you’ll need to complete the following steps.

  1. Get yourself a Spotify premium account, which is required for libspotify to work. You username and password (either classic Spotify, or facebook credentials) will be used to connect to Spotify later.
  2. Download your application key from developer.spotify.com, and place it in a known location. You’ll have the option of downloading it either in binary or c-code. You want the binary one. If you do not have an application key already, you will be asked to create one.
  3. Once the above are done, you are ready to try out Hallon.

Using Hallon

First, begin by installing the latest version of Hallon.

gem install hallon

Great! Now you’re ready to start experimenting. Everything in Hallon, from searching to looking up tracks, requires you to have an active Spotify session. You create it by initializing it with your application key.

require 'hallon'

session = Hallon::Session.initialize IO.read('./spotify_appkey.key')

Now that you have your session you may also want to login (even though you can still do a few things without logging in).

session.login!('username', 'password')

You may now experiment with just about anything. For an API reference, please see Hallon’s page at rdoc.info. As a starter tip, many objects can be constructed by giving it a Spotify URI, like this.

track = Hallon::Track.new("spotify:track:1ZPsdTkzhDeHjA5c2Rnt2I").load
artist = track.artist.load

puts "#{track.name} by #{artist.name}"

If you want to play audio…

If you want to play audio you’ll need to install an audio driver. As of current writing there is only one driver in existence. You can install it with:

gem install hallon-openal

For more information about audio support in Hallon, see the section "Audio support" below.

Hallon and Spotify objects

All objects from libspotify have a counterpart in Hallon, and just like in libspotify the objects are populated with information as it becomes available. All objects that behave in this way respond to #loaded?, which’ll return true if the object has been populated with data.

To ease loading objects, all loadable objects also respond to #load. This method simply polls on the target object, repeatedly calling Session#process_events until either a time limit is reached or the object has finished loading.

user = Hallon::User.new("spotify:user:burgestrand").load
puts user.loaded? # => true

As far as usage of the library goes, what applies to libspotify also applies to Hallon, so I would suggest you also read the libspotify library overview and related documentation.

Callbacks

Some objects may fire callbacks, most of the time as a direct result of Session#process_events. In libspotify the callbacks are only fired once for every object, but in Hallon you may have more than one object attached to the same libspotify object. As a result, callbacks are handled individually for each Hallon object.

imageA = Hallon::Image.new("spotify:image:548957670a3e9950e87ce61dc0c188debd22b0cb")
imageB = Hallon::Image.new("spotify:image:548957670a3e9950e87ce61dc0c188debd22b0cb")

imageA.pointer == imageB.pointer # => true, same spotify pointer
imageA.object_id == imageB.object_id # => false, different objects

imageA.on(:load) do
  puts "imageA loaded!"
end

imageB.on(:load) do
  puts "imageB loaded!"
end

imageA.load # might load imageB as well, we don’t know
imageB.load # but the callbacks will both fire on load

A list of all objects that may fire callbacks can be found on the API page for Hallon::Observable.

Errors

On failed libspotify API calls, a Hallon::Error will be raised with a message explaining the error. Methods that might fail in this way (e.g. Session.initialize) should have this clearly stated in its’ documentation.

For a full list of possible errors, see the official libspotify documentation on error handling.

Enumerators

Some methods (e.g. Track#artists) return a Hallon::Enumerator object. Enumerators are lazily loaded, which means that calling track.artists won’t create any artist objects until you try to retrieve one of the records out of the returned enumerator. If you want to load all artists for a track you should retrieve them all then load them in bulk.

artists = track.artists.to_a # avoid laziness, instantiate all artist objects
artists.map(&:load)

An additional note is that the size of an enumerator may change, and its contents may move as libspotify updates its information.

For the API reference and existing subclasses, see Hallon::Enumerator.

Garbage collection

Hallon makes use of Ruby’s own garbage collection to automatically release libspotify objects when they are no longer in use. There is no need to retain or release the spotify objects manually.

Audio support

Hallon supports streaming audio from Spotify via Hallon::Player. When you create the player you give it your audio driver of choice, which the player will then use for audio playback.

require 'hallon'
require 'hallon-openal'

session = Hallon::Session.initialize(IO.read('./spotify_appkey.key'))
session.login!('username', 'password')

track = Hallon::Track.new("spotify:track:1ZPsdTkzhDeHjA5c2Rnt2I")
track.load

player = Hallon::Player.new(Hallon::OpenAL)
player.play!(track)

Available drivers are:

For information on how to write your own audio driver, see Hallon::ExampleAudioDriver.

Finally, here are some important notes

Contributing to Hallon

Fork Hallon, write tests for everything 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.

Hallon uses semantic versioning as of v0.0.0

As long as Hallon stays at major version 0 its API should be considered experimental. I expect it to change a lot to version v1.0.0.

Hallon is not without version policy, however. As of version v0.18.0 I aim to only increase the minor version when backwards-incompatible changes are made. Therefore, it should be safe to upgrade between minor versions, i.e. specify version constraints with the patch version as the variable version: hallon ~> v0.18.0.

Hallon only supports one session per process

You can only keep one session with Spotify alive at a time within the same process, due to a limitation of libspotify.

When forking, you need to be extra careful

If you fork, you need to instantiate the session within the process you plan to use Hallon in. You want to use Hallon in the parent? Create the session in the parent. You want to use it in the child? Create the session in the child! This is a limitation of libspotify itself.

You must not share cache directory between processes

Hallon uses tmp/hallon as both cache and settings directory by default. If you launch Hallon in multiple processes, you must make sure that cache_location is not shared between them, by changing it in the call to Session.initialize, or libspotify will lock up.

Hallon and platforms

Hallon aims to support the available platforms of the Spotify gem, which in turn depends somewhat on the platforms that libspotify support. As of current, Hallon officially supports Mac OS and Linux distributions that libspotify supports. Windows support is possible, but is yet to have been needed.

Having trouble with libspotify missing?

If so, it may be the case that your platform is not supported by the libspotify gem. Hallon’s wiki has an article on How to install libspotify for you. However, please also report an issue on the libspotify gem, I’d appreciate it, thank you!

Credits

  • Per Reimers, cracking synchronization bugs with me deep in the night (4 AM), thanks. :)
  • Jesper Särnesjö, unknowingly providing me a starting point with Greenstripes
  • Linus Oleander, originally inspiring me to write Hallon (for the radiofy.se project)
  • Emil “@mrevilme” Palm, for his patience in helping me debug Hallon deadlock issues

License

Hallon is licensed under a 2-clause (Simplified) BSD license.

Copyright 2012 Kim Burgestrand. All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

  1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.

  2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY KIM BURGESTRAND ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL KIM BURGESTRAND OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

hallon's People

Contributors

burgestrand avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

hallon's Issues

Playlist creation is not always reflected on other clients

The tests, for example, will create a playlist named “rspec” and another named “omgwtfbbq”. Both of these are rarely (if ever) added to the container on the Spotify servers, nor actually uploaded.

When a playlist has been created it should exist everywhere, not just in local memory.

Hallon::Enumerable, for lazy collections

It should support Enumerable module, but also needs to implement some methods like #at and possibly #slice.

How to do this in a general way I have no idea, but I'll look at it tomorro'.

hn_cEvents_build_handler sometimes return an unusable handler

Given this file in the path spec/bug.rb:
$: << File.expand_path('../../lib', FILE)
require 'hallon'
require_relative 'support/config'

session = Hallon::Session.instance(Hallon::APPKEY)
puts "Handler: #{session.handler.inspect}"

This will segfault on line #6 when inspecting the handler. I do not yet know why. Here’s some more info:

Restructure C files

Everything is written within the same file. This is bad for extensibility and needs to be adressed.

Abstract Enumerator further

Turns out Playlists, Playlist Containers and… Searches not only allow you to retrieve a specific object by its’ index, but also some status information on it and even allow you to modify fields.

Say, for Playlists, we have sp_track *sp_playlist_track_message (sp_playlist *playlist, int index). Thing is, we also have:

int sp_playlist_track_create_time (sp_playlist *playlist, int index);
sp_user * sp_playlist_track_creator (sp_playlist *playlist, int index);
bool sp_playlist_track_seen (sp_playlist *playlist, int index);
sp_error sp_playlist_track_set_seen (sp_playlist *playlist, int index, bool seen);
const char * sp_playlist_track_message (sp_playlist *playlist, int index);

It is clear that Hallon need to support these (or similar) API calls:

playlist.track[0].name # called on track
playlist.track[0].artist   # called on track
playlist.track[0].creator # called through playlist_track_creator
playlist.track[0].seen = true # called through playlist
playlist.track[0].message # called through playlist

I am thinking some kind of proxy object where you explicitly define your extra methods, and any other methods fall through to the underlying object at that position. Oh well, that’s for another day.

Add Session#login!

It would do the same thing as Session#login, except it’d be blocking and wait for an error or successful login.

Might also be worth it adding a Session#relogin!, too, as it’s pretty much the same functionality.

The reasoning behind this change is that you pretty much always want to log in to Spotify when using libspotify. It would be silly and tedious to copy-paste four lines of code just to log in if you need no special functionality.

Hallon sometimes deadlocks when running specs

After adding the rest of the Session callbacks, I noticed the specs sometimes deadlock. All occasions so far are on Hallon::Link.

I believe the cause of this is that ruby maintains GVL during the libspotify function calls on Hallon::Link, and that these function calls do some callback calling behind the curtains.

Some bugtracking is required.

Usage of Session.instance is inconsistent

Image.new takes the Session as a parameter, but defaults to Session.instance if none is given.

AlbumBrowse.new, on the other hand, retrieves it directly from Session.instance.

Pick one or the other, not both. If you do the former, keep in mind there’s the issue of methods like Track#starred, that can’t take the second parameter.

If libspotify ever gets support for multiple sessions, there’s room for a Session.with_instance(session) in the future. So I see no issues with the latter implementation.

FFI does not find libspotify functions on Windows

Following error is raised when loading Hallon:

in `attach_function': Function 'sp_error_message' not found in [libspotify] (FFI::NotFoundError)

This is a result of name mangling in windows DLLs, and I’ve created an issue for the libspotify dudes: http://getsatisfaction.com/spotify/topics/libspotify_windows_dll_contain_mangled_function_names

As of currently, this can be hacked (really, it’s evil) around by hooking the attach_function method in FFI::Library, and then iterating over the arguments to calculate the total byte size to finally mangle the name before trying to bind it.

Session#process_events_on should be able to listen for events on other objects

Possible solutions:

  1. Pass a Hash<Object,Event> (would allow one to listen for events on multiple objects):

    session.process_events_on(playlist_container => [:container_loaded], playlist => [:playlist_state_changed])

    Would be useful waiting for multiple tracks to load, for example (?). But… YAGNI?

  2. Allow passing the object to listen to as the first parameter:

    session.process_events_on(playlist, :playlist_state_changed, :playlist_metadata_updated)

    This would only allow you to listen for events on a single object, and this is the simplest thing
    that could possibly work. I think I will go with this implementation first, and later refactor if needed.

Number 2 has the benefit of making it easier to implement #24, as well.

Hallon deadlocks on exit

One funny way to solve this would to put an event in the event queue that kills the event thread when executed. Of course, none of the libspotify callbacks after this will be handled.

Special accessors for libmockspotify

Some specs right now only test to see that the correct libspotify functions are called. Should we instead add accessors, so it can be verified through the C code instead?

Link.new has a race condition with callbacks

Under the hood, it seems that libspotify might call callbacks as a side-effect of calling sp_link_create_from_string. If it does, the process will deadlock.

This issue is probably better fitting in Burgestrand/libspotify-ruby, but for now it’ll reside here. It would be nice with a list from the libspotify devs of which functions might exhibit this behaviour (that, or assume all are blocking, could be done I guess).

Mocking the libspotify API for testing

Right now we’re testing both Hallon, as well as libspotify itself. Considering we use FFI now, we could easily mock the API in Ruby, without having to write any C code for it.

Now, do we need to mock it? One could argue it’s sufficient to make sure the proper API methods are called, but then how do we handle the callbacks? Even worse, how do we know the libspotify bindings are correct (with types and such)?

Testing live against Spotify is fragile, requires an internet connection, a premium account and a great deal of faith from the person running the tests (making sure I don’t do anything evil on the account). However, it does make sure Hallon works as expected when using it for real.

As of now, live testing will do, but this is an open question.

  • pyspotify is tested using a mock module
  • libspotify-node tests live
  • Hallon (as of current) tests live

Solving the issue of synchronization

As libspotify is asynchronous, Hallon also aims to provide asynchronous callbacks. However, in order to test Hallon properly, one needs to write the code in a slightly more synchronous fashion. Doing this in a clean way is essential, both for using the library and for testing it. It’s proven hard to solve, but there’s a clear pattern in how I use the library.

Making sure no events are fired in the meantime, we:

  1. define event handlers on various objects (setting them up so we can wait for them to fire later)
  2. call our Hallon API method (login, load playlist etc)
  3. wait for callbacks from point no. 1 to fire, doing a specific action for each callback
  4. once a user-defined condition has been fulfilled, program flow continues

I’ll keep this issue up until I have a robust solution. For now, I have Session#process_events_on which should suffice for simple cases.

Segmentation fault on Link creation

Input:
require 'lib/hallon'
Hallon::Link.new 'spotify:track:4yJmwG2C1SDgcBbV50xI91'

Output:
[BUG] Segmentation fault

I believe this is an issue with libspotify.

Allow multiple handlers for a single event

Once the event is fired, the handlers are to be executed in the order they were added. Handlers can be removed, and the execution chain should be interruptible, allowing any handler to hide the event from the other handlers to be executed next.

Enumerator susceptible to memory bloat

# All {Artist}s who performed this Track.

Given a block, the returned Enumerator will have a reference to the “self” of the current track. Since this block is stored, Ruby needs to keep this reference for as long as the Enumerator exists.

What we should be doing is simply just keeping a reference to the @pointer. We don’t care about anything else.

Segfault on sp_session_release in cSession_free on GC

  1. trying to create a session but it fails, then releasing it (might be a bug, might be intended)
  2. creating a session then releasing it (libspotify bug, happens about 5-10% of the time)

I will not use sp_session_release at all until the bug is fixed. It is in the code, but commented out.

Support for sp_image_*

Support for image loading would be greatly appreciated.

If I only understood C, I would be more than willing to help implementing this.

Integration tests

libmockspotify for unit tests, which is nice because it means the unit tests will be speedy and some-what accurate.

However, to approach proper test coverage one would want some more thorough (and slower) tests, which would also serve as usage examples. These would log in to the Spotify service, perform a series of tasks, assert that they’ve been executed properly, clean up and then exit.

Examples of this are:

  • loading an image
  • loading track information
  • loading a playlist
    … etc

Upgrade to rspec v2

Suggestion: add a context for operations that require you to be offline / online

Versioning policy

Hallon should have a 0.x.y-version already (because of the current features). Construct a simple versioning policy and add it to the README.

Hallon and Resque don’t like each other

For some reason, requiring Resque and Hallon at the same time seem to cause things to come to a deadlock.

To reproduce, one should:

  1. Create a brand new Rails 3.0.x application (might not be necessary with rails, but it’s what we had just now).
  2. Add Hallon (HEAD) and Resque to the Gemfile.
  3. Start the console.
  4. Create a session.
  5. Log in to Spotify.
  6. Create a Link, a Track or whatever. Process events and stuff; just using Hallon causes it to hang.

Track.local adds an additional ref to the new track

Track.local calls Spotify.localtrack_create, which by name convention returns a track with its’ refcount increased. This means that in Track#initialize we should not be adding a ref to this pointer, but we do.

Now that I think of it, we could probably support Track.local by means of Track.new, simply by checking which arguments we’re given. If it’s one argument, we’re fetching an existing track; if it’s two, we’re creating a new local track. Bad idea? Not sure yet.

RUBY_UBF_IO does not interrupt hn_sem_wait

The UBF function should only interrupt the blocking function call. For example, unlocking the mutex or signaling the waiting condition. However, this cannot be reliably done for one thread so I need a special UBF for this.

If signals are delivered during these two pthread functions I believe RUBY_UBF_PROCESS uses SIGVTALARM. Maybe that can be used?

PS: This is only an issue if a thread needs to be killed while blocking on one of these methods. As it is now, it will not die. If the main thread deadlocks on a wait/lock (in a rb_blocking_region) the program will never exit.

Update to libspotify v10

Spotify also noted that all older libspotify versions are now deprecated. Yay cutting edge \o/

Events are sometimes missed by the event producer

It’s proven to be a tricky bug for me to solve. For some reason, the condition being waited on in events.c:27 is missing out on signals from the libspotify callbacks. The event_mutex lock is held when events.c is processing any events; and when it is not it is waiting on the event_cond. How can this be!?

Related files:

  • (brings C events into ruby) event producer: events.c

    Starts out by locking the event_mutex before anything else can be done. It
    then goes into a tight loop, waiting on signals on event_cond. When a signal
    is received, the shared memory structure is used to build an object in ruby
    equivalent to the C event.

  • (saves C events in shared memory) callbacks.c

    In here is the code that actually locks the event_mutex, fills the event
    structure and signals the event producer. The idea is that the event_cond is
    being waited on by the event producer, and when signalled it will pick up the
    event structure and dispatch it within ruby.

Refactor the way pointers are handled

A proper solution to this problem would hopefully solve #41 as well.

Currently, pretty much all Hallon objects accept an FFI::Pointer as an argument. Problem with this is that Hallon cannot know if this pointer needs its’ reference to be upped or not, but tries to make an educated guess. Most of the time this works, but it’s unreliable and frankly it makes the code harder to grasp.

In addition, Hallon also wraps this pointer into a Spotify::Pointer. Even at this point, we do not know if this pointer has an added reference. Still, this is the only object we care about existing in the first place.

Now, assuming we convert fully into using only AutoPointers (and such, Spotify::Pointer), we need some basic assumptions:

  1. If we have a Spotify::Pointer, its’ GC is fully taken care of (and will be released when it’s GC’d by ruby).
  2. If we don’t have a Spotify::Pointer, we can wrap an existing pointer and make it one.
  3. Wrapping an existing pointer into a Spotify::Pointer means we extend the life of the underlying pointer until the Spotify::Pointer is released by ruby (at which point we free it).

This spawns the question, who is responsible for giving life to the underlying possible with add_ref, if necessary? Is it the creator, or the wrapper? Thinking of it for a while makes it pretty clear that the wrapper object itself should be self-managing, and if it’s given a pointer that already has its’ refcount at 1 instead of 0, it’s the callers’ duty to decrease the refcount by one after wrapping the pointer.

As a final crazy idea, I had this thought that I could monkeypatch/wrap/proxy the Spotify API, so that the pointers it returns always has their refcount set at 0. That would make this a non-issue for me, but it would make it harder to use the raw API in conjunction with Hallon, and I think it’s already hard enough, tyvm.

Top priority on this at the moment. Creating the issue so I remember my thought process tomorrow morning when I’m sane enough to judge my own ideas (and not subject poor Elin to them).

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.