PageRenderTime 61ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/data/js/tomahawk.js

http://github.com/tomahawk-player/tomahawk
JavaScript | 1790 lines | 1524 code | 122 blank | 144 comment | 171 complexity | 8bab3f3e08bdb05bb5c93e82036a7076 MD5 | raw file
Possible License(s): LGPL-2.1, BSD-3-Clause, GPL-3.0, GPL-2.0
  1. /* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
  2. *
  3. * Copyright 2011, Dominik Schmidt <domme@tomahawk-player.org>
  4. * Copyright 2011-2012, Leo Franchi <lfranchi@kde.org>
  5. * Copyright 2011, Thierry Goeckel
  6. * Copyright 2013, Teo Mrnjavac <teo@kde.org>
  7. * Copyright 2013-2014, Uwe L. Korn <uwelk@xhochy.com>
  8. * Copyright 2014, Enno Gottschalk <mrmaffen@googlemail.com>
  9. *
  10. * Permission is hereby granted, free of charge, to any person obtaining a copy
  11. * of this software and associated documentation files (the "Software"), to deal
  12. * in the Software without restriction, including without limitation the rights
  13. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  14. * copies of the Software, and to permit persons to whom the Software is
  15. * furnished to do so, subject to the following conditions:
  16. *
  17. * The above copyright notice and this permission notice shall be included in
  18. * all copies or substantial portions of the Software.
  19. */
  20. // if run in phantomjs add fake Tomahawk environment
  21. if ((typeof Tomahawk === "undefined") || (Tomahawk === null)) {
  22. var Tomahawk = {
  23. fakeEnv: function () {
  24. return true;
  25. },
  26. resolverData: function () {
  27. return {
  28. scriptPath: function () {
  29. return "/home/tomahawk/resolver.js";
  30. }
  31. };
  32. },
  33. log: function () {
  34. console.log.apply(arguments);
  35. }
  36. };
  37. }
  38. Tomahawk.apiVersion = "0.2.2";
  39. //Statuses considered a success for HTTP request
  40. var httpSuccessStatuses = [200, 201];
  41. Tomahawk.error = console.error;
  42. // install RSVP error handler for uncaught(!) errors
  43. RSVP.on('error', function (reason) {
  44. var resolverName = "";
  45. if (Tomahawk.resolver.instance) {
  46. resolverName = Tomahawk.resolver.instance.settings.name + " - ";
  47. }
  48. if (reason) {
  49. Tomahawk.error(resolverName + 'Uncaught error:', reason);
  50. } else {
  51. Tomahawk.error(resolverName + 'Uncaught error: error thrown from RSVP but it was empty');
  52. }
  53. });
  54. /**
  55. * Compares versions strings
  56. * (version1 < version2) == -1
  57. * (version1 = version2) == 0
  58. * (version1 > version2) == 1
  59. */
  60. Tomahawk.versionCompare = function (version1, version2) {
  61. var v1 = version1.split('.').map(function (item) {
  62. return parseInt(item);
  63. });
  64. var v2 = version2.split('.').map(function (item) {
  65. return parseInt(item);
  66. });
  67. var length = Math.max(v1.length, v2.length);
  68. var i = 0;
  69. for (; i < length; i++) {
  70. if (typeof v1[i] == "undefined" || v1[i] === null) {
  71. if (typeof v2[i] == "undefined" || v2[i] === null) {
  72. // v1 == v2
  73. return 0;
  74. } else if (v2[i] === 0) {
  75. continue;
  76. } else {
  77. // v1 < v2
  78. return -1;
  79. }
  80. } else if (typeof v2[i] == "undefined" || v2[i] === null) {
  81. if (v1[i] === 0) {
  82. continue;
  83. } else {
  84. // v1 > v2
  85. return 1;
  86. }
  87. } else if (v2[i] > v1[i]) {
  88. // v1 < v2
  89. return -1;
  90. } else if (v2[i] < v1[i]) {
  91. // v1 > v2
  92. return 1;
  93. }
  94. }
  95. // v1 == v2
  96. return 0;
  97. };
  98. /**
  99. * Check if this is at least specified tomahawk-api-version.
  100. */
  101. Tomahawk.atLeastVersion = function (version) {
  102. return (Tomahawk.versionCompare(Tomahawk.apiVersion, version) >= 0);
  103. };
  104. Tomahawk.resolver = {
  105. scriptPath: Tomahawk.resolverData().scriptPath
  106. };
  107. Tomahawk.timestamp = function () {
  108. return Math.round(new Date() / 1000);
  109. };
  110. Tomahawk.htmlDecode = (function () {
  111. // this prevents any overhead from creating the object each time
  112. var element = document.createElement('textarea');
  113. function decodeHTMLEntities(str) {
  114. if (str && typeof str === 'string') {
  115. str = str.replace(/</g, "&lt;");
  116. str = str.replace(/>/g, "&gt;");
  117. element.innerHTML = str;
  118. str = element.textContent;
  119. element.textContent = '';
  120. }
  121. return str;
  122. }
  123. return decodeHTMLEntities;
  124. })();
  125. Tomahawk.dumpResult = function (result) {
  126. var results = result.results;
  127. Tomahawk.log("Dumping " + results.length + " results for query " + result.qid + "...");
  128. for (var i = 0; i < results.length; i++) {
  129. Tomahawk.log(results[i].artist + " - " + results[i].track + " | " + results[i].url);
  130. }
  131. Tomahawk.log("Done.");
  132. };
  133. // javascript part of Tomahawk-Object API
  134. Tomahawk.extend = function (object, members) {
  135. var F = function () {};
  136. F.prototype = object;
  137. var newObject = new F();
  138. for (var key in members) {
  139. newObject[key] = members[key];
  140. }
  141. return newObject;
  142. };
  143. //Deprecated for 0.9 resolvers. Reporting resolver capabilities is no longer necessary.
  144. var TomahawkResolverCapability = {
  145. NullCapability: 0,
  146. Browsable: 1,
  147. PlaylistSync: 2,
  148. AccountFactory: 4,
  149. UrlLookup: 8
  150. };
  151. //Deprecated for 0.9 resolvers. Use Tomahawk.UrlType instead.
  152. var TomahawkUrlType = {
  153. Any: 0,
  154. Playlist: 1,
  155. Track: 2,
  156. Album: 4,
  157. Artist: 8,
  158. Xspf: 16
  159. };
  160. //Deprecated for 0.9 resolvers. Use Tomahawk.ConfigTestResultType instead.
  161. var TomahawkConfigTestResultType = {
  162. Other: 0,
  163. Success: 1,
  164. Logout: 2,
  165. CommunicationError: 3,
  166. InvalidCredentials: 4,
  167. InvalidAccount: 5,
  168. PlayingElsewhere: 6,
  169. AccountExpired: 7
  170. };
  171. /**
  172. * Resolver BaseObject, inherit it to implement your own resolver.
  173. */
  174. var TomahawkResolver = {
  175. init: function () {
  176. },
  177. scriptPath: function () {
  178. return Tomahawk.resolverData().scriptPath;
  179. },
  180. getConfigUi: function () {
  181. return {};
  182. },
  183. getUserConfig: function () {
  184. return JSON.parse(window.localStorage[this.scriptPath()] || "{}");
  185. },
  186. saveUserConfig: function () {
  187. var configJson = JSON.stringify(Tomahawk.resolverData().config);
  188. window.localStorage[this.scriptPath()] = configJson;
  189. this.newConfigSaved();
  190. },
  191. newConfigSaved: function () {
  192. },
  193. resolve: function (qid, artist, album, title) {
  194. return {
  195. qid: qid
  196. };
  197. },
  198. search: function (qid, searchString) {
  199. return this.resolve(qid, "", "", searchString);
  200. },
  201. artists: function (qid) {
  202. return {
  203. qid: qid
  204. };
  205. },
  206. albums: function (qid, artist) {
  207. return {
  208. qid: qid
  209. };
  210. },
  211. tracks: function (qid, artist, album) {
  212. return {
  213. qid: qid
  214. };
  215. },
  216. collection: function () {
  217. return {};
  218. },
  219. _adapter_testConfig: function (config) {
  220. return RSVP.Promise.resolve(this.testConfig(config)).then(function () {
  221. return {result: Tomahawk.ConfigTestResultType.Success};
  222. });
  223. },
  224. testConfig: function () {
  225. this.configTest();
  226. },
  227. getStreamUrl: function (qid, url) {
  228. Tomahawk.reportStreamUrl(qid, url);
  229. }
  230. };
  231. Tomahawk.Resolver = {
  232. init: function () {
  233. },
  234. scriptPath: function () {
  235. return Tomahawk.resolverData().scriptPath;
  236. },
  237. getConfigUi: function () {
  238. return {};
  239. },
  240. getUserConfig: function () {
  241. return JSON.parse(window.localStorage[this.scriptPath()] || "{}");
  242. },
  243. saveUserConfig: function () {
  244. window.localStorage[this.scriptPath()] = JSON.stringify(Tomahawk.resolverData().config);
  245. this.newConfigSaved(Tomahawk.resolverData().config);
  246. },
  247. newConfigSaved: function () {
  248. },
  249. testConfig: function () {
  250. },
  251. getStreamUrl: function (params) {
  252. return params;
  253. },
  254. getDownloadUrl: function (params) {
  255. return params;
  256. },
  257. resolve: function() {
  258. },
  259. _adapter_resolve: function (params) {
  260. return RSVP.Promise.resolve(this.resolve(params)).then(function (results) {
  261. if(Array.isArray(results)) {
  262. return {
  263. 'tracks': results
  264. };
  265. }
  266. return results;
  267. });
  268. },
  269. _adapter_search: function (params) {
  270. return RSVP.Promise.resolve(this.search(params)).then(function (results) {
  271. if(Array.isArray(results)) {
  272. return {
  273. 'tracks': results
  274. };
  275. }
  276. return results;
  277. });
  278. },
  279. _adapter_testConfig: function (config) {
  280. return RSVP.Promise.resolve(this.testConfig(config)).then(function (results) {
  281. results = results || Tomahawk.ConfigTestResultType.Success;
  282. return results;
  283. }, function (error) {
  284. return error;
  285. });
  286. }
  287. };
  288. // help functions
  289. Tomahawk.valueForSubNode = function (node, tag) {
  290. if (node === undefined) {
  291. throw new Error("Tomahawk.valueForSubnode: node is undefined!");
  292. }
  293. var element = node.getElementsByTagName(tag)[0];
  294. if (element === undefined) {
  295. return undefined;
  296. }
  297. return element.textContent;
  298. };
  299. /**
  300. * Internal counter used to identify retrievedMetadata call back from native
  301. * code.
  302. */
  303. Tomahawk.retrieveMetadataIdCounter = 0;
  304. /**
  305. * Internal map used to map metadataIds to the respective JavaScript callbacks.
  306. */
  307. Tomahawk.retrieveMetadataCallbacks = {};
  308. /**
  309. * Retrieve metadata for a media stream.
  310. *
  311. * @param url String The URL which should be scanned for metadata.
  312. * @param mimetype String The mimetype of the stream, e.g. application/ogg
  313. * @param sizehint Size in bytes if not supplied possibly the whole file needs
  314. * to be downloaded
  315. * @param options Object Map to specify various parameters related to the media
  316. * URL. This includes:
  317. * * headers: Object of HTTP(S) headers that should be set on doing the
  318. * request.
  319. * * method: String HTTP verb to be used (default: GET)
  320. * * username: Username when using authentication
  321. * * password: Password when using authentication
  322. * @param callback Function(Object,String) This function is called on completeion.
  323. * If an error occured, error is set to the corresponding message else
  324. * null.
  325. */
  326. Tomahawk.retrieveMetadata = function (url, mimetype, sizehint, options, callback) {
  327. var metadataId = Tomahawk.retrieveMetadataIdCounter;
  328. Tomahawk.retrieveMetadataIdCounter++;
  329. Tomahawk.retrieveMetadataCallbacks[metadataId] = callback;
  330. Tomahawk.nativeRetrieveMetadata(metadataId, url, mimetype, sizehint, options);
  331. };
  332. /**
  333. * Pass the natively retrieved metadata back to the JavaScript callback.
  334. *
  335. * Internal use only!
  336. */
  337. Tomahawk.retrievedMetadata = function (metadataId, metadata, error) {
  338. // Check that we have a matching callback stored.
  339. if (!Tomahawk.retrieveMetadataCallbacks.hasOwnProperty(metadataId)) {
  340. return;
  341. }
  342. // Call the real callback
  343. if (Tomahawk.retrieveMetadataCallbacks.hasOwnProperty(metadataId)) {
  344. Tomahawk.retrieveMetadataCallbacks[metadataId](metadata, error);
  345. }
  346. // Callback are only used once.
  347. delete Tomahawk.retrieveMetadataCallbacks[metadataId];
  348. };
  349. /**
  350. * This method is externalized from Tomahawk.asyncRequest, so that other clients
  351. * (like tomahawk-android) can inject their own logic that determines whether or not to do a request
  352. * natively.
  353. *
  354. * @returns boolean indicating whether or not to do a request with the given parameters natively
  355. */
  356. var shouldDoNativeRequest = function (options) {
  357. var extraHeaders = options.headers;
  358. return (extraHeaders && (extraHeaders.hasOwnProperty("Referer")
  359. || extraHeaders.hasOwnProperty("referer")
  360. || extraHeaders.hasOwnProperty("User-Agent")));
  361. };
  362. /**
  363. * Possible options:
  364. * - url: The URL to call
  365. * - method: The HTTP request method (default: GET)
  366. * - username: The username for HTTP Basic Auth
  367. * - password: The password for HTTP Basic Auth
  368. * - errorHandler: callback called if the request was not completed
  369. * - data: body data included in POST requests
  370. * - needCookieHeader: boolean indicating whether or not the request needs to be able to get the
  371. * "Set-Cookie" response header
  372. * - headers: headers set on the request
  373. */
  374. var doRequest = function(options) {
  375. if (shouldDoNativeRequest(options)) {
  376. return Tomahawk.NativeScriptJobManager.invoke('httpRequest', options).then(function(xhr) {
  377. xhr.responseHeaders = xhr.responseHeaders || {};
  378. xhr.getAllResponseHeaders = function() {
  379. return this.responseHeaders;
  380. };
  381. xhr.getResponseHeader = function (header) {
  382. for(key in xhr.responseHeaders) {
  383. if(key.toLowerCase() === header.toLowerCase()) {
  384. return xhr.responseHeaders[key];
  385. }
  386. }
  387. return null;
  388. };
  389. return xhr;
  390. });
  391. } else {
  392. return new RSVP.Promise(function(resolve, reject) {
  393. var xmlHttpRequest = new XMLHttpRequest();
  394. xmlHttpRequest.open(options.method, options.url, true, options.username, options.password);
  395. if (options.headers) {
  396. for (var headerName in options.headers) {
  397. xmlHttpRequest.setRequestHeader(headerName, options.headers[headerName]);
  398. }
  399. }
  400. xmlHttpRequest.onreadystatechange = function () {
  401. if (xmlHttpRequest.readyState == 4
  402. && httpSuccessStatuses.indexOf(xmlHttpRequest.status) != -1) {
  403. resolve(xmlHttpRequest);
  404. } else if (xmlHttpRequest.readyState === 4) {
  405. Tomahawk.log("Failed to do " + options.method + " request: to: " + options.url);
  406. Tomahawk.log("Status Code was: " + xmlHttpRequest.status);
  407. reject(xmlHttpRequest);
  408. }
  409. };
  410. xmlHttpRequest.send(options.data || null);
  411. });
  412. }
  413. };
  414. Tomahawk.ajax = function (url, settings) {
  415. if (typeof url === "object") {
  416. settings = url;
  417. } else {
  418. settings = settings || {};
  419. settings.url = url;
  420. }
  421. settings.type = settings.type || settings.method || 'get';
  422. settings.method = settings.type;
  423. settings.dataFormat = settings.dataFormat || 'form';
  424. if (settings.data) {
  425. var formEncode = function (obj) {
  426. var str = [];
  427. for (var p in obj) {
  428. if (obj[p] !== undefined) {
  429. if (Array.isArray(obj[p])) {
  430. for (var i = 0; i < obj[p].length; i++) {
  431. str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p][i]));
  432. }
  433. } else {
  434. str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p]));
  435. }
  436. }
  437. }
  438. str.sort();
  439. return str.join("&");
  440. };
  441. if (typeof settings.data === 'object') {
  442. if (settings.dataFormat == 'form') {
  443. settings.data = formEncode(settings.data);
  444. settings.contentType = settings.contentType || 'application/x-www-form-urlencoded';
  445. } else if (settings.dataFormat == 'json') {
  446. settings.data = JSON.stringify(settings.data);
  447. settings.contentType = settings.contentType || 'application/json';
  448. } else {
  449. throw new Error("Tomahawk.ajax: unknown dataFormat requested: "
  450. + settings.dataFormat);
  451. }
  452. } else {
  453. throw new Error("Tomahawk.ajax: data should be either object or string");
  454. }
  455. if (settings.type.toLowerCase() === 'get') {
  456. settings.url += '?' + settings.data;
  457. delete settings.data;
  458. } else {
  459. settings.headers = settings.headers || {};
  460. if (!settings.headers.hasOwnProperty('Content-Type')) {
  461. settings.headers['Content-Type'] = settings.contentType;
  462. }
  463. }
  464. }
  465. return doRequest(settings).then(function (xhr) {
  466. if (settings.rawResponse) {
  467. return xhr;
  468. }
  469. var responseText = xhr.responseText;
  470. var contentType;
  471. if (settings.dataType === 'json') {
  472. contentType = 'application/json';
  473. } else if (settings.dataType === 'xml') {
  474. contentType = 'text/xml';
  475. } else if (typeof xhr.getResponseHeader !== 'undefined') {
  476. contentType = xhr.getResponseHeader('Content-Type');
  477. } else if (xhr.hasOwnProperty('contentType')) {
  478. contentType = xhr['contentType'];
  479. } else {
  480. contentType = 'text/html';
  481. }
  482. if (~contentType.indexOf('application/json')) {
  483. return JSON.parse(responseText);
  484. }
  485. if (~contentType.indexOf('text/xml')) {
  486. var domParser = new DOMParser();
  487. return domParser.parseFromString(responseText, "text/xml");
  488. }
  489. return xhr.responseText;
  490. });
  491. };
  492. Tomahawk.post = function (url, settings) {
  493. if (typeof url === "object") {
  494. settings = url;
  495. } else {
  496. settings = settings || {};
  497. settings.url = url;
  498. }
  499. settings.method = 'POST';
  500. return Tomahawk.ajax(settings);
  501. };
  502. Tomahawk.get = function (url, settings) {
  503. return Tomahawk.ajax(url, settings);
  504. };
  505. Tomahawk.assert = function (assertion, message) {
  506. Tomahawk.nativeAssert(assertion, message);
  507. };
  508. Tomahawk.sha256 = Tomahawk.sha256 || function (message) {
  509. return CryptoJS.SHA256(message).toString(CryptoJS.enc.Hex);
  510. };
  511. Tomahawk.md5 = Tomahawk.md5 || function (message) {
  512. return CryptoJS.MD5(message).toString(CryptoJS.enc.Hex);
  513. };
  514. // Return a HMAC (md5) signature of the input text with the desired key
  515. Tomahawk.hmac = function (key, message) {
  516. return CryptoJS.HmacMD5(message, key).toString(CryptoJS.enc.Hex);
  517. };
  518. // Extracted from https://github.com/andrewrk/diacritics version 1.2.0
  519. // Thanks to Andrew Kelley for this MIT-licensed diacritic removal code
  520. // Initialisation / precomputation
  521. (function() {
  522. var replacementList = [
  523. {base: ' ', chars: "\u00A0"},
  524. {base: '0', chars: "\u07C0"},
  525. {base: 'A', chars: "\u24B6\uFF21\u00C0\u00C1\u00C2\u1EA6\u1EA4\u1EAA\u1EA8\u00C3\u0100\u0102\u1EB0\u1EAE\u1EB4\u1EB2\u0226\u01E0\u00C4\u01DE\u1EA2\u00C5\u01FA\u01CD\u0200\u0202\u1EA0\u1EAC\u1EB6\u1E00\u0104\u023A\u2C6F"},
  526. {base: 'AA', chars: "\uA732"},
  527. {base: 'AE', chars: "\u00C6\u01FC\u01E2"},
  528. {base: 'AO', chars: "\uA734"},
  529. {base: 'AU', chars: "\uA736"},
  530. {base: 'AV', chars: "\uA738\uA73A"},
  531. {base: 'AY', chars: "\uA73C"},
  532. {base: 'B', chars: "\u24B7\uFF22\u1E02\u1E04\u1E06\u0243\u0181"},
  533. {base: 'C', chars: "\uFF43\u24b8\uff23\uA73E\u1E08"},
  534. {base: 'D', chars: "\u24B9\uFF24\u1E0A\u010E\u1E0C\u1E10\u1E12\u1E0E\u0110\u018A\u0189\u1D05\uA779"},
  535. {base: 'Dh', chars: "\u00D0"},
  536. {base: 'DZ', chars: "\u01F1\u01C4"},
  537. {base: 'Dz', chars: "\u01F2\u01C5"},
  538. {base: 'E', chars: "\u025B\u24BA\uFF25\u00C8\u00C9\u00CA\u1EC0\u1EBE\u1EC4\u1EC2\u1EBC\u0112\u1E14\u1E16\u0114\u0116\u00CB\u1EBA\u011A\u0204\u0206\u1EB8\u1EC6\u0228\u1E1C\u0118\u1E18\u1E1A\u0190\u018E\u1D07"},
  539. {base: 'F', chars: "\uA77C\u24BB\uFF26\u1E1E\u0191\uA77B"},
  540. {base: 'G', chars: "\u24BC\uFF27\u01F4\u011C\u1E20\u011E\u0120\u01E6\u0122\u01E4\u0193\uA7A0\uA77D\uA77E\u0262"},
  541. {base: 'H', chars: "\u24BD\uFF28\u0124\u1E22\u1E26\u021E\u1E24\u1E28\u1E2A\u0126\u2C67\u2C75\uA78D"},
  542. {base: 'I', chars: "\u24BE\uFF29\xCC\xCD\xCE\u0128\u012A\u012C\u0130\xCF\u1E2E\u1EC8\u01CF\u0208\u020A\u1ECA\u012E\u1E2C\u0197"},
  543. {base: 'J', chars: "\u24BF\uFF2A\u0134\u0248\u0237"},
  544. {base: 'K', chars: "\u24C0\uFF2B\u1E30\u01E8\u1E32\u0136\u1E34\u0198\u2C69\uA740\uA742\uA744\uA7A2"},
  545. {base: 'L', chars: "\u24C1\uFF2C\u013F\u0139\u013D\u1E36\u1E38\u013B\u1E3C\u1E3A\u0141\u023D\u2C62\u2C60\uA748\uA746\uA780"},
  546. {base: 'LJ', chars: "\u01C7"},
  547. {base: 'Lj', chars: "\u01C8"},
  548. {base: 'M', chars: "\u24C2\uFF2D\u1E3E\u1E40\u1E42\u2C6E\u019C\u03FB"},
  549. {base: 'N', chars: "\uA7A4\u0220\u24C3\uFF2E\u01F8\u0143\xD1\u1E44\u0147\u1E46\u0145\u1E4A\u1E48\u019D\uA790\u1D0E"},
  550. {base: 'NJ', chars: "\u01CA"},
  551. {base: 'Nj', chars: "\u01CB"},
  552. {base: 'O', chars: "\u24C4\uFF2F\xD2\xD3\xD4\u1ED2\u1ED0\u1ED6\u1ED4\xD5\u1E4C\u022C\u1E4E\u014C\u1E50\u1E52\u014E\u022E\u0230\xD6\u022A\u1ECE\u0150\u01D1\u020C\u020E\u01A0\u1EDC\u1EDA\u1EE0\u1EDE\u1EE2\u1ECC\u1ED8\u01EA\u01EC\xD8\u01FE\u0186\u019F\uA74A\uA74C"},
  553. {base: 'OE', chars: "\u0152"},
  554. {base: 'OI', chars: "\u01A2"},
  555. {base: 'OO', chars: "\uA74E"},
  556. {base: 'OU', chars: "\u0222"},
  557. {base: 'P', chars: "\u24C5\uFF30\u1E54\u1E56\u01A4\u2C63\uA750\uA752\uA754"},
  558. {base: 'Q', chars: "\u24C6\uFF31\uA756\uA758\u024A"},
  559. {base: 'R', chars: "\u24C7\uFF32\u0154\u1E58\u0158\u0210\u0212\u1E5A\u1E5C\u0156\u1E5E\u024C\u2C64\uA75A\uA7A6\uA782"},
  560. {base: 'S', chars: "\u24C8\uFF33\u1E9E\u015A\u1E64\u015C\u1E60\u0160\u1E66\u1E62\u1E68\u0218\u015E\u2C7E\uA7A8\uA784"},
  561. {base: 'T', chars: "\u24C9\uFF34\u1E6A\u0164\u1E6C\u021A\u0162\u1E70\u1E6E\u0166\u01AC\u01AE\u023E\uA786"},
  562. {base: 'Th', chars: "\u00DE"},
  563. {base: 'TZ', chars: "\uA728"},
  564. {base: 'U', chars: "\u24CA\uFF35\xD9\xDA\xDB\u0168\u1E78\u016A\u1E7A\u016C\xDC\u01DB\u01D7\u01D5\u01D9\u1EE6\u016E\u0170\u01D3\u0214\u0216\u01AF\u1EEA\u1EE8\u1EEE\u1EEC\u1EF0\u1EE4\u1E72\u0172\u1E76\u1E74\u0244"},
  565. {base: 'V', chars: "\u24CB\uFF36\u1E7C\u1E7E\u01B2\uA75E\u0245"},
  566. {base: 'VY', chars: "\uA760"},
  567. {base: 'W', chars: "\u24CC\uFF37\u1E80\u1E82\u0174\u1E86\u1E84\u1E88\u2C72"},
  568. {base: 'X', chars: "\u24CD\uFF38\u1E8A\u1E8C"},
  569. {base: 'Y', chars: "\u24CE\uFF39\u1EF2\xDD\u0176\u1EF8\u0232\u1E8E\u0178\u1EF6\u1EF4\u01B3\u024E\u1EFE"},
  570. {base: 'Z', chars: "\u24CF\uFF3A\u0179\u1E90\u017B\u017D\u1E92\u1E94\u01B5\u0224\u2C7F\u2C6B\uA762"},
  571. {base: 'a', chars: "\u24D0\uFF41\u1E9A\u00E0\u00E1\u00E2\u1EA7\u1EA5\u1EAB\u1EA9\u00E3\u0101\u0103\u1EB1\u1EAF\u1EB5\u1EB3\u0227\u01E1\u00E4\u01DF\u1EA3\u00E5\u01FB\u01CE\u0201\u0203\u1EA1\u1EAD\u1EB7\u1E01\u0105\u2C65\u0250\u0251"},
  572. {base: 'aa', chars: "\uA733"},
  573. {base: 'ae', chars: "\u00E6\u01FD\u01E3"},
  574. {base: 'ao', chars: "\uA735"},
  575. {base: 'au', chars: "\uA737"},
  576. {base: 'av', chars: "\uA739\uA73B"},
  577. {base: 'ay', chars: "\uA73D"},
  578. {base: 'b', chars: "\u24D1\uFF42\u1E03\u1E05\u1E07\u0180\u0183\u0253\u0182"},
  579. {base: 'c', chars: "\u24D2\u0107\u0109\u010B\u010D\u00E7\u1E09\u0188\u023C\uA73F\u2184\u0043\u0106\u0108\u010A\u010C\u00C7\u0187\u023B"},
  580. {base: 'd', chars: "\u24D3\uFF44\u1E0B\u010F\u1E0D\u1E11\u1E13\u1E0F\u0111\u018C\u0256\u0257\u018B\u13E7\u0501\uA7AA"},
  581. {base: 'dh', chars: "\u00F0"},
  582. {base: 'dz', chars: "\u01F3\u01C6"},
  583. {base: 'e', chars: "\u24D4\uFF45\u00E8\u00E9\u00EA\u1EC1\u1EBF\u1EC5\u1EC3\u1EBD\u0113\u1E15\u1E17\u0115\u0117\u00EB\u1EBB\u011B\u0205\u0207\u1EB9\u1EC7\u0229\u1E1D\u0119\u1E19\u1E1B\u0247\u01DD"},
  584. {base: 'f', chars: "\u24D5\uFF46\u1E1F\u0192"},
  585. {base: 'ff', chars: "\uFB00"},
  586. {base: 'fi', chars: "\uFB01"},
  587. {base: 'fl', chars: "\uFB02"},
  588. {base: 'ffi', chars: "\uFB03"},
  589. {base: 'ffl', chars: "\uFB04"},
  590. {base: 'g', chars: "\u24D6\uFF47\u01F5\u011D\u1E21\u011F\u0121\u01E7\u0123\u01E5\u0260\uA7A1\uA77F\u1D79"},
  591. {base: 'h', chars: "\u24D7\uFF48\u0125\u1E23\u1E27\u021F\u1E25\u1E29\u1E2B\u1E96\u0127\u2C68\u2C76\u0265"},
  592. {base: 'hv', chars: "\u0195"},
  593. {base: 'i', chars: "\u24D8\uFF49\xEC\xED\xEE\u0129\u012B\u012D\xEF\u1E2F\u1EC9\u01D0\u0209\u020B\u1ECB\u012F\u1E2D\u0268\u0131"},
  594. {base: 'j', chars: "\u24D9\uFF4A\u0135\u01F0\u0249"},
  595. {base: 'k', chars: "\u24DA\uFF4B\u1E31\u01E9\u1E33\u0137\u1E35\u0199\u2C6A\uA741\uA743\uA745\uA7A3"},
  596. {base: 'l', chars: "\u24DB\uFF4C\u0140\u013A\u013E\u1E37\u1E39\u013C\u1E3D\u1E3B\u017F\u0142\u019A\u026B\u2C61\uA749\uA781\uA747\u026D"},
  597. {base: 'lj', chars: "\u01C9"},
  598. {base: 'm', chars: "\u24DC\uFF4D\u1E3F\u1E41\u1E43\u0271\u026F"},
  599. {base: 'n', chars: "\u24DD\uFF4E\u01F9\u0144\xF1\u1E45\u0148\u1E47\u0146\u1E4B\u1E49\u019E\u0272\u0149\uA791\uA7A5\u043B\u0509"},
  600. {base: 'nj', chars: "\u01CC"},
  601. {base: 'o', chars: "\u24DE\uFF4F\xF2\xF3\xF4\u1ED3\u1ED1\u1ED7\u1ED5\xF5\u1E4D\u022D\u1E4F\u014D\u1E51\u1E53\u014F\u022F\u0231\xF6\u022B\u1ECF\u0151\u01D2\u020D\u020F\u01A1\u1EDD\u1EDB\u1EE1\u1EDF\u1EE3\u1ECD\u1ED9\u01EB\u01ED\xF8\u01FF\uA74B\uA74D\u0275\u0254\u1D11"},
  602. {base: 'oe', chars: "\u0153"},
  603. {base: 'oi', chars: "\u01A3"},
  604. {base: 'oo', chars: "\uA74F"},
  605. {base: 'ou', chars: "\u0223"},
  606. {base: 'p', chars: "\u24DF\uFF50\u1E55\u1E57\u01A5\u1D7D\uA751\uA753\uA755\u03C1"},
  607. {base: 'q', chars: "\u24E0\uFF51\u024B\uA757\uA759"},
  608. {base: 'r', chars: "\u24E1\uFF52\u0155\u1E59\u0159\u0211\u0213\u1E5B\u1E5D\u0157\u1E5F\u024D\u027D\uA75B\uA7A7\uA783"},
  609. {base: 's', chars: "\u24E2\uFF53\u015B\u1E65\u015D\u1E61\u0161\u1E67\u1E63\u1E69\u0219\u015F\u023F\uA7A9\uA785\u1E9B\u0282"},
  610. {base: 'ss', chars: "\xDF"},
  611. {base: 't', chars: "\u24E3\uFF54\u1E6B\u1E97\u0165\u1E6D\u021B\u0163\u1E71\u1E6F\u0167\u01AD\u0288\u2C66\uA787"},
  612. {base: 'th', chars: "\u00FE"},
  613. {base: 'tz', chars: "\uA729"},
  614. {base: 'u', chars: "\u24E4\uFF55\xF9\xFA\xFB\u0169\u1E79\u016B\u1E7B\u016D\xFC\u01DC\u01D8\u01D6\u01DA\u1EE7\u016F\u0171\u01D4\u0215\u0217\u01B0\u1EEB\u1EE9\u1EEF\u1EED\u1EF1\u1EE5\u1E73\u0173\u1E77\u1E75\u0289"},
  615. {base: 'v', chars: "\u24E5\uFF56\u1E7D\u1E7F\u028B\uA75F\u028C"},
  616. {base: 'vy', chars: "\uA761"},
  617. {base: 'w', chars: "\u24E6\uFF57\u1E81\u1E83\u0175\u1E87\u1E85\u1E98\u1E89\u2C73"},
  618. {base: 'x', chars: "\u24E7\uFF58\u1E8B\u1E8D"},
  619. {base: 'y', chars: "\u24E8\uFF59\u1EF3\xFD\u0177\u1EF9\u0233\u1E8F\xFF\u1EF7\u1E99\u1EF5\u01B4\u024F\u1EFF"},
  620. {base: 'z', chars: "\u24E9\uFF5A\u017A\u1E91\u017C\u017E\u1E93\u1E95\u01B6\u0225\u0240\u2C6C\uA763"}
  621. ];
  622. Tomahawk.diacriticsMap = {};
  623. var i, j, chars;
  624. for (i = 0; i < replacementList.length; i += 1) {
  625. chars = replacementList[i].chars;
  626. for (j = 0; j < chars.length; j += 1) {
  627. Tomahawk.diacriticsMap[chars[j]] = replacementList[i].base;
  628. }
  629. }
  630. })();
  631. Tomahawk.removeDiacritics = function (str) {
  632. return str.replace(/[^\u0000-\u007E]/g, function (c) {
  633. return Tomahawk.diacriticsMap[c] || c;
  634. });
  635. };
  636. Tomahawk.localStorage = Tomahawk.localStorage || {
  637. setItem: function (key, value) {
  638. window.localStorage[key] = value;
  639. },
  640. getItem: function (key) {
  641. return window.localStorage[key];
  642. },
  643. removeItem: function (key) {
  644. delete window.localStorage[key];
  645. }
  646. };
  647. // some aliases
  648. Tomahawk.setTimeout = Tomahawk.setTimeout || window.setTimeout;
  649. Tomahawk.setInterval = Tomahawk.setInterval || window.setInterval;
  650. Tomahawk.base64Decode = function (a) {
  651. return window.atob(a);
  652. };
  653. Tomahawk.base64Encode = function (b) {
  654. return window.btoa(b);
  655. };
  656. Tomahawk.PluginManager = {
  657. wrapperPrefix: '_adapter_',
  658. objects: {},
  659. objectCounter: 0,
  660. identifyObject: function (object) {
  661. if (!object.hasOwnProperty('id')) {
  662. object.id = this.objectCounter++;
  663. }
  664. return object.id;
  665. },
  666. registerPlugin: function (type, object) {
  667. this.objects[this.identifyObject(object)] = object;
  668. Tomahawk.log("registerPlugin: " + type + " id: " + object.id);
  669. Tomahawk.registerScriptPlugin(type, object.id);
  670. },
  671. unregisterPlugin: function (type, object) {
  672. this.objects[this.identifyObject(object)] = object;
  673. Tomahawk.log("unregisterPlugin: " + type + " id: " + object.id);
  674. Tomahawk.unregisterScriptPlugin(type, object.id);
  675. },
  676. resolve: [],
  677. invokeSync: function (requestId, objectId, methodName, params) {
  678. if (this.objects[objectId][this.wrapperPrefix + methodName]) {
  679. methodName = this.wrapperPrefix + methodName;
  680. }
  681. if (!this.objects[objectId]) {
  682. Tomahawk.log("Object not found! objectId: " + objectId + " methodName: " + methodName);
  683. } else {
  684. if (!this.objects[objectId][methodName]) {
  685. Tomahawk.log("Function not found: " + methodName);
  686. }
  687. }
  688. if (typeof this.objects[objectId][methodName] !== 'function' && this.objects[objectId][methodName]) {
  689. return this.objects[objectId][methodName];
  690. } else if (typeof this.objects[objectId][methodName] !== 'function') {
  691. throw new Error('\'' + methodName + '\' on ScriptObject ' + objectId + ' is not a function', typeof this.objects[objectId][methodName]);
  692. }
  693. return this.objects[objectId][methodName](params);
  694. },
  695. invoke: function (requestId, objectId, methodName, params) {
  696. RSVP.Promise.resolve(this.invokeSync(requestId, objectId, methodName, params))
  697. .then(function (result) {
  698. var params = {
  699. requestId: requestId,
  700. data: result
  701. };
  702. Tomahawk.reportScriptJobResults(encodeParamsToNativeFunctions(params));
  703. }, function (error) {
  704. var params = {
  705. requestId: requestId,
  706. error: error
  707. };
  708. Tomahawk.reportScriptJobResults(encodeParamsToNativeFunctions(params));
  709. });
  710. }
  711. };
  712. var encodeParamsToNativeFunctions = function(param) {
  713. return param;
  714. };
  715. Tomahawk.NativeScriptJobManager = {
  716. idCounter: 0,
  717. deferreds: {},
  718. invoke: function (methodName, params) {
  719. params = params || {};
  720. var requestId = this.idCounter++;
  721. var deferred = RSVP.defer();
  722. this.deferreds[requestId] = deferred;
  723. Tomahawk.invokeNativeScriptJob(requestId, methodName, encodeParamsToNativeFunctions(params));
  724. return deferred.promise;
  725. },
  726. reportNativeScriptJobResult: function(requestId, result) {
  727. var deferred = this.deferreds[requestId];
  728. if (!deferred) {
  729. Tomahawk.log("Deferred object with the given requestId is not present!");
  730. }
  731. deferred.resolve(result);
  732. delete this.deferreds[requestId];
  733. },
  734. reportNativeScriptJobError: function(requestId, error) {
  735. var deferred = this.deferreds[requestId];
  736. if (!deferred) {
  737. console.log("Deferred object with the given requestId is not present!");
  738. }
  739. deferred.reject(error);
  740. delete this.deferreds[requestId];
  741. }
  742. };
  743. Tomahawk.UrlType = {
  744. Any: 0,
  745. Playlist: 1,
  746. Track: 2,
  747. Album: 3,
  748. Artist: 4,
  749. XspfPlaylist: 5
  750. };
  751. Tomahawk.ConfigTestResultType = {
  752. Other: 0,
  753. Success: 1,
  754. Logout: 2,
  755. CommunicationError: 3,
  756. InvalidCredentials: 4,
  757. InvalidAccount: 5,
  758. PlayingElsewhere: 6,
  759. AccountExpired: 7
  760. };
  761. Tomahawk.Country = {
  762. AnyCountry: 0,
  763. Afghanistan: 1,
  764. Albania: 2,
  765. Algeria: 3,
  766. AmericanSamoa: 4,
  767. Andorra: 5,
  768. Angola: 6,
  769. Anguilla: 7,
  770. Antarctica: 8,
  771. AntiguaAndBarbuda: 9,
  772. Argentina: 10,
  773. Armenia: 11,
  774. Aruba: 12,
  775. Australia: 13,
  776. Austria: 14,
  777. Azerbaijan: 15,
  778. Bahamas: 16,
  779. Bahrain: 17,
  780. Bangladesh: 18,
  781. Barbados: 19,
  782. Belarus: 20,
  783. Belgium: 21,
  784. Belize: 22,
  785. Benin: 23,
  786. Bermuda: 24,
  787. Bhutan: 25,
  788. Bolivia: 26,
  789. BosniaAndHerzegowina: 27,
  790. Botswana: 28,
  791. BouvetIsland: 29,
  792. Brazil: 30,
  793. BritishIndianOceanTerritory: 31,
  794. BruneiDarussalam: 32,
  795. Bulgaria: 33,
  796. BurkinaFaso: 34,
  797. Burundi: 35,
  798. Cambodia: 36,
  799. Cameroon: 37,
  800. Canada: 38,
  801. CapeVerde: 39,
  802. CaymanIslands: 40,
  803. CentralAfricanRepublic: 41,
  804. Chad: 42,
  805. Chile: 43,
  806. China: 44,
  807. ChristmasIsland: 45,
  808. CocosIslands: 46,
  809. Colombia: 47,
  810. Comoros: 48,
  811. DemocraticRepublicOfCongo: 49,
  812. PeoplesRepublicOfCongo: 50,
  813. CookIslands: 51,
  814. CostaRica: 52,
  815. IvoryCoast: 53,
  816. Croatia: 54,
  817. Cuba: 55,
  818. Cyprus: 56,
  819. CzechRepublic: 57,
  820. Denmark: 58,
  821. Djibouti: 59,
  822. Dominica: 60,
  823. DominicanRepublic: 61,
  824. EastTimor: 62,
  825. Ecuador: 63,
  826. Egypt: 64,
  827. ElSalvador: 65,
  828. EquatorialGuinea: 66,
  829. Eritrea: 67,
  830. Estonia: 68,
  831. Ethiopia: 69,
  832. FalklandIslands: 70,
  833. FaroeIslands: 71,
  834. FijiCountry: 72,
  835. Finland: 73,
  836. France: 74,
  837. MetropolitanFrance: 75,
  838. FrenchGuiana: 76,
  839. FrenchPolynesia: 77,
  840. FrenchSouthernTerritories: 78,
  841. Gabon: 79,
  842. Gambia: 80,
  843. Georgia: 81,
  844. Germany: 82,
  845. Ghana: 83,
  846. Gibraltar: 84,
  847. Greece: 85,
  848. Greenland: 86,
  849. Grenada: 87,
  850. Guadeloupe: 88,
  851. Guam: 89,
  852. Guatemala: 90,
  853. Guinea: 91,
  854. GuineaBissau: 92,
  855. Guyana: 93,
  856. Haiti: 94,
  857. HeardAndMcDonaldIslands: 95,
  858. Honduras: 96,
  859. HongKong: 97,
  860. Hungary: 98,
  861. Iceland: 99,
  862. India: 100,
  863. Indonesia: 101,
  864. Iran: 102,
  865. Iraq: 103,
  866. Ireland: 104,
  867. Israel: 105,
  868. Italy: 106,
  869. Jamaica: 107,
  870. Japan: 108,
  871. Jordan: 109,
  872. Kazakhstan: 110,
  873. Kenya: 111,
  874. Kiribati: 112,
  875. DemocraticRepublicOfKorea: 113,
  876. RepublicOfKorea: 114,
  877. Kuwait: 115,
  878. Kyrgyzstan: 116,
  879. Lao: 117,
  880. Latvia: 118,
  881. Lebanon: 119,
  882. Lesotho: 120,
  883. Liberia: 121,
  884. LibyanArabJamahiriya: 122,
  885. Liechtenstein: 123,
  886. Lithuania: 124,
  887. Luxembourg: 125,
  888. Macau: 126,
  889. Macedonia: 127,
  890. Madagascar: 128,
  891. Malawi: 129,
  892. Malaysia: 130,
  893. Maldives: 131,
  894. Mali: 132,
  895. Malta: 133,
  896. MarshallIslands: 134,
  897. Martinique: 135,
  898. Mauritania: 136,
  899. Mauritius: 137,
  900. Mayotte: 138,
  901. Mexico: 139,
  902. Micronesia: 140,
  903. Moldova: 141,
  904. Monaco: 142,
  905. Mongolia: 143,
  906. Montserrat: 144,
  907. Morocco: 145,
  908. Mozambique: 146,
  909. Myanmar: 147,
  910. Namibia: 148,
  911. NauruCountry: 149,
  912. Nepal: 150,
  913. Netherlands: 151,
  914. NetherlandsAntilles: 152,
  915. NewCaledonia: 153,
  916. NewZealand: 154,
  917. Nicaragua: 155,
  918. Niger: 156,
  919. Nigeria: 157,
  920. Niue: 158,
  921. NorfolkIsland: 159,
  922. NorthernMarianaIslands: 160,
  923. Norway: 161,
  924. Oman: 162,
  925. Pakistan: 163,
  926. Palau: 164,
  927. PalestinianTerritory: 165,
  928. Panama: 166,
  929. PapuaNewGuinea: 167,
  930. Paraguay: 168,
  931. Peru: 169,
  932. Philippines: 170,
  933. Pitcairn: 171,
  934. Poland: 172,
  935. Portugal: 173,
  936. PuertoRico: 174,
  937. Qatar: 175,
  938. Reunion: 176,
  939. Romania: 177,
  940. RussianFederation: 178,
  941. Rwanda: 179,
  942. SaintKittsAndNevis: 180,
  943. StLucia: 181,
  944. StVincentAndTheGrenadines: 182,
  945. Samoa: 183,
  946. SanMarino: 184,
  947. SaoTomeAndPrincipe: 185,
  948. SaudiArabia: 186,
  949. Senegal: 187,
  950. SerbiaAndMontenegro: 241,
  951. Seychelles: 188,
  952. SierraLeone: 189,
  953. Singapore: 190,
  954. Slovakia: 191,
  955. Slovenia: 192,
  956. SolomonIslands: 193,
  957. Somalia: 194,
  958. SouthAfrica: 195,
  959. SouthGeorgiaAndTheSouthSandwichIslands: 196,
  960. Spain: 197,
  961. SriLanka: 198,
  962. StHelena: 199,
  963. StPierreAndMiquelon: 200,
  964. Sudan: 201,
  965. Suriname: 202,
  966. SvalbardAndJanMayenIslands: 203,
  967. Swaziland: 204,
  968. Sweden: 205,
  969. Switzerland: 206,
  970. SyrianArabRepublic: 207,
  971. Taiwan: 208,
  972. Tajikistan: 209,
  973. Tanzania: 210,
  974. Thailand: 211,
  975. Togo: 212,
  976. Tokelau: 213,
  977. TongaCountry: 214,
  978. TrinidadAndTobago: 215,
  979. Tunisia: 216,
  980. Turkey: 217,
  981. Turkmenistan: 218,
  982. TurksAndCaicosIslands: 219,
  983. Tuvalu: 220,
  984. Uganda: 221,
  985. Ukraine: 222,
  986. UnitedArabEmirates: 223,
  987. UnitedKingdom: 224,
  988. UnitedStates: 225,
  989. UnitedStatesMinorOutlyingIslands: 226,
  990. Uruguay: 227,
  991. Uzbekistan: 228,
  992. Vanuatu: 229,
  993. VaticanCityState: 230,
  994. Venezuela: 231,
  995. VietNam: 232,
  996. BritishVirginIslands: 233,
  997. USVirginIslands: 234,
  998. WallisAndFutunaIslands: 235,
  999. WesternSahara: 236,
  1000. Yemen: 237,
  1001. Yugoslavia: 238,
  1002. Zambia: 239,
  1003. Zimbabwe: 240,
  1004. Montenegro: 242,
  1005. Serbia: 243,
  1006. SaintBarthelemy: 244,
  1007. SaintMartin: 245,
  1008. LatinAmericaAndTheCaribbean: 246
  1009. };
  1010. Tomahawk.Collection = {
  1011. BrowseCapability: {
  1012. Artists: 1,
  1013. Albums: 2,
  1014. Tracks: 4
  1015. },
  1016. cachedDbs: {},
  1017. Transaction: function (collection, id) {
  1018. this.ensureDb = function () {
  1019. return new RSVP.Promise(function (resolve, reject) {
  1020. if (!collection.cachedDbs.hasOwnProperty(id)) {
  1021. Tomahawk.log("Opening database");
  1022. var estimatedSize = 5 * 1024 * 1024; // 5MB
  1023. collection.cachedDbs[id] =
  1024. openDatabase(id + "_collection", "", "Collection", estimatedSize);
  1025. collection.cachedDbs[id].transaction(function (tx) {
  1026. Tomahawk.log("Creating initial db tables");
  1027. tx.executeSql("CREATE TABLE IF NOT EXISTS artists(" +
  1028. "_id INTEGER PRIMARY KEY AUTOINCREMENT," +
  1029. "artist TEXT ," +
  1030. "artistDisambiguation TEXT," +
  1031. "UNIQUE (artist, artistDisambiguation) ON CONFLICT IGNORE)", []);
  1032. tx.executeSql("CREATE TABLE IF NOT EXISTS albumArtists(" +
  1033. "_id INTEGER PRIMARY KEY AUTOINCREMENT," +
  1034. "albumArtist TEXT ," +
  1035. "albumArtistDisambiguation TEXT," +
  1036. "UNIQUE (albumArtist, albumArtistDisambiguation) ON CONFLICT IGNORE)",
  1037. []);
  1038. tx.executeSql("CREATE TABLE IF NOT EXISTS albums(" +
  1039. "_id INTEGER PRIMARY KEY AUTOINCREMENT," +
  1040. "album TEXT," +
  1041. "albumArtistId INTEGER," +
  1042. "UNIQUE (album, albumArtistId) ON CONFLICT IGNORE," +
  1043. "FOREIGN KEY(albumArtistId) REFERENCES albumArtists(_id))", []);
  1044. tx.executeSql("CREATE TABLE IF NOT EXISTS artistAlbums(" +
  1045. "_id INTEGER PRIMARY KEY AUTOINCREMENT," +
  1046. "albumId INTEGER," +
  1047. "artistId INTEGER," +
  1048. "UNIQUE (albumId, artistId) ON CONFLICT IGNORE," +
  1049. "FOREIGN KEY(albumId) REFERENCES albums(_id)," +
  1050. "FOREIGN KEY(artistId) REFERENCES artists(_id))", []);
  1051. tx.executeSql("CREATE TABLE IF NOT EXISTS tracks(" +
  1052. "_id INTEGER PRIMARY KEY AUTOINCREMENT," +
  1053. "track TEXT," +
  1054. "artistId INTEGER," +
  1055. "albumId INTEGER," +
  1056. "url TEXT," +
  1057. "duration INTEGER," +
  1058. "albumPos INTEGER," +
  1059. "linkUrl TEXT," +
  1060. 'releaseyear INTEGER,' +
  1061. 'bitrate INTEGER,' +
  1062. "UNIQUE (track, artistId, albumId) ON CONFLICT IGNORE," +
  1063. "FOREIGN KEY(artistId) REFERENCES artists(_id)," +
  1064. "FOREIGN KEY(albumId) REFERENCES albums(_id))", []);
  1065. });
  1066. }
  1067. resolve(collection.cachedDbs[id]);
  1068. });
  1069. };
  1070. this.beginTransaction = function () {
  1071. var that = this;
  1072. return this.ensureDb().then(function (db) {
  1073. return new RSVP.Promise(function (resolve, reject) {
  1074. that.db = db;
  1075. that.statements = [];
  1076. resolve();
  1077. })
  1078. });
  1079. };
  1080. this.execDeferredStatements = function (resolve, reject) {
  1081. var that = this;
  1082. that.stmtsToResolve = that.statements.length;
  1083. that.results = that.statements.slice();
  1084. Tomahawk.log('Executing ' + that.stmtsToResolve
  1085. + ' deferred SQL statements in transaction');
  1086. return new RSVP.Promise(function (resolve, reject) {
  1087. if (that.statements.length == 0) {
  1088. resolve([]);
  1089. } else {
  1090. that.db.transaction(function (tx) {
  1091. for (var i = 0; i < that.statements.length; ++i) {
  1092. var stmt = that.statements[i];
  1093. tx.executeSql(stmt.statement, stmt.args,
  1094. (function () {
  1095. //A function returning a function to
  1096. //capture value of i
  1097. var originalI = i;
  1098. return function (tx, results) {
  1099. if (typeof that.statements[originalI].map !== 'undefined') {
  1100. var map = that.statements[originalI].map;
  1101. that.results[originalI] = [];
  1102. for (var ii = 0; ii < results.rows.length; ii++) {
  1103. that.results[originalI].push(map(
  1104. results.rows.item(ii)
  1105. ));
  1106. }
  1107. }
  1108. else {
  1109. that.results[originalI] = results;
  1110. }
  1111. that.stmtsToResolve--;
  1112. if (that.stmtsToResolve == 0) {
  1113. that.statements = [];
  1114. resolve(that.results);
  1115. }
  1116. };
  1117. })(), function (tx, error) {
  1118. Tomahawk.log("Error in tx.executeSql: " + error.code + " - "
  1119. + error.message);
  1120. that.statements = [];
  1121. reject(error);
  1122. }
  1123. );
  1124. }
  1125. });
  1126. }
  1127. });
  1128. };
  1129. this.sql = function (sqlStatement, sqlArgs, mapFunction) {
  1130. this.statements.push({statement: sqlStatement, args: sqlArgs, map: mapFunction});
  1131. };
  1132. this.sqlSelect = function (table, mapResults, fields, where, join) {
  1133. var whereKeys = [];
  1134. var whereValues = [];
  1135. for (var whereKey in where) {
  1136. if (where.hasOwnProperty(whereKey)) {
  1137. whereKeys.push(table + "." + whereKey + " = ?");
  1138. whereValues.push(where[whereKey]);
  1139. }
  1140. }
  1141. var whereString = whereKeys.length > 0 ? " WHERE " + whereKeys.join(" AND ") : "";
  1142. var joinString = "";
  1143. for (var i = 0; join && i < join.length; i++) {
  1144. var joinConditions = [];
  1145. for (var joinKey in join[i].conditions) {
  1146. if (join[i].conditions.hasOwnProperty(joinKey)) {
  1147. joinConditions.push(table + "." + joinKey + " = " + join[i].table + "."
  1148. + join[i].conditions[joinKey]);
  1149. }
  1150. }
  1151. joinString += " INNER JOIN " + join[i].table + " ON "
  1152. + joinConditions.join(" AND ");
  1153. }
  1154. var fieldsString = fields && fields.length > 0 ? fields.join(", ") : "*";
  1155. var statement = "SELECT " + fieldsString + " FROM " + table + joinString + whereString;
  1156. return this.sql(statement, whereValues, mapResults);
  1157. };
  1158. this.sqlInsert = function (table, fields) {
  1159. var fieldsKeys = [];
  1160. var fieldsValues = [];
  1161. var valuesString = "";
  1162. for (var key in fields) {
  1163. fieldsKeys.push(key);
  1164. fieldsValues.push(fields[key]);
  1165. if (valuesString.length > 0) {
  1166. valuesString += ", ";
  1167. }
  1168. valuesString += "?";
  1169. }
  1170. var statement = "INSERT INTO " + table + " (" + fieldsKeys.join(", ") + ") VALUES ("
  1171. + valuesString + ")";
  1172. return this.sql(statement, fieldsValues);
  1173. };
  1174. this.sqlDrop = function (table) {
  1175. var statement = "DROP TABLE IF EXISTS " + table;
  1176. return this.sql(statement, []);
  1177. };
  1178. },
  1179. addTracks: function (params) {
  1180. var that = this;
  1181. var id = params.id;
  1182. var tracks = params.tracks;
  1183. var cachedAlbumArtists = {},
  1184. cachedArtists = {},
  1185. cachedAlbums = {},
  1186. cachedArtistIds = {},
  1187. cachedAlbumIds = {};
  1188. var t = new Tomahawk.Collection.Transaction(this, id);
  1189. return t.beginTransaction().then(function () {
  1190. // First we insert all artists and albumArtists
  1191. t.sqlInsert("artists", {
  1192. artist: "Various Artists",
  1193. artistDisambiguation: ""
  1194. });
  1195. for (var i = 0; i < tracks.length; i++) {
  1196. tracks[i].track = tracks[i].track || "";
  1197. tracks[i].album = tracks[i].album || "";
  1198. tracks[i].artist = tracks[i].artist || "";
  1199. tracks[i].artistDisambiguation = tracks[i].artistDisambiguation || "";
  1200. tracks[i].albumArtist = tracks[i].albumArtist || "";
  1201. tracks[i].albumArtistDisambiguation = tracks[i].albumArtistDisambiguation || "";
  1202. (function (track) {
  1203. t.sqlInsert("artists", {
  1204. artist: track.artist,
  1205. artistDisambiguation: track.artistDisambiguation
  1206. });
  1207. t.sqlInsert("albumArtists", {
  1208. albumArtist: track.albumArtist,
  1209. albumArtistDisambiguation: track.albumArtistDisambiguation
  1210. });
  1211. })(tracks[i]);
  1212. }
  1213. return t.execDeferredStatements();
  1214. }).then(function () {
  1215. // Get all artists' and albumArtists' db ids
  1216. t.sqlSelect("albumArtists", function (r) {
  1217. return {
  1218. albumArtist: r.albumArtist,
  1219. albumArtistDisambiguation: r.albumArtistDisambiguation,
  1220. _id: r._id
  1221. };
  1222. });
  1223. t.sqlSelect("artists", function (r) {
  1224. return {
  1225. artist: r.artist,
  1226. artistDisambiguation: r.artistDisambiguation,
  1227. _id: r._id
  1228. };
  1229. });
  1230. return t.execDeferredStatements();
  1231. }).then(function (resultsArray) {
  1232. // Store the db ids in a map
  1233. var i, row, albumArtists = {};
  1234. for (i = 0; i < resultsArray[0].length; i++) {
  1235. row = resultsArray[0][i];
  1236. albumArtists[row.albumArtist + "♣" + row.albumArtistDisambiguation] = row._id;
  1237. }
  1238. for (i = 0; i < resultsArray[1].length; i++) {
  1239. row = resultsArray[1][i];
  1240. cachedArtists[row.artist + "♣" + row.artistDisambiguation] = row._id;
  1241. cachedArtistIds[row._id] = {
  1242. artist: row.artist,
  1243. artistDisambiguation: row.artistDisambiguation
  1244. };
  1245. }
  1246. for (i = 0; i < tracks.length; i++) {
  1247. var track = tracks[i];
  1248. var album = cachedAlbumArtists[track.album];
  1249. if (!album) {
  1250. album = cachedAlbumArtists[track.album] = {
  1251. artists: {}
  1252. };
  1253. }
  1254. album.artists[track.artist] = true;
  1255. var artistCount = Object.keys(album.artists).length;
  1256. if (artistCount == 1) {
  1257. album.albumArtistId =
  1258. cachedArtists[track.artist + "♣" + track.artistDisambiguation];
  1259. } else if (artistCount == 2) {
  1260. album.albumArtistId = cachedArtists["Various Artists♣"];
  1261. }
  1262. }
  1263. }).then(function () {
  1264. // Insert all albums
  1265. for (var i = 0; i < tracks.length; i++) {
  1266. (function (track) {
  1267. var albumArtistId = cachedAlbumArtists[track.album].albumArtistId;
  1268. t.sqlInsert("albums", {
  1269. album: track.album,
  1270. albumArtistId: albumArtistId
  1271. });
  1272. })(tracks[i]);
  1273. }
  1274. return t.execDeferredStatements();
  1275. }).then(function () {
  1276. // Get the albums' db ids
  1277. t.sqlSelect("albums", function (r) {
  1278. return {
  1279. album: r.album,
  1280. albumArtistId: r.albumArtistId,
  1281. _id: r._id
  1282. };
  1283. });
  1284. return t.execDeferredStatements();
  1285. }).then(function (results) {
  1286. // Store the db ids in a map
  1287. results = results[0];
  1288. for (var i = 0; i < results.length; i++) {
  1289. var row = results[i];
  1290. cachedAlbums[row.album + "♣" + row.albumArtistId] = row._id;
  1291. cachedAlbumIds[row._id] = {
  1292. album: row.album,
  1293. albumArtistId: row.albumArtistId
  1294. };
  1295. }
  1296. }).then(function () {
  1297. // Now we are ready to insert the tracks
  1298. for (var i = 0; i < tracks.length; i++) {
  1299. (function (track) {
  1300. // Get all relevant ids that we stored in the previous steps
  1301. var artistId = cachedArtists[track.artist + "♣" + track.artistDisambiguation];
  1302. var albumArtistId = cachedAlbumArtists[track.album].albumArtistId;
  1303. var albumId = cachedAlbums[track.album + "♣" + albumArtistId];
  1304. // Insert the artist <=> album relations
  1305. t.sqlInsert("artistAlbums", {
  1306. albumId: albumId,
  1307. artistId: artistId
  1308. });
  1309. // Insert the tracks
  1310. t.sqlInsert("tracks", {
  1311. track: track.track,
  1312. artistId: artistId,
  1313. albumId: albumId,
  1314. url: track.url,
  1315. duration: track.duration,
  1316. linkUrl: track.linkUrl,
  1317. releaseyear: track.releaseyear,
  1318. bitrate: track.bitrate,
  1319. albumPos: track.albumpos
  1320. });
  1321. })(tracks[i]);
  1322. }
  1323. return t.execDeferredStatements();
  1324. }).then(function () {
  1325. var resultMap = function (r) {
  1326. return {
  1327. _id: r._id,
  1328. artistId: r.artistId,
  1329. albumId: r.albumId,
  1330. track: r.track
  1331. };
  1332. };
  1333. // Get the tracks' db ids
  1334. t.sqlSelect("tracks", resultMap, ["_id", "artistId", "albumId", "track"]);
  1335. return t.execDeferredStatements();
  1336. }).then(function (results) {
  1337. that._trackCount = results[0].length;
  1338. Tomahawk.log("Added " + results[0].length + " tracks to collection '" + id + "'");
  1339. // Add the db ids together with the basic metadata to the fuzzy index list
  1340. var fuzzyIndexList = [];
  1341. for (var i = 0; i < results[0].length; i++) {
  1342. var row = results[0][i];
  1343. fuzzyIndexList.push({
  1344. id: row._id,
  1345. artist: cachedArtistIds[row.artistId].artist,
  1346. album: cachedAlbumIds[row.albumId].album,
  1347. track: row.track
  1348. });
  1349. }
  1350. Tomahawk.createFuzzyIndex(fuzzyIndexList);
  1351. });
  1352. },
  1353. wipe: function (params) {
  1354. var id = params.id;
  1355. var that = this;
  1356. var t = new Tomahawk.Collection.Transaction(this, id);
  1357. return t.beginTransaction().then(function () {
  1358. t.sqlDrop("artists");
  1359. t.sqlDrop("albumArtists");
  1360. t.sqlDrop("albums");
  1361. t.sqlDrop("artistAlbums");
  1362. t.sqlDrop("tracks");
  1363. return t.execDeferredStatements();
  1364. }).then(function () {
  1365. return new RSVP.Promise(function (resolve, reject) {
  1366. that.cachedDbs[id].changeVersion(that.cachedDbs[id].version, "", null,
  1367. function (err) {
  1368. Tomahawk.error("Error trying to change db version!", err);
  1369. reject();
  1370. }, function () {
  1371. delete that.cachedDbs[id];
  1372. Tomahawk.deleteFuzzyIndex(id);
  1373. Tomahawk.log("Wiped collection '" + id + "'");
  1374. resolve();
  1375. });
  1376. });
  1377. });
  1378. },
  1379. revision: function (params) {
  1380. return RSVP.resolve();
  1381. },
  1382. _fuzzyIndexIdsToTracks: function (resultIds, id) {
  1383. if (typeof id === 'undefined') {
  1384. id = this.settings.id;
  1385. }
  1386. var t = new Tomahawk.Collection.Transaction(this, id);
  1387. return t.beginTransaction().then(function () {
  1388. var mapFn = function (row) {
  1389. return {
  1390. artist: row.artist,
  1391. artistDisambiguation: row.artistDisambiguation,
  1392. album: row.album,
  1393. track: row.track,
  1394. duration: row.duration,
  1395. url: row.url,
  1396. linkUrl: row.linkUrl,
  1397. releaseyear: row.releaseyear,
  1398. bitrate: row.bitrate,
  1399. albumpos: row.albumPos
  1400. };
  1401. };
  1402. for (var idx = 0; resultIds && idx < resultIds.length; idx++) {
  1403. var trackid = resultIds[idx][0];
  1404. var where = {_id: trackid};
  1405. t.sqlSelect("tracks", mapFn,
  1406. [],
  1407. where, [
  1408. {
  1409. table: "artists",
  1410. conditions: {
  1411. artistId: "_id"
  1412. }
  1413. }, {
  1414. table: "albums",
  1415. conditions: {
  1416. albumId: "_id"
  1417. }
  1418. }
  1419. ]
  1420. );
  1421. }
  1422. return t.execDeferredStatements();
  1423. }).then(function (results) {
  1424. var merged = [];
  1425. return merged.concat.apply(merged,
  1426. results.map(function (e) {
  1427. //every result has one track
  1428. return e[0];
  1429. }));
  1430. });
  1431. },
  1432. _adapter_resolve: function (params) {
  1433. return RSVP.Promise.resolve(this.resolve(params)).then(function (results) {
  1434. return {
  1435. 'tracks': results
  1436. };
  1437. });
  1438. },
  1439. resolve: function (params) {
  1440. var resultIds = Tomahawk.resolveFromFuzzyIndex(params.artist, params.album, params.track);
  1441. return this._fuzzyIndexIdsToTracks(resultIds);
  1442. },
  1443. search: function (params) {
  1444. var resultIds = Tomahawk.searchFuzzyIndex(params.query);
  1445. return this._fuzzyIndexIdsToTracks(resultIds).then(function(tracks) {
  1446. return {
  1447. tracks: tracks
  1448. };
  1449. });
  1450. },
  1451. tracks: function (params, where) {
  1452. //TODO filter/where support
  1453. var id = params.id;
  1454. if (typeof id === 'undefined') {
  1455. id = this.settings.id;
  1456. }
  1457. var t = new Tomahawk.Collection.Transaction(this, id);
  1458. return t.beginTransaction().then(function () {
  1459. var mapFn = function (row) {
  1460. return {
  1461. artist: row.artist,
  1462. artistDisambiguation: row.artistDisambiguation,
  1463. album: row.album,
  1464. track: row.track,
  1465. duration: row.duration,
  1466. url: row.url,
  1467. linkUrl: row.linkUrl,
  1468. releaseyear: row.releaseyear,
  1469. bitrate: row.bitrate,
  1470. albumpos: row.albumPos
  1471. };
  1472. };
  1473. t.sqlSelect("tracks", mapFn,
  1474. [],
  1475. where, [
  1476. {
  1477. table: "artists",
  1478. conditions: {
  1479. artistId: "_id"
  1480. }
  1481. }, {
  1482. table: "albums",
  1483. conditions: {
  1484. albumId: "_id"
  1485. }
  1486. }
  1487. ]
  1488. );
  1489. return t.execDeferredStatements();
  1490. }).then(function (results) {
  1491. return {tracks: results[0]};
  1492. });
  1493. },
  1494. albums: function (params, where) {
  1495. //TODO filter/where support
  1496. var id = params.id;
  1497. if (typeof id === 'undefined') {
  1498. id = this.settings.id;
  1499. }
  1500. var t = new Tomahawk.Collection.Transaction(this, id);
  1501. return t.beginTransaction().then(function () {
  1502. var mapFn = function (row) {
  1503. return {
  1504. albumArtist: row.artist,
  1505. albumArtistDisambiguation: row.artistDisambiguation,
  1506. album: row.album
  1507. };
  1508. };
  1509. t.sqlSelect("albums", mapFn,
  1510. ["album", "artist", "artistDisambiguation"],
  1511. where, [
  1512. {
  1513. table: "artists",
  1514. conditions: {
  1515. albumArtistId: "_id"
  1516. }
  1517. }
  1518. ]
  1519. );
  1520. return t.execDeferredStatements();
  1521. }).then(function (results) {
  1522. results = results[0].filter(function (e) {
  1523. return (e.albumArtist != '' && e.album != '');
  1524. });
  1525. return {
  1526. artists: results.map(function (i) {
  1527. return i.albumArtist;
  1528. }),
  1529. albums: results.map(function (i) {
  1530. return i.album;
  1531. })
  1532. };
  1533. });
  1534. },
  1535. artists: function (params) {
  1536. //TODO filter/where support
  1537. var id = params.id;
  1538. if (typeof id === 'undefined') {
  1539. id = this.settings.id;
  1540. }
  1541. var t = new Tomahawk.Collection.Transaction(this, id);
  1542. return t.beginTransaction().then(function () {
  1543. var mapFn = function (r) {
  1544. return r.artist;
  1545. };
  1546. t.sqlSelect("artists", mapFn, ["artist", "artistDisambiguation"]);
  1547. return t.execDeferredStatements();
  1548. }).then(function (artists) {
  1549. return {artists: artists[0]};
  1550. });
  1551. },
  1552. //TODO: not exactly sure how is this one supposed to work
  1553. //albumArtists: function (params) {
  1554. //var id = params.id;
  1555. //var t = new Tomahawk.Collection.Transaction(this, id);
  1556. //return t.beginTransaction().then(function () {
  1557. //var mapFn = function(row) {
  1558. //return {
  1559. //albumArtist: row.albumArtist,
  1560. //albumArtistDisambiguation: row.albumArtistDisambiguation
  1561. //};
  1562. //};
  1563. //t.sqlSelect("albumArtists", ["albumArtist", "albumArtistDisambiguation"]);
  1564. //return t.execDeferredStatements();
  1565. //}).then(function (results) {
  1566. //return results[0];
  1567. //});
  1568. //},
  1569. artistAlbums: function (params) {
  1570. //TODO filter/where support
  1571. var id = params.id;
  1572. if (typeof id === 'undefined') {
  1573. id = this.settings.id;
  1574. }
  1575. var artist = params.artist;
  1576. //var artistDisambiguation = params.artistDisambiguation;
  1577. var t = new Tomahawk.Collection.Transaction(this, id);
  1578. return t.beginTransaction().then(function () {
  1579. t.sqlSelect("artists", function (r) {
  1580. return r._id;
  1581. }, ["_id"], {
  1582. artist: artist
  1583. //artistDisambiguation: artistDisambiguation
  1584. });
  1585. return t.execDeferredStatements();
  1586. }).then(function (results) {
  1587. var artistId = results[0][0];
  1588. t.sqlSelect("artistAlbums", function (r) {
  1589. return r.album;
  1590. }, ["albumId", 'album'], {
  1591. artistId: artistId
  1592. }, [
  1593. {
  1594. table: "albums",
  1595. conditions: {
  1596. albumId: "_id"
  1597. }
  1598. }
  1599. ]);
  1600. return t.execDeferredStatements();
  1601. }).then(function (results) {
  1602. return {
  1603. artist: artist,
  1604. albums: results[0]
  1605. };
  1606. });
  1607. },
  1608. albumTracks: function (params) {
  1609. //TODO filter/where support
  1610. var id = params.id;
  1611. if (typeof id === 'undefined') {
  1612. id = this.settings.id;
  1613. }
  1614. var albumArtist = params.artist;
  1615. //var albumArtistDisambiguation = params.albumArtistDisambiguation;
  1616. var album = params.album;
  1617. var that = this;
  1618. var t = new Tomahawk.Collection.Transaction(this, id);
  1619. return t.beginTransaction().then(function () {
  1620. t.sqlSelect("artists", function (r) {
  1621. return r._id;
  1622. }, ["_id"], {
  1623. artist: albumArtist
  1624. //artistDisambiguation: albumArtistDisambiguation
  1625. });
  1626. return t.execDeferredStatements();
  1627. }).then(function (results) {
  1628. var albumArtistId = results[0][0];
  1629. t.sqlSelect("albums", function (r) {
  1630. return r._id;
  1631. }, ["_id"], {
  1632. album: album,
  1633. albumArtistId: albumArtistId
  1634. });
  1635. return t.execDeferredStatements();
  1636. }).then(function (results) {
  1637. var albumId = results[0][0];
  1638. return that.tracks(params, {
  1639. albumId: albumId
  1640. });
  1641. });
  1642. },
  1643. collection: function () {
  1644. this.settings.trackcount = this._trackCount;
  1645. if (!this.settings.description) {
  1646. this.settings.description = this.settings.prettyname;
  1647. }
  1648. this.settings.capabilities = [Tomahawk.Collection.BrowseCapability.Artists,
  1649. Tomahawk.Collection.BrowseCapability.Albums,
  1650. Tomahawk.Collection.BrowseCapability.Tracks];
  1651. if (!this.settings.weight && this.resolver && this.resolver.settings.weight) {
  1652. this.settings.weight = this.resolver.settings.weight;
  1653. }
  1654. return this.settings;
  1655. },
  1656. getStreamUrl: function(params) {
  1657. if(this.resolver) {
  1658. return this.resolver.getStreamUrl(params);
  1659. }
  1660. return params;
  1661. },
  1662. getDownloadUrl: function(params) {
  1663. if(this.resolver) {
  1664. return this.resolver.getDownloadUrl(params);
  1665. }
  1666. return params;
  1667. }
  1668. };