diff --git a/binding.gyp b/binding.gyp index f6caa2e..11e7349 100644 --- a/binding.gyp +++ b/binding.gyp @@ -5,6 +5,7 @@ "sources": [ "src/album.cc", "src/artist.cc", + "src/artistbrowse.cc", "src/audio.cc", "src/binding.cc", "src/link.cc", diff --git a/lib/Artist.js b/lib/Artist.js index 8f75a4d..848141d 100644 --- a/lib/Artist.js +++ b/lib/Artist.js @@ -27,5 +27,40 @@ Artist.prototype.toString = function toString() { return this.name; }; +Artist.prototype.getAlbums = function getAlbums(cb) { + var browser = b.artistbrowse_create(this.getSession()._sp_session, this._sp_object, function () { + albums = new Array(b.artistbrowse_num_albums(browser)); + + for(var i = 0; i Album_Is_Loaded(const Arguments& args) { return scope.Close(Boolean::New(loaded)); } +/** + * JS album_is_available implementation. checks if a given album is available + */ +static Handle Album_Is_Available(const Arguments& args) { + HandleScope scope; + + // test arguments sanity + assert(args.Length() == 1); + assert(args[0]->IsObject()); + + // gets sp_album pointer from given object + ObjectHandle* album = ObjectHandle::Unwrap(args[0]); + + // actually call sp_album_is_available + bool available = sp_album_is_available(album->pointer); + + return scope.Close(Boolean::New(available)); +} + /** * JS album_name implementation. checks if a given album is loaded */ @@ -196,6 +215,7 @@ static Handle Album_Cover(const Arguments& args) { void nsp::init_album(Handle target) { NODE_SET_METHOD(target, "album_is_loaded", Album_Is_Loaded); + NODE_SET_METHOD(target, "album_is_available", Album_Is_Available); NODE_SET_METHOD(target, "album_name", Album_Name); NODE_SET_METHOD(target, "album_year", Album_Year); NODE_SET_METHOD(target, "album_type", Album_Type); diff --git a/src/artistbrowse.cc b/src/artistbrowse.cc new file mode 100644 index 0000000..ada0487 --- /dev/null +++ b/src/artistbrowse.cc @@ -0,0 +1,102 @@ +/* + * ===================================================================================== + * + * Filename: artistbrowse.cc + * + * Description: bindings for the artist subsystem + * + * Version: 1.0 + * Revision: none + * Compiler: gcc + * + * Author: Linus Unnebäck, linus@folkdatorn.se + * Company: LinusU AB + * + * ===================================================================================== + */ + + +#include "common.h" + +using namespace v8; +using namespace nsp; + +void cb_artistbrowse_complete (sp_artistbrowse *result, void *userdata) { + Persistent callback = static_cast(userdata); + + callback->Call(callback, 0, NULL); + callback.Dispose(); +} + +static Handle ArtistBrowse_Create(const Arguments& args) { + HandleScope scope; + + // test arguments sanity + assert(args.Length() == 3); + assert(args[0]->IsObject()); // sp_session + assert(args[1]->IsObject()); // sp_artist + assert(args[2]->IsFunction()); // callback + + ObjectHandle *session = ObjectHandle::Unwrap(args[0]); + ObjectHandle *artist = ObjectHandle::Unwrap(args[1]); + Handle callback = Persistent::New(Handle::Cast(args[2])); + + ObjectHandle* artistbrowse = new ObjectHandle("sp_artistbrowse"); + artistbrowse->pointer = sp_artistbrowse_create(session->pointer, artist->pointer, SP_ARTISTBROWSE_NO_TRACKS, cb_artistbrowse_complete, *callback); + + return scope.Close(artistbrowse->object); +} + +static Handle ArtistBrowse_Num_Albums(const Arguments& args) { + HandleScope scope; + + // test arguments sanity + assert(args.Length() == 1); + assert(args[0]->IsObject()); // sp_artistbrowse + + ObjectHandle *artistbrowse = ObjectHandle::Unwrap(args[0]); + const int num = sp_artistbrowse_num_albums(artistbrowse->pointer); + + return scope.Close(Number::New(num)); +} + +static Handle ArtistBrowse_Album(const Arguments& args) { + HandleScope scope; + + // test arguments sanity + assert(args.Length() == 2); + assert(args[0]->IsObject()); // sp_artistbrowse + assert(args[1]->IsNumber()); // index + + // input + ObjectHandle *artistbrowse = ObjectHandle::Unwrap(args[0]); + int index = args[1]->ToNumber()->Int32Value(); + + // output + sp_album* spalbum = sp_artistbrowse_album(artistbrowse->pointer, index); + ObjectHandle* album = new ObjectHandle("sp_album"); + album->pointer = spalbum; + + return scope.Close(album->object); +} + +static Handle ArtistBrowse_Release(const Arguments& args) { + HandleScope scope; + + // test arguments sanity + assert(args.Length() == 1); + assert(args[0]->IsObject()); // sp_artistbrowse + + ObjectHandle *artistbrowse = ObjectHandle::Unwrap(args[0]); + sp_error error = sp_artistbrowse_release(artistbrowse->pointer); + NSP_THROW_IF_ERROR(error); + + return scope.Close(Undefined()); +} + +void nsp::init_artistbrowse(Handle target) { + NODE_SET_METHOD(target, "artistbrowse_create", ArtistBrowse_Create); + NODE_SET_METHOD(target, "artistbrowse_num_albums", ArtistBrowse_Num_Albums); + NODE_SET_METHOD(target, "artistbrowse_album", ArtistBrowse_Album); + NODE_SET_METHOD(target, "artistbrowse_release", ArtistBrowse_Release); +} diff --git a/src/binding.cc b/src/binding.cc index f1484bf..c8b39a0 100644 --- a/src/binding.cc +++ b/src/binding.cc @@ -28,6 +28,7 @@ extern "C" { // initializing all modules nsp::init_album(target); nsp::init_artist(target); + nsp::init_artistbrowse(target); nsp::init_link(target); nsp::init_player(target); nsp::init_search(target); diff --git a/src/common.h b/src/common.h index 79b8e53..aa70b55 100644 --- a/src/common.h +++ b/src/common.h @@ -144,6 +144,7 @@ namespace nsp { * init the artist related functions to the target module exports */ void init_artist(v8::Handle target); + void init_artistbrowse(v8::Handle target); /** * init the link related functions to the target module exports */ @@ -156,7 +157,7 @@ namespace nsp { * init the playlist related functions to the target module exports */ void init_playlist(v8::Handle target); - + /** * This utility class allows to keep track of a C pointer that we attached * to a JS object. It differs from node's ObjectWrap in the fact that it @@ -191,7 +192,7 @@ namespace nsp { * We do create this one */ v8::Persistent object; - + /** * Get the name of the ObjectHandle that we gave it during instanciation */ @@ -229,7 +230,7 @@ namespace nsp { object->SetPointerInInternalField(0, this); } - + template ObjectHandle* ObjectHandle::Unwrap(v8::Handle obj) { assert(obj->IsObject()); diff --git a/src/link.cc b/src/link.cc index 947c0ea..93001d4 100644 --- a/src/link.cc +++ b/src/link.cc @@ -3,7 +3,7 @@ * * Filename: link.cc * - * Description: bindings for links subsystem + * Description: bindings for links subsystem * * Version: 1.0 * Created: 07/01/2013 12:37:03 @@ -42,6 +42,24 @@ static Handle Link_Create_From_Track(const Arguments& args) { return scope.Close(String::New(url)); } +static Handle Link_Create_From_Album(const Arguments& args) { + HandleScope scope; + + // test arguments sanity + assert(args.Length() == 1); + assert(args[0]->IsObject()); + + // gets sp_album pointer from given object + ObjectHandle* album = ObjectHandle::Unwrap(args[0]); + + sp_link* link = sp_link_create_from_album(album->pointer); + char url[256]; + // TODO handle truncated urls + sp_link_as_string(link, url, 256); + + return scope.Close(String::New(url)); +} + static Handle Link_Create_From_Artist(const Arguments& args) { HandleScope scope; @@ -96,6 +114,24 @@ static Handle Link_As_Track(const Arguments& args) { return scope.Close(track->object); } +static Handle Link_As_Album(const Arguments& args) { + HandleScope scope; + + // test arguments sanity + assert(args.Length() == 1); + assert(args[0]->IsString()); + + String::Utf8Value url(args[0]); + + sp_link* link = sp_link_create_from_string(*url); + assert(sp_link_type(link) == SP_LINKTYPE_ALBUM); + + ObjectHandle* album = new ObjectHandle("sp_album"); + album->pointer = sp_link_as_album(link); + + return scope.Close(album->object); +} + static Handle Link_As_Artist(const Arguments& args) { HandleScope scope; @@ -132,7 +168,7 @@ static Handle Link_As_Playlist(const Arguments& args) { ObjectHandle* playlist = new ObjectHandle("sp_playlist"); playlist->pointer = sp_playlist_create(session->pointer, link); - + // Add callbacks sp_error error = sp_playlist_add_callbacks(playlist->pointer, &nsp_playlist_callbacks, playlist); NSP_THROW_IF_ERROR(error); @@ -162,6 +198,9 @@ static Handle Link_Type(const Arguments& args) { case SP_LINKTYPE_ARTIST: type = "artist"; break; + case SP_LINKTYPE_ALBUM: + type = "album"; + break; case SP_LINKTYPE_TRACK: type = "track"; break; @@ -177,9 +216,11 @@ static Handle Link_Type(const Arguments& args) { void nsp::init_link(Handle target) { NODE_SET_METHOD(target, "link_create_from_track", Link_Create_From_Track); + NODE_SET_METHOD(target, "link_create_from_album", Link_Create_From_Album); NODE_SET_METHOD(target, "link_create_from_artist", Link_Create_From_Artist); NODE_SET_METHOD(target, "link_create_from_playlist", Link_Create_From_Playlist); NODE_SET_METHOD(target, "link_as_track", Link_As_Track); + NODE_SET_METHOD(target, "link_as_album", Link_As_Album); NODE_SET_METHOD(target, "link_as_artist", Link_As_Artist); NODE_SET_METHOD(target, "link_as_playlist", Link_As_Playlist); NODE_SET_METHOD(target, "link_type", Link_Type); diff --git a/test/test-034-artistbrowse.js b/test/test-034-artistbrowse.js new file mode 100644 index 0000000..7b6872d --- /dev/null +++ b/test/test-034-artistbrowse.js @@ -0,0 +1,39 @@ +var sp = require('../lib/libspotify'); +var testutil = require('./util'); + +var getArtist = function(test, cb) { + var search = new sp.Search('artist:"Hurts"'); + search.trackCount = 1; + search.execute(function() { + test.ok(search.tracks.length > 0, 'the track was found'); + test.ok(search.tracks[0] instanceof sp.Track, 'track is an track'); + test.ok(search.tracks[0].album instanceof sp.Album, 'album is an album'); + test.ok(search.tracks[0].album.artist instanceof sp.Artist, 'artist is an artist'); + cb(search.tracks[0].album.artist); + }); +}; + +var session = null; + +exports.artistbrowse = { + setUp: function(cb) { + testutil.getDefaultTestSession(function(s) { + session = s; + cb(); + }); + }, + 'get albums from artist': function(test) { + getArtist(test, function(artist) { + artist.getAvailableAlbums(function(err, albums) { + test.ifError(err); + /* FIXME: Should be 22, see comment in lib/Artist.js line 50 */ + test.equal(albums.length, 23, 'the artist has 23 available albums'); + test.equal(albums.map(function(e) {return e instanceof sp.Album;}).indexOf(false), -1, 'It should only contain albums'); + test.equal(albums.reduce(function(prev, current) { + return prev && current.isReady(); + }, true), true, 'All albums should be loaded'); + test.done(); + }); + }); + } +} diff --git a/test/test-065-link-types.js b/test/test-065-link-types.js index 9ffa5d9..b1d7fa8 100644 --- a/test/test-065-link-types.js +++ b/test/test-065-link-types.js @@ -28,10 +28,12 @@ exports.links = { }, "Getting link type from anything else than string should throw"); var track_link = 'spotify:track:4BdSLkzKO6iMVCgw7A7JBl'; + var album_link = 'spotify:album:2UGJa9DjYhXpBDKsCTyhSh'; var artist_link = 'spotify:artist:3zD5liDjbqljSRorrrcEjs'; var playlist_link = 'spotify:user:flobyiv:playlist:5ZMnMnJWGXZ9qm4gacHpQF'; test.doesNotThrow(function() { test.equal('track', sp.getLinkType(track_link), "Link type should be 'track'"); + test.equal('album', sp.getLinkType(album_link), "Link type should be 'album'"); test.equal('artist', sp.getLinkType(artist_link), "Link type should be 'artist'"); test.equal('playlist', sp.getLinkType(playlist_link), "Link type should be 'playlist'"); }, "Getting link types should not throw"); diff --git a/test/test-066-link-from-objects.js b/test/test-066-link-from-objects.js index 9e6cbdb..2caadab 100644 --- a/test/test-066-link-from-objects.js +++ b/test/test-066-link-from-objects.js @@ -27,6 +27,7 @@ exports.links = { }); }, "get link from track": testLink(sp.Track, 'spotify:track:4BdSLkzKO6iMVCgw7A7JBl'), + "get link from album": testLink(sp.Album, 'spotify:album:2UGJa9DjYhXpBDKsCTyhSh'), "get link from artist": testLink(sp.Artist, 'spotify:artist:4ZCLbhEKI7019HKbk5RsUq'), "get link from playlist": testLink(sp.Playlist, 'spotify:user:flobyiv:playlist:2t8yWR57SFWSKHtOlWr095'), 'get artist link from artist': function(test) { @@ -40,13 +41,21 @@ exports.links = { }); }, 'get artist from link': function(test) { - var track = sp.Artist.getFromUrl('spotify:artist:3zD5liDjbqljSRorrrcEjs'); - test.ok(track instanceof sp.Artist, 'the returned object should be an artist'); - track.on('ready', function() { - test.equal('Guillemots', track.name, 'this should be a guillemots track'); + var artist = sp.Artist.getFromUrl('spotify:artist:3zD5liDjbqljSRorrrcEjs'); + test.ok(artist instanceof sp.Artist, 'the returned object should be an artist'); + artist.on('ready', function() { + test.equal('Guillemots', artist.name, 'this should be the Guillemots artist'); test.done(); }); }, + 'get album from link': function(test) { + var album = sp.Album.getFromUrl('spotify:album:2UGJa9DjYhXpBDKsCTyhSh'); + test.ok(album instanceof sp.Album, 'the returned object should be an album'); + album.on('ready', function () { + test.equal('Exile (Deluxe)', album.name, 'this should be the Exile (Deluxe) album'); + test.done(); + }); + } };