|
|
|
/*jslint laxbreak:true */
|
|
|
|
/*jslint laxcomma:true */
|
|
|
|
/*jslint loopfunc:true */
|
|
|
|
/*jslint strict:true */
|
|
|
|
/*jslint browser:true */
|
|
|
|
/*jslint devel:true */
|
|
|
|
/**
|
|
|
|
* TODO: If the user agrees, set the username/passs in a cookie
|
|
|
|
**/
|
|
|
|
define([
|
|
|
|
"underscore"
|
|
|
|
, "backbone"
|
|
|
|
, "toolbox"
|
|
|
|
, "models/dmap-type"
|
|
|
|
, "models/dmap"
|
|
|
|
], function (_, Backbone, Toolbox, DMAPType, DMAPModel) {
|
|
|
|
"use strict";
|
|
|
|
|
|
|
|
var Client = Toolbox.Base.extend(_.extend({
|
|
|
|
|
|
|
|
constructor: function(attributes, options) {
|
|
|
|
_.bindAll(this
|
|
|
|
, "setAuth"
|
|
|
|
, "setState"
|
|
|
|
, "getState"
|
|
|
|
, "init"
|
|
|
|
, "deinit"
|
|
|
|
, "logout"
|
|
|
|
, "buildRequestUid"
|
|
|
|
, "url"
|
|
|
|
, "urlHost"
|
|
|
|
, "urlMedia"
|
|
|
|
|
|
|
|
, "xhr"
|
|
|
|
, "fetchXHR"
|
|
|
|
, "fetchContentTypes"
|
|
|
|
, "fetchServerInfo"
|
|
|
|
, "fetchLogin"
|
|
|
|
, "fetchDatabases"
|
|
|
|
, "fetchDatabase"
|
|
|
|
, "fetchPlaylists"
|
|
|
|
, "fetchPlaylist"
|
|
|
|
, "fetchBrowse"
|
|
|
|
, "fetchGenres"
|
|
|
|
, "fetchArtists"
|
|
|
|
, "fetchAlbums"
|
|
|
|
|
|
|
|
, "on"
|
|
|
|
, "off"
|
|
|
|
, "trigger"
|
|
|
|
);
|
|
|
|
|
|
|
|
_.extend(this, Client.defaults);
|
|
|
|
this.attributes = _.extend({}, Client.prototype.attributes, attributes);
|
|
|
|
|
|
|
|
this.setAuth();
|
|
|
|
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
, setAuth: function(username, password) {
|
|
|
|
if (!_.isUndefined(username)) {
|
|
|
|
this.attributes.username = username;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!_.isUndefined(password)) {
|
|
|
|
this.attributes.password = password;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!_.isEmpty(this.attributes.password) || !_.isEmpty(this.attributes.username)) {
|
|
|
|
this.attributes.basicauth = window.btoa((this.attributes.username || "") + ":" + (this.attributes.password || ""));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: Many process should be able to set a state at the same time and it will change only when all of them are finoshed
|
|
|
|
, setState: function (state) {
|
|
|
|
if (_.isNumber(state)) {
|
|
|
|
this.trigger("state", this, state);
|
|
|
|
|
|
|
|
return this.state = state;
|
|
|
|
} else if (_.isString(state) && !_.isUndefined(this._states[state])) {
|
|
|
|
this.trigger("state", this, this._states[state]);
|
|
|
|
|
|
|
|
return this.state = this._states[state];
|
|
|
|
}
|
|
|
|
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
|
|
|
|
, getState: function () {
|
|
|
|
return this.state;
|
|
|
|
}
|
|
|
|
|
|
|
|
, init: function () {
|
|
|
|
var that = this
|
|
|
|
;
|
|
|
|
|
|
|
|
this.setState("connecting");
|
|
|
|
|
|
|
|
//console.debug("Fetching content types");
|
|
|
|
that.fetchContentTypes(function __fnClientCbFetchContentTypes(contentTypes) {
|
|
|
|
//console.debug("Fetching server info");
|
|
|
|
that.fetchServerInfo(function __fnClientCbFetchServerInfo(serverInfo) {
|
|
|
|
//console.debug("Fetching login");
|
|
|
|
that.fetchLogin(function __fnClientCbFetchLogin(sessionInfo) {
|
|
|
|
// TODO: Here we need to request /update for the database version
|
|
|
|
//console.debug("Fetching databases info");
|
|
|
|
//that.fetchDatabases(function () {
|
|
|
|
//console.debug("Fetching database 1");
|
|
|
|
//that.fetchDatabase(function () {
|
|
|
|
//console.debug("Fetching playlists info");
|
|
|
|
//that.fetchPlaylists(function () {
|
|
|
|
//console.debug("Fetching playlist 1 of db 1");
|
|
|
|
//that.fetchPlaylist(function () {
|
|
|
|
//console.debug("Fetching genres of db 1");
|
|
|
|
//that.fetchGenres(function () {
|
|
|
|
//console.debug("Fetching artists of db 1");
|
|
|
|
//that.fetchArtists(function () {
|
|
|
|
//console.debug("Fetching albums of db 1");
|
|
|
|
//that.fetchAlbums(function () {
|
|
|
|
that.setState("connected");
|
|
|
|
that.trigger("inited", serverInfo);
|
|
|
|
//});
|
|
|
|
//});
|
|
|
|
//});
|
|
|
|
//});
|
|
|
|
//});
|
|
|
|
//});
|
|
|
|
//});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
, deinit: function () {
|
|
|
|
var that = this
|
|
|
|
;
|
|
|
|
|
|
|
|
this.logout();
|
|
|
|
}
|
|
|
|
|
|
|
|
,logout: function () {
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
, buildRequestUid: function(path, fields, query) {
|
|
|
|
var that = this
|
|
|
|
;
|
|
|
|
|
|
|
|
if (_.isEmpty(path) || !_.isString(path)) {
|
|
|
|
throw new Error("INVALID_TYPE_ERROR");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!_.isArray(fields)) {
|
|
|
|
fields = [];
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!_.isArray(query)) {
|
|
|
|
query = [];
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
,url: function (request) {
|
|
|
|
var that = this
|
|
|
|
, uri = ""
|
|
|
|
;
|
|
|
|
|
|
|
|
if (typeof request === "string") {
|
|
|
|
uri = request; // Add leading slash here!
|
|
|
|
}
|
|
|
|
|
|
|
|
if (_.isNumber(that.collections.session.id)) {
|
|
|
|
var prefix = "?";
|
|
|
|
|
|
|
|
if (uri.indexOf("?") >= 0) {
|
|
|
|
prefix = "&";
|
|
|
|
}
|
|
|
|
|
|
|
|
uri += prefix + "session-id=" + that.collections.session.id;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (uri.charAt(0) !== "/" && uri.charAt(0) !== "?") {
|
|
|
|
uri = "/" + uri;
|
|
|
|
}
|
|
|
|
|
|
|
|
return this.attributes.protocol + "://" + this.urlHost() + uri;
|
|
|
|
}
|
|
|
|
|
|
|
|
, urlHost: function () {
|
|
|
|
var that = this
|
|
|
|
;
|
|
|
|
|
|
|
|
return this.attributes.hostname + ":" + this.attributes.port;
|
|
|
|
}
|
|
|
|
|
|
|
|
, urlMedia: function (itemid, type, dbid) {
|
|
|
|
if (_.isUndefined(dbid) || !_.isNumber(dbid)) {
|
|
|
|
dbid = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return this.url("/databases/" + dbid + "/items/" + itemid + "." + type);
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: Could be interesting to set a useragent, could possibly affect transcoding on the server side
|
|
|
|
, xhr: function (url, responseType, callback) {
|
|
|
|
var that = this
|
|
|
|
;
|
|
|
|
|
|
|
|
var xhr = new XMLHttpRequest();
|
|
|
|
var contentType = "text/plain; charset=utf-8";
|
|
|
|
|
|
|
|
if (typeof responseType === "function") {
|
|
|
|
callback = responseType;
|
|
|
|
responseType = "arraybuffer";
|
|
|
|
}
|
|
|
|
|
|
|
|
xhr.open("GET", url, true);
|
|
|
|
xhr.responseType = responseType;
|
|
|
|
xhr.timeout = 2000;
|
|
|
|
xhr.setRequestHeader("Content-type", contentType);
|
|
|
|
xhr.overrideMimeType("text/plain; charset=x-user-defined"); // Technically not need with v2 and responseType but for good measures
|
|
|
|
|
|
|
|
if (!_.isEmpty(that.attributes.basicauth)) {
|
|
|
|
xhr.withCredentials = true;
|
|
|
|
xhr.setRequestHeader("Authorization", "Basic " + that.attributes.basicauth);
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
xhr.onerror = function () {console.log("Error", arguments, this);};
|
|
|
|
xhr.onabort = function () {console.log("Abort", arguments, this);};
|
|
|
|
xhr.ontimeout = function () {
|
|
|
|
that.setState("timedout");
|
|
|
|
};
|
|
|
|
|
|
|
|
xhr.onload = function (e) {
|
|
|
|
if (this.status === 200) {
|
|
|
|
callback(null, this.response);
|
|
|
|
} else if (this.status === 401) {
|
|
|
|
that.setState("unauthorized");
|
|
|
|
that.trigger("unauthorized", that, [this, e]);
|
|
|
|
} else {
|
|
|
|
that.setState("failed");
|
|
|
|
callback(e.status);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
xhr.send();
|
|
|
|
} catch (e) {
|
|
|
|
console.error(e);
|
|
|
|
callback(e, null);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
, fetchXHR: function (path, success, error) {
|
|
|
|
var that = this
|
|
|
|
;
|
|
|
|
|
|
|
|
that.xhr(that.url(path), function __fetchXHRCallback(err, content) {
|
|
|
|
if (err) {
|
|
|
|
if (!_.isFunction(error)) {
|
|
|
|
error = function (err, cont) {
|
|
|
|
throw [err, cont];
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
error(err, content);
|
|
|
|
} else {
|
|
|
|
success(content);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
, fetchContentTypes: function (success, error) {
|
|
|
|
var that = this
|
|
|
|
;
|
|
|
|
|
|
|
|
that.fetchXHR("/content-codes", function __fetchContentTypesCallback(content) {
|
|
|
|
that.collections.contentCodes = new DMAPModel(content);
|
|
|
|
if (_.isFunction(success)) {
|
|
|
|
success(that.collections.contentCodes);
|
|
|
|
}
|
|
|
|
}, error);
|
|
|
|
}
|
|
|
|
|
|
|
|
, fetchServerInfo: function (success, error) {
|
|
|
|
var that = this
|
|
|
|
;
|
|
|
|
|
|
|
|
that.fetchXHR("/server-info", function __fetchServerInfoCallback(content) {
|
|
|
|
that.collections.serverInfo = new DMAPModel(content, {
|
|
|
|
contentCodes: that.collections.contentCodes
|
|
|
|
});
|
|
|
|
|
|
|
|
if (_.isFunction(success)) {
|
|
|
|
success(that.collections.serverInfo);
|
|
|
|
}
|
|
|
|
}, error);
|
|
|
|
}
|
|
|
|
|
|
|
|
, fetchLogin: function (success, error) {
|
|
|
|
var that = this
|
|
|
|
;
|
|
|
|
|
|
|
|
// TODO: Check for a cookie here...
|
|
|
|
that.fetchXHR("/login", function __fetchServerInfoCallback(content) {
|
|
|
|
var Results = new DMAPModel(content, {
|
|
|
|
contentCodes: that.collections.contentCodes
|
|
|
|
});
|
|
|
|
|
|
|
|
that.collections.session.id = Results.get("dmap_sessionid");
|
|
|
|
|
|
|
|
if (_.isFunction(success)) {
|
|
|
|
success(that.collections.session);
|
|
|
|
}
|
|
|
|
}, error);
|
|
|
|
}
|
|
|
|
|
|
|
|
, fetchDatabases: function (success, error) {
|
|
|
|
var that = this
|
|
|
|
;
|
|
|
|
|
|
|
|
|
|
|
|
this.setState("loading");
|
|
|
|
|
|
|
|
that.fetchXHR("/databases", function __fetchDatabasesCallback(content) {
|
|
|
|
that.collections.databasesInfo = new DMAPModel(content, {
|
|
|
|
contentCodes: that.collections.contentCodes
|
|
|
|
});
|
|
|
|
|
|
|
|
if (_.isFunction(success)) {
|
|
|
|
success(that.collections.databasesInfo);
|
|
|
|
}
|
|
|
|
|
|
|
|
that.setState("loaded");
|
|
|
|
}, error);
|
|
|
|
}
|
|
|
|
|
|
|
|
, fetchDatabase: function (success, error, dbId) {
|
|
|
|
var that = this
|
|
|
|
;
|
|
|
|
|
|
|
|
if (_.isUndefined(dbId) || !_.isNumeric(dbId)) {
|
|
|
|
dbId = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
this.setState("loading");
|
|
|
|
|
|
|
|
that.fetchXHR("/databases/" + dbId + "/items", function __fetchDatabaseCallback(content) {
|
|
|
|
that.collections.databases[dbId] = new DMAPModel(content, {
|
|
|
|
contentCodes: that.collections.contentCodes
|
|
|
|
});
|
|
|
|
|
|
|
|
if (_.isFunction(success)) {
|
|
|
|
success(that.collections.databases[dbId]);
|
|
|
|
}
|
|
|
|
|
|
|
|
that.setState("loaded");
|
|
|
|
}, error);
|
|
|
|
}
|
|
|
|
|
|
|
|
, fetchPlaylists: function (success, error, dbId) {
|
|
|
|
var that = this
|
|
|
|
;
|
|
|
|
|
|
|
|
if (_.isUndefined(dbId) || !_.isNumeric(dbId)) {
|
|
|
|
dbId = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
this.setState("loading");
|
|
|
|
|
|
|
|
that.fetchXHR("/databases/" + dbId + "/containers", function __fetchPlaylistsCallback(content) {
|
|
|
|
that.collections.playlistsInfo = new DMAPModel(content, {
|
|
|
|
contentCodes: that.collections.contentCodes
|
|
|
|
});
|
|
|
|
|
|
|
|
if (_.isFunction(success)) {
|
|
|
|
success(that.collections.playlistsInfo);
|
|
|
|
}
|
|
|
|
|
|
|
|
that.setState("loaded");
|
|
|
|
}, error);
|
|
|
|
}
|
|
|
|
|
|
|
|
, fetchPlaylist: function (success, error, playlistId, dbId) {
|
|
|
|
var that = this
|
|
|
|
;
|
|
|
|
|
|
|
|
if (_.isUndefined(playlistId) || !_.isNumeric(playlistId)) {
|
|
|
|
playlistId = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (_.isUndefined(dbId) || !_.isNumeric(dbId)) {
|
|
|
|
dbId = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
var requestUri = "/databases/"
|
|
|
|
;
|
|
|
|
|
|
|
|
requestUri += dbId;
|
|
|
|
requestUri += "/containers/";
|
|
|
|
requestUri += playlistId;
|
|
|
|
requestUri += "/items";
|
|
|
|
requestUri += "?meta=dmap.itemid,dmap.persistentid,dmap.containeritemid";
|
|
|
|
|
|
|
|
|
|
|
|
this.setState("loading");
|
|
|
|
|
|
|
|
that.fetchXHR(requestUri, function __fetchPlaylistCallback(content) {
|
|
|
|
if (!_.isArray(that.collections.playlists[dbId])) {
|
|
|
|
that.collections.playlists[dbId] = [];
|
|
|
|
}
|
|
|
|
|
|
|
|
that.collections.playlists[dbId][playlistId] = new DMAPModel(content, {
|
|
|
|
contentCodes: that.collections.contentCodes
|
|
|
|
});
|
|
|
|
|
|
|
|
if (_.isFunction(success)) {
|
|
|
|
success(that.collections.playlists[dbId][playlistId]);
|
|
|
|
}
|
|
|
|
|
|
|
|
that.setState("loaded");
|
|
|
|
}, error);
|
|
|
|
}
|
|
|
|
|
|
|
|
, fetchBrowse: function (success, error, dbId, type) {
|
|
|
|
var that = this
|
|
|
|
;
|
|
|
|
|
|
|
|
// TODO: We need to detect if the server supports browsing or not an implement our own otherwise.
|
|
|
|
if (_.isEmpty(type) || !_.isString(type) || !_.isArray(that.collections.browser[type])) {
|
|
|
|
throw new Error("INVALID_BROWSE_TYPE");
|
|
|
|
}
|
|
|
|
|
|
|
|
var requestUri = "/databases/"
|
|
|
|
;
|
|
|
|
|
|
|
|
requestUri += dbId;
|
|
|
|
requestUri += "/browse/";
|
|
|
|
requestUri += type;
|
|
|
|
requestUri += "?meta=dmap.itemid,dmap.persistentid,dmap.containeritemid";
|
|
|
|
|
|
|
|
|
|
|
|
this.setState("loading");
|
|
|
|
|
|
|
|
that.fetchXHR(requestUri, function __fetchBrowseCallback(content) {
|
|
|
|
|
|
|
|
that.collections.browser[type][dbId] = new DMAPModel(content, {
|
|
|
|
contentCodes: that.collections.contentCodes
|
|
|
|
});
|
|
|
|
|
|
|
|
if (_.isFunction(success)) {
|
|
|
|
success(that.collections.browser[type][dbId]);
|
|
|
|
}
|
|
|
|
|
|
|
|
that.setState("loaded");
|
|
|
|
}, error);
|
|
|
|
}
|
|
|
|
|
|
|
|
, fetchGenres: function (success, error, dbId) {
|
|
|
|
var that = this
|
|
|
|
;
|
|
|
|
|
|
|
|
if (_.isUndefined(dbId) || !_.isNumeric(dbId)) {
|
|
|
|
dbId = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return that.fetchBrowse(success, error, dbId, "genres");
|
|
|
|
}
|
|
|
|
|
|
|
|
, fetchArtists: function (success, error, dbId) {
|
|
|
|
var that = this
|
|
|
|
;
|
|
|
|
|
|
|
|
if (_.isUndefined(dbId) || !_.isNumeric(dbId)) {
|
|
|
|
dbId = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return that.fetchBrowse(success, error, dbId, "artists");
|
|
|
|
}
|
|
|
|
|
|
|
|
, fetchAlbums: function (success, error, dbId) {
|
|
|
|
var that = this
|
|
|
|
;
|
|
|
|
|
|
|
|
if (_.isUndefined(dbId) || !_.isNumeric(dbId)) {
|
|
|
|
dbId = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return that.fetchBrowse(success, error, dbId, "albums");
|
|
|
|
}
|
|
|
|
|
|
|
|
}, Backbone.Events), {
|
|
|
|
defaults: {
|
|
|
|
attributes: {
|
|
|
|
protocol: window.location.protocol.replace(":", "")
|
|
|
|
, hostname: window.location.hostname
|
|
|
|
, port: 3689
|
|
|
|
, path: ""
|
|
|
|
, username: ""
|
|
|
|
, password: ""
|
|
|
|
}
|
|
|
|
|
|
|
|
, state: 0
|
|
|
|
|
|
|
|
, _states: {
|
|
|
|
failed: -1
|
|
|
|
, disconnected: 0
|
|
|
|
, timedout: 1
|
|
|
|
, connecting: 2
|
|
|
|
, unauthorized: 3
|
|
|
|
, connected: 4
|
|
|
|
, loading: 5
|
|
|
|
, loaded: 6
|
|
|
|
}
|
|
|
|
|
|
|
|
, collections: {
|
|
|
|
contentCodes: null
|
|
|
|
|
|
|
|
, serverInfo: null
|
|
|
|
|
|
|
|
, session: {
|
|
|
|
id: null
|
|
|
|
}
|
|
|
|
|
|
|
|
, databasesInfo:null
|
|
|
|
|
|
|
|
, databases: []
|
|
|
|
|
|
|
|
, playlistsInfo:null
|
|
|
|
|
|
|
|
, playlists: []
|
|
|
|
|
|
|
|
, browser: {
|
|
|
|
genres: []
|
|
|
|
|
|
|
|
, artists: []
|
|
|
|
|
|
|
|
, albums: []
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
//_.extend(Client.prototype, Backbone.Events);
|
|
|
|
|
|
|
|
return Client;
|
|
|
|
});
|