Skip to content

Commit

Permalink
Fix tracks ordering : latest first. Add setting option to recreate tr…
Browse files Browse the repository at this point in the history
…acks in playlist. Various other changes.
  • Loading branch information
leeroybrun committed Dec 15, 2016
1 parent 30da455 commit 5a8134b
Show file tree
Hide file tree
Showing 13 changed files with 397 additions and 83 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@
## v0.4.4

- [x] Add tracks in the right order on Spotify (new tracks first)
- [x] Add setting to recreate tracks in playlist
- [x] Update script to automatically reorder tracks in playlist when updated to v0.4.4
- [x] When app update is completed, send message to popup to force update
- [x] Show tags & app updates in settings too
- [x] Change rotation or CanvasIcon to match popup icons' rotation

## v0.4.3

Expand Down
28 changes: 28 additions & 0 deletions _locales/en/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -199,5 +199,33 @@
"exportLogsText": {
"message": "If you need to debug the extension, you can export the logs here.",
"description": ""
},
"recreatePlaylist": {
"message": "Recreate and reorder tracks",
"description": ""
},
"recreatePlaylistText": {
"message": "If the Spotify playlist became messy and you want to recreate the tracks it contains. We will reorder all the tracks too, and put the latest at top. We only change the playlist created by Shazify, nothing else will be modified.",
"description": ""
},
"recreatePlaylistRemoveWhat": {
"message": "Remove and recreate",
"description": ""
},
"recreatePlaylistRemoveAll": {
"message": "ALL tracks in playlist",
"description": ""
},
"recreatePlaylistDontRemoveAll": {
"message": "Only tracks from Shazify",
"description": ""
},
"recreatePlaylistAreYouSure": {
"message": "Are you sure you want to recreate all tracks in playlist?",
"description": ""
},
"recreatePlaylistButton": {
"message": "Recreate tracks",
"description": ""
}
}
28 changes: 28 additions & 0 deletions _locales/fr/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -195,5 +195,33 @@
"exportLogsText": {
"message": "Afin de déceler et débugger d'éventuels problèmes, vous pouvez exporter les logs de l'application.",
"description": ""
},
"recreatePlaylist": {
"message": "Recréer et réordonner les morceaux",
"description": ""
},
"recreatePlaylistText": {
"message": "Si la playlist Spotify vous semble en désordre et que vous voulez recréer les morceaux qu'elle contient. Nous allons également les réordonner afin de mettre les derniers en haut de la liste. Nous ne modifions que la playlist créée par Shazify, rien d'autre ne sera touché!",
"description": ""
},
"recreatePlaylistRemoveWhat": {
"message": "Supprimer et recréer",
"description": ""
},
"recreatePlaylistRemoveAll": {
"message": "TOUS les morceaux de la playlist",
"description": ""
},
"recreatePlaylistDontRemoveAll": {
"message": "Seulement les morceaux créés par Shazify",
"description": ""
},
"recreatePlaylistAreYouSure": {
"message": "Êtes-vous sûr de vouloir recréer les morceaux dans la playlist?",
"description": ""
},
"recreatePlaylistButton": {
"message": "Recréer les morceaux",
"description": ""
}
}
35 changes: 33 additions & 2 deletions popup/partials/settings.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,24 @@
<h2 chrome-translate>settings</h2>
</div>
<div class="settings">
<div class="waiting-overlay updating" ng-if="updating() === true && updatingApp() === false">
<div class="content">
<div class="simplelineicon">
<i class="icon-refresh icon-spin"></i>
</div>
<p><span class="updating-tags" chrome-translate>updatingTags</span></p>
<p chrome-translate>doNotCloseBrowser</p>
</div>
</div>
<div class="waiting-overlay updating-app" ng-if="updatingApp() === true">
<div class="content">
<div class="simplelineicon">
<i class="icon-settings icon-spin-slow"></i>
</div>
<p chrome-translate class="updating-app">updatingApp</p>
<p chrome-translate>doNotCloseBrowser</p>
</div>
</div>
<h3 chrome-translate>loginStatus</h3>
<div class="login-status">
<div class="status" ng-class="{on: login.shazam.status, off: !login.shazam.status}">
Expand Down Expand Up @@ -56,8 +74,21 @@ <h3>Spotify</h3>
<p><a class="pure-button button-warning" ng-click="login.spotify.disconnect()" chrome-translate analytics-on analytics-event="Spotify logout" analytics-category="Settings">logout</a></p>
</div>
</div>
<div class="advanced-settings">
<a ng-click="advanced.toggle()" class="clickable" analytics-on analytics-event="Toggle advanced settings" analytics-category="Settings"><span chrome-translate>advancedSettings</span> <span class="arrow" ng-if="advanced.hidden">&#x25BC;</span><span class="arrow" ng-if="!advanced.hidden">&#x25B2;</span></a>
<div class="settings-section-container">
<a ng-click="recreatePlaylist.toggle()" class="settings-section-toggle clickable" analytics-on analytics-event="Toggle recreate playlist" analytics-category="Settings"><span chrome-translate>recreatePlaylist</span> <span class="arrow" ng-if="recreatePlaylist.hidden"><i class="icon-arrow-down"></i></span><span class="arrow" ng-if="!recreatePlaylist.hidden"><i class="icon-arrow-up"></i></span></a>
<div class="settings-section-content" ng-if="!recreatePlaylist.hidden">
<h3 chrome-translate>recreatePlaylist</h3>
<p chrome-translate>recreatePlaylistText</p>
<p>
<span chrome-translate>recreatePlaylistRemoveWhat</span>:<br />
<label><input type="radio" ng-model="recreatePlaylist.removeAll" ng-value="true" /><span chrome-translate>recreatePlaylistRemoveAll</span></label><br />
<label><input type="radio" ng-model="recreatePlaylist.removeAll" ng-value="false" /><span chrome-translate>recreatePlaylistDontRemoveAll</span></label>
</p>
<p><a class="pure-button pure-button-primary" ng-click="recreatePlaylist.send()" chrome-translate analytics-on analytics-event="Recreate playlist" analytics-category="Settings">recreatePlaylistButton</a></p>
</div>
</div>
<div class="settings-section-container">
<a ng-click="advanced.toggle()" class="settings-section-toggle clickable" analytics-on analytics-event="Toggle advanced settings" analytics-category="Settings"><span chrome-translate>advancedSettings</span> <span class="arrow" ng-if="advanced.hidden"><i class="icon-arrow-down"></i></span><span class="arrow" ng-if="!advanced.hidden"><i class="icon-arrow-up"></i></span></a>
<div class="advanced-content" ng-if="!advanced.hidden">
<h3 chrome-translate>clearData</h3>
<p chrome-translate>clearDataText</p>
Expand Down
2 changes: 1 addition & 1 deletion src/background/CanvasIcon.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
icon.ctx.save();
icon.ctx.clearRect(0, 0, icon.canvas.width, icon.canvas.height); //clear the canvas
icon.ctx.translate(icon.cache.width/2, icon.cache.height/2);
icon.ctx.rotate(Math.PI / 180 * (icon.ang += 10)); //increment the angle and rotate the image
icon.ctx.rotate(Math.PI / 180 * (icon.ang -= 10)); //increment the angle and rotate the image
icon.ctx.drawImage(icon.img, -icon.cache.width / 2, -icon.cache.height / 2, icon.cache.width, icon.cache.height); //draw the image ;)
icon.ctx.restore();

