Permalink
Browse files

document a lot of stuff. tests are running. life is good

  • Loading branch information...
Floby committed Dec 23, 2012
1 parent a126006 commit c274f275dba131823a2e362a3bf3189f9b5f558d
Showing with 373 additions and 28 deletions.
  1. +82 −4 lib/Session.js
  2. +1 −0 package.json
  3. +81 −1 src/common.h
  4. +177 −14 src/session.cc
  5. +8 −0 test/test-000-a-check.js
  6. +15 −8 test/test-010-session01-instance.js
  7. +9 −1 test/test-010-session02-login.js
View
@@ -5,11 +5,18 @@ var fs = require('fs');
var util = require('util');
var EventEmitter = require('events').EventEmitter;
+/**
+ * Default settings for the session_config struct
+ * see libspotify documentation for more info on which is what
+ * https://developer.spotify.com/technologies/libspotify/docs/12.1.45/annotated.html
+ */
var defaultSessionConfig = {
cache_location: "/tmp/node-libspotify",
settings_location: "/tmp/node-libspotify",
application_key: new Buffer(0),
user_agent: "node-libspotify",
+ // we could completely remove callbacks here, because
+ // we don't create a callbacks struct from JS at all
callbacks: null,
compress_playlists: false,
dont_save_metadata_for_playlists: true,
@@ -22,48 +29,87 @@ var defaultSessionConfig = {
tracefile: null
};
+/**
+ * a Spotify Session object. Everything in spotify occurs within a given session
+ * currently, the C libspotify library provided by spotify is _really_ buggy when using more
+ * than one session per process (like really really). So in effect, this JS class
+ * makes sure that no C session struct gets created and instead re-uses the same JS object
+ * over and over. Cleaning it each time new is called
+ * @constructor
+ * @param
+ */
function Session (params) {
+ // since we only authorize one session object,
+ // make sure we do
if (Session.currentSession) {
+ // throw if current session is still open
+ if(Session.currentSession.isOpen()) {
+ throw new Error('Only one spotify session can live at once');
+ }
Session.currentSession.close();
+ // increment current session id
Session.currentSession._id++;
+ // cleaning everything
Session.currentSession.removeAllListeners();
Session.currentSession._setupNativeCallbacks();
Session.currentSession._startProcessingEvents();
return Session.currentSession;
}
- this._id = 0;
+ this._id = 0; ///> the id of the current session. this is used to make sure we only have one at a given time
params = params || {};
+ // read parameters from the config object, changing them to a C-style name
var cparams = new Object(defaultSessionConfig);
for(var key in params) {
var k = i.underscore(key);
cparams[k] = params[key];
}
+ // try to read application key from the given parameter
var key = null;
+ // if the parameter is a string, then it's a path to the key file
if(typeof cparams.application_key == 'string') {
cparams.application_key = fs.readFileSync(cparams.application_key);
}
+ // if the parameter is not a node buffer, then it's probably not what we want
else if(! (cparams.application_key instanceof Buffer)) {
throw new TypeError('No application key specified for this spotify session');
}
+ // if the parameter is an empty buffer, we've got a problem
else if(cparams.application_key.length == 0) {
throw new TypeError('Empty application key specified for this spotify session');
}
+ // create a config object from these parameters
var session_config = b.session_config(cparams);
- this._sp_session = b.session_create(session_config);
+ // create the underlying sp_session object
+ this._sp_session = b.session_create(session_config);
+ // set this session as new current session
Session.currentSession = this;
+ // setup callbacks from spotify events called from C
this._setupNativeCallbacks();
+
+ // start the loop to process pending events
this._startProcessingEvents();
}
util.inherits(Session, EventEmitter);
+function sessionCallback (session, sid, cb) {
+ return function() {
+ if(sid != session._id) return;
+ return cb.apply(session, args);
+ }
+}
+
+/**
+ * C callbacks for spotify events do nothing more than calling their JS equivalent
+ * here we setup the functions that must be called from C upon these events
+ */
Session.prototype._setupNativeCallbacks = function _setupNativeCallbacks() {
var self = this;
var session_id = this._id;
@@ -77,40 +123,72 @@ Session.prototype._setupNativeCallbacks = function _setupNativeCallbacks() {
}
};
+/**
+ * call spotify sp_session_start_processing_events so that pending events
+ * can be processed. Current implementation processes events every 0.8 seconds
+ * regardless of what libspotify returns as next timeout.
+ * TODO ^
+ */
Session.prototype._startProcessingEvents = function _startProcessingEvents() {
if(this._process_events_timeout) return;
var self = this;
var id = this._id;
this._process_events_timeout = setInterval(function() {
- console.log('processing events', id);
+ //console.log('processing events', id);
if(id != self._id) return;
var next_timeout = b.session_process_events(self._sp_session);
+ //console.log('processing again in %d', next_timeout);
}, 800);
};
+/**
+ * close this session. In effect, just stopping processing events
+ * TODO maybe make sure we are logged out before considering ourselves closed
+ * then fire a 'close' event
+ */
Session.prototype.close = function close() {
clearInterval(this._process_events_timeout);
this._process_events_timeout = null;
};
+/**
+ * tests if the current session is closed
+ */
Session.prototype.isClosed = function isClosed() {
- return this._sp_session == null;
+ return this._process_events_timeout === null;
};
+/**
+ * tests if the current sessino is open
+ */
Session.prototype.isOpen = function isOpen() {
return !this.isClosed();
};
+/**
+ * try to login with the given credentials
+ * when this request is processed by libspotify, the 'login' event is
+ * fired with an error or null as first argument
+ * @param {String} login
+ * @param {String} password
+ */
Session.prototype.login = function login(login, password) {
if('string' != typeof login) throw new TypeError('login should be a string')
if('string' != typeof password) throw new TypeError('password should be a string')
b.session_login(this._sp_session, login, password);
};
+
+/**
+ * try to logout.
+ * when this request is processed by libspotify, the 'logout' event is triggered
+ * @param {Function} cb. a callback to automatically attach to the logout event
+ */
Session.prototype.logout = function logout(cb) {
b.session_logout(this._sp_session);
if(typeof cb === 'function') {
this.on('logout', cb)
}
};
+// exports this Class
module.exports = Session;
View
@@ -30,6 +30,7 @@
},
"devDependencies": {
"nodeunit": "~0.7",
+ "trycatch": "*",
"colors": "*"
},
"scripts": {
View
@@ -25,17 +25,33 @@
#include <assert.h>
+/**
+ * In a C++ function called from JS, takes a spotify error code, tests if not OK and throws an exception
+ * with the appropriate error message
+ */
#define NSP_THROW_IF_ERROR(error) if(error != SP_ERROR_OK) {return v8::ThrowException( \
v8::Exception::Error(v8::String::New(sp_error_message( error )))\
);}
+/**
+ * Reads a value at the given key and returns a C boolean value
+ * @param o: the JS object on which to read at the given key
+ * @param name: the key to read from
+ * @return the boolean value read from the object
+ */
inline bool NSP_BOOL_KEY(v8::Handle<v8::Object> o, const char* name) {
assert(o->IsObject());
v8::Handle<v8::Value> value = o->Get(v8::String::New(name));
assert(value->IsBoolean());
return value->BooleanValue();
}
+/**
+ * Reads a value at the given key and returns a C int value
+ * @param o: the JS object on which to read at the given key
+ * @param name: the key to read from
+ * @return the int value read from the object
+ */
inline int NSP_INT_KEY(v8::Handle<v8::Object> o, const char* name) {
assert(o->IsObject());
v8::Handle<v8::Value> value = o->Get(v8::String::New(name));
@@ -44,6 +60,14 @@ inline int NSP_INT_KEY(v8::Handle<v8::Object> o, const char* name) {
return value->Int32Value();
}
+/**
+ * Reads a value at the given key and returns a C string value
+ * NOTE: this function allocates the needed space for the string
+ * it is the responsibility of the caller to free this pointer
+ * @param o: the JS object on which to read at the given key
+ * @param name: the key to read from
+ * @return the string value read from the object
+ */
inline char* NSP_STRING_KEY(v8::Handle<v8::Object> o, const char* name) {
assert(o->IsObject());
v8::Handle<v8::Value> value = o->Get(v8::String::New(name));
@@ -57,35 +81,87 @@ inline char* NSP_STRING_KEY(v8::Handle<v8::Object> o, const char* name) {
return v;
}
+/**
+ * Reads a value at the given key and returns a Node buffer object
+ * @param o: the JS object on which to read at the given key
+ * @param name: the key to read from
+ * @return the node buffer object read from the object
+ */
inline char* NSP_BUFFER_KEY(v8::Handle<v8::Object> o, const char* name) {
assert(o->IsObject());
v8::Handle<v8::Value> value = o->Get(v8::String::New(name));
assert(node::Buffer::HasInstance(value));
return node::Buffer::Data(value->ToObject());
}
+/**
+ * Reads a value at the given key and returns a C int value which is the
+ * length of a Node buffer object
+ * @param o: the JS object on which to read at the given key
+ * @param name: the key to read from
+ * @return the length of the buffer read from the object
+ */
inline int NSP_BUFFERLENGTH_KEY(v8::Handle<v8::Object> o, const char* name) {
assert(o->IsObject());
v8::Handle<v8::Value> value = o->Get(v8::String::New(name));
assert(node::Buffer::HasInstance(value));
return node::Buffer::Length(value->ToObject());
}
+/**
+ * Stands for Node+Spotify
+ * namespace in which I declare most of the stuff I need for these bindings
+ */
namespace nsp {
+
+ /**
+ * A function to use as a JS function that does nothing and returns this
+ */
v8::Handle<v8::Value> JsNoOp(const v8::Arguments&);
+
+ /**
+ * init the session related functions to the target module exports
+ */
void init_session(v8::Handle<v8::Object> 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
+ * does not need a constructor and both attributes are public.
+ * Node's ObjectWrap is useful to wrap C++ classes whereas this class is useful
+ * to wrap C structs. THIS CLASS DOES NOT MANAGE C MEMORY ALLOCATION
+ */
template <typename T>
class ObjectHandle {
public:
+ /**
+ * @constructor
+ * Create a new ObjectHandle object with the given name
+ * the name can be used later to identify the wrapped objects
+ */
ObjectHandle(const char* name);
- //~ObjectHandle();
+ /**
+ * Utility function to retrieve an ObjectHandle from a JS object
+ * @param obj, the JS Object in which the ObjectHandle was wrapped
+ */
static ObjectHandle<T>* Unwrap(v8::Handle<v8::Value> obj);
+ /**
+ * A pointer to the C struct (most often) that we want to wrap
+ * We do not allocate this
+ */
T* pointer;
+
+ /**
+ * The JS Object that we set our pointer in
+ * We do create this one
+ */
v8::Persistent<v8::Object> object;
+ /**
+ * Get the name of the ObjectHandle that we gave it during instanciation
+ */
char* GetName() {
return *(v8::String::Utf8Value(name_));
}
@@ -94,6 +170,10 @@ namespace nsp {
private:
v8::Persistent<v8::String> name_;
+ /**
+ * Empty function to set as constructor for an FunctionTemplate
+ * @deprecated
+ */
v8::Handle<v8::Value> New(const v8::Arguments& args) {
v8::HandleScope scope;
// do nothing;
Oops, something went wrong.

0 comments on commit c274f27

Please sign in to comment.