var metaHandlers = function() { function addedATrack(rdata,d2,d3) { debug.log("ADD ALBUM","Success",rdata); if (rdata) { collectionHelper.updateCollectionDisplay(rdata); } } function didntAddATrack(rdata) { debug.error("ADD ALBUM","Failure",rdata,JSON.parse(rdata.responseText)); infobar.error(language.gettext('label_general_error')); } function getPostData(playlistinfo) { var data = {}; if (playlistinfo.Title) { data.title = playlistinfo.Title; } if (playlistinfo.trackartist) { data.artist = playlistinfo.trackartist; } if (playlistinfo.Track) { data.trackno = playlistinfo.Track; } if (playlistinfo.Time) { data.duration = playlistinfo.Time; } else { data.duration = 0; } if (playlistinfo.type) { data.type = playlistinfo.type; } if (playlistinfo.Disc) { data.disc = playlistinfo.Disc; } if (playlistinfo.albumartist && playlistinfo.Album != "SoundCloud" && playlistinfo.type != "stream") { data.albumartist = playlistinfo.albumartist; } else { if (playlistinfo.trackartist) { data.albumartist = playlistinfo.trackartist; } } if (playlistinfo.metadata && playlistinfo.metadata.album.uri) { data.albumuri = playlistinfo.metadata.album.uri; } if (playlistinfo.type != "stream" && playlistinfo.images && playlistinfo.images.small) { data.image = playlistinfo.images.small; } if ((playlistinfo.type == "local" || playlistinfo.type == "podcast") && playlistinfo.Album) { data.album = playlistinfo.Album; } if (playlistinfo.file) { if (playlistinfo.type == "local" || playlistinfo.type == "podcast") { if (playlistinfo.file.match(/api\.soundcloud\.com\/tracks\/(\d+)\//) && prefs.player_backend == "mpd") { var sc = playlistinfo.file.match(/api\.soundcloud\.com\/tracks\/(\d+)\//); data.uri = "soundcloud://track/"+sc[1]; } else { data.uri = playlistinfo.file; } } else if (playlistinfo.type == "stream") { data.streamname = playlistinfo.Album; data.streamimage = playlistinfo.images.small; data.streamuri = playlistinfo.file; } } if (playlistinfo.year) { data.date = playlistinfo.year; } else { data.date = 0; } return data; } return { fromUiElement: { doMeta: function(action, name, attributes, fn) { var tracks = new Array(); $.each($('.selected').filter(removeOpenItems), function (index, element) { var uri = unescapeHtml(decodeURIComponent($(element).attr("name"))); debug.log("DROPPLUGIN","Dragged",uri,"to",name); if ($(element).hasClass('directory')) { tracks.push({ uri: decodeURIComponent($(element).children('input').first().attr('name')), artist: 'geturisfordir', title: 'dummy', urionly: '1', action: action, attributes: attributes }); } else if ($(element).hasClass('clickalbum')) { tracks.push({ uri: uri, artist: 'geturis', title: 'dummy', urionly: '1', action: action, attributes: attributes }); } else if ($(element).hasClass('playlistalbum')) { var tits = playlist.getAlbum($(element).attr('name')); for (var i in tits) { var t = getPostData(tits[i]); t.urionly = 1; t.action = action; t.attributes = attributes; tracks.push(t); } $(element).removeClass('selected'); } else if (element.hasAttribute('romprid')) { var t = getPostData(playlist.getId($(element).attr('romprid'))); t.urionly = 1; t.action = action; t.attributes = attributes; tracks.push(t); $(element).removeClass('selected'); } else if ($(element).hasClass('playlisttrack') || $(element).hasClass('clickloadplaylist') || $(element).hasClass('clickloaduserplaylist')) { infobar.notify("Sorry, you can't add tracks from playlists"); } else { tracks.push({ uri: uri, artist: 'dummy', title: 'dummy', urionly: '1', action: action, attributes: attributes }); } }); if (tracks.length > 0) { dbQueue.request(tracks, function(rdata) { collectionHelper.updateCollectionDisplay(rdata); fn(name); }, function(data) { debug.warn("DROPPLUGIN","Failed to set attributes for",track,data); infobar.error(language.gettext('label_general_error')); } ); } }, removeTrackFromDb: function(element) { var trackDiv = element.parent(); if (!trackDiv.hasClass('clicktrack')) { trackDiv = trackDiv.parent(); } var trackToGo = trackDiv.attr("name"); debug.log("DB_TRACKS","Remove track from database",trackToGo); trackDiv.fadeOut('fast'); dbQueue.request( [{action: 'delete', uri: decodeURIComponent(trackToGo)}], collectionHelper.updateCollectionDisplay, function(data) { debug.warn("Failed to remove track! Possibly duplicate request?"); } ); } }, fromSpotifyData: { addAlbumTracksToCollection: function(data, albumartist) { debug.mark('AAGH','Adding an album'); var thisIsMessy = new Array(); if (data.tracks && data.tracks.items) { debug.log("AAAGH","Adding Album From",data); infobar.notify(language.gettext('label_addingalbum')); for (var i in data.tracks.items) { var track = {}; track.title = data.tracks.items[i].name; track.artist = joinartists(data.tracks.items[i].artists); track.trackno = data.tracks.items[i].track_number; track.duration = data.tracks.items[i].duration_ms/1000; track.disc = data.tracks.items[i].disc_number; track.albumartist = albumartist; track.albumuri = data.uri; if (data.images) { for (var j in data.images) { if (data.images[j].url) { track.image = "getRemoteImage.php?url="+data.images[j].url; break; } } } track.album = data.name; track.uri = data.tracks.items[i].uri; track.date = data.release_date; track.action = 'add'; thisIsMessy.push(track); } if (thisIsMessy.length > 0) { dbQueue.request(thisIsMessy, addedATrack, didntAddATrack); } } else { debug.fail("SPOTIFY","Failed to add album - no tracks",data); infobar.error(language.gettext('label_general_error')); } } }, fromPlaylistInfo: { getMeta: function(playlistinfo, success, fail) { var data = metaHandlers.fromPlaylistInfo.mapData(playlistinfo, 'get', false); dbQueue.request([data], success, fail); }, setMeta: function(playlistinfo, action, attributes, success, fail) { var data = metaHandlers.fromPlaylistInfo.mapData(playlistinfo, action, attributes); dbQueue.request([data], success, fail); }, mapData: function(playlistinfo, action, attributes) { var data = getPostData(playlistinfo); data.action = action; if (attributes) { data.attributes = attributes; } return data; } }, fromLastFMData: { getMeta: function(data, success, fail) { var track = metaHandlers.fromLastFMData.mapData(data, 'get', false); dbQueue.request([track], success, fail); }, setMeta: function(data, action, attributes, success, fail) { var track = metaHandlers.fromLastFMData.mapData(data, action, attributes); dbQueue.request([track], success, fail); // Hackety hack // As this is currently only for incrementing playcounts from Last.FM // We use the data to also check if it's a podcast episode we need to mark as listened // Note use of CloneObject, because podcasts urlencodes the content podcasts.checkForEpisode(cloneObject(track)); }, mapData: function(data, action, attributes) { var track = {action: action}; track.title = data.name; if (data.album) { track.album = data.album['#text']; } if (data.artist) { var a = data.artist.name; // Join multiple names together so they match what our backend does // Mopidy-Scrobbler has a habit of using commas to separate multiple artists if (!a.match(/ & /)) { // Don't do this if it's already got an '&' in it, as this could be one // of our Scrobbles, or just something else track.artist = concatenate_artist_names(a.split(', ')); debug.log("DBQUEUE","Concatenated artist names to",track.artist); } else { track.artist = a; } track.albumartist = track.artist; } if (data.date) { track.lastplayed = data.date.uts; } if (attributes) { track.attributes = attributes; } debug.log("DBQUEUE", "LFM Mapped Data is",track); return track; } }, genericAction: function(action, success, fail) { if (typeof action == "object") { dbQueue.request(action, success, fail); } else { dbQueue.request([{action: action}], success, fail); } }, addToListenLater: function(album) { var data = { action: 'addtolistenlater', json: album } dbQueue.request( [data], function() { debug.log("METAHANDLERS","Album Added To Listen Later"); infobar.notify(language.gettext('label_addedtolistenlater')); if (typeof(albumstolistento) != 'undefined') { albumstolistento.update(); } }, function() { debug.error("METAHANDLERS","Tailed To Add Album To Listen Later"); } ) }, resetSyncCounts: function() { metaHandlers.genericAction('resetallsyncdata', metaHandlers.genericSuccess, metaHandlers.genericFail); }, genericSuccess: function() { }, genericFail: function() { } } }(); var dbQueue = function() { // This is a queueing mechanism for the local database in order to avoid deadlocks. var queue = new Array(); var throttle = null; var cleanuptimer = null; var cleanuprequired = false; // Cleanup cleans the database but it also updates the track stats var actions_requiring_cleanup = [ 'add', 'set', 'remove', 'amendalbum', 'deletetag', 'delete', 'deletewl', 'clearwishlist' ]; return { request: function(data, success, fail) { queue.push( {flag: false, data: data, success: success, fail: fail } ); debug.trace("DB QUEUE","New request",data); if (throttle == null && queue.length == 1) { dbQueue.dorequest(); } }, queuelength: function() { return queue.length; }, dorequest: function() { clearTimeout(throttle); clearTimeout(cleanuptimer); var req = queue[0]; if (req && player.updatingcollection) { debug.log("DB QUEUE","Deferring",req.data[0].action,"request because collection is being updated"); throttle = setTimeout(dbQueue.dorequest, 1000); } else { if (req) { if (req.flag) { debug.trace("DB QUEUE","Request just pulled from queue is already being handled"); return; } queue[0].flag = true; debug.trace("DB QUEUE","Taking next request from queue",req); $.ajax({ url: "backends/sql/userRatings.php", type: "POST", contentType: false, data: JSON.stringify(req.data), dataType: 'json' }) .done(function(data) { req = queue.shift(); debug.trace("DB QUEUE","Request Success",req,data); for (var i in req.data) { if (actions_requiring_cleanup.indexOf(req.data[i].action) > -1) { debug.log("DB QUEUE","Setting cleanup flag for",req.data[i].action,"request"); cleanuprequired = true; } } if (req.success) { req.success(data); } throttle = setTimeout(dbQueue.dorequest, 1); }) .fail(function(data) { req = queue.shift(); debug.fail("DB QUEUE","Request Failed",req,data); if (req.fail) { req.fail(data); } throttle = setTimeout(dbQueue.dorequest, 1); }); } else { throttle = null; cleanuptimer = setTimeout(dbQueue.doCleanup, 1000); } } }, doCleanup: function() { // We do these out-of-band to improve the responsiveness of the GUI. clearTimeout(cleanuptimer); if (cleanuprequired) { debug.log("DB QUEUE", "Doing backend Cleanup"); dbQueue.request([{action: 'cleanup'}], dbQueue.cleanupComplete, dbQueue.cleanupFailed); } }, cleanupComplete: function(data) { collectionHelper.updateCollectionDisplay(data); cleanuprequired = false; }, cleanupFailed: function(data) { debug.fail("DB QUEUE","Cleanup Failed"); } } }();