Expand Down
154 changes: 84 additions & 70 deletions src/background/SpotifyService.js
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,28 @@
});
},

removeAllTracks: function(callback) {
Logger.info('[Spotify] Removing all tracks in playlist...');

Spotify.getUserAndPlaylist(function(err, userId, playlistId) {
if(err) { return callback(err); }

Spotify.call({
endpoint: '/v1/users/'+ userId +'/playlists/'+ playlistId +'/tracks',
method: 'PUT',
data: JSON.stringify({ uris: [] })
}, function(err, data) {
if(err) {
Logger.info('[Spotify] Error removing all tracks from playlist.');
Logger.error(err);
return callback(err);
}

return callback(null, data);
});
});
},

// Add an array of trackIds to playlist
addTracks: function(tracksIds, callback) {
if(tracksIds.length === 0) {
Expand Down Expand Up @@ -198,7 +220,7 @@
tracksPaths.push('spotify:track:'+ id);
});

Spotify.playlist._addTracksPaths(tracksPaths, callback);
Spotify.playlist._splitPlaylistTracksRequest(userId, playlistId, 'POST', tracksPaths, callback);
});
});
},
Expand All @@ -214,95 +236,87 @@

Logger.info('[Spotify] '+ tracksIds.length +' tracks to remove from playlist.');

var tracks = [];
var tracksPaths = [];
tracksIds.forEach(function(id) {
tracks.push({ uri: 'spotify:track:'+ id });
tracksPaths.push({ uri: 'spotify:track:'+ id });
});

Spotify.call({
method: 'DELETE',
endpoint: '/v1/users/'+ userId +'/playlists/'+ playlistId +'/tracks',
data: JSON.stringify({ tracks: tracks })
}, function(err, data) {
if(err) {
Logger.error('[Spotify] Error removing tracks from playlist.');
Logger.error(err);

return callback(err);
}

if(data.snapshot_id) {
return callback();
} else {
Logger.error('[Spotify] No snapshot_id returned for tracks removal.');

return callback(new Error('[Spotify] No snapshot_id returned for tracks removal.'));
}
});
Spotify.playlist._splitPlaylistTracksRequest(userId, playlistId, 'DELETE', tracksPaths, callback);
});
},

