/*jslint laxbreak:true */ /*jslint laxcomma:true */ /*jslint loopfunc:true */ /*jslint strict:true */ /*jslint browser:true */ /*jslint devel:true */ define([ "underscore" , "backbone" , "toolbox" , "models/dmap-type" , "collections/dmap" ] , function (_, Backbone, Toolbox, DMAPType, DMAPCollection) { "use strict"; var that , browserTags = { "abal": "abal" , "abar": "abar" , "abcp": "abcp" , "abgn": "abgn" , "abro": "abro" } , Model = Backbone.Model.extend({ idAttribute: "_id" , initialize: function (attributes, options) { that = this; var contentCodes = options && options.contentCodes || null; if (attributes instanceof ArrayBuffer) {//(options.buffer instanceof ArrayBuffer) { attributes = parseWrapper(attributes, contentCodes); } if (attributes && attributes.byteLength) { delete attributes.byteLength; } attributes._id = that.getAttributesId(attributes); this.set(attributes); return this; } , getAttributesId: function(attributes) { return /*attributes["dmap_persistentid"] || */attributes["dmap_itemid"] || attributes["dmap_containeritemid"] || null; } , toJSON: function(options) { var attrs = _.clone(this.attributes); if (!_.isUndefined(attrs._id)) { attrs.id = attrs._id; delete attrs._id; } return attrs; } , update: function (attributes, options) { that.trigger("dmap.model.update", this, arguments); return that.apply(that, arguments); } , parse: function (reponse) { return that.attributes; } , fetch: function (options) { that.trigger("dmap.model.fetch", this, arguments); return that; } , save: function (attributes, options) { that.trigger("dmap.model.save", this, arguments); return that; } , destroy: function (options) { that.trigger("dmap.model.destroy", this, arguments); return that; } , unset: function (options) { that.trigger("dmap.model.unset", this, arguments); return that; } , clear: function (options) { that.trigger("dmap.model.unset", this, arguments); return that; } }) , parseContentCode = function(dictChunk) { var codeElement = {}; while (dictChunk.byteLength > 8) { var contentCode = DMAPType.prototype.getType(dictChunk); var contentLength = DMAPType.prototype.getInt32(dictChunk.slice(4,8)); //console.log(contentLength); switch (contentCode) { case 'mcnm': codeElement.code = DMAPType.prototype.getString(dictChunk.slice(8, 8+contentLength)); break; case 'mcna': var codeName = DMAPType.prototype.getString(dictChunk.slice(8, 8+contentLength)); codeElement.name = codeName.replace(/\./g, '_'); break; case 'mcty': var typeData = DMAPType.prototype.getInt16(dictChunk.slice(8, 8+contentLength)); codeElement.type = new DMAPType(typeData); break; } dictChunk = dictChunk.slice(8+contentLength); } return codeElement; } , parseBinaryChunk = function (binaryChunk, contentCodes) { var entry = {}; while (binaryChunk.byteLength > 8) { var tagName = DMAPType.prototype.getType(binaryChunk); var tagLength = DMAPType.prototype.getInt32(binaryChunk.slice(4,8)); if (tagName !== "mstt") { // Skip the status code, we really don't care for it. if (tagName === "mdcl") { // Our contentCodes is probably empty as we found the content codes var contentCode = parseContentCode(binaryChunk.slice(8, 8+tagLength)); var codeId = contentCode.code.toLowerCase(); entry[codeId] = _.extend({}, contentCode, contentCode.type); } else { var codeInfo = contentCodes.get(tagName); if (!_.isObject(codeInfo)) { console.info("Unknow content code: " + tagName, tagLength); } else { if (codeInfo.getCode() !== 12 ) { entry[codeInfo.name] = codeInfo.unpack(binaryChunk.slice(8), tagLength); } else if ((tagName in browserTags) === true) { tagLength = binaryChunk.byteLength; // This is how fucked DAAP is, we overwrite the length because // in this specific case 'mlit' which is reported as a container is in fact a string var browserList = binaryChunk.slice(8); entry[codeInfo.name] = []; while (browserList.byteLength > 8) { var itemTag = DMAPType.prototype.getType(browserList); var itemLength = DMAPType.prototype.getInt32(browserList.slice(4,8)); entry[codeInfo.name].push(DMAPType.prototype.getUTFString(browserList.slice(8), itemLength)); browserList = browserList.slice(8 + itemLength); } } else if (codeInfo.getCode() === 12) { var result = parseBinaryChunk(binaryChunk.slice(8, tagLength), contentCodes); if (result) { var keys = null; if (_.isString(result)) { if ( !(entry[codeInfo.name] instanceof DMAPCollection) ) { entry[codeInfo.name] = []; } entry[codeInfo.name].push(result); } else if (getItemId(result) !== null) { if ( !(entry[codeInfo.name] instanceof DMAPCollection) ) { entry[codeInfo.name] = new DMAPCollection(); } entry[codeInfo.name].add(new Model(result)); // Here we found a listing item and we are going to squash the single item array } else if (_.isObject(result) && Object.keys(result).length === 1) { entry[codeInfo.name] = result[Object.keys(result)[0]]; } } else { // We could alternatively autodetect the type here } } } } } binaryChunk = binaryChunk.slice(8 + tagLength); } return entry; } , parseWrapper = function(binaryData, contentCodes) { if (binaryData.byteLength <= 8) { return parseBinaryChunk(binaryData, contentCodes); } else { var containerType = DMAPType.prototype.getType(binaryData); //var binaryLength = DMAPType.prototype.getUint32(binaryData.slice(4,8)); var contentData = binaryData.slice(8); switch (containerType) { // Update //case "mupd": // // Unsupported // return; //break; // Browsing list case "abro": // Content codes case "mccr": // Server Info case "msrv": // Login case "mlog": // Item listing case "mlit": // Database case "adbs": // Database info case "avdb": // Playlist info case "aply": // List of songs case "apso": return parseBinaryChunk(contentData, contentCodes); default: console.info("Unknown type: " + containerType, contentData); return parseBinaryChunk(binaryData, contentCodes); } } } , getItemId = function (item) { return item["dmap_persistentid"] || item["dmap_itemid"] || item["dmap_containeritemid"] || null; } ; return Model; });