1
0
Fork 0
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

252 lines
6.6 KiB

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