// Private : called from addTracks, add an array of trackPaths to playlist.
// Handle arrays bigger than 100 items (should be splitted in multiple requests for Spotify API)
_addTracksPaths: function(tracksPaths, callback) {
_splitPlaylistTracksRequest: function(userId, playlistId, method, tracksPaths, callback) {
// We don't have any tracks to add anymore
if(tracksPaths.length === 0) {
Logger.info('[Spotify] No tracks to add to playlist.');
Logger.info('[Spotify] No tracks to '+ method +' to playlist.');
return callback();
}

Spotify.getUserAndPlaylist(function(err, userId, playlistId) {
if(err) { return callback(err); }
Logger.info('[Spotify] Going to '+ method +' tracks to playlist '+ playlistId +'...');

//
// Spotify API allow only send 100 tracks per requests, so we need to split it.
// Slit it from the end, as tracks will be added to the start of playlist and needs to keep the right order.
// For deletion (DELETE method), the order of track does not matters, so it's fine to get them from the end too.
//
// Example tracks to send (10 is the newest, 1 is the oldest) : 10, 9, 8, 7, 6, 5, 4, 3, 2, 1
// Get 5 from the end : 5, 4, 3, 2, 1 -> add these to position 0 in playlist -> playlist = 5, 4, 3, 2, 1
// Remaining tracks : 10, 9, 8, 7, 6
// Get 5 from the end : 10, 9, 8, 7, 6 -> add these to position 0 in playlist -> playlist = 10, 9, 8, 7, 6, 5, 4, 3, 2, 1
//
var remainingTracks = tracksPaths.slice(0, -100);
if(remainingTracks.length > 0) {
Logger.info('[Spotify] Due to Spotify limitation (max 100 tracks/request), we will split this request.');
Logger.info('[Spotify] Starting to process the last 100 tracks.');
Logger.info('[Spotify] Then, we will have '+ remainingTracks.length +' tracks remaining to process.');

tracksPaths = tracksPaths.slice(-100);
}

Logger.info('[Spotify] Going to add tracks to playlist '+ playlistId +'...');
Logger.info('[Spotify] Sending '+ method +' request to playlist '+playlistId+' with tracks:');
Logger.info(tracksPaths);

// Spotify API allow only to add 100 tracks per requests, so we need to split it
// Slit it from the end, as tracks will be added to the start of playlist and needs to keep order
// 10, 9, 8, 7, 6, 5, 4, 3, 2, 1 (10 is the newest, 1 is the oldest)
// 5, 4, 3, 2, 1 (split and add these to position 0 in playlist) -> playlist = 5, 4, 3, 2, 1
// 10, 9, 8, 7, 6 (split and add these to position 0 in playlist) -> playlist = 10, 9, 8, 7, 6, 5, 4, 3, 2, 1
var remainingTracks = tracksPaths.slice(0, -100);
if(remainingTracks.length > 0) {
Logger.info('[Spotify] Due to Spotify limitation (max 100 tracks addition/request), we will split this request.');
Logger.info('[Spotify] Starting to add the first 100 tracks.');
Logger.info('[Spotify] Then, we will have '+ remainingTracks.length +' tracks remaining to add.');
var requestParams = {};
var requestData = {};

tracksPaths = tracksPaths.slice(-100);
}
// If method is POST (add tracks), set position to 0 to add them at start of playlist
if(method === 'POST') {
requestParams.position = 0;
requestData.uris = tracksPaths;
} else if(method === 'DELETE') {
requestData.tracks = tracksPaths;
}

Logger.info('[Spotify] Saving tracks to playlist '+playlistId+' :');
Logger.info(tracksPaths);
var requestOpt = {
method: method,
endpoint: '/v1/users/'+ userId +'/playlists/'+ playlistId +'/tracks',
data: JSON.stringify(requestData),
params: requestParams
};

Spotify.call({
method: 'POST',
endpoint: '/v1/users/'+ userId +'/playlists/'+ playlistId +'/tracks',
data: JSON.stringify({ uris: tracksPaths }),
params: {
position: 0
}
}, function(err, data) {
if(err) {
Logger.info('[Spotify] Error saving tracks to playlist.');
Logger.error(err);
Spotify.call(requestOpt, function(err, data) {
if(err) {
Logger.info('[Spotify] Error sending tracks to playlist.');
Logger.error(err);

return callback(err);
}
return callback(err);
}

Logger.info('[Spotify] Tracks saved to playlist.');
Logger.info('[Spotify] Request sent to playlist.');

// If we have remaining tracks to add, call the method again
if(remainingTracks.length > 0) {
Logger.info('[Spotify] Waiting 2s before processing the next batch of tracks.');

setTimeout(function() {
Spotify.playlist._addTracksPaths(remainingTracks, callback);
}, 2000);
} else {
// All tracks added, finished !
Logger.info('[Spotify] All done !');
callback();
}
});
// If we have remaining tracks to process, call the method again
if(remainingTracks.length > 0) {
Logger.info('[Spotify] Waiting 2s before processing the next batch of tracks.');

setTimeout(function() {
Spotify.playlist._splitPlaylistTracksRequest(userId, playlistId, method, remainingTracks, callback);
}, 2000);
} else {
// All tracks processed, finished !
Logger.info('[Spotify] All done !');
callback();
}
});
}
},
Expand Down
Loading

0 comments on commit 5a8134b

Please sign in to comment.