|
|
|
/*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 browserTags = {
|
|
|
|
"abal": "abal"
|
|
|
|
, "abar": "abar"
|
|
|
|
, "abcp": "abcp"
|
|
|
|
, "abgn": "abgn"
|
|
|
|
, "abro": "abro"
|
|
|
|
}
|
|
|
|
|
|
|
|
, Model = Backbone.Model.extend({
|
|
|
|
idAttribute: "_id"
|
|
|
|
|
|
|
|
, initialize: function (attributes, options) {
|
|
|
|
_.bindAll(this
|
|
|
|
, "getAttibutesId"
|
|
|
|
, "toJSON"
|
|
|
|
, "update"
|
|
|
|
, "parse"
|
|
|
|
, "fetch"
|
|
|
|
, "save"
|
|
|
|
, "destroy"
|
|
|
|
, "unset"
|
|
|
|
, "clear"
|
|
|
|
);
|
|
|
|
|
|
|
|
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 = this.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) {
|
|
|
|
this.trigger("dmap.model.update", this, arguments);
|
|
|
|
|
|
|
|
return this.apply(this, arguments);
|
|
|
|
}
|
|
|
|
|
|
|
|
, parse: function (reponse) {
|
|
|
|
return this.attributes;
|
|
|
|
}
|
|
|
|
|
|
|
|
, fetch: function (options) {
|
|
|
|
this.trigger("dmap.model.fetch", this, arguments);
|
|
|
|
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
, save: function (attributes, options) {
|
|
|
|
this.trigger("dmap.model.save", this, arguments);
|
|
|
|
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
, destroy: function (options) {
|
|
|
|
this.trigger("dmap.model.destroy", this, arguments);
|
|
|
|
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
, unset: function (options) {
|
|
|
|
this.trigger("dmap.model.unset", this, arguments);
|
|
|
|
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
, clear: function (options) {
|
|
|
|
this.trigger("dmap.model.unset", this, arguments);
|
|
|
|
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
, 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;
|
|
|
|
});
|