/*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; });