Release 4.0.3 4.0.3
authorRemixDev <DeezloaderRemix@gmail.com>
Sat, 25 Aug 2018 20:23:43 +0000 (22:23 +0200)
committerRemixDev <DeezloaderRemix@gmail.com>
Sat, 25 Aug 2018 20:23:43 +0000 (22:23 +0200)
27 files changed:
README.md
app/app.js
app/deezer-api.js
app/default.json
app/icon.png
app/lib/flac-metadata/.gitignore [new file with mode: 0644]
app/lib/flac-metadata/README.md [new file with mode: 0644]
app/lib/flac-metadata/index.js [new file with mode: 0644]
app/lib/flac-metadata/lib/Processor.js [new file with mode: 0644]
app/lib/flac-metadata/lib/data/MetaDataBlock.js [new file with mode: 0644]
app/lib/flac-metadata/lib/data/MetaDataBlockPicture.js [new file with mode: 0644]
app/lib/flac-metadata/lib/data/MetaDataBlockStreamInfo.js [new file with mode: 0644]
app/lib/flac-metadata/lib/data/MetaDataBlockVorbisComment.js [new file with mode: 0644]
app/lib/flac-metadata/package-lock.json [new file with mode: 0644]
app/lib/flac-metadata/package.json [new file with mode: 0644]
app/logger.js
app/main.js
app/package.json
app/public/css/style.css
app/public/favicon.ico
app/public/img/noCover.jpg [new file with mode: 0644]
app/public/index.html
app/public/js/appBase.js
app/public/js/main.js
build/icon.icns
build/icon.ico
package.json

index e0916db895b675a41b650ec554d24a4634f37a75..5b2f93fe3e394bab3ecdf022a3ebec4c1ac04915 100644 (file)
--- a/README.md
+++ b/README.md
@@ -1,10 +1,35 @@
 # Deezloader Remix
-### Latest Version: 4.0.2
+### Latest Version: 4.0.3
 Deezloader Remix is an improved version of Deezloader based on the Reborn branch.<br/>
 With this app you can download songs, playlists and albums directly from Deezers Server in a single and well packaged app.
 
+![](https://i.imgur.com/DI3B4Ti.png)
+## Features
+### Base Features
+* Download MP3s and FLACs directly from Deezer Servers
+* Search and Discover music from the App
+* Download music directly from a URL
+* Download entire Artists library
+* See your public playlist on Deezer
+* Tagged music files (ID3s and Vorbis Comments)
+* Great UI and UX
+
+### Exclusive to Remix
+* Implementation with Spotify APIs (No third party services)
+* Improved download speed
+* Extensive set of options for a personalized experience
+* Server mode to launch the app headless
+* MOAR Optimizations
+
 ## Build
 If you want to buid it yourself you will need Node.js installed and npm or yarn.<br/>
+There is a missing file containing the clientSecret e clientId of my SpotifyApp (for spotify integration).<br/>
+You need to get them [here](https://developer.spotify.com/dashboard/applications) and change the values from here.<br/>
+```module.exports = {
+  clientId: 'CLIENTID_HERE',
+  clientSecret: 'CLIENTSECRET_HERE'
+}```<br/>
+The file should be put into `./app/authCredentials.js` (the same folder where `deezer-api.js` is).<br/>
 Then to build it you just need to run `npm install` or `yarn install` and after that `compile<OS>.sh` or `.bat`.<br/>
 
 ## Disclaimer
index 00c4324c1c0c037344cde75c57b0c137b610ffe6..5e845843665da054d1a35b268f03016d38fbe3c0 100644 (file)
@@ -14,7 +14,7 @@
 const express = require('express');
 const app = express();
 const server = require('http').createServer(app);
-const mflac = require('flac-metadata');
+const mflac = require('./lib/flac-metadata');
 const io = require('socket.io').listen(server, {log: false, wsEngine: 'ws'});
 const fs = require('fs-extra');
 const async = require('async');
@@ -27,13 +27,14 @@ const crypto = require('crypto');
 const logger = require('./logger.js');
 const Spotify = require('spotify-web-api-node');
 const authCredentials = require('./authCredentials.js')
+const queue = require('queue')
 
 // Load Config File
 var userdata = "";
 var homedata = "";
 if(process.env.APPDATA){
        homedata = os.homedir();
-       userdata = process.env.APPDATA + path.sep + "Deezloader Remix\\";
+       userdata = process.env.APPDATA + path.sep + "Deezloader Remix" + path.sep;
 }else if(process.platform == "darwin"){
        homedata = os.homedir();
        userdata = homedata + '/Library/Application Support/Deezloader Remix/';
@@ -51,20 +52,6 @@ if(!fs.existsSync(userdata+"config.json")){
 
 var spotifyApi = new Spotify(authCredentials);
 
-// Settings update fix
-let configFile = require(userdata+path.sep+"config.json");
-if( typeof configFile.userDefined.numplaylistbyalbum != "boolean" ||
-       typeof configFile.userDefined.syncedlyrics != "boolean" ||
-       typeof configFile.userDefined.padtrck != "boolean" ||
-       typeof configFile.userDefined.extendedTags != "boolean"||
-       typeof configFile.userDefined.partOfSet != "boolean"||
-       typeof configFile.userDefined.chartsCountry != "string"||
-       typeof configFile.userDefined.albumNameTemplate != "string" ||
-       typeof configFile.userDefined.spotifyUser != "string"){
-               fs.outputFileSync(userdata+"config.json",fs.readFileSync(__dirname+path.sep+"default.json",'utf8'));
-               configFile = require(userdata+path.sep+"config.json");
-}
-
 // Main Constants
 const configFileLocation = userdata+"config.json";
 const autologinLocation = userdata+"autologin";
@@ -73,9 +60,17 @@ const defaultDownloadDir = homedata + path.sep + "Music" + path.sep + 'Deezloade
 const defaultSettings = require('./default.json').userDefined;
 
 // Setup the folders START
-let mainFolder = defaultDownloadDir;
+var mainFolder = defaultDownloadDir;
+
+// Settings update fix
+var configFile = require(userdata+path.sep+"config.json");
+for (let x in defaultSettings){
+       if (typeof configFile.userDefined[x] != typeof defaultSettings[x]){
+               configFile.userDefined[x] = defaultSettings[x]
+       }
+}
 
-if (configFile.userDefined.downloadLocation != null) {
+if (configFile.userDefined.downloadLocation != "") {
        mainFolder = configFile.userDefined.downloadLocation;
 }
 
@@ -85,33 +80,33 @@ initFolders();
 // Route and Create server
 app.use('/', express.static(__dirname + '/public/'));
 server.listen(configFile.serverPort);
-logger.logs('Info', 'Server is running @ localhost:' + configFile.serverPort);
+logger.info('Server is running @ localhost:' + configFile.serverPort);
 
 //Autologin encryption/decryption
 var ekey = "62I9smDurjvfOdn2JhUdi99yeoAhxikw";
 
 function alencrypt(input) {
-       var iv = crypto.randomBytes(16);
-       var data = new Buffer(input).toString('binary');
+       let iv = crypto.randomBytes(16);
+       let data = new Buffer(input).toString('binary');
        key = new Buffer(ekey, "utf8");
-       var cipher = crypto.createCipheriv('aes-256-cbc', key, iv);
-       var encrypted;
+       let cipher = crypto.createCipheriv('aes-256-cbc', key, iv);
+       let encrypted;
        encrypted =  cipher.update(data, 'utf8', 'binary') +  cipher.final('binary');
-       var encoded = new Buffer(iv, 'binary').toString('hex') + new Buffer(encrypted, 'binary').toString('hex');
+       let encoded = new Buffer(iv, 'binary').toString('hex') + new Buffer(encrypted, 'binary').toString('hex');
 
        return encoded;
 }
 
 function aldecrypt(encoded) {
-       var combined = new Buffer(encoded, 'hex');
+       let combined = new Buffer(encoded, 'hex');
        key = new Buffer(ekey, "utf8");
        // Create iv
-       var iv = new Buffer(16);
+       let iv = new Buffer(16);
        combined.copy(iv, 0, 0, 16);
        edata = combined.slice(16).toString('binary');
        // Decipher encrypted data
-       var decipher = crypto.createDecipheriv('aes-256-cbc', key, iv);
-       var decrypted, plaintext;
+       let decipher = crypto.createDecipheriv('aes-256-cbc', key, iv);
+       let decrypted, plaintext;
        plaintext = (decipher.update(edata, 'binary', 'utf8') + decipher.final('utf8'));
 
        return plaintext;
@@ -119,28 +114,32 @@ function aldecrypt(encoded) {
 
 // START sockets clusterfuck
 io.sockets.on('connection', function (socket) {
-       socket.downloadQueue = [];
+       socket.downloadQueue = {};
        socket.currentItem = null;
        socket.lastQueueId = null;
+       socket.trackQueue = queue({
+               autostart: true
+       });
+       socket.trackQueue.concurrency = configFile.userDefined.queueConcurrency;
 
        socket.on("login", function (username, password, autologin) {
                Deezer.init(username, password, function (err) {
                        if(err){
-                               socket.emit("login", err.message);
-                               logger.logs('Error',"Failed to login, "+err.message);
+                               socket.emit("login", {error: err.message});
+                               logger.error("Failed to login, "+err.stack);
                        }else{
                                if(autologin){
-                                       var data = username + "\n" + password;
+                                       let data = username + "\n" + password;
                                        fs.outputFile(autologinLocation, alencrypt(data) , function(){
                                                if(!err){
-                                                       logger.logs('Info',"Added autologin successfully");
+                                                       logger.info("Added autologin successfully");
                                                }else{
-                                                       logger.logs('Info',"Failed to add autologin file");
+                                                       logger.info("Failed to add autologin file");
                                                }
                                        });
                                }
-                               socket.emit("login", "none");
-                               logger.logs('Info',"Logged in successfully");
+                               socket.emit("login", {username: Deezer.userName, picture: Deezer.userPicture});
+                               logger.info("Logged in successfully");
                        }
                });
        });
@@ -148,14 +147,14 @@ io.sockets.on('connection', function (socket) {
        socket.on("autologin", function(){
                fs.readFile(autologinLocation, function(err, data){
                        if(err){
-                               logger.logs('Info',"No auto login found");
+                               logger.info("No auto login found");
                                return;
                        }
                        try{
                                var fdata = aldecrypt(data.toString('utf8'));
 
                        }catch(e){
-                               logger.logs('Warning',"Invalid autologin file, deleting");
+                               logger.warn("Invalid autologin file, deleting");
                                fs.unlink(autologinLocation,function(){
                                });
                                return;
@@ -166,7 +165,7 @@ io.sockets.on('connection', function (socket) {
        });
 
        socket.on("logout", function(){
-               logger.logs('Info',"Logged out");
+               logger.info("Logged out");
                fs.unlink(autologinLocation,function(){
                });
                return;
@@ -176,13 +175,12 @@ io.sockets.on('connection', function (socket) {
                if (!track.trackSocket) {
                        return;
                }
-
-               if(track.trackSocket.currentItem.type == "track"){
+               if(track.trackSocket.currentItem && track.trackSocket.currentItem.type == "track"){
                        let complete;
                        if (!track.trackSocket.currentItem.percentage) {
                                track.trackSocket.currentItem.percentage = 0;
                        }
-                       if(configFile.userDefined.hifi){
+                       if(track.format == 9){
                                complete = track.FILESIZE_FLAC;
                        }else{
                                if (track.FILESIZE_MP3_320) {
@@ -193,9 +191,7 @@ io.sockets.on('connection', function (socket) {
                                        complete = track.FILESIZE_MP3_128 || 0;
                                }
                        }
-
                        let percentage = (progress / complete) * 100;
-
                        if ((percentage - track.trackSocket.currentItem.percentage > 1) || (progress == complete)) {
                                track.trackSocket.currentItem.percentage = percentage;
                                track.trackSocket.emit("downloadProgress", {
@@ -207,26 +203,146 @@ io.sockets.on('connection', function (socket) {
        };
 
        function addToQueue(object) {
-               socket.downloadQueue.push(object);
+               socket.downloadQueue[object.queueId] = object;
                socket.emit('addToQueue', object);
-
                queueDownload(getNextDownload());
        }
 
        function getNextDownload() {
-               if (socket.currentItem != null || socket.downloadQueue.length == 0) {
-                       if (socket.downloadQueue.length == 0 && socket.currentItem == null) {
+               if (socket.currentItem != null || Object.keys(socket.downloadQueue).length == 0) {
+                       if (Object.keys(socket.downloadQueue).length == 0 && socket.currentItem == null) {
                                socket.emit("emptyDownloadQueue", {});
                        }
                        return null;
                }
-               socket.currentItem = socket.downloadQueue[0];
+               socket.currentItem = socket.downloadQueue[Object.keys(socket.downloadQueue)[0]];
                return socket.currentItem;
        }
 
+       function socketDownloadTrack(data){
+               Deezer.getTrack(data.id, data.settings.hifi, function (track, err) {
+                       if (err) {
+                               return;
+                       }
+                       let queueId = "id" + Math.random().toString(36).substring(2);
+                       let _track = {
+                               name: track["SNG_TITLE"],
+                               artist: track["ART_NAME"],
+                               size: 1,
+                               downloaded: 0,
+                               failed: 0,
+                               queueId: queueId,
+                               id: track["SNG_ID"],
+                               type: "track"
+                       };
+                       data.settings.trackInfo= slimDownTrackInfo(track);
+                       if (track["VERSION"]) _track.name = _track.name + " " + track["VERSION"];
+                       _track.settings = data.settings || {};
+                       addToQueue(_track);
+               });
+       }
+       socket.on("downloadtrack", data=>{socketDownloadTrack(data)});
+
+       function socketDownloadPlaylist(data){
+               Deezer.getPlaylist(data.id, function (playlist, err) {
+                       if (err) {
+                               return;
+                       }
+                       let queueId = "id" + Math.random().toString(36).substring(2);
+                       let _playlist = {
+                               name: playlist["title"],
+                               size: playlist.nb_tracks,
+                               downloaded: 0,
+                               artist: playlist.creator.name,
+                               failed: 0,
+                               queueId: queueId,
+                               id: playlist["id"],
+                               type: "playlist",
+                               cover: playlist["picture_small"],
+                               tracks: playlist.tracks.data
+                       };
+                       _playlist.settings = data.settings || {};
+                       if (_playlist.size>400){
+                               Deezer.getPlaylistTracks(data.id, function (playlist, err) {
+                                       _playlist.size = playlist.data.length
+                                       _playlist.tracks = playlist.data
+                                       addToQueue(_playlist);
+                               })
+                       }else{
+                               addToQueue(_playlist);
+                       }
+               });
+       }
+       socket.on("downloadplaylist", data=>{socketDownloadPlaylist(data)});
+
+       function socketDownloadAlbum(data){
+               Deezer.getAlbum(data.id, function (album, err) {
+                       if (err) {
+                               return;
+                       }
+                       let queueId = "id" + Math.random().toString(36).substring(2);
+                       let _album = {
+                               name: album["title"],
+                               label: album["label"],
+                               artist: album["artist"].name,
+                               size: album.tracks.data.length,
+                               downloaded: 0,
+                               failed: 0,
+                               queueId: queueId,
+                               id: album["id"],
+                               type: "album",
+                               tracks: album.tracks.data
+                       };
+                       data.settings.albumInfo = slimDownAlbumInfo(album)
+                       _album.settings = data.settings || {};
+                       addToQueue(_album);
+               });
+       }
+       socket.on("downloadalbum", data=>{socketDownloadAlbum(data)});
+
+       function socketDownloadArtist(data){
+               Deezer.getArtistAlbums(data.id, function (albums, err) {
+                       if (err) {
+                               return;
+                       }
+                       (function sendAllAlbums(i) {
+                               setTimeout(function () {
+                     data.id = albums.data[albums.data.length-1-i].id;
+                                       socketDownloadAlbum(data);
+                     if (--i+1) sendAllAlbums(i);
+                       }, 100)
+                       })(albums.data.length-1);
+               });
+       }
+       socket.on("downloadartist", data=>{socketDownloadArtist(data)});
+
+
+       socket.on("downloadspotifyplaylist", function (data) {
+               spotifyApi.clientCredentialsGrant().then(function(creds) {
+                       spotifyApi.setAccessToken(creds.body['access_token']);
+                       return spotifyApi.getPlaylist(data.settings.currentSpotifyUser, data.id, {fields: "id,name,owner,images,tracks(total,items(track.artists,track.name))"})
+               }).then(function(resp) {
+                       let queueId = "id" + Math.random().toString(36).substring(2);
+                       let _playlist = {
+                               name: resp.body["name"],
+                               artist: (resp.body["owner"]["display_name"] ? resp.body["owner"]["display_name"] : resp.body["owner"]["id"]),
+                               size: resp.body["tracks"]["total"],
+                               downloaded: 0,
+                               failed: 0,
+                               queueId: queueId,
+                               id: resp.body["id"],
+                               type: "spotifyplaylist",
+                               cover: (resp.body["images"] ? resp.body["images"][0]["url"] : null),
+                               tracks: resp.body["tracks"]["items"]
+                       };
+                       _playlist.settings = data.settings || {};
+                       addToQueue(_playlist);
+               })
+       });
+
        //currentItem: the current item being downloaded at that moment such as a track or an album
        //downloadQueue: the tracks in the queue to be downloaded
-       //lastQueueId: the most recent queueID
+       //lastQueueId: the most recent queueId
        //queueId: random number generated when user clicks download on something
        function queueDownload(downloading) {
                if (!downloading) return;
@@ -239,318 +355,348 @@ io.sockets.on('connection', function (socket) {
                        socket.lastQueueId = downloading.queueId;
                }
 
-               if (downloading.type == "track") {
-                       logger.logs('Info',"Registered a track "+downloading.id);
-                       downloadTrack([downloading.id,0], downloading.settings, null, function (err) {
-                               if (err) {
-                                       downloading.failed++;
-                               } else {
-                                       downloading.downloaded++;
-                               }
-                               socket.emit("updateQueue", downloading);
-                               if (socket.downloadQueue[0] && (socket.downloadQueue[0].queueId == downloading.queueId)) {
-                                       socket.downloadQueue.shift();
-                               }
-                               socket.currentItem = null;
-                               //fs.rmdirSync(coverArtDir);
-                               queueDownload(getNextDownload());
-                       });
-               } else if (downloading.type == "playlist") {
-                       logger.logs('Info',"Registered a playlist "+downloading.id);
-                       Deezer.getPlaylistTracks(downloading.id, function (tracks, err) {
-                               downloading.settings.plName = downloading.name;
-                               async.eachSeries(tracks.data, function (t, callback) {
-                                       if (downloading.cancelFlag) {
-                                               logger.logs('Info',"Stopping the playlist queue");
-                                               callback("stop");
-                                               return;
+               logger.info(`Registered ${downloading.type}: ${downloading.id} | ${downloading.artist} - ${downloading.name}`);
+               switch(downloading.type){
+                       case "track":
+                               let alternativeID = 0;
+                               if (downloading.settings.trackInfo.FALLBACK)
+                                       if (downloading.settings.trackInfo.FALLBACK.SNG_ID)
+                                               alternativeID = downloading.settings.trackInfo.FALLBACK.SNG_ID;
+                               downloadTrack({id: downloading.id, fallback: (alternativeID == 0 ? null : alternativeID), name: downloading.name, artist: downloading.artist, queueId: downloading.queueId}, downloading.settings, null, function (err, track) {
+                                       if (err) {
+                                               downloading.failed++;
+                                       } else {
+                                               downloading.downloaded++;
                                        }
-                                       downloading.settings.playlist = {
-                                               position: tracks.data.indexOf(t),
-                                               fullSize: tracks.data.length
-                                       };
-                                       downloadTrack([t.id,0], downloading.settings, null, function (err) {
-                                               if (!err) {
-                                                       downloading.downloaded++;
-                                               } else {
-                                                       downloading.failed++;
-                                               }
-                                               socket.emit("downloadProgress", {
-                                                       queueId: downloading.queueId,
-                                                       percentage: ((downloading.downloaded+downloading.failed) / downloading.size) * 100
+                                       downloading.settings = null;
+                                       socket.emit("updateQueue", downloading);
+                                       socket.emit("downloadProgress", {
+                                               queueId: downloading.queueId,
+                                               percentage: 100
+                                       });
+                                       if (downloading && socket.downloadQueue[Object.keys(socket.downloadQueue)[0]] && (Object.keys(socket.downloadQueue)[0] == downloading.queueId)) delete socket.downloadQueue[Object.keys(socket.downloadQueue)[0]];
+                                       socket.currentItem = null;
+                                       queueDownload(getNextDownload());
+                               });
+                               break;
+                       case "album":
+                               downloading.playlistContent = downloading.tracks.map((t) => {
+                                       if (t.FALLBACK){
+                                               if (t.FALLBACK.SNG_ID)
+                                                       return {id: t.id, fallback: t.FALLBACK.SNG_ID, name: t.title, artist: t.artist.name, queueId: downloading.queueId}
+                                       }else{
+                                               return {id: t.id, name: t.title, artist: t.artist.name, queueId: downloading.queueId}
+                                       }
+                               })
+                               downloading.settings.albName = downloading.name;
+                               downloading.settings.artName = downloading.artist;
+                               downloading.errorLog = "";
+                               downloading.settings.playlistArr = Array(downloading.size);
+                               downloading.finished = new Promise((resolve,reject)=>{
+                                       downloading.playlistContent.every(function (t) {
+                                               socket.trackQueue.push(cb=>{
+                                                       if (!socket.downloadQueue[downloading.queueId]) {
+                                                               reject();
+                                                               return false;
+                                                       }
+                                                       logger.info(`Now downloading: ${t.artist} - ${t.name}`)
+                                                       downloadTrack(t, downloading.settings, null, function (err, track) {
+                                                               if (!err) {
+                                                                       downloading.downloaded++;
+                                                                       downloading.settings.playlistArr[track[0]] = track[1];
+                                                               } else {
+                                                                       downloading.failed++;
+                                                                       downloading.errorLog += track+"\r\n";
+                                                               }
+                                                               socket.emit("downloadProgress", {
+                                                                       queueId: downloading.queueId,
+                                                                       percentage: ((downloading.downloaded+downloading.failed) / downloading.size) * 100
+                                                               });
+                                                               socket.emit("updateQueue", downloading);
+                                                               if (downloading.downloaded + downloading.failed == downloading.size)
+                                                                       resolve();
+                                                               cb();
+                                                       });
                                                });
+                                               return true;
+                                       });
+                               })
+                               downloading.finished.then(()=>{
+                                       if (downloading.countPerAlbum) {
+                                               if (Object.keys(socket.downloadQueue).length > 1 && Object.keys(socket.downloadQueue)[1] == downloading.queueId) {
+                                                       socket.downloadQueue[downloading.queueId].download = downloading.downloaded;
+                                               }
                                                socket.emit("updateQueue", downloading);
-                                               callback();
+                                       }
+                                       logger.info("Album finished "+downloading.name);
+                                       socket.emit("downloadProgress", {
+                                               queueId: downloading.queueId,
+                                               percentage: 100
                                        });
-                               }, function (err) {
-                                       logger.logs('Info',"Playlist finished "+downloading.name);
-                                       if(typeof socket.downloadQueue[0] != 'undefined'){
-                                               socket.emit("downloadProgress", {
-                                                       queueId: socket.downloadQueue[0].queueId,
-                                                       percentage: 100
+                                       let filePath = mainFolder;
+                                       if (downloading.settings.createArtistFolder || downloading.settings.createAlbumFolder) {
+                                               if (downloading.settings.createArtistFolder) {
+                                                       filePath += antiDot(fixName(downloading.settings.artName)) + path.sep;
+                                               }
+                                               if (downloading.settings.createAlbumFolder) {
+                                                       filePath += antiDot(fixName(settingsRegexAlbum(downloading.settings.foldername,downloading.settings.artName,downloading.settings.albName,downloading.settings.albumInfo.release_date.slice(0, 4),downloading.settings.albumInfo.record_type))) + path.sep;
+                                               }
+                                       } else if (downloading.settings.artName) {
+                                               filePath += antiDot(fixName(settingsRegexAlbum(downloading.settings.foldername,downloading.settings.artName,downloading.settings.albName,downloading.settings.albumInfo.release_date.slice(0, 4),downloading.settings.albumInfo.record_type))) + path.sep;
+                                       }
+                                       if (downloading.settings.logErrors){
+                                               if (downloading.errorLog != ""){
+                                                       if (!fs.existsSync(filePath)) fs.mkdirSync(filePath);
+                                                       fs.writeFileSync(filePath+"notFound.txt",downloading.errorLog)
+                                               }else{
+                                                       if (fs.existsSync(filePath+"notFound.txt")) fs.unlinkSync(filePath+"notFound.txt");
+                                               }
+                                       }
+                                       if (downloading.settings.createM3UFile){
+                                               fs.writeFileSync(filePath + "playlist.m3u", downloading.settings.playlistArr.join("\r\n"));
+                                       }
+                                       if (downloading && socket.downloadQueue[Object.keys(socket.downloadQueue)[0]] && (Object.keys(socket.downloadQueue)[0] == downloading.queueId)) delete socket.downloadQueue[Object.keys(socket.downloadQueue)[0]];
+                                       socket.currentItem = null;
+                                       queueDownload(getNextDownload());
+                               }).catch((err)=>{
+                                       if (err) return logger.error(err.stack);
+                                       logger.info("Stopping the album queue");
+                                       if (downloading && socket.downloadQueue[Object.keys(socket.downloadQueue)[0]] && (Object.keys(socket.downloadQueue)[0] == downloading.queueId)) delete socket.downloadQueue[Object.keys(socket.downloadQueue)[0]];
+                                       socket.currentItem = null;
+                                       queueDownload(getNextDownload());
+                               });
+                               break;
+                       case "playlist":
+                               downloading.playlistContent = downloading.tracks.map((t,i) => {
+                                       if (t.FALLBACK){
+                                               if (t.FALLBACK.SNG_ID)
+                                                       return {id: t.id, fallback: t.FALLBACK.SNG_ID, name: t.title, artist: t.artist.name, index: i, queueId: downloading.queueId}
+                                       }else{
+                                               return {id: t.id, name: t.title, artist: t.artist.name, index: i, queueId: downloading.queueId}
+                                       }
+                               })
+                               downloading.settings.plName = downloading.name;
+                               downloading.errorLog = ""
+                               downloading.settings.playlistArr = Array(downloading.size);
+                               downloading.settings.playlist = {
+                                       fullSize: downloading.playlistContent.length
+                               };
+                               downloading.finished = new Promise((resolve,reject)=>{
+                                       downloading.playlistContent.every(function (t) {
+                                               socket.trackQueue.push(cb=>{
+                                                       if (!socket.downloadQueue[downloading.queueId]) {
+                                                               reject();
+                                                               return false;
+                                                       }
+                                                       logger.info(`Now downloading: ${t.artist} - ${t.name}`)
+                                                       downloadTrack(t, downloading.settings, null, function (err, track) {
+                                                               if (!err) {
+                                                                       downloading.downloaded++;
+                                                                       downloading.settings.playlistArr[track[0]] = track[1];
+                                                               } else {
+                                                                       downloading.failed++;
+                                                                       downloading.errorLog += track+"\r\n"
+                                                               }
+                                                               socket.emit("downloadProgress", {
+                                                                       queueId: downloading.queueId,
+                                                                       percentage: ((downloading.downloaded+downloading.failed) / downloading.size) * 100
+                                                               });
+                                                               socket.emit("updateQueue", downloading);
+                                                               if (downloading.downloaded + downloading.failed == downloading.size)
+                                                                       resolve();
+                                                               cb();
+                                                       });
                                                });
+                                               return true;
+                                       })
+                               });
+                               downloading.finished.then(()=>{
+                                       logger.info("Playlist finished "+downloading.name);
+                                       socket.emit("downloadProgress", {
+                                               queueId: downloading.queueId,
+                                               percentage: 100
+                                       });
+                                       let filePath = mainFolder+antiDot(fixName(downloading.settings.plName)) + path.sep
+                                       if (downloading.settings.logErrors){
+                                               if (downloading.errorLog != ""){
+                                                       if (!fs.existsSync(filePath)) fs.mkdirSync(filePath);
+                                                       fs.writeFileSync(filePath+"notFound.txt",downloading.errorLog)
+                                               }else{
+                                                       if (fs.existsSync(filePath+"notFound.txt")) fs.unlinkSync(filePath+"notFound.txt");
+                                               }
+                                       }
+                                       if (downloading.settings.createM3UFile){
+                                               fs.writeFileSync(filePath + "playlist.m3u", downloading.settings.playlistArr.join("\r\n"));
+                                       }
+                                       if (downloading.settings.saveArtwork){
+                                               if (!fs.existsSync(filePath)) fs.mkdirSync(filePath);
+                                               let imgPath = filePath + "folder.jpg";
+                                               if (downloading.cover){
+                                                       downloading.cover = downloading.cover.replace("56x56",downloading.settings.artworkSize.split(".")[0].substr(1))
+                                                       request.get(downloading.cover, {encoding: 'binary'}, function(error,response,body){
+                                                               if(error){
+                                                                       logger.error(error.stack);
+                                                                       return;
+                                                               }
+                                                               fs.outputFile(imgPath,body,'binary',function(err){
+                                                                       if(err){
+                                                                               logger.error(err.stack);
+                                                                               return;
+                                                                       }
+                                                                       logger.info(`Cover downloaded for: ${downloading.settings.plName}`)
+                                                               })
+                                                       });
+                                               }
                                        }
-                                       if (downloading && socket.downloadQueue[0] && socket.downloadQueue[0].queueId == downloading.queueId) socket.downloadQueue.shift();
+                                       if (downloading && socket.downloadQueue[Object.keys(socket.downloadQueue)[0]] && (Object.keys(socket.downloadQueue)[0] == downloading.queueId)) delete socket.downloadQueue[Object.keys(socket.downloadQueue)[0]];
+                                       socket.currentItem = null;
+                                       queueDownload(getNextDownload());
+                               }).catch((err)=>{
+                                       if (err) return logger.error(err.stack);
+                                       logger.info("Stopping the playlist queue");
+                                       if (downloading && socket.downloadQueue[Object.keys(socket.downloadQueue)[0]] && (Object.keys(socket.downloadQueue)[0] == downloading.queueId)) delete socket.downloadQueue[Object.keys(socket.downloadQueue)[0]];
                                        socket.currentItem = null;
-                                       //fs.rmdirSync(coverArtDir);
                                        queueDownload(getNextDownload());
                                });
-                       });
-               } else if (downloading.type == "spotifyplaylist") {
-                               spotifyApi.clientCredentialsGrant().then(function(creds) {
-                                       downloading.settings.plName = downloading.name;
-                                       spotifyApi.setAccessToken(creds.body['access_token']);
-                                       numPages=Math.floor((downloading.size-1)/100);
-                                       let pages = []
-                                       downloading.playlistContent = new Array(downloading.size);
-                                       for (let offset = 0; offset<=numPages; offset++){
+                               break;
+                       case "spotifyplaylist":
+                       spotifyApi.clientCredentialsGrant().then(function(creds) {
+                               downloading.settings.plName = downloading.name;
+                               downloading.settings.playlistArr = Array(downloading.size);
+                               spotifyApi.setAccessToken(creds.body['access_token']);
+                               numPages=Math.floor((downloading.size-1)/100);
+                               let pages = []
+                               downloading.playlistContent = new Array(downloading.size);
+                               downloading.tracks.map((t,i)=>{
+                                       downloading.playlistContent[i]=new Promise(function(resolve, reject) {
+                                               Deezer.track2ID(t.track.artists[0].name, t.track.name, function (response,err){
+                                                       resolve(response);
+                                               });
+                                       });
+                               })
+                               if (downloading.size>100){
+                                       for (let offset = 1; offset<=numPages; offset++){
                                                pages.push(new Promise(function(resolvePage) {
-                                                       spotifyApi.getPlaylistTracks(downloading.settings.spotifyUser, downloading.id, {fields: "", offset: offset}).then(function(resp) {
+                                                       spotifyApi.getPlaylistTracks(downloading.settings.currentSpotifyUser, downloading.id, {fields: "items(track.artists,track.name)", offset: offset*100}).then(function(resp) {
                                                                resp.body['items'].forEach((t, index) => {
                                                                        downloading.playlistContent[(offset*100)+index] = new Promise(function(resolve, reject) {
                                                                                Deezer.track2ID(t.track.artists[0].name, t.track.name, function (response,err){
-                                                                                       resolve([response,0]);
+                                                                                       resolve(response);
                                                                                });
                                                                        });
                                                                });
                                                                resolvePage();
-                                                       }, function(err) {logger.logs('Error','Something went wrong!'+err)});
+                                                       });
                                                }));
                                        }
-                                       logger.logs("Info","Waiting for all pages");
-                                       Promise.all(pages).then((val)=>{
-                                               logger.logs("Info","Waiting for all tracks to be converted");
-                                               Promise.all(downloading.playlistContent).then((values)=>{
-                                                       logger.logs("Info","All tracks converted, starting download");
-                                                       socket.emit("downloadStarted", {queueId: downloading.queueId});
-                                                       async.eachSeries(values, function (t, callback) {
-                                                               if (downloading.cancelFlag) {
-                                                                       logger.logs('Info',"Stopping the playlist queue");
-                                                                       callback("stop");
-                                                                       return;
+                               }
+                               logger.info("Waiting for all pages");
+                               Promise.all(pages).then((val)=>{
+                                       logger.info("Waiting for all tracks to be converted");
+                                       return Promise.all(downloading.playlistContent)
+                               }).then((values)=>{
+                                       if (!socket.downloadQueue[downloading.queueId]) {
+                                               logger.info("Stopping the playlist queue");
+                                               if (downloading && socket.downloadQueue[Object.keys(socket.downloadQueue)[0]] && (Object.keys(socket.downloadQueue)[0] == downloading.queueId)) delete socket.downloadQueue[Object.keys(socket.downloadQueue)[0]];
+                                               socket.currentItem = null;
+                                               queueDownload(getNextDownload());
+                                               return;
+                                       }
+                                       logger.info("All tracks converted, starting download");
+                                       socket.emit("downloadStarted", {queueId: downloading.queueId});
+                                       downloading.errorLog = "";
+                                       downloading.settings.playlist = {
+                                               fullSize: values.length
+                                       };
+                                       downloading.finished = new Promise((resolve,reject)=>{
+                                               values.every(function (t) {
+                                                       t.index = values.indexOf(t)
+                                                       t.queueId = downloading.queueId
+                                                       socket.trackQueue.push(cb=>{
+                                                               if (!socket.downloadQueue[downloading.queueId]) {
+                                                                       reject();
+                                                                       return false;
                                                                }
-                                                               downloading.settings.playlist = {
-                                                                       position: values.indexOf(t),
-                                                                       fullSize: values.length
-                                                               };
-                                                               downloadTrack(t, downloading.settings, null, function (err) {
+                                                               logger.info(`Now downloading: ${t.artist} - ${t.name}`)
+                                                               downloadTrack(t, downloading.settings, null, function (err, track) {
                                                                        if (!err) {
                                                                                downloading.downloaded++;
+                                                                               downloading.settings.playlistArr[track[0]] = track[1];
                                                                        } else {
                                                                                downloading.failed++;
+                                                                               downloading.errorLog += track+"\r\n"
                                                                        }
                                                                        socket.emit("downloadProgress", {
                                                                                queueId: downloading.queueId,
                                                                                percentage: ((downloading.downloaded+downloading.failed) / downloading.size) * 100
                                                                        });
+                                                                       if (downloading.downloaded + downloading.failed == downloading.size)
+                                                                               resolve();
                                                                        socket.emit("updateQueue", downloading);
-                                                                       callback();
+                                                                       cb();
                                                                });
-                                                       }, function (err) {
-                                                               logger.logs('Info',"Playlist finished "+downloading.name);
-                                                               if(typeof socket.downloadQueue[0] != 'undefined'){
-                                                                       socket.emit("downloadProgress", {
-                                                                               queueId: socket.downloadQueue[0].queueId,
-                                                                               percentage: 100
-                                                                       });
-                                                               }
-                                                               if (downloading && socket.downloadQueue[0] && socket.downloadQueue[0].queueId == downloading.queueId) socket.downloadQueue.shift();
-                                                               socket.currentItem = null;
-                                                               //fs.rmdirSync(coverArtDir);
-                                                               queueDownload(getNextDownload());
                                                        });
-                                               }).catch((err)=>{
-                                                       logger.logs('Error','Something went wrong!'+err);
+                                                       return true;
                                                });
-                                       }).catch((err)=>{
-                                               logger.logs('Error','Something went wrong!'+err);
                                        });
-                               }, function(err) {logger.logs('Error','Something went wrong!'+err)});
-               } else if (downloading.type == "album") {
-                       logger.logs('Info',"Registered an album "+downloading.id);
-                       Deezer.getAlbumTracks(downloading.id, function (tracks, err) {
-                               downloading.settings.tagPosition = true;
-                               downloading.settings.albName = downloading.name;
-                               downloading.settings.artName = downloading.artist;
-                               async.eachSeries(tracks.data, function (t, callback) {
-                                       if (downloading.cancelFlag) {
-                                               logger.logs('Info',"Stopping the album queue");
-                                               callback("stop");
-                                               return;
-                                       }
-                                       downloading.settings.playlist = {
-                                               position: tracks.data.indexOf(t),
-                                               fullSize: tracks.data.length
-                                       };
-                                       downloadTrack([t.id,0], downloading.settings, null, function (err) {
-                                               if (!err) {
-                                                       downloading.downloaded++;
-                                               } else {
-                                                       downloading.failed++;
-                                               }
+                                       downloading.finished.then(()=>{
+                                               logger.info("Playlist finished "+downloading.name);
                                                socket.emit("downloadProgress", {
                                                        queueId: downloading.queueId,
-                                                       percentage: ((downloading.downloaded+downloading.failed) / downloading.size) * 100
-                                               });
-                                               socket.emit("updateQueue", downloading);
-                                               callback();
-                                       });
-                               }, function (err) {
-                                       if (downloading.countPerAlbum) {
-                                               if (socket.downloadQueue.length > 1 && socket.downloadQueue[1].queueId == downloading.queueId) {
-                                                       socket.downloadQueue[1].download = downloading.downloaded;
-                                               }
-                                               socket.emit("updateQueue", downloading);
-                                       }
-                                       logger.logs('Info',"Album finished "+downloading.name);
-                                       if(typeof socket.downloadQueue[0] != 'undefined'){
-                                               socket.emit("downloadProgress", {
-                                                       queueId: socket.downloadQueue[0].queueId,
                                                        percentage: 100
                                                });
-                                       }
-                                       if (downloading && socket.downloadQueue[0] && socket.downloadQueue[0].queueId == downloading.queueId) socket.downloadQueue.shift();
-                                       socket.currentItem = null;
-                                       queueDownload(getNextDownload());
+                                               let filePath = mainFolder+antiDot(fixName(downloading.settings.plName)) + path.sep
+                                               if (downloading.settings.logErrors){
+                                                       if (downloading.errorLog != ""){
+                                                               if (!fs.existsSync(filePath)) fs.mkdirSync(filePath);
+                                                               fs.writeFileSync(filePath+"notFound.txt",downloading.errorLog)
+                                                       }else{
+                                                               if (fs.existsSync(filePath+"notFound.txt")) fs.unlinkSync(filePath+"notFound.txt");
+                                                       }
+                                               }
+                                               if (downloading.settings.createM3UFile){
+                                                       fs.writeFileSync(filePath + "playlist.m3u", downloading.settings.playlistArr.join("\r\n"));
+                                               }
+                                               if (downloading.settings.saveArtwork){
+                                                       if (!fs.existsSync(filePath)) fs.mkdirSync(filePath);
+                                                       let imgPath = filePath + "folder.jpg";
+                                                       if (downloading.cover){
+                                                               request.get(downloading.cover, {encoding: 'binary'}, function(error,response,body){
+                                                                       if(error){
+                                                                               logger.error(error.stack);
+                                                                               return;
+                                                                       }
+                                                                       fs.outputFile(imgPath,body,'binary',function(err){
+                                                                               if(err){
+                                                                                       logger.error(err.stack);
+                                                                                       return;
+                                                                               }
+                                                                               logger.info(`Cover downloaded for: ${downloading.settings.plName}`)
+                                                                       })
+                                                               });
+                                                       }
+                                               }
+                                               if (downloading && socket.downloadQueue[Object.keys(socket.downloadQueue)[0]] && (Object.keys(socket.downloadQueue)[0] == downloading.queueId)) delete socket.downloadQueue[Object.keys(socket.downloadQueue)[0]];
+                                               socket.currentItem = null;
+                                               queueDownload(getNextDownload());
+                                       }).catch((err)=>{
+                                               if (err) return logger.error(err.stack);
+                                               logger.info("Stopping the playlist queue");
+                                               if (downloading && socket.downloadQueue[Object.keys(socket.downloadQueue)[0]] && (Object.keys(socket.downloadQueue)[0] == downloading.queueId)) delete socket.downloadQueue[Object.keys(socket.downloadQueue)[0]];
+                                               socket.currentItem = null;
+                                               queueDownload(getNextDownload());
+                                       });
+                               }).catch((err)=>{
+                                       logger.error('Something went wrong!'+err.stack);
                                });
+                       }).catch((err)=>{
+                               logger.error('Something went wrong!'+err.stack);
                        });
+                       break;
                }
        }
 
-       socket.on("downloadtrack", function (data) {
-               Deezer.getTrack(data.id, configFile.userDefined.hifi, function (track, err) {
-                       if (err) {
-                               return;
-                       }
-                       let queueId = "id" + Math.random().toString(36).substring(2);
-                       let _track = {
-                               name: track["SNG_TITLE"],
-                               size: 1,
-                               downloaded: 0,
-                               failed: 0,
-                               queueId: queueId,
-                               id: track["SNG_ID"],
-                               type: "track"
-                       };
-                       if (track["VERSION"]) _track.name = _track.name + " " + track["VERSION"];
-                       _track.settings = data.settings || {};
-                       addToQueue(_track);
-               });
-       });
-
-       socket.on("downloadplaylist", function (data) {
-               Deezer.getPlaylist(data.id, function (playlist, err) {
-                       if (err) {
-                               return;
-                       }
-                       Deezer.getPlaylistSize(data.id, function (size, err) {
-                               if (err) {
-                                       return;
-                               }
-                               let queueId = "id" + Math.random().toString(36).substring(2);
-                               let _playlist = {
-                                       name: playlist["title"],
-                                       size: size,
-                                       downloaded: 0,
-                                       failed: 0,
-                                       queueId: queueId,
-                                       id: playlist["id"],
-                                       type: "playlist"
-                               };
-                               _playlist.settings = data.settings || {};
-                               addToQueue(_playlist);
-                       });
-               });
-       });
-
-       socket.on("downloadspotifyplaylist", function (data) {
-               spotifyApi.clientCredentialsGrant().then(function(creds) {
-                       spotifyApi.setAccessToken(creds.body['access_token']);
-                       spotifyApi.getPlaylist(data.settings.spotifyUser, data.id, {fields: "id,name,tracks.total"}).then(function(resp) {
-                               let queueId = "id" + Math.random().toString(36).substring(2);
-                               let _playlist = {
-                                       name: resp.body["name"],
-                                       size: resp.body["tracks"]["total"],
-                                       downloaded: 0,
-                                       failed: 0,
-                                       queueId: queueId,
-                                       id: resp.body["id"],
-                                       type: "spotifyplaylist"
-                               };
-                               _playlist.settings = data.settings || {};
-                               addToQueue(_playlist);
-                       }, function(err) {
-                               logger.logs('Error','Something went wrong!'+err);
-                       });
-               },
-               function(err) {
-                       logger.logs('Error','Something went wrong!'+err);
-               });
-       });
-
-       socket.on("downloadalbum", function (data) {
-               Deezer.getAlbum(data.id, function (album, err) {
-                       if (err) {
-                               return;
-                       }
-                       Deezer.getAlbumSize(data.id, function (size, err) {
-                               if (err) {
-                                       return;
-                               }
-                               let queueId = "id" + Math.random().toString(36).substring(2);
-                               let _album = {
-                                       name: album["title"],
-                                       label: album["label"],
-                                       artist: album["artist"].name,
-                                       size: size,
-                                       downloaded: 0,
-                                       failed: 0,
-                                       queueId: queueId,
-                                       id: album["id"],
-                                       type: "album"
-                               };
-                               _album.settings = data.settings || {};
-                               addToQueue(_album);
-                       });
-               });
-       });
-
-       socket.on("downloadartist", function (data) {
-               Deezer.getArtist(data.id, function (artist, err) {
-                       if (err) {
-                               return;
-                       }
-                       Deezer.getArtistAlbums(data.id, function (albums, err) {
-                               if (err) {
-                                       return;
-                               }
-                               for (let i = 0; i < albums.data.length; i++) {
-                                       Deezer.getAlbumSize(albums.data[i].id, function(size, err){
-                                               if(err) {
-                                                 return;
-                                               }
-                                               let queueId = "id" + Math.random().toString(36).substring(2);
-                                               let album = albums.data[i];
-                                               let _album = {
-                                                       name: album["title"],
-                                                       artist: artist.name,
-                                                       size: size,
-                                                       downloaded: 0,
-                                                       failed: 0,
-                                                       queueId: queueId,
-                                                       id: album["id"],
-                                                       type: "album",
-                                                       countPerAlbum: true
-                                               };
-                                               _album.settings = data.settings || {};
-                                               addToQueue(_album);
-                                       });
-                               }
-                       });
-               });
-       });
-
-       socket.on("getChartsTopCountry", function () {
+       socket.on("getChartsCountryList", function (data) {
                Deezer.getChartsTopCountry(function (charts, err) {
                        if(err){
                                return;
@@ -560,15 +706,27 @@ io.sockets.on('connection', function (socket) {
                        }else{
                                charts = [];
                        }
-                       socket.emit("getChartsTopCountry", {charts: charts.data, err: err});
+                       let countries = [];
+                       for (let i = 0; i < charts.length; i++) {
+                               let obj = {
+                                       country: charts[i].title.replace("Top ", ""),
+                                       picture_small: charts[i].picture_small,
+                                       picture_medium: charts[i].picture_medium,
+                                       picture_big: charts[i].picture_big
+                               };
+                               countries.push(obj);
+                       }
+                       socket.emit("getChartsCountryList", {countries: countries, selected: data.selected});
                });
        });
 
-       socket.on("getChartsCountryList", function (data) {
+       socket.on("getChartsTrackListByCountry", function (data) {
+               if (!data.country) {
+                       socket.emit("getChartsTrackListByCountry", {err: "No country passed"});
+                       return;
+               }
                Deezer.getChartsTopCountry(function (charts, err) {
-                       if(err){
-                               return;
-                       }
+                       if(err) return;
                        if(charts){
                                charts = charts.data || [];
                        }else{
@@ -576,20 +734,29 @@ io.sockets.on('connection', function (socket) {
                        }
                        let countries = [];
                        for (let i = 0; i < charts.length; i++) {
-                               let obj = {
-                                       country: charts[i].title.replace("Top ", ""),
-                                       picture_small: charts[i].picture_small,
-                                       picture_medium: charts[i].picture_medium,
-                                       picture_big: charts[i].picture_big
-                               };
-                               countries.push(obj);
+                               countries.push(charts[i].title.replace("Top ", ""));
+                       }
+
+                       if (countries.indexOf(data.country) == -1) {
+                               socket.emit("getChartsTrackListByCountry", {err: "Country not found"});
+                               return;
                        }
-                       socket.emit("getChartsCountryList", {countries: countries, selected: data.selected});
+                       let playlistId = charts[countries.indexOf(data.country)].id;
+                       Deezer.getPlaylistTracks(playlistId, function (tracks, err) {
+                               if (err) {
+                                       socket.emit("getChartsTrackListByCountry", {err: err});
+                                       return;
+                               }
+                               socket.emit("getChartsTrackListByCountry", {
+                                       playlist: charts[countries.indexOf(data.country)],
+                                       tracks: tracks.data
+                               });
+                       });
                });
        });
 
        socket.on("getMePlaylistList", function (d) {
-               logger.logs("Info","Loading Personal Playlists")
+               logger.info("Loading Personal Playlists")
                Deezer.getMePlaylists(function (data, err) {
                        if(err){
                                return;
@@ -599,7 +766,7 @@ io.sockets.on('connection', function (socket) {
                        }else{
                                data = [];
                        }
-                       var playlists = [];
+                       let playlists = [];
                        for (let i = 0; i < data.length; i++) {
                                let obj = {
                                        title: data[i].title,
@@ -616,7 +783,7 @@ io.sockets.on('connection', function (socket) {
                                                let total = data.body.total
                                                let numPages=Math.floor((total-1)/20);
                                                let pages = [];
-                                               var playlistList = new Array(total);
+                                               let playlistList = new Array(total);
                                                for (let offset = 0; offset<=numPages; offset++){
                                                        pages.push(new Promise(function(resolvePage) {
                                                                spotifyApi.getUserPlaylists(configFile.userDefined.spotifyUser, {fields: "items(images,name,owner.id,tracks.total,uri)", offset: offset*20}).then(data=>{
@@ -634,65 +801,25 @@ io.sockets.on('connection', function (socket) {
                                                }
                                                Promise.all(pages).then(()=>{
                                                        playlists = playlists.concat(playlistList);
+                                                       logger.info(`Loaded ${playlists.length} Playlist${playlists.length>1 ? "s" : ""}`);
                                                        socket.emit("getMePlaylistList", {playlists: playlists});
                                                });
                                        }).catch(err=>{
-                                               logger.logs("Error",err);
+                                               logger.error(err.stack);
                                        });
                                }).catch(err=>{
-                                       logger.logs("Error",err);
+                                       logger.error(err.stack);
                                });
                        }else{
+                               logger.info(`Loaded ${playlists.length} Playlist${playlists.length>1 ? "s" : ""}`);
                                socket.emit("getMePlaylistList", {playlists: playlists});
                        }
                });
        });
 
-       socket.on("getChartsTrackListByCountry", function (data) {
-               if (!data.country) {
-                       socket.emit("getChartsTrackListByCountry", {err: "No country passed"});
-                       return;
-               }
-
-               Deezer.getChartsTopCountry(function (charts, err) {
-                       if(err){
-                               return;
-                       }
-                       if(charts){
-                               charts = charts.data || [];
-                       }else{
-                               charts = [];
-                       }
-                       let countries = [];
-                       for (let i = 0; i < charts.length; i++) {
-                               countries.push(charts[i].title.replace("Top ", ""));
-                       }
-
-                       if (countries.indexOf(data.country) == -1) {
-                               socket.emit("getChartsTrackListByCountry", {err: "Country not found"});
-                               return;
-                       }
-
-                       let playlistId = charts[countries.indexOf(data.country)].id;
-
-                       Deezer.getPlaylistTracks(playlistId, function (tracks, err) {
-                               if (err) {
-                                       socket.emit("getChartsTrackListByCountry", {err: err});
-                                       return;
-                               }
-                               socket.emit("getChartsTrackListByCountry", {
-                                       playlist: charts[countries.indexOf(data.country)],
-                                       tracks: tracks.data
-                               });
-                       });
-               });
-       });
-
        socket.on("search", function (data) {
                data.type = data.type || "track";
-               if (["track", "playlist", "album", "artist"].indexOf(data.type) == -1) {
-                       data.type = "track";
-               }
+               if (["track", "playlist", "album", "artist"].indexOf(data.type) == -1) data.type = "track";
 
                // Remove "feat."  "ft." and "&" (causes only problems)
                data.text = data.text.replace(/ feat[\.]? /g, " ").replace(/ ft[\.]? /g, " ").replace(/\(feat[\.]? /g, " ").replace(/\(ft[\.]? /g, " ").replace(/\&/g, "");
@@ -706,29 +833,11 @@ io.sockets.on('connection', function (socket) {
                });
        });
 
-       socket.on("getInformation", function (data) {
-               if (!data.type || (["track", "playlist", "album", "artist"].indexOf(data.type) == -1) || !data.id) {
-                       socket.emit("getInformation", {err: -1, response: {}, id: data.id});
-                       return;
-               }
-
-               let reqType = data.type.charAt(0).toUpperCase() + data.type.slice(1);
-
-               Deezer["get" + reqType](data.id, function (response, err) {
-                       if (err) {
-                               socket.emit("getInformation", {err: "wrong id "+reqType, response: {}, id: data.id});
-                               return;
-                       }
-                       socket.emit("getInformation", {response: response, id: data.id});
-               });
-       });
-
        socket.on("getTrackList", function (data) {
                if (!data.type || (["playlist", "album", "artist"].indexOf(data.type) == -1) || !data.id) {
                        socket.emit("getTrackList", {err: -1, response: {}, id: data.id, reqType: data.type});
                        return;
                }
-
                if (data.type == 'artist') {
                        Deezer.getArtistAlbums(data.id, function (response, err) {
                                if (err) {
@@ -739,7 +848,6 @@ io.sockets.on('connection', function (socket) {
                        });
                } else {
                        let reqType = data.type.charAt(0).toUpperCase() + data.type.slice(1);
-
                        Deezer["get" + reqType + "Tracks"](data.id, function (response, err) {
                                if (err) {
                                        socket.emit("getTrackList", {err: "wrong id "+reqType, response: {}, id: data.id, reqType: data.type});
@@ -748,34 +856,26 @@ io.sockets.on('connection', function (socket) {
                                socket.emit("getTrackList", {response: response, id: data.id, reqType: data.type});
                        });
                }
-
        });
 
        socket.on("cancelDownload", function (data) {
                if (!data.queueId) {
                        return;
                }
-
                let cancel = false;
                let cancelSuccess;
-
-               for (let i = 0; i < socket.downloadQueue.length; i++) {
-                       if (data.queueId == socket.downloadQueue[i].queueId) {
-                               socket.downloadQueue.splice(i, 1);
-                               i--;
-                               cancel = true;
-                       }
+               if (socket.downloadQueue[data.queueId]){
+                       cancel = true;
+                       delete socket.downloadQueue[data.queueId];
                }
-
                if (socket.currentItem && socket.currentItem.queueId == data.queueId) {
-                       cancelSuccess = Deezer.cancelDecryptTrack();
+                       cancelSuccess = Deezer.cancelDecryptTrack(data.queueId);
+                       socket.trackQueue = queue({
+                               autostart: true,
+                               concurrency: socket.trackQueue.concurrency
+                       })
                        cancel = cancel || cancelSuccess;
                }
-
-
-               if (cancelSuccess && socket.currentItem) {
-                       socket.currentItem.cancelFlag = true;
-               }
                if (cancel) {
                        socket.emit("cancelDownload", {queueId: data.queueId});
                }
@@ -798,222 +898,175 @@ io.sockets.on('connection', function (socket) {
                if (!settings.downloadLocation) {
                        settings.downloadLocation = mainFolder;
                }
-
                socket.emit('getUserSettings', {settings: settings});
        });
 
        socket.on("saveSettings", function (settings) {
                if (settings.userDefined.downloadLocation == defaultDownloadDir) {
-                       settings.userDefined.downloadLocation = null;
+                       settings.userDefined.downloadLocation = "";
                } else {
                        settings.userDefined.downloadLocation = path.resolve(settings.userDefined.downloadLocation + path.sep) + path.sep;
                        mainFolder = settings.userDefined.downloadLocation;
                }
 
+               if (settings.userDefined.queueConcurrency < 1) settings.userDefined.queueConcurrency = 1;
+
+               if (settings.userDefined.queueConcurrency != socket.trackQueue.concurrency){
+                       socket.trackQueue.concurrency = settings.userDefined.queueConcurrency;
+               }
+
+               if (settings.userDefined.chartsCountry != configFile.userDefined.chartsCountry){
+                       socket.emit("setChartsCountry", {selected: settings.userDefined.chartsCountry});
+                       Deezer.getChartsTopCountry(function (charts, err) {
+                               if(err){
+                                       return;
+                               }
+                               if(charts){
+                                       charts = charts.data || [];
+                               }else{
+                                       charts = [];
+                               }
+                               let countries = [];
+                               for (let i = 0; i < charts.length; i++) {
+                                       countries.push(charts[i].title.replace("Top ", ""));
+                               }
+
+                               if (countries.indexOf(settings.userDefined.chartsCountry) == -1) {
+                                       socket.emit("getChartsTrackListByCountry", {err: "Country not found"});
+                                       return;
+                               }
+
+                               let playlistId = charts[countries.indexOf(settings.userDefined.chartsCountry)].id;
+
+                               Deezer.getPlaylistTracks(playlistId, function (tracks, err) {
+                                       if (err) {
+                                               socket.emit("getChartsTrackListByCountry", {err: err});
+                                               return;
+                                       }
+                                       socket.emit("getChartsTrackListByCountry", {
+                                               playlist: charts[countries.indexOf(settings.userDefined.chartsCountry)],
+                                               tracks: tracks.data
+                                       });
+                               });
+                       });
+               }
+
                configFile.userDefined = settings.userDefined;
                fs.outputFile(configFileLocation, JSON.stringify(configFile, null, 2), function (err) {
                        if (err) return;
-                       logger.logs('Info',"Settings updated");
+                       logger.info("Settings updated");
                        initFolders();
                });
        });
 
-       function downloadTrack(id, settings, altmetadata, callback) {
-               logger.logs('Info',"Getting track data");
-               Deezer.getTrack(id[0], configFile.userDefined.hifi, function (track, err) {
-                       if (err) {
-                               if(id[1] != 0){
-                                       logger.logs('Warning',"Failed to download track, falling on alternative");
-                                       downloadTrack([id[1],0], settings, null, function(err){
-                                               callback(err);
-                                       });
-                               }else{
-                                       logger.logs('Error',"Failed to download track");
-                                       callback(err);
-                               }
-                               return;
-                       }
-                       logger.logs('Info',"Getting album data");
-                       Deezer.getAlbum(track["ALB_ID"], function(res, err){
-                               if(err){
-                                       if(id[1] != 0){
-                                               logger.logs('Warning',"Failed to download track, falling on alternative");
-                                               downloadTrack([id[1],0], settings, null, function(err){
-                                                       callback(err);
-                                               });
-                                       }else{
-                                               logger.logs('Error',"Failed to download track");
-                                               callback(new Error("Album does not exists."));
-                                       }
-                                       return;
-                               }
-                               logger.logs('Info',"Getting ATrack data");
-                               Deezer.getATrack(res.tracks.data[res.tracks.data.length - 1].id, function(tres){
-                                       track.trackSocket = socket;
-
-                                       settings = settings || {};
-                                       if (track["VERSION"]) track["SNG_TITLE"] += " " + track["VERSION"];
-                                       var ajson = res;
-                                       var tjson = tres;
-                                       if(track["SNG_CONTRIBUTORS"]){
-                                               if(track["SNG_CONTRIBUTORS"].composer){
-                                                       var composertag = "";
-                                                       for (var i = 0; i < track["SNG_CONTRIBUTORS"].composer.length; i++) {
-                                                               composertag += track["SNG_CONTRIBUTORS"].composer[i] + ", ";
-                                                       }
-                                                       composertag = composertag.substring(0,composertag.length-2);
-                                               }
-                                               if(track["SNG_CONTRIBUTORS"].musicpublisher){
-                                                       var publishertag = "";
-                                                       for (var i = 0; i < track["SNG_CONTRIBUTORS"].musicpublisher.length; i++) {
-                                                               publishertag += track["SNG_CONTRIBUTORS"].musicpublisher[i] + ", ";
-                                                       }
-                                                       publishertag = publishertag.substring(0,publishertag.length-2);
-                                               }
-                                               if(track["SNG_CONTRIBUTORS"].producer){
-                                                       var producertag = "";
-                                                       for (var i = 0; i < track["SNG_CONTRIBUTORS"].producer.length; i++) {
-                                                               producertag += track["SNG_CONTRIBUTORS"].producer[i] + ", ";
-                                                       }
-                                                       producertag = producertag.substring(0,producertag.length-2);
-                                               }
-                                               if(track["SNG_CONTRIBUTORS"].engineer){
-                                                       var engineertag = "";
-                                                       for (var i = 0; i < track["SNG_CONTRIBUTORS"].engineer.length; i++) {
-                                                               engineertag += track["SNG_CONTRIBUTORS"].engineer[i] + ", ";
-                                                       }
-                                                       engineertag = engineertag.substring(0,engineertag.length-2);
-                                               }
-                                               if(track["SNG_CONTRIBUTORS"].writer){
-                                                       var writertag = "";
-                                                       for (var i = 0; i < track["SNG_CONTRIBUTORS"].writer.length; i++) {
-                                                               writertag += track["SNG_CONTRIBUTORS"].writer[i] + ", ";
-                                                       }
-                                                       writertag = writertag.substring(0,writertag.length-2);
-                                               }
-                                               if(track["SNG_CONTRIBUTORS"].author){
-                                                       var authortag = "";
-                                                       for (var i = 0; i < track["SNG_CONTRIBUTORS"].author.length; i++) {
-                                                               authortag += track["SNG_CONTRIBUTORS"].author[i] + ", ";
-                                                       }
-                                                       authortag = authortag.substring(0,authortag.length-2);
-                                               }
-                                               if(track["SNG_CONTRIBUTORS"].mixer){
-                                                       var mixertag = "";
-                                                       for (var i = 0; i < track["SNG_CONTRIBUTORS"].mixer.length; i++) {
-                                                               mixertag += track["SNG_CONTRIBUTORS"].mixer[i] + ", ";
-                                                       }
-                                                       mixertag = mixertag.substring(0,mixertag.length-2);
+       function downloadTrack(t, settings, altmetadata, callback) {
+               if (!socket.downloadQueue[t.queueId]) {
+                       logger.error("Not in queue");
+                       callback(new Error("Not in queue"), `${t.id} | ${t.artist} - ${t.name}`);
+                       return;
+               }
+               if (t.id == 0){
+                       logger.error("Failed to download track: Wrong ID");
+                       callback(new Error("Failed to download track: Wrong ID"), `${t.id} | ${t.artist} - ${t.name}`);
+                       return;
+               }
+               let temp1 = new Promise((resolve, reject)=>{
+                       if (!settings.trackInfo){
+                               logger.info("Getting track data");
+                               Deezer.getTrack(t.id, settings.hifi, function (trackInfo, err) {
+                                       if (err) {
+                                               if(t.fallback){
+                                                       logger.warn("Failed to download track, falling on alternative");
+                                                       t.id = t.fallback
+                                                       t.fallback = 0
+                                                       downloadTrack(t, settings, null, function(err){
+                                                               callback(err, `${t.id} | ${t.artist} - ${t.name}`);
+                                                       });
+                                               }else if(!t.searched){
+                                                       logger.warn("Failed to download track, searching for alternative");
+                                                       Deezer.track2ID(t.artist, t.name, data=>{
+                                                               t.searched = true;
+                                                               t.id = data.id;
+                                                               t.artist = data.artist;
+                                                               t.name = data.name;
+                                                               downloadTrack(t, settings, null, function(err){
+                                                                       callback(err, `${t.id} | ${t.artist} - ${t.name}`);
+                                                               });
+                                                       });
+                                               }else{
+                                                       logger.error("Failed to download track: "+ err);
+                                                       callback(err, `${t.id} | ${t.artist} - ${t.name}`);
                                                }
+                                               return;
                                        }
-                                       let metadata;
-                                       if(altmetadata){
-                                               metadata = altmetadata;
-                                               if(track["LYRICS_TEXT"] && !metadata.unsynchronisedLyrics){
-                                                       metadata.unsynchronisedLyrics = {
-                                                               description: "",
-                                                               lyrics: track["LYRICS_TEXT"]
-                                                       };
-                                               }
-                                       }else{
-                                               metadata = {
-                                                       title: track["SNG_TITLE"],
-                                                       artist: track["ART_NAME"],
-                                                       album: track["ALB_TITLE"],
-                                                       performerInfo: ajson.artist.name,
-                                                       trackNumber: track["TRACK_NUMBER"] + "/" + ajson.nb_tracks,
-                                                       partOfSet: track["DISK_NUMBER"] + "/" + tjson.disk_number,
-                                                       explicit: track["EXPLICIT_LYRICS"],
-                                                       ISRC: track["ISRC"],
-                                               };
-                                               if (configFile.userDefined.extendedTags){
-                                                       metadata.length= track["DURATION"];
-                                                       metadata.BARCODE= ajson.upc;
-                                                       metadata.rtype= ajson.record_type;
-                                                       if(track["COPYRIGHT"]){
-                                                               metadata.copyright = track["COPYRIGHT"];
-                                                       }
-                                                       if(composertag){
-                                                               metadata.composer = composertag;
-                                                       }
-                                                       if(mixertag){
-                                                               metadata.mixer = mixertag;
-                                                       }
-                                                       if(authortag){
-                                                               metadata.author = authortag;
-                                                       }
-                                                       if(writertag){
-                                                               metadata.writer = writertag;
-                                                       }
-                                                       if(engineertag){
-                                                               metadata.engineer = engineertag;
-                                                       }
-                                                       if(producertag){
-                                                               metadata.producer = producertag;
-                                                       }
-                                                       if(track["LYRICS_TEXT"]){
-                                                               metadata.unsynchronisedLyrics = {
-                                                                       description: "",
-                                                                       lyrics: track["LYRICS_TEXT"]
-                                                               };
-                                                       }
-                                                       if (track["GAIN"]) {
-                                                               metadata.trackgain = track["GAIN"];
+                                       resolve(trackInfo);
+                               });
+                       }else{
+                               resolve(settings.trackInfo);
+                       }
+               })
+               temp1.then(data=>{
+                       let track = data;
+                       track.trackSocket = socket;
+                       logger.info("Getting album data");
+                       let temp2 = new Promise((resolve, reject)=>{
+                               if (!settings.albumInfo){
+                                       Deezer.getAlbum(track["ALB_ID"], function(res, err){
+                                               if(err){
+                                                       if(t.fallback){
+                                                               logger.warn("Failed to download track, falling on alternative");
+                                                               t.id = t.fallback
+                                                               t.fallback = 0
+                                                               downloadTrack(t, settings, null, function(err){
+                                                                       callback(err, `${t.id} | ${t.artist} - ${t.name}`);
+                                                               });
+                                                       }else if(!t.searched){
+                                                               logger.warn("Failed to download track, searching for alternative");
+                                                               Deezer.track2ID(t.artist, t.name, data=>{
+                                                                       t.searched = true;
+                                                                       t.id = data.id;
+                                                                       t.artist = data.artist;
+                                                                       t.name = data.name;
+                                                                       downloadTrack(t, settings, null, function(err){
+                                                                               callback(err, `${t.id} | ${t.artist} - ${t.name}`);
+                                                                       });
+                                                               });
+                                                       }else{
+                                                               logger.error("Failed to download track: "+ err);
+                                                               callback(new Error("Album does not exists."), `${t.id} | ${t.artist} - ${t.name}`);
                                                        }
+                                                       return;
                                                }
-
-
-                                               if(ajson.label){
-                                                       metadata.publisher = ajson.label;
-                                               }
-                                               if(settings.plName && !(settings.createArtistFolder || settings.createAlbumFolder) && !configFile.userDefined.numplaylistbyalbum){
-                                                       metadata.trackNumber = (parseInt(settings.playlist.position)+1).toString() + "/" + settings.playlist.fullSize;
-                                                       metadata.partOfSet = "1/1";
-                                               }
-                                               if(settings.artName){
-                                                       metadata.trackNumber = (settings.playlist.position+1).toString() + "/" + ajson.nb_tracks;
-                                               }
-                                               if (0 < parseInt(track["BPM"])) {
-                                                       metadata.bpm = track["BPM"];
-                                               }
-                                               if(ajson.genres && ajson.genres.data[0] && ajson.genres.data[0].name){
-                                                       metadata.genre = ajson.genres.data[0].name;
-                                                   if (track.format == 9){
-                                                           metadata.genre = ajson.genres.data[0].name;
-                                                   } else {
-                                                       genreArray = [];
-                                                       var first = true;
-                                                       ajson.genres.data.forEach(function(genre){
-                                                               genreArray.push(genre.name);
-                                                       });
-                                                       Array.from(new Set(genreArray)).forEach(function(genre){
-                                                               if(first){
-                                                                       metadata.genre = genre;
-                                                                       first = false;
-                                                               } else{
-                                                                       if(metadata.genre.indexOf(genre) == -1)
-                                                                               metadata.genre += String.fromCharCode(parseInt("\u0000",16)) + genre;
-                                                               }
-                                                       });
-                                               }
-                                               }
-                                               if (track["ALB_PICTURE"]) {
-                                                       metadata.image = Deezer.albumPicturesHost + track["ALB_PICTURE"] + settings.artworkSize;
-                                               }
-
-                                               if (ajson.release_date) {
-                                                       metadata.year = ajson.release_date.slice(0, 4);
-                                                       metadata.date = ajson.release_date;
-                                               }else if(track["PHYSICAL_RELEASE_DATE"]){
-                                                       metadata.year = track["PHYSICAL_RELEASE_DATE"].slice(0, 4);
-                                                       metadata.date = track["PHYSICAL_RELEASE_DATE"];
-                                               }
-                                       }
+                                               resolve(res);
+                                       })
+                               }else{
+                                       resolve(settings.albumInfo)
+                               }
+                       });
+                       temp2.then(res=>{
+                               settings = settings || {};
+                               let ajson = res;
+                               let totalDiskNumber;
+                               let temp3;
+                               if (settings.partOfSet){
+                                       logger.info("Getting total disk number data");
+                                       temp3 = new Promise((resolve, reject) =>{
+                                               Deezer.getATrack(ajson.tracks.data[ajson.tracks.data.length-1].id, function(tres){
+                                                       totalDiskNumber = tres.disk_number;
+                                                       resolve();
+                                               });
+                                       })
+                               }else{
+                                       temp3 = new Promise((resolve, reject) =>{
+                                               resolve()
+                                       })
+                               }
+                               temp3.then(()=>{
+                                       let metadata = parseMetadata(track, ajson, totalDiskNumber, settings, t.index, altmetadata);
                                        let filename = fixName(`${metadata.artist} - ${metadata.title}`);
                                        if (settings.filename) {
                                                filename = fixName(settingsRegex(metadata, settings.filename, settings.playlist));
                                        }
-
                                        let filepath = mainFolder;
                                        if (settings.createArtistFolder || settings.createAlbumFolder) {
                                                if(settings.plName){
@@ -1029,147 +1082,156 @@ io.sockets.on('connection', function (socket) {
 
                                                if (settings.createAlbumFolder) {
                                                        if(settings.artName){
-                                                               filepath += antiDot(fixName(settingsRegexAlbum(metadata,settings.foldername,settings.artName,settings.albName))) + path.sep;
+                                                               filepath += antiDot(fixName(settingsRegexAlbum(settings.foldername,settings.artName,settings.albName,metadata.year,metadata.rtype))) + path.sep;
                                                        }else{
-                                                               filepath += antiDot(fixName(settingsRegexAlbum(metadata,settings.foldername,metadata.performerInfo,metadata.album))) + path.sep;
+                                                               filepath += antiDot(fixName(settingsRegexAlbum(settings.foldername,metadata.performerInfo,metadata.album,metadata.year,metadata.rtype))) + path.sep;
                                                        }
                                                }
                                        } else if (settings.plName) {
                                                filepath += antiDot(fixName(settings.plName)) + path.sep;
                                        } else if (settings.artName) {
-                                               filepath += antiDot(fixName(settingsRegexAlbum(metadata,settings.foldername,settings.artName,settings.albName))) + path.sep;
+                                               filepath += antiDot(fixName(settingsRegexAlbum(settings.foldername,settings.artName,settings.albName,metadata.year,metadata.rtype))) + path.sep;
                                        }
-
                                        let writePath;
                                        if(track.format == 9){
                                                writePath = filepath + filename + '.flac';
                                        }else{
                                                writePath = filepath + filename + '.mp3';
                                        }
-                                       if(track["LYRICS_SYNC_JSON"] && configFile.userDefined.syncedlyrics){
-                                               var lyricsbuffer = "";
-                                               for(var i=0;i<track["LYRICS_SYNC_JSON"].length;i++){
+                                       if(track["LYRICS_SYNC_JSON"] && settings.syncedlyrics){
+                                               let lyricsbuffer = "";
+                                               for(let i=0;i<track["LYRICS_SYNC_JSON"].length;i++){
                                                        if(track["LYRICS_SYNC_JSON"][i].lrc_timestamp){
                                                                lyricsbuffer += track["LYRICS_SYNC_JSON"][i].lrc_timestamp+track["LYRICS_SYNC_JSON"][i].line+"\r\n";
                                                        }else if(i+1 < track["LYRICS_SYNC_JSON"].length){
                                                                lyricsbuffer += track["LYRICS_SYNC_JSON"][i+1].lrc_timestamp+track["LYRICS_SYNC_JSON"][i].line+"\r\n";
                                                        }
                                                }
-                                               if(track.format == 9){
-                                                       fs.outputFile(writePath.substring(0,writePath.length-5)+".lrc",lyricsbuffer,function(){});
+                                               fs.outputFile(writePath.substring(0,writePath.lastIndexOf('.'))+".lrc",lyricsbuffer,function(){});
+                                       }
+                                       if (settings.createM3UFile && (settings.plName || settings.albName)) {
+                                               if (!settings.numplaylistbyalbum){
+                                                       t.playlistData = [splitNumber(metadata.trackNumber,false)-1, filename + (track.format == 9 ? ".flac" : ".mp3")];
                                                }else{
-                                                       fs.outputFile(writePath.substring(0,writePath.length-4)+".lrc",lyricsbuffer,function(){});
+                                                       t.playlistData = [t.index, filename + (track.format == 9 ? ".flac" : ".mp3")];
                                                }
+                                       }else{
+                                               t.playlistData = [0,""];
                                        }
-                                       logger.logs('Info','Downloading file to ' + writePath);
                                        if (fs.existsSync(writePath)) {
-                                               logger.logs('Info',"Already downloaded: " + metadata.artist + ' - ' + metadata.title);
-                                               callback();
+                                               logger.info("Already downloaded: " + metadata.artist + ' - ' + metadata.title);
+                                               callback(null, t.playlistData);
                                                return;
+                                       }else{
+                                               logger.info('Downloading file to ' + writePath);
                                        }
-
                                        //Get image
-                                       if (metadata.image) {
-                                               let imgPath;
-                                               //If its not from an album but a playlist.
-                                               if(!settings.tagPosition && !settings.createAlbumFolder){
-                                                       imgPath = coverArtFolder + fixName(metadata.ISRC) + ".jpg";
-                                               }else{
-                                                       imgPath = filepath + "folder.jpg";
-                                               }
-                                               if(fs.existsSync(imgPath) && !imgPath.includes(coverArtFolder)){
-                                                       metadata.imagePath = (imgPath).replace(/\\/g, "/");
-                                                       logger.logs('Info',"Starting the download process CODE:1");
-                                                       condownload();
-                                               }else{
-                                                       request.get(metadata.image, {encoding: 'binary'}, function(error,response,body){
-                                                               if(error){
-                                                                       logger.logs('Error', error.stack);
-                                                                       metadata.image = undefined;
-                                                                       metadata.imagePath = undefined;
-                                                                       return;
-                                                               }
-                                                               fs.outputFile(imgPath,body,'binary',function(err){
-                                                                       if(err){
-                                                                               logger.logs('Error', err.stack);
-                                                                       metadata.image = undefined;
-                                                                       metadata.imagePath = undefined;
+                                       let temp4 = new Promise((resolve, reject)=>{
+                                               if (metadata.image) {
+                                                       let imgPath;
+                                                       //If its not from an album but a playlist.
+                                                       if(!(settings.albName || settings.createAlbumFolder)){
+                                                               imgPath = coverArtFolder + fixName(metadata.ISRC) + ".jpg";
+                                                       }else{
+                                                               if (settings.saveArtwork)
+                                                                       imgPath = filepath + "folder.jpg";
+                                                               else
+                                                                       imgPath = coverArtFolder + fixName(settings.artName+" - "+settings.albName) + ".jpg";
+                                                       }
+                                                       if(fs.existsSync(imgPath)){
+                                                               metadata.imagePath = (imgPath).replace(/\\/g, "/");
+                                                               logger.info("Starting the download process CODE:1");
+                                                               resolve();
+                                                       }else{
+                                                               request.get(metadata.image, {encoding: 'binary'}, function(error,response,body){
+                                                                       if(error){
+                                                                               logger.error(error.stack);
+                                                                               metadata.image = undefined;
+                                                                               metadata.imagePath = undefined;
                                                                                return;
                                                                        }
-                                                                       metadata.imagePath = (imgPath).replace(/\\/g, "/");
-                                                                       logger.logs('Info',"Starting the download process CODE:2");
-                                                                       condownload();
-                                                               })
-                                                       });
+                                                                       fs.outputFile(imgPath,body,'binary',function(err){
+                                                                               if(err){
+                                                                                       logger.error(err.stack);
+                                                                               metadata.image = undefined;
+                                                                               metadata.imagePath = undefined;
+                                                                                       return;
+                                                                               }
+                                                                               metadata.imagePath = (imgPath).replace(/\\/g, "/");
+                                                                               logger.info("Starting the download process CODE:2");
+                                                                               resolve();
+                                                                       })
+                                                               });
+                                                       }
+                                               }else{
+                                                       metadata.image = undefined;
+                                                       logger.info("Starting the download process CODE:3");
+                                                       resolve();
                                                }
-                                       }else{
-                                               metadata.image = undefined;
-                                               logger.logs('Info',"Starting the download process CODE:3");
-                                               condownload();
-                                       }
-                                       function condownload(){
-                                               var tempPath = writePath+".temp";
-                                               logger.logs('Info',"Downloading and decrypting");
-                                               Deezer.decryptTrack(tempPath,track, function (err) {
+                                       })
+                                       temp4.then(()=>{
+                                               let tempPath = writePath+".temp";
+                                               logger.info("Downloading and decrypting");
+                                               Deezer.decryptTrack(tempPath, track, t.queueId, function (err) {
                                                        if (err && err.message == "aborted") {
-                                                               socket.currentItem.cancelFlag = true;
-                                                               logger.logs('Info',"Track got aborted");
-                                                               callback();
+                                                               logger.info("Track got aborted");
+                                                               callback(null, t.playlistData);
                                                                return;
                                                        }
                                                        if (err) {
-                                                               Deezer.hasTrackAlternative(id[0], function (alternative, err) {
-                                                                       if (err || !alternative) {
-                                                                               logger.logs('Error',"Failed to download: " + metadata.artist + " - " + metadata.title);
-                                                                               callback(err);
-                                                                               return;
-                                                                       }
-                                                                       logger.logs('Error',"Failed to download: " + metadata.artist + " - " + metadata.title+", falling on alternative");
-                                                                       downloadTrack([alternative.SNG_ID,0], settings, metadata, callback);
-                                                               });
-                                                               return;
-                                                       }
-                                                       if (settings.createM3UFile && settings.playlist) {
-                                                               if(track.format == 9){
-                                                                       fs.appendFileSync(filepath + "playlist.m3u", filename + ".flac\r\n");
+                                                               if (t.fallback){
+                                                                       logger.error("Failed to download: " + metadata.artist + " - " + metadata.title+", falling on alternative");
+                                                                       t.id = t.fallback
+                                                                       t.fallback = 0
+                                                                       downloadTrack(t, settings, metadata, callback);
+                                                               }else if(!t.searched){
+                                                                       logger.warn("Failed to download track, searching for alternative");
+                                                                       Deezer.track2ID(t.artist, t.name, data=>{
+                                                                               t.searched = true;
+                                                                               t.id = data.id;
+                                                                               t.artist = data.artist;
+                                                                               t.name = data.name;
+                                                                               downloadTrack(t, settings, null, function(err){
+                                                                                       callback(err, `${t.id} | ${t.artist} - ${t.name}`);
+                                                                               });
+                                                                       });
                                                                }else{
-                                                                       fs.appendFileSync(filepath + "playlist.m3u", filename + ".mp3\r\n");
+                                                                       logger.error("Failed to download: " + metadata.artist + " - " + metadata.title);
+                                                                       callback(err, `${t.id} | ${t.artist} - ${t.name}`)
                                                                }
+                                                               return;
                                                        }
-                                                       logger.logs('Info',"Downloaded: " + metadata.artist + " - " + metadata.title);
-                                                       metadata.artist = '';
-                                                       var first = true;
+                                                       logger.info("Downloaded: " + metadata.artist + " - " + metadata.title);
+                                                       metadata.artist = [];
                                                        artistArray = []
                                                        track['ARTISTS'].forEach(function(artist){
                                                                artistArray.push(artist['ART_NAME']);
                                                        });
-                                                       var separator = String.fromCharCode(parseInt("\u0000",16));
-                                                       if (track.format == 9)
-                                                           separator = ', ';
                                                        Array.from(new Set(artistArray)).forEach(function(artist){
-                                                               if(first){
-                                                                       metadata.artist = artist;
-                                                                       first = false;
-                                                               } else{
-                                                                       if(metadata.artist.indexOf(artist) == -1)
-                                                                               metadata.artist += separator + artist;
-                                                               }
+                                                               if(metadata.artist.indexOf(artist) == -1)
+                                                                       metadata.artist.push(artist);
                                                        });
+                                                       let separator = settings.multitagSeparator;
+                                                       if (separator == "null") separator = String.fromCharCode(parseInt("\u0000",16));
+                                                       if (track.format != 9) metadata.artist = metadata.artist.join(separator);
 
                                                        if(track.format == 9){
                                                                let flacComments = [
                                                                        'TITLE=' + metadata.title,
                                                                        'ALBUM=' + metadata.album,
                                                                        'ALBUMARTIST=' + metadata.performerInfo,
-                                                                       'ARTIST=' + metadata.artist,
                                                                        'TRACKNUMBER=' + splitNumber(metadata.trackNumber,false),
                                                                        'DISCNUMBER=' + splitNumber(metadata.partOfSet,false),
                                                                        'TRACKTOTAL=' + splitNumber(metadata.trackNumber,true),
-                                                                       'DISCTOTAL=' + splitNumber(metadata.partOfSet,true),
                                                                        'ITUNESADVISORY=' + metadata.explicit,
                                                                        'ISRC=' + metadata.ISRC
                                                                ];
-                                                               if(configFile.userDefined.extendedTags){
+                                                               metadata.artist.forEach(x=>{
+                                                                       flacComments.push('ARTIST=' + x);
+                                                               })
+                                                               if (settings.partOfSet)
+                                                                       flacComments.push('DISCTOTAL='+splitNumber(metadata.partOfSet,true))
+                                                               if(settings.extendedTags){
                                                                        flacComments.push(
                                                                                'LENGTH=' + metadata.length,
                                                                                'BARCODE=' + metadata.BARCODE
@@ -1179,13 +1241,14 @@ io.sockets.on('connection', function (socket) {
                                                                        flacComments.push('LYRICS='+metadata.unsynchronisedLyrics.lyrics);
                                                                }
                                                                if(metadata.genre){
-                                                                       flacComments.push('GENRE=' + metadata.genre);
+                                                                       metadata.genre.forEach(x=>{
+                                                                               flacComments.push('GENRE=' + x);
+                                                                       })
                                                                }
                                                                if(metadata.copyright){
                                                                        flacComments.push('COPYRIGHT=' + metadata.copyright);
                                                                }
                                                                if (0 < parseInt(metadata.year)) {
-                                                                       flacComments.push('DATE=' + metadata.date);
                                                                        flacComments.push('YEAR=' + metadata.year);
                                                                }
                                                                if (0 < parseInt(metadata.bpm)) {
@@ -1218,7 +1281,6 @@ io.sockets.on('connection', function (socket) {
                                                                const reader = fs.createReadStream(tempPath);
                                                                const writer = fs.createWriteStream(writePath);
                                                                let processor = new mflac.Processor({parseMetaDataBlocks: true});
-
                                                                let vendor = 'reference libFLAC 1.2.1 20070917';
                                                                let cover = null;
                                                                if(metadata.imagePath){
@@ -1233,18 +1295,17 @@ io.sockets.on('connection', function (socket) {
                                                                        } else if (mflac.Processor.MDB_TYPE_PICTURE === mdb.type) {
                                                                                mdb.remove();
                                                                        }
-
                                                                        if (mdb.isLast) {
-                                                                               var res = 0;
-                                                                               if(configFile.userDefined.artworkSize.includes("1400")){
+                                                                               let res = 0;
+                                                                               if(settings.artworkSize.includes("1400")){
                                                                                        res = 1400;
-                                                                               }else if(configFile.userDefined.artworkSize.includes("1200")){
+                                                                               }else if(settings.artworkSize.includes("1200")){
                                                                                        res = 1200;
-                                                                               }else if(configFile.userDefined.artworkSize.includes("1000")){
+                                                                               }else if(settings.artworkSize.includes("1000")){
                                                                                        res = 1000;
-                                                                               }else if(configFile.userDefined.artworkSize.includes("800")){
+                                                                               }else if(settings.artworkSize.includes("800")){
                                                                                        res = 800;
-                                                                               }else if(configFile.userDefined.artworkSize.includes("500")){
+                                                                               }else if(settings.artworkSize.includes("500")){
                                                                                        res = 500;
                                                                                }
                                                                                if(cover){
@@ -1254,12 +1315,10 @@ io.sockets.on('connection', function (socket) {
                                                                                mdb.isLast = false;
                                                                        }
                                                                });
-
                                                                processor.on('postprocess', (mdb) => {
                                                                        if (mflac.Processor.MDB_TYPE_VORBIS_COMMENT === mdb.type && null !== mdb.vendor) {
                                                                                vendor = mdb.vendor;
                                                                        }
-
                                                                        if (mdbVorbisPicture && mdbVorbisComment) {
                                                                                processor.push(mdbVorbisComment.publish());
                                                                                processor.push(mdbVorbisPicture.publish());
@@ -1267,28 +1326,26 @@ io.sockets.on('connection', function (socket) {
                                                                                processor.push(mdbVorbisComment.publish());
                                                                        }
                                                                });
-
                                                                reader.on('end', () => {
                                                                        fs.remove(tempPath);
                                                                });
-
                                                                reader.pipe(processor).pipe(writer);
                                                        }else{
                                                                const songBuffer = fs.readFileSync(tempPath);
                                                                const writer = new ID3Writer(songBuffer);
                                                                writer.setFrame('TIT2', metadata.title)
-                                                                       .setFrame('TPE1', [metadata.artist])
-                                                                       .setFrame('TALB', metadata.album)
-                                                                       .setFrame('TPE2', metadata.performerInfo)
-                                                                       .setFrame('TRCK', (configFile.userDefined.partOfSet ? metadata.trackNumber : splitNumber(metadata.trackNumber,false)))
-                                                                       .setFrame('TPOS', (configFile.userDefined.partOfSet ? metadata.partOfSet : splitNumber(metadata.partOfSet,false)))
-                                                                       .setFrame('TSRC', metadata.ISRC);
-                                                               if (configFile.userDefined.extendedTags){
+                                                               .setFrame('TPE1', [metadata.artist])
+                                                               .setFrame('TALB', metadata.album)
+                                                               .setFrame('TPE2', metadata.performerInfo)
+                                                               .setFrame('TRCK', (settings.partOfSet ? metadata.trackNumber : splitNumber(metadata.trackNumber,false)))
+                                                               .setFrame('TPOS', (settings.partOfSet ? metadata.partOfSet : splitNumber(metadata.partOfSet,false)))
+                                                               .setFrame('TSRC', metadata.ISRC);
+                                                               if (settings.extendedTags){
                                                                        writer.setFrame('TLEN', metadata.length)
-                                                                               .setFrame('TXXX', {
-                                                                                       description: 'BARCODE',
-                                                                                       value: metadata.BARCODE
-                                                                               })
+                                                                       .setFrame('TXXX', {
+                                                                               description: 'BARCODE',
+                                                                               value: metadata.BARCODE
+                                                                       })
                                                                }
                                                                if(metadata.imagePath){
                                                                        const coverBuffer = fs.readFileSync(metadata.imagePath);
@@ -1311,7 +1368,6 @@ io.sockets.on('connection', function (socket) {
                                                                        writer.setFrame('TCOP', metadata.copyright);
                                                                }
                                                                if (0 < parseInt(metadata.year)) {
-                                                                       writer.setFrame('TDAT', metadata.date);
                                                                        writer.setFrame('TYER', metadata.year);
                                                                }
                                                                if (0 < parseInt(metadata.bpm)) {
@@ -1327,27 +1383,25 @@ io.sockets.on('connection', function (socket) {
                                                                        });
                                                                }
                                                                writer.addTag();
-
                                                                const taggedSongBuffer = Buffer.from(writer.arrayBuffer);
                                                                fs.writeFileSync(writePath, taggedSongBuffer);
                                                                fs.remove(tempPath);
                                                        }
-
-                                                       callback();
-                                               });
-                                       }
-                               });
-                       });
-               });
+                                                       callback(null, t.playlistData);
+                                               })
+                                       })
+                               })
+                       })
+               })
        }
 
        function checkIfAlreadyInQueue(id) {
                let exists = false;
-               for (let i = 0; i < socket.downloadQueue.length; i++) {
-                       if (socket.downloadQueue[i].id == id) {
+               Object.keys(socket.downloadQueue).forEach(x=>{
+                       if (socket.downloadQueue[x].id == id) {
                                exists = socket.downloadQueue[i].queueId;
                        }
-               }
+               });
                if (socket.currentItem && (socket.currentItem.id == id)) {
                        exists = socket.currentItem.queueId;
                }
@@ -1367,7 +1421,7 @@ function updateSettingsFile(config, value) {
 
        fs.outputFile(configFileLocation, JSON.stringify(configFile, null, 2), function (err) {
                if (err) return;
-               logger.logs('Info',"Settings updated");
+               logger.info("Settings updated");
 
                // FIXME: Endless Loop, due to call from initFolders()...crashes soon after startup
                // initFolders();
@@ -1432,11 +1486,11 @@ function settingsRegex(metadata, filename, playlist) {
  * @param foldername
  * @returns {XML|string|*}
  */
-function settingsRegexAlbum(metadata, foldername, artist, album) {
+function settingsRegexAlbum(foldername, artist, album, year, rtype) {
        foldername = foldername.replace(/%album%/g, album);
        foldername = foldername.replace(/%artist%/g, artist);
-       foldername = foldername.replace(/%year%/g, metadata.year);
-       foldername = foldername.replace(/%type%/g, metadata.rtype);
+       foldername = foldername.replace(/%year%/g, year);
+       foldername = foldername.replace(/%type%/g, rtype);
        return foldername;
 }
 
@@ -1459,7 +1513,7 @@ function pad(str, max) {
  */
 function splitNumber(str,total){
        str = str.toString();
-       var i = str.indexOf("/");
+       let i = str.indexOf("/");
        if(total && i > 0){
                return str.slice(i+1, str.length);
        }else if(i > 0){
@@ -1470,11 +1524,192 @@ function splitNumber(str,total){
        return i > 0 ? str.slice(0, i) : str;
 }
 
+function slimDownTrackInfo(trackOld){
+       let track = {};
+       track['SNG_ID'] = trackOld["SNG_ID"]
+       track['ARTISTS'] = trackOld["ARTISTS"]
+       track["ALB_ID"] = trackOld["ALB_ID"]
+       track["ALB_PICTURE"] = trackOld["ALB_PICTURE"]
+       track["ALB_TITLE"] = trackOld["ALB_TITLE"]
+       track["ART_NAME"] = trackOld["ART_NAME"]
+       track["BPM"] = trackOld["BPM"]
+       track["COPYRIGHT"] = trackOld["COPYRIGHT"]
+       track["DISK_NUMBER"] = trackOld["DISK_NUMBER"]
+       track["DURATION"] = trackOld["DURATION"]
+       track["EXPLICIT_LYRICS"] = trackOld["EXPLICIT_LYRICS"]
+       track["GAIN"] = trackOld["GAIN"]
+       track["ISRC"] = trackOld["ISRC"]
+       track["LYRICS_SYNC_JSON"] = trackOld["LYRICS_SYNC_JSON"]
+       track["LYRICS_TEXT"] = trackOld["LYRICS_TEXT"]
+       track["PHYSICAL_RELEASE_DATE"] = trackOld["PHYSICAL_RELEASE_DATE"]
+       track["SNG_CONTRIBUTORS"] = trackOld["SNG_CONTRIBUTORS"]
+       track["SNG_TITLE"] = trackOld["SNG_TITLE"]
+       track["TRACK_NUMBER"] = trackOld["TRACK_NUMBER"]
+       track["VERSION"] = trackOld["VERSION"]
+       track["FILESIZE_FLAC"] = trackOld["FILESIZE_FLAC"]
+       track["FILESIZE_MP3_320"] = trackOld["FILESIZE_MP3_320"]
+       track["FILESIZE_MP3_256"] = trackOld["FILESIZE_MP3_256"]
+       track["FILESIZE_MP3_128"] = trackOld["FILESIZE_MP3_128"]
+       track["FALLBACK"] = trackOld["FALLBACK"]
+       track.downloadUrl = trackOld.downloadUrl
+       track.format = trackOld.format
+       return track
+}
+
+function slimDownAlbumInfo(ajsonOld){
+       let ajson = {};
+       ajson.artist = {}
+       ajson.artist.name = ajsonOld.artist.name
+       ajson.nb_tracks = ajsonOld.nb_tracks
+       ajson.upc = ajsonOld.upc
+       ajson.record_type = ajsonOld.record_type
+       ajson.label = ajsonOld.label
+       ajson.genres = ajsonOld.genres
+       ajson.release_date = ajsonOld.release_date
+       ajson.tracks = {
+               data: ajsonOld.tracks.data.map(x=>{
+                       return {id: x.id};
+               })
+       }
+       ajson.tracks.total = ajsonOld.tracks.total
+       return ajson
+}
+
+function parseMetadata(track, ajson, totalDiskNumber, settings, position, altmetadata){
+       let metadata;
+       if (track["VERSION"]) track["SNG_TITLE"] += " " + track["VERSION"];
+       if(altmetadata){
+               metadata = altmetadata;
+               if(track["LYRICS_TEXT"] && !metadata.unsynchronisedLyrics){
+                       metadata.unsynchronisedLyrics = {
+                               description: "",
+                               lyrics: track["LYRICS_TEXT"]
+                       };
+               }
+       }else{
+               metadata = {
+                       title: track["SNG_TITLE"],
+                       artist: track["ART_NAME"],
+                       album: track["ALB_TITLE"],
+                       performerInfo: ajson.artist.name,
+                       trackNumber: track["TRACK_NUMBER"] + "/" + ajson.nb_tracks,
+                       partOfSet: track["DISK_NUMBER"],
+                       explicit: track["EXPLICIT_LYRICS"],
+                       ISRC: track["ISRC"],
+               };
+               if (settings.extendedTags){
+                       metadata.length = track["DURATION"];
+                       metadata.BARCODE = ajson.upc;
+                       metadata.rtype = ajson.record_type;
+                       if(track["COPYRIGHT"]){
+                               metadata.copyright = track["COPYRIGHT"];
+                       }
+                       if(track["SNG_CONTRIBUTORS"]){
+                               if(track["SNG_CONTRIBUTORS"].composer){
+                                       let composertag = "";
+                                       for (let i = 0; i < track["SNG_CONTRIBUTORS"].composer.length; i++) {
+                                               composertag += track["SNG_CONTRIBUTORS"].composer[i] + ", ";
+                                       }
+                                       metadata.composer = composertag.substring(0,composertag.length-2);
+                               }
+                               if(track["SNG_CONTRIBUTORS"].musicpublisher){
+                                       let publishertag = "";
+                                       for (let i = 0; i < track["SNG_CONTRIBUTORS"].musicpublisher.length; i++) {
+                                               publishertag += track["SNG_CONTRIBUTORS"].musicpublisher[i] + ", ";
+                                       }
+                                       metadata.publisher = publishertag.substring(0,publishertag.length-2);
+                               }
+                               if(track["SNG_CONTRIBUTORS"].producer){
+                                       let producertag = "";
+                                       for (let i = 0; i < track["SNG_CONTRIBUTORS"].producer.length; i++) {
+                                               producertag += track["SNG_CONTRIBUTORS"].producer[i] + ", ";
+                                       }
+                                       metadata.producer = producertag.substring(0,producertag.length-2);
+                               }
+                               if(track["SNG_CONTRIBUTORS"].engineer){
+                                       let engineertag = "";
+                                       for (let i = 0; i < track["SNG_CONTRIBUTORS"].engineer.length; i++) {
+                                               engineertag += track["SNG_CONTRIBUTORS"].engineer[i] + ", ";
+                                       }
+                                       metadata.engineer = engineertag.substring(0,engineertag.length-2);
+                               }
+                               if(track["SNG_CONTRIBUTORS"].writer){
+                                       let writertag = "";
+                                       for (let i = 0; i < track["SNG_CONTRIBUTORS"].writer.length; i++) {
+                                               writertag += track["SNG_CONTRIBUTORS"].writer[i] + ", ";
+                                       }
+                                       metadata.writer = writertag.substring(0,writertag.length-2);
+                               }
+                               if(track["SNG_CONTRIBUTORS"].author){
+                                       let authortag = "";
+                                       for (let i = 0; i < track["SNG_CONTRIBUTORS"].author.length; i++) {
+                                               authortag += track["SNG_CONTRIBUTORS"].author[i] + ", ";
+                                       }
+                                       metadata.author = authortag.substring(0,authortag.length-2);
+                               }
+                               if(track["SNG_CONTRIBUTORS"].mixer){
+                                       let mixertag = "";
+                                       for (let i = 0; i < track["SNG_CONTRIBUTORS"].mixer.length; i++) {
+                                               mixertag += track["SNG_CONTRIBUTORS"].mixer[i] + ", ";
+                                       }
+                                       metadata.mixer = mixertag.substring(0,mixertag.length-2);
+                               }
+                       }
+                       if(track["LYRICS_TEXT"]){
+                               metadata.unsynchronisedLyrics = {
+                                       description: "",
+                                       lyrics: track["LYRICS_TEXT"]
+                               };
+                       }
+                       if (track["GAIN"]) {
+                               metadata.trackgain = track["GAIN"];
+                       }
+               }
+               if(ajson.label && !metadata.publisher){
+                       metadata.publisher = ajson.label;
+               }
+               if (0 < parseInt(track["BPM"])) {
+                       metadata.bpm = track["BPM"];
+               }
+               if(ajson.genres && ajson.genres.data[0] && ajson.genres.data[0].name){
+                       metadata.genre = [];
+                       genreArray = [];
+                       ajson.genres.data.forEach(function(genre){
+                               genreArray.push(genre.name);
+                       });
+                       Array.from(new Set(genreArray)).forEach(function(genre){
+                               if(metadata.genre.indexOf(genre) == -1)
+                                       metadata.genre.push(genre);
+                       });
+                       let separator = settings.multitagSeparator;
+                       if (separator == "null") separator = String.fromCharCode(parseInt("\u0000",16))
+                       if (track.format != 9) metadata.genre = metadata.genre.join(separator);
+               }
+               if (track["ALB_PICTURE"]) {
+                       metadata.image = Deezer.albumPicturesHost + track["ALB_PICTURE"] + settings.artworkSize;
+               }
+               if(track["PHYSICAL_RELEASE_DATE"]){
+                       metadata.year = track["PHYSICAL_RELEASE_DATE"].slice(0, 4);
+               }else if (ajson.release_date) {
+                       metadata.year = ajson.release_date.slice(0, 4);
+               }
+               if(settings.plName && !(settings.createArtistFolder || settings.createAlbumFolder) && !settings.numplaylistbyalbum){
+                       metadata.trackNumber = (position+1).toString() + "/" + settings.playlist.fullSize;
+                       metadata.partOfSet = "1/1";
+               }
+               if (totalDiskNumber){
+                       metadata.partOfSet += "/"+totalDiskNumber
+               }
+       }
+       return metadata;
+}
+
+process.on('unhandledRejection', function (err) {
+       logger.error(err.stack);
+});
 // Show crash error in console for debugging
 process.on('uncaughtException', function (err) {
-       logger.logs('Error',err.stack,function(){
-               socket.emit("message", "Critical Error, report to the developer", err.stack);
-       });
+       logger.error(err.stack);
 });
 
 // Exporting vars
index 32471adf889f2ca42356b1c3a7903a77d4546f08..4a36fce9f1c7ea1aa957dffe18ccc4d2db701ee5 100644 (file)
@@ -1,4 +1,3 @@
-const NRrequest = require('request');
 const request = require('requestretry').defaults({maxAttempts: 2147483647, retryDelay: 1000, timeout: 8000});
 const crypto = require('crypto');
 const fs = require("fs-extra");
@@ -22,34 +21,23 @@ function Deezer() {
                "Accept-Language": "de-DE,de;q=0.8,en-US;q=0.6,en;q=0.4"
        }
        this.albumPicturesHost = "https://e-cdns-images.dzcdn.net/images/cover/";
-       this.reqStream = null;
+       this.reqStream = {}
+       this.delStream = []
 }
 
 Deezer.prototype.init = function(username, password, callback) {
        var self = this;
-       NRrequest.post({url: "https://www.deezer.com/ajax/action.php", headers: this.httpHeaders, form: {type:'login',mail:username,password:password}, jar: true}, (function(err, res, body) {
+       request.post({url: "https://www.deezer.com/ajax/action.php", headers: this.httpHeaders, form: {type:'login',mail:username,password:password}, jar: true}, (function(err, res, body) {
                if(err || res.statusCode != 200) {
                        callback(new Error("Unable to load deezer.com"));
                }else if(body.indexOf("success") > -1){
                        request.get({url: "https://www.deezer.com/", headers: this.httpHeaders, jar: true}, (function(err, res, body) {
                                if(!err && res.statusCode == 200) {
-                                       var regex = new RegExp(/"api_key":"([^",]*)/g);
-                                       var _token = regex.exec(body);
-                                       if(! (_token instanceof Array && _token[1])) {
-                                               var regex = new RegExp(/((?!"api_key\\":\\").*(?=\\"))/g);
-                                               var _token = regex.exec(body);
-                                               if(! (_token instanceof Array && _token[1])) {
-                                                       var _token = ["", " "]
-                                                       if(! (_token instanceof Array && _token[1])) {
-                                                               callback(new Error("Unable to initialize Deezer API"));
-                                                               return;
-                                                       }
-                                               }
-                                       }
-                                       self.apiQueries.api_token = _token[1];
-                                       const userRegex = new RegExp(/{"USER_ID":"([^",]*)/g);
-                                       const userId = userRegex.exec(body)[1];
-                                       self.userId = userId;
+                                       const userRegex = new RegExp(/var USER =([^(;\n)]*)/g);
+                                       const user = JSON.parse(userRegex.exec(body)[1]);
+                                       self.userId = user.USER_ID;
+                                       self.userName = user.BLOG_NAME;
+                                       self.userPicture = `https:\/\/e-cdns-images.dzcdn.net\/images\/user\/${user.USER_PICTURE}\/250x250-000000-80-0-0.jpg`;
                                        callback(null, null);
                                } else {
                                        callback(new Error("Unable to load deezer.com "+err));
@@ -104,17 +92,6 @@ Deezer.prototype.getArtist = function(id, callback) {
 
 }
 
-Deezer.prototype.getPlaylistSize = function(id, callback) {
-       getJSON("https://api.deezer.com/playlist/" + id + "/tracks?limit=1", function(res){
-               if (!(res instanceof Error)){
-                       callback(res.total);
-               } else {
-                       callback(null, res)
-               }
-       });
-
-}
-
 Deezer.prototype.getPlaylistTracks = function(id, callback) {
        getJSON("https://api.deezer.com/playlist/" + id + "/tracks?limit=-1", function(res){
                if (!(res instanceof Error)){
@@ -204,7 +181,7 @@ Deezer.prototype.getTrack = function(id, wantFlac, callback) {
                try{
                        _data = rexec[1];
                }catch(e){
-                       callback(new Error("Unable to get Track"));
+                       callback(null, new Error("Unable to get Track"));
                        return;
                }
                if(!err && res.statusCode == 200 && typeof JSON.parse(_data)["DATA"] != 'undefined') {
@@ -217,7 +194,7 @@ Deezer.prototype.getTrack = function(id, wantFlac, callback) {
                                json["LYRICS_WRITERS"] = lyrics["LYRICS_WRITERS"];
                        }
                        if(json["TOKEN"]) {
-                               callback(new Error("Uploaded Files are currently not supported"));
+                               callback(null, new Error("Uploaded Files are currently not supported"));
                                return;
                        }
                        var id = json["SNG_ID"];
@@ -238,15 +215,16 @@ Deezer.prototype.getTrack = function(id, wantFlac, callback) {
                        json.format = format;
                        var mediaVersion = parseInt(json["MEDIA_VERSION"]);
                        json.downloadUrl = self.getDownloadUrl(md5Origin, id, format, mediaVersion);
+
                        self.getATrack(id,function(trckjson, err){
                                if (err)
                                        json["BPM"] = 0;
                                else
-                                       json["BPM"] = trckjson["bpm"];
+                                       json["BPM"] = trckjson.bpm;
                                callback(json);
                        });
                } else {
-                       callback(new Error("Unable to get Track " + id));
+                       callback(null, new Error("Unable to get Track " + id));
                }
        }).bind(self));
 }
@@ -274,7 +252,9 @@ Deezer.prototype.search = function(text, type, callback) {
 
 Deezer.prototype.track2ID = function(artist, track, callback, trim=false) {
        var self = this;
-       request.get({url: 'https://api.deezer.com/search/?q=track:"'+encodeURIComponent(track)+'" artist:"'+encodeURIComponent(artist)+'"&limit=1', headers: this.httpHeaders, jar: true}, function(err, res, body) {
+       artist = artist.replace(/–/g,"-").replace(/’/g, "'");
+       track = track.replace(/–/g,"-").replace(/’/g, "'");
+       request.get({url: 'https://api.deezer.com/search/?q=track:"'+encodeURIComponent(track)+'" artist:"'+encodeURIComponent(artist)+'"&limit=1&strict=on', headers: this.httpHeaders, jar: true}, function(err, res, body) {
                if(!err && res.statusCode == 200) {
                        var json = JSON.parse(body);
                        if(json.error) {
@@ -282,12 +262,15 @@ Deezer.prototype.track2ID = function(artist, track, callback, trim=false) {
                                        self.track2ID(artist, track, callback, trim);
                                        return;
                                }else{
-                                       callback(0, new Error(json.error.code+" - "+json.error.message));
+                                       callback({id:0, name: track, artist: artist}, new Error(json.error.code+" - "+json.error.message));
                                        return;
                                }
                        }
-                       if (json.total>0){
-                               callback(json.data[0].id);
+                       if (json.data && json.data[0]){
+                               if (json.data[0].title_version && json.data[0].title.indexOf(json.data[0].title_version) == -1){
+                                       json.data[0].title += " "+json.data[0].title_version
+                               }
+                               callback({id:json.data[0].id, name: json.data[0].title, artist: json.data[0].artist.name});
                        }else {
                                if (!trim){
                                        if (track.indexOf("(") < track.indexOf(")")){
@@ -297,11 +280,11 @@ Deezer.prototype.track2ID = function(artist, track, callback, trim=false) {
                                                self.track2ID(artist, track.split(" - ")[0], callback, true);
                                                return;
                                        }else{
-                                               callback(0, new Error("Track not Found"));
+                                               callback({id:0, name: track, artist: artist}, new Error("Track not Found"));
                                                return;
                                        }
                                }else{
-                                       callback(0, new Error("Track not Found"));
+                                       callback({id:0, name: track, artist: artist}, new Error("Track not Found"));
                                        return;
                                }
                        }
@@ -349,27 +332,38 @@ Deezer.prototype.getDownloadUrl = function(md5Origin, id, format, mediaVersion)
        return "https://e-cdns-proxy-" + md5Origin.substring(0, 1) + ".dzcdn.net/mobile/1/" + buffer.toString("hex").toLowerCase();
 }
 
-Deezer.prototype.decryptTrack = function(writePath, track, callback) {
+Deezer.prototype.decryptTrack = function(writePath, track, queueId, callback) {
        var self = this;
        var chunkLength = 0;
-       this.reqStream = request.get({url: track.downloadUrl, headers: this.httpHeaders, jar: true, encoding: null}, function(err, res, body) {
-               if(!err && res.statusCode == 200) {
-                       var decryptedSource = decryptDownload(new Buffer(body, 'binary'), track);
-                       fs.outputFile(writePath,decryptedSource,function(err){
-                               if(err){callback(err);return;}
-                               callback();
-                       });
-               } else {
-                       logger.logs("Error","Decryption error");
-                       callback(err || new Error("Can't download the track"));
-               }
-       }).on("data", function(data) {
-               chunkLength += data.length;
-               self.onDownloadProgress(track, chunkLength);
-       }).on("abort", function() {
-               logger.logs("Error","Decryption aborted");
+       if (self.delStream.indexOf(queueId) == -1){
+               if (typeof self.reqStream[queueId] != "object") self.reqStream[queueId] = [];
+               self.reqStream[queueId].push(
+                       request.get({url: track.downloadUrl, headers: self.httpHeaders, encoding: null}, function(err, res, body) {
+                               if(!err && res.statusCode == 200) {
+                                       var decryptedSource = decryptDownload(new Buffer(body, 'binary'), track);
+                                       fs.outputFile(writePath,decryptedSource,function(err){
+                                               if(err){callback(err);return;}
+                                               callback();
+                                       });
+                                       if (self.reqStream[queueId]) self.reqStream[queueId].splice(self.reqStream[queueId].indexOf(this),1);
+                               } else {
+                                       logger.error("Decryption error"+(err ? " | "+err : "")+ (res ? ": "+res.statusCode : ""));
+                                       if (self.reqStream[queueId]) self.reqStream[queueId].splice(self.reqStream[queueId].indexOf(this),1);
+                                       callback(err || new Error("Can't download the track"));
+                               }
+                       }).on("data", function(data) {
+                               chunkLength += data.length;
+                               self.onDownloadProgress(track, chunkLength);
+                       }).on("abort", function() {
+                               logger.error("Decryption aborted");
+                               if (self.reqStream[queueId]) self.reqStream[queueId].splice(self.reqStream[queueId].indexOf(this),1);
+                               callback(new Error("aborted"));
+                       })
+               );
+       }else{
+               logger.error("Decryption aborted");
                callback(new Error("aborted"));
-       });
+       }
 }
 
 function decryptDownload(source, track) {
@@ -390,16 +384,17 @@ function decryptDownload(source, track) {
                        chunk_size = source.length - position;
                }
                chunk = new Buffer(chunk_size);
+               let chunkString
                chunk.fill(0);
                source.copy(chunk, 0, position, position + chunk_size);
                if(i % 3 > 0 || chunk_size < 2048){
-                       //Do nothing
+                               chunkString = chunk.toString('binary')
                }else{
                        var cipher = crypto.createDecipheriv('bf-cbc', blowFishKey, new Buffer([0, 1, 2, 3, 4, 5, 6, 7]));
                        cipher.setAutoPadding(false);
-                       chunk = cipher.update(chunk, 'binary', 'binary') + cipher.final();
+                       chunkString = cipher.update(chunk, 'binary', 'binary') + cipher.final();
                }
-               destBuffer.write(chunk.toString("binary"), position, 'binary');
+               destBuffer.write(chunkString, position, chunkString.length, 'binary');
                position += chunk_size
                i++;
        }
@@ -420,10 +415,16 @@ function getBlowfishKey(trackInfos) {
        return bfKey;
 }
 
-Deezer.prototype.cancelDecryptTrack = function() {
-       if(this.reqStream) {
-               this.reqStream.abort();
-               this.reqStream = null;
+Deezer.prototype.cancelDecryptTrack = function(queueId) {
+       if(Object.keys(this.reqStream).length != 0) {
+               if (this.reqStream[queueId]){
+                       while (this.reqStream[queueId][0]){
+                               this.reqStream[queueId][0].abort();
+                       }
+                       delete this.reqStream[queueId];
+                       this.delStream.push(queueId);
+                       return true;
+               }
                return true;
        } else {
                false;
@@ -437,13 +438,17 @@ Deezer.prototype.onDownloadProgress = function(track, progress) {
 function getJSON(url, callback){
        request.get({url: url, headers: this.httpHeaders, jar: true}, function(err, res, body) {
                if(err || res.statusCode != 200 || !body) {
-                       logger.logs("Error","Unable to initialize Deezer API");
-                       callback(new Error());
+                       logger.error("Unable to initialize Deezer API");
+                       callback(new Error("Unable to initialize Deezer API"));
                } else {
                        var json = JSON.parse(body);
                        if (json.error) {
-                               logger.logs("Error",json.error.message);
-                               callback(new Error());
+                               if (json.error.message == "Quota limit exceeded"){
+                                       logger.warn("Quota limit exceeded, retrying in 500ms");
+                                       setTimeout(function(){ getJSON(url, callback); }, 500);
+                                       return;
+                               }
+                               logger.error(json.error.message);
                                return;
                        }
                        callback(json);
index 7b5ee73240b7322f62c3c45f9a5df72083cc068f..2f303e70fb0e5439ff9ddf6e353207562f3786ff 100644 (file)
@@ -8,8 +8,8 @@
                "createM3UFile": false,
                "createArtistFolder": false,
                "createAlbumFolder": false,
-               "downloadLocation": null,
-               "artworkSize": "/800x800.jpg",
+               "downloadLocation": "",
+               "artworkSize": "/1400x1400.jpg",
                "hifi": false,
                "padtrck": false,
                "syncedlyrics": false,
                "extendedTags": false,
                "partOfSet": false,
                "chartsCountry": "UK",
-               "spotifyUser": ""
+               "spotifyUser": "",
+               "saveArtwork": false,
+               "logErrors": false,
+               "queueConcurrency": 3,
+               "multitagSeparator": ";"
        }
 }
index 752f8ff0c56f019e90b445e68ef23ef9fd301990..b5854b0e9f7b196591608434c6c2aad2810594d2 100644 (file)
Binary files a/app/icon.png and b/app/icon.png differ
diff --git a/app/lib/flac-metadata/.gitignore b/app/lib/flac-metadata/.gitignore
new file mode 100644 (file)
index 0000000..496ee2c
--- /dev/null
@@ -0,0 +1 @@
+.DS_Store
\ No newline at end of file
diff --git a/app/lib/flac-metadata/README.md b/app/lib/flac-metadata/README.md
new file mode 100644 (file)
index 0000000..dc27500
--- /dev/null
@@ -0,0 +1,143 @@
+# flac-metadata
+
+A FLAC metadata processor for Node.js, implemented as Transform stream.
+
+## Installation
+
+```npm install flac-metadata```
+
+## Usage Examples
+
+Some simple examples to get you started:
+
+#### Noop
+
+Does nothing, just pipes a source FLAC through the Processor into a target FLAC.
+
+```js
+var fs = require("fs");
+var flac = require("flac-metadata");
+
+var reader = fs.createReadStream("source.flac");
+var writer = fs.createWriteStream("target.flac");
+var processor = new flac.Processor();
+
+reader.pipe(processor).pipe(writer);
+```
+
+#### Trace Metadata
+
+Traces out the metadata from a FLAC file.
+
+```js
+var fs = require("fs");
+var flac = require("flac-metadata");
+
+var reader = fs.createReadStream("source.flac");
+var processor = new flac.Processor({ parseMetaDataBlocks: true });
+processor.on("postprocess", function(mdb) {
+  console.log(mdb.toString());
+});
+
+reader.pipe(processor);
+```
+
+The output should be something like this:
+
+```
+[MetaDataBlockStreamInfo] type: 0, isLast: false
+  minBlockSize: 4096
+  maxBlockSize: 4096
+  minFrameSize: 14
+  maxFrameSize: 12389
+  samples: 9750804
+  sampleRate: 44100
+  channels: 2
+  bitsPerSample: 16
+  duration: 3:41.107
+  checksum: 1746dff27beb6d1875a88cfeed8a576b
+
+[MetaDataBlockVorbisComment] type: 4, isLast: false
+  vendor: reference libFLAC 1.2.1 20070917
+  comments:
+    ALBUM: Close to the Glass
+    ARTIST: The Notwist
+    GENRE: Rock
+    DATE: 2014
+    TITLE: Signals
+
+[MetaDataBlockPicture] type: 6, isLast: true
+  pictureType: 3
+  mimeType: image/png
+  description:
+  width: 120
+  height: 120
+  bitsPerPixel: 32
+  colors: 0
+  pictureData: 391383
+```
+
+#### Strip All Metadata
+
+Pipes a source FLAC through the Processor into a target FLAC, removing all metadata.
+
+```js
+var fs = require("fs");
+var flac = require("flac-metadata");
+
+var reader = fs.createReadStream("source.flac");
+var writer = fs.createWriteStream("target.flac");
+var processor = new flac.Processor();
+
+processor.on("preprocess", function(mdb) {
+  // STREAMINFO is always the first (and only mandatory) metadata block.
+  if (mdb.type === flac.Processor.MDB_TYPE_STREAMINFO) {
+    // When a metadata block's isLast flag is set to true in preprocess,
+    // subsequent blocks are automatically discarded.
+    mdb.isLast = true;
+  }
+});
+
+reader.pipe(processor).pipe(writer);
+```
+
+#### Inject Metadata
+
+Injects a VORBIS_COMMENT block (and removes the existing one, if any).
+
+```js
+var fs = require("fs");
+var flac = require("flac-metadata");
+
+var reader = fs.createReadStream("source.flac");
+var writer = fs.createWriteStream("target.flac");
+var processor = new flac.Processor();
+
+var vendor = "reference libFLAC 1.2.1 20070917";
+var comments = [
+  "ARTIST=Boyracer",
+  "TITLE=I've Got It And It's Not Worth Having",
+  "ALBUM=B Is For Boyracer",
+  "TRACKNUMBER=A1",
+  "DATE=1993",
+  "DISCOGS=22379"
+];
+
+processor.on("preprocess", function(mdb) {
+  // Remove existing VORBIS_COMMENT block, if any.
+  if (mdb.type === flac.Processor.MDB_TYPE_VORBIS_COMMENT) {
+    mdb.remove();
+  }
+  // Inject new VORBIS_COMMENT block.
+  if (mdb.removed || mdb.isLast) {
+    var mdbVorbis = flac.data.MetaDataBlockVorbisComment.create(mdb.isLast, vendor, comments);
+    this.push(mdbVorbis.publish());
+  }
+});
+
+reader.pipe(processor).pipe(writer);
+```
+
+## License
+
+MIT
diff --git a/app/lib/flac-metadata/index.js b/app/lib/flac-metadata/index.js
new file mode 100644 (file)
index 0000000..9d72b14
--- /dev/null
@@ -0,0 +1,8 @@
+module.exports.Processor = require("./lib/Processor");
+
+module.exports.data = {
+  MetaDataBlock: require("./lib/data/MetaDataBlock"),
+  MetaDataBlockStreamInfo: require("./lib/data/MetaDataBlockStreamInfo"),
+  MetaDataBlockVorbisComment: require("./lib/data/MetaDataBlockVorbisComment"),
+  MetaDataBlockPicture: require("./lib/data/MetaDataBlockPicture")
+};
diff --git a/app/lib/flac-metadata/lib/Processor.js b/app/lib/flac-metadata/lib/Processor.js
new file mode 100644 (file)
index 0000000..f0a54c2
--- /dev/null
@@ -0,0 +1,218 @@
+var util = require("util");
+var stream = require('stream');
+var Transform = stream.Transform || require('readable-stream').Transform;
+
+var MetaDataBlock = require("./data/MetaDataBlock");
+var MetaDataBlockStreamInfo = require("./data/MetaDataBlockStreamInfo");
+var MetaDataBlockVorbisComment = require("./data/MetaDataBlockVorbisComment");
+var MetaDataBlockPicture = require("./data/MetaDataBlockPicture");
+
+const STATE_IDLE = 0;
+const STATE_MARKER = 1;
+const STATE_MDB_HEADER = 2;
+const STATE_MDB = 3;
+const STATE_PASS_THROUGH = 4;
+
+
+var Processor = function (options) {
+
+  this.state = STATE_IDLE;
+
+  this.isFlac = false;
+
+  this.buf;
+  this.bufPos = 0;
+
+  this.mdb;
+  this.mdbLen = 0;
+  this.mdbLast = false;
+  this.mdbPush = false;
+  this.mdbLastWritten = false;
+
+  this.parseMetaDataBlocks = false;
+
+  if (!(this instanceof Processor)) return new Processor(options);
+  if (options && !!options.parseMetaDataBlocks) { this.parseMetaDataBlocks = true; }
+  Transform.call(this, options);
+}
+
+util.inherits(Processor, Transform);
+
+// MDB types
+Processor.MDB_TYPE_STREAMINFO = 0;
+Processor.MDB_TYPE_PADDING = 1;
+Processor.MDB_TYPE_APPLICATION = 2;
+Processor.MDB_TYPE_SEEKTABLE = 3;
+Processor.MDB_TYPE_VORBIS_COMMENT = 4;
+Processor.MDB_TYPE_CUESHEET = 5;
+Processor.MDB_TYPE_PICTURE = 6;
+Processor.MDB_TYPE_INVALID = 127;
+
+Processor.prototype._transform = function (chunk, enc, done) {
+  var chunkPos = 0;
+  var chunkLen = chunk.length;
+  var isChunkProcessed = false;
+  var _this = this;
+
+  function _safePush (minCapacity, persist, validate) {
+    var slice;
+    var chunkAvailable = chunkLen - chunkPos;
+    var isDone = (chunkAvailable + this.bufPos >= minCapacity);
+    validate = (typeof validate === "function") ? validate : function() { return true; };
+    if (isDone) {
+      // Enough data available
+      if (persist) {
+        // Persist the entire block so it can be parsed
+        if (this.bufPos > 0) {
+          // Part of this block's data is in backup buffer, copy rest over
+          chunk.copy(this.buf, this.bufPos, chunkPos, chunkPos + minCapacity - this.bufPos);
+          slice = this.buf.slice(0, minCapacity);
+        } else {
+          // Entire block fits in current chunk
+          slice = chunk.slice(chunkPos, chunkPos + minCapacity);
+        }
+      } else {
+        slice = chunk.slice(chunkPos, chunkPos + minCapacity - this.bufPos);
+      }
+      // Push block after validation
+      validate(slice, isDone) && _this.push(slice);
+      chunkPos += minCapacity - this.bufPos;
+      this.bufPos = 0;
+      this.buf = null;
+    } else {
+      // Not enough data available
+      if (persist) {
+        // Copy/append incomplete block to backup buffer
+        this.buf = this.buf || new Buffer(minCapacity);
+        chunk.copy(this.buf, this.bufPos, chunkPos, chunkLen);
+      } else {
+        // Push incomplete block after validation
+        slice = chunk.slice(chunkPos, chunkLen);
+        validate(slice, isDone) && _this.push(slice);
+      }
+      this.bufPos += chunkLen - chunkPos;
+    }
+    return isDone;
+  };
+  var safePush = _safePush.bind(this);
+
+  while (!isChunkProcessed) {
+    switch (this.state) {
+      case STATE_IDLE:
+        this.state = STATE_MARKER;
+        break;
+      case STATE_MARKER:
+        if (safePush(4, true, this._validateMarker.bind(this))) {
+          this.state = this.isFlac ? STATE_MDB_HEADER : STATE_PASS_THROUGH;
+        } else {
+          isChunkProcessed = true;
+        }
+        break;
+      case STATE_MDB_HEADER:
+        if (safePush(4, true, this._validateMDBHeader.bind(this))) {
+          this.state = STATE_MDB;
+        } else {
+          isChunkProcessed = true;
+        }
+        break;
+      case STATE_MDB:
+        if (safePush(this.mdbLen, this.parseMetaDataBlocks, this._validateMDB.bind(this))) {
+          if (this.mdb.isLast) {
+            // This MDB has the isLast flag set to true.
+            // Ignore all following MDBs.
+            this.mdbLastWritten = true;
+          }
+          this.emit("postprocess", this.mdb);
+          this.state = this.mdbLast ? STATE_PASS_THROUGH : STATE_MDB_HEADER;
+        } else {
+          isChunkProcessed = true;
+        }
+        break;
+      case STATE_PASS_THROUGH:
+        safePush(chunkLen - chunkPos, false);
+        isChunkProcessed = true;
+        break;
+    }
+  }
+
+  done();
+}
+
+Processor.prototype._validateMarker = function(slice, isDone) {
+  this.isFlac = (slice.toString("utf8", 0) === "fLaC");
+  // TODO: completely bail out if file is not a FLAC?
+  return true;
+}
+
+Processor.prototype._validateMDBHeader = function(slice, isDone) {
+  // Parse MDB header
+  var header = slice.readUInt32BE(0);
+  var type = (header >>> 24) & 0x7f;
+  this.mdbLast = (((header >>> 24) & 0x80) !== 0);
+  this.mdbLen = header & 0xffffff;
+
+  // Create appropriate MDB object
+  // (data is injected later in _validateMDB, if parseMetaDataBlocks option is set to true)
+  switch (type) {
+    case Processor.MDB_TYPE_STREAMINFO:
+      this.mdb = new MetaDataBlockStreamInfo(this.mdbLast);
+      break;
+    case Processor.MDB_TYPE_VORBIS_COMMENT:
+      this.mdb = new MetaDataBlockVorbisComment(this.mdbLast);
+      break;
+    case Processor.MDB_TYPE_PICTURE:
+      this.mdb = new MetaDataBlockPicture(this.mdbLast);
+      break;
+    case Processor.MDB_TYPE_PADDING:
+    case Processor.MDB_TYPE_APPLICATION:
+    case Processor.MDB_TYPE_SEEKTABLE:
+    case Processor.MDB_TYPE_CUESHEET:
+    case Processor.MDB_TYPE_INVALID:
+    default:
+      this.mdb = new MetaDataBlock(this.mdbLast, type);
+      break
+  }
+
+  this.emit("preprocess", this.mdb);
+
+  if (this.mdbLastWritten) {
+    // A previous MDB had the isLast flag set to true.
+    // Ignore all following MDBs.
+    this.mdb.remove();
+  } else {
+    // The consumer may change the MDB's isLast flag in the preprocess handler.
+    // Here that flag is updated in the MDB header.
+    if (this.mdbLast !== this.mdb.isLast) {
+      if (this.mdb.isLast) {
+        header |= 0x80000000;
+      } else {
+        header &= 0x7fffffff;
+      }
+      slice.writeUInt32BE(header >>> 0, 0);
+    }
+  }
+  this.mdbPush = !this.mdb.removed;
+  return this.mdbPush;
+}
+
+Processor.prototype._validateMDB = function(slice, isDone) {
+  // Parse the MDB if parseMetaDataBlocks option is set to true
+  if (this.parseMetaDataBlocks && isDone) {
+    this.mdb.parse(slice);
+  }
+  return this.mdbPush;
+}
+
+Processor.prototype._flush = function(done) {
+  // All chunks have been processed
+  // Clean up
+  this.state = STATE_IDLE;
+  this.mdbLastWritten = false;
+  this.isFlac = false;
+  this.bufPos = 0;
+  this.buf = null;
+  this.mdb = null;
+  done();
+}
+
+module.exports = Processor;
diff --git a/app/lib/flac-metadata/lib/data/MetaDataBlock.js b/app/lib/flac-metadata/lib/data/MetaDataBlock.js
new file mode 100644 (file)
index 0000000..cc1421d
--- /dev/null
@@ -0,0 +1,21 @@
+var MetaDataBlock = module.exports = function(isLast, type) {
+  this.isLast = isLast;
+  this.type = type;
+  this.error = null;
+  this.hasData = false;
+  this.removed = false;
+}
+
+MetaDataBlock.prototype.remove = function() {
+  this.removed = true;
+}
+
+MetaDataBlock.prototype.parse = function(buffer) {
+}
+
+MetaDataBlock.prototype.toString = function() {
+  var str = "[MetaDataBlock]";
+  str += " type: " + this.type;
+  str += ", isLast: " + this.isLast;
+  return str;
+}
diff --git a/app/lib/flac-metadata/lib/data/MetaDataBlockPicture.js b/app/lib/flac-metadata/lib/data/MetaDataBlockPicture.js
new file mode 100644 (file)
index 0000000..5694664
--- /dev/null
@@ -0,0 +1,131 @@
+var util = require("util");
+var MetaDataBlock = require("./MetaDataBlock");
+
+var MetaDataBlockPicture = module.exports = function(isLast) {
+  MetaDataBlock.call(this, isLast, 6);
+
+  this.pictureType = 0;
+  this.mimeType = "";
+  this.description = "";
+  this.width = 0;
+  this.height = 0;
+  this.bitsPerPixel = 0;
+  this.colors = 0;
+  this.pictureData = null;
+}
+
+util.inherits(MetaDataBlockPicture, MetaDataBlock);
+
+MetaDataBlockPicture.create = function(isLast, pictureType, mimeType, description, width, height, bitsPerPixel, colors, pictureData) {
+  var mdb = new MetaDataBlockPicture(isLast);
+  mdb.pictureType = pictureType;
+  mdb.mimeType = mimeType;
+  mdb.description = description;
+  mdb.width = width;
+  mdb.height = height;
+  mdb.bitsPerPixel = bitsPerPixel;
+  mdb.colors = colors;
+  mdb.pictureData = pictureData;
+  mdb.hasData = true;
+  return mdb;
+}
+
+MetaDataBlockPicture.prototype.parse = function(buffer) {
+  try {
+
+    var pos = 0;
+
+    this.pictureType = buffer.readUInt32BE(pos);
+    pos += 4;
+
+    var mimeTypeLength = buffer.readUInt32BE(pos);
+    this.mimeType = buffer.toString("utf8", pos + 4, pos + 4 + mimeTypeLength);
+    pos += 4 + mimeTypeLength;
+
+    var descriptionLength = buffer.readUInt32BE(pos);
+    this.description = buffer.toString("utf8", pos + 4, pos + 4 + descriptionLength);
+    pos += 4 + descriptionLength;
+
+    this.width = buffer.readUInt32BE(pos);
+    this.height = buffer.readUInt32BE(pos + 4);
+    this.bitsPerPixel = buffer.readUInt32BE(pos + 8);
+    this.colors = buffer.readUInt32BE(pos + 12);
+    pos += 16;
+
+    var pictureDataLength = buffer.readUInt32BE(pos);
+    this.pictureData = new Buffer(pictureDataLength);
+    buffer.copy(this.pictureData, 0, pos + 4, pictureDataLength);
+
+    this.hasData = true;
+
+  }
+  catch (e) {
+    this.error = e;
+    this.hasData = false;
+  }
+}
+
+MetaDataBlockPicture.prototype.publish = function() {
+  var pos = 0;
+  var size = this.getSize();
+  var buffer = new Buffer(4 + size);
+
+  var header = size;
+  header |= (this.type << 24);
+  header |= (this.isLast ? 0x80000000 : 0);
+  buffer.writeUInt32BE(header >>> 0, pos);
+  pos += 4;
+
+  buffer.writeUInt32BE(this.pictureType, pos);
+  pos += 4;
+
+  var mimeTypeLen = Buffer.byteLength(this.mimeType);
+  buffer.writeUInt32BE(mimeTypeLen, pos);
+  buffer.write(this.mimeType, pos + 4);
+  pos += 4 + mimeTypeLen;
+
+  var descriptionLen = Buffer.byteLength(this.description);
+  buffer.writeUInt32BE(descriptionLen, pos);
+  buffer.write(this.description, pos + 4);
+  pos += 4 + descriptionLen;
+
+  buffer.writeUInt32BE(this.width, pos);
+  buffer.writeUInt32BE(this.height, pos + 4);
+  buffer.writeUInt32BE(this.bitsPerPixel, pos + 8);
+  buffer.writeUInt32BE(this.colors, pos + 12);
+  pos += 16;
+
+  buffer.writeUInt32BE(this.pictureData.length, pos);
+  this.pictureData.copy(buffer, pos + 4);
+
+  return buffer;
+}
+
+MetaDataBlockPicture.prototype.getSize = function() {
+  var size = 4;
+  size += 4 + Buffer.byteLength(this.mimeType);
+  size += 4 + Buffer.byteLength(this.description);
+  size += 16;
+  size += 4 + this.pictureData.length;
+  return size;
+}
+
+MetaDataBlockPicture.prototype.toString = function() {
+  var str = "[MetaDataBlockPicture]";
+  str += " type: " + this.type;
+  str += ", isLast: " + this.isLast;
+  if (this.error) {
+    str += "\n  ERROR: " + this.error;
+  }
+  if (this.hasData) {
+    str += "\n  pictureType: " + this.pictureType;
+    str += "\n  mimeType: " + this.mimeType;
+    str += "\n  description: " + this.description;
+    str += "\n  width: " + this.width;
+    str += "\n  height: " + this.height;
+    str += "\n  bitsPerPixel: " + this.bitsPerPixel;
+    str += "\n  colors: " + this.colors;
+    str += "\n  pictureData: " + (this.pictureData ? this.pictureData.length : "<null>");
+  }
+  return str;
+}
diff --git a/app/lib/flac-metadata/lib/data/MetaDataBlockStreamInfo.js b/app/lib/flac-metadata/lib/data/MetaDataBlockStreamInfo.js
new file mode 100644 (file)
index 0000000..c178a06
--- /dev/null
@@ -0,0 +1,86 @@
+var util = require("util");
+var MetaDataBlock = require("./MetaDataBlock");
+
+var MetaDataBlockStreamInfo = module.exports = function(isLast) {
+  MetaDataBlock.call(this, isLast, 0);
+
+  this.minBlockSize = 0;
+  this.maxBlockSize = 0;
+  this.minFrameSize = 0;
+  this.maxFrameSize = 0;
+  this.sampleRate = 0;
+  this.channels = 0;
+  this.bitsPerSample = 0;
+  this.samples = 0;
+  this.checksum = null;
+  this.duration = 0;
+  this.durationStr = "0:00.000";
+}
+
+util.inherits(MetaDataBlockStreamInfo, MetaDataBlock);
+
+MetaDataBlockStreamInfo.prototype.remove = function() {
+  console.error("WARNING: Can't remove StreamInfo block!");
+}
+
+MetaDataBlockStreamInfo.prototype.parse = function(buffer) {
+  try {
+
+    var pos = 0;
+
+    this.minBlockSize = buffer.readUInt16BE(pos);
+    this.maxBlockSize = buffer.readUInt16BE(pos + 2);
+    this.minFrameSize = (buffer.readUInt8(pos + 4) << 16) | buffer.readUInt16BE(pos + 5);
+    this.maxFrameSize = (buffer.readUInt8(pos + 7) << 16) | buffer.readUInt16BE(pos + 8);
+
+    var tmp = buffer.readUInt32BE(pos + 10);
+    this.sampleRate = tmp >>> 12;
+    this.channels = (tmp >>> 9) & 0x07;
+    this.bitsPerSample = (tmp >>> 4) & 0x1f;
+    this.samples = +((tmp & 0x0f) << 4) + buffer.readUInt32BE(pos + 14);
+
+    this.checksum = new Buffer(16);
+    buffer.copy(this.checksum, 0, 18, 34);
+
+    this.duration = this.samples / this.sampleRate;
+
+    var minutes = "" + Math.floor(this.duration / 60);
+    var seconds = pad(Math.floor(this.duration % 60), 2);
+    var milliseconds = pad(Math.round(((this.duration % 60) - Math.floor(this.duration % 60)) * 1000), 3);
+    this.durationStr = minutes + ":" + seconds + "." + milliseconds;
+
+    this.hasData = true;
+
+  }
+  catch (e) {
+    this.error = e;
+    this.hasData = false;
+  }
+}
+
+MetaDataBlockStreamInfo.prototype.toString = function() {
+  var str = "[MetaDataBlockStreamInfo]";
+  str += " type: " + this.type;
+  str += ", isLast: " + this.isLast;
+  if (this.error) {
+    str += "\n  ERROR: " + this.error;
+  }
+  if (this.hasData) {
+    str += "\n  minBlockSize: " + this.minBlockSize;
+    str += "\n  maxBlockSize: " + this.maxBlockSize;
+    str += "\n  minFrameSize: " + this.minFrameSize;
+    str += "\n  maxFrameSize: " + this.maxFrameSize;
+    str += "\n  samples: " + this.samples;
+    str += "\n  sampleRate: " + this.sampleRate;
+    str += "\n  channels: " + (this.channels + 1);
+    str += "\n  bitsPerSample: " + (this.bitsPerSample + 1);
+    str += "\n  duration: " + this.durationStr;
+    str += "\n  checksum: " + (this.checksum ? this.checksum.toString("hex") : "<null>");
+  }
+  return str;
+}
+
+function pad(n, width) {
+  n = "" + n;
+  return (n.length >= width) ? n : new Array(width - n.length + 1).join("0") + n;
+}
diff --git a/app/lib/flac-metadata/lib/data/MetaDataBlockVorbisComment.js b/app/lib/flac-metadata/lib/data/MetaDataBlockVorbisComment.js
new file mode 100644 (file)
index 0000000..bd82f8a
--- /dev/null
@@ -0,0 +1,108 @@
+var util = require("util");
+var MetaDataBlock = require("./MetaDataBlock");
+
+var MetaDataBlockVorbisComment = module.exports = function(isLast) {
+  MetaDataBlock.call(this, isLast, 4);
+
+  this.vendor = "";
+  this.comments = [];
+}
+
+util.inherits(MetaDataBlockVorbisComment, MetaDataBlock);
+
+MetaDataBlockVorbisComment.create = function(isLast, vendor, comments) {
+  var mdb = new MetaDataBlockVorbisComment(isLast);
+  mdb.vendor = vendor;
+  mdb.comments = comments;
+  mdb.hasData = true;
+  return mdb;
+}
+
+MetaDataBlockVorbisComment.prototype.parse = function(buffer) {
+  try {
+
+    var pos = 0;
+
+    var vendorLen = buffer.readUInt32LE(pos);
+    var vendor = buffer.toString("utf8", pos + 4, pos + 4 + vendorLen);
+    this.vendor = vendor;
+    pos += 4 + vendorLen;
+
+    var commentCount = buffer.readUInt32LE(pos);
+    pos += 4;
+
+    while (commentCount-- > 0) {
+      var commentLen = buffer.readUInt32LE(pos);
+      var comment = buffer.toString("utf8", pos + 4, pos + 4 + commentLen);
+      this.comments.push(comment);
+      pos += 4 + commentLen;
+    }
+
+    this.hasData = true;
+
+  }
+  catch (e) {
+    this.error = e;
+    this.hasData = false;
+  }
+}
+
+MetaDataBlockVorbisComment.prototype.publish = function() {
+  var pos = 0;
+  var size = this.getSize();
+  var buffer = new Buffer(4 + size);
+
+  var header = size;
+  header |= (this.type << 24);
+  header |= (this.isLast ? 0x80000000 : 0);
+  buffer.writeUInt32BE(header >>> 0, pos);
+  pos += 4;
+
+  var vendorLen = Buffer.byteLength(this.vendor);
+  buffer.writeUInt32LE(vendorLen, pos);
+  buffer.write(this.vendor, pos + 4);
+  pos += 4 + vendorLen;
+
+  var commentCount = this.comments.length;
+  buffer.writeUInt32LE(commentCount, pos);
+  pos += 4;
+
+  for (var i = 0; i < commentCount; i++) {
+    var comment = this.comments[i];
+    var commentLen = Buffer.byteLength(comment);
+    buffer.writeUInt32LE(commentLen, pos);
+    buffer.write(comment, pos + 4);
+    pos += 4 + commentLen;
+  }
+
+  return buffer;
+}
+
+MetaDataBlockVorbisComment.prototype.getSize = function() {
+  var size = 8 + Buffer.byteLength(this.vendor);
+  for (var i = 0; i < this.comments.length; i++) {
+    size += 4 + Buffer.byteLength(this.comments[i]);
+  }
+  return size;
+}
+
+MetaDataBlockVorbisComment.prototype.toString = function() {
+  var str = "[MetaDataBlockVorbisComment]";
+  str += " type: " + this.type;
+  str += ", isLast: " + this.isLast;
+  if (this.error) {
+    str += "\n  ERROR: " + this.error;
+  }
+  if (this.hasData) {
+    str += "\n  vendor: " + this.vendor;
+    if (this.comments.length) {
+      str += "\n  comments:";
+      for (var i = 0; i < this.comments.length; i++) {
+        str += "\n    " + this.comments[i].split("=").join(": ");
+      }
+    } else {
+      str += "\n  comments: none";
+    }
+  }
+  return str;
+}
diff --git a/app/lib/flac-metadata/package-lock.json b/app/lib/flac-metadata/package-lock.json
new file mode 100644 (file)
index 0000000..c816d65
--- /dev/null
@@ -0,0 +1,5 @@
+{
+  "name": "flac-metadata",
+  "version": "0.1.1",
+  "lockfileVersion": 1
+}
diff --git a/app/lib/flac-metadata/package.json b/app/lib/flac-metadata/package.json
new file mode 100644 (file)
index 0000000..360167d
--- /dev/null
@@ -0,0 +1,28 @@
+{
+  "name": "flac-metadata",
+  "description": "FLAC metadata processor implemented as Transform stream",
+  "keywords": [
+    "flac",
+    "metadata",
+    "audio"
+  ],
+  "author": "Claus Wahlers <claus@codeazur.com.br> (http://wahlers.com.br/claus/)",
+  "homepage": "https://github.com/claus/flac-metadata",
+  "version": "0.1.1",
+  "license": "MIT",
+  "main": "index.js",
+  "directories": {
+    "lib": "./lib"
+  },
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/claus/flac-metadata.git"
+  },
+  "bugs": {
+    "url": "https://github.com/claus/flac-metadata/issues",
+    "email": "claus@codeazur.com.br"
+  },
+  "engines": {
+    "node": ">=0.8"
+  }
+}
index 5607f2128a4040ee3af18df71bf15e360cbb3170..b1f95b5b4bf228938ffa50b96b4c456e03261402 100644 (file)
@@ -1,11 +1,12 @@
 const fs = require("fs-extra");
 const path = require('path');
 const os = require('os');
+const dateformat = require('dateformat');
 
 var userdata = "";
 var homedata = "";
 if(process.env.APPDATA){
-       userdata = process.env.APPDATA + path.sep + "Deezloader Remix\\";
+       userdata = process.env.APPDATA + path.sep + "Deezloader Remix" + path.sep;
        homedata = os.homedir();
 }else if(process.platform == "darwin"){
        homedata = os.homedir();
@@ -18,13 +19,49 @@ if(process.env.APPDATA){
        userdata = homedata + '/.config/Deezloader Remix/';
 }
 
-const logsLocation = userdata + "/deezloader.log";
+fs.ensureDirSync(path.join(userdata, 'logs'))
+const logsLocation = path.join(userdata, 'logs', `${dateformat(new Date(), 'yyyy-mm-dd')}.txt`)
+fs.appendFileSync(logsLocation, "\r\n\r\n");
 
-function logs(level, message, callback){
-       var str = "["+level+"]"+message;
+function logs(level, message){
+       var str = "["+level+"] "+message;
        console.log(str);
-       fs.appendFileSync(logsLocation, str+"\n");
+       fs.appendFileSync(logsLocation, str+"\r\n");
+       return;
+}
+
+function removeColors(string){
+       return string.replace(/\x1b\[\d+m/g,"");
+}
+
+function debug(message){
+       var str = "[\x1b[32mDebug\x1b[0m] "+message;
+       console.log(str);
+       fs.appendFileSync(logsLocation, removeColors(str)+"\r\n");
+       return;
+}
+function info(message){
+       var str = "[\x1b[35mInfo\x1b[0m] "+message;
+       console.log(str);
+       fs.appendFileSync(logsLocation, removeColors(str)+"\r\n");
+       return;
+}
+function warn(message){
+       var str = "[\x1b[33mWarning\x1b[0m] "+message;
+       console.log(str);
+       fs.appendFileSync(logsLocation, removeColors(str)+"\r\n");
+       return;
+}
+function error(message){
+       var str = "[\x1b[31mError\x1b[0m] "+message;
+       console.log(str);
+       fs.appendFileSync(logsLocation, removeColors(str)+"\r\n");
        return;
 }
 
 module.exports.logs = logs;
+
+module.exports.debug = debug;
+module.exports.info = info;
+module.exports.warn = warn;
+module.exports.error = error;
index f8f12048c0287657ca43a305be2e59f6f21ef886..8eefff4074d669cb0cad66ed50d7e8219bfd72fe 100644 (file)
@@ -29,9 +29,9 @@ require('electron-context-menu')({
 function loadSettings(){
        var userdata = "";
        if(process.platform == "android"){
-               userdata = os.homedir() + "/storage/shared/Deezloader/";
+               userdata = os.homedir() + "/storage/shared/Deezloader Remix/";
        }else{
-               userdata = app.getPath("appData")+path.sep+"Deezloader"+path.sep;
+               userdata = app.getPath("appData")+path.sep+"Deezloader Remix"+path.sep;
        }
 
        if(!fs.existsSync(userdata+"config.json")){
@@ -51,41 +51,41 @@ function loadSettings(){
 }
 
 function createWindow () {
-       // Create the browser window.
-       mainWindow = new BrowserWindow({
-               width: mainWindowState.width,
-               height: mainWindowState.height,
-               x: mainWindowState.x,
-               y: mainWindowState.y,
-               alwaysOnTop: false,
-               frame: false,
-               icon: __dirname + "/icon.png",
-               minWidth: 415,
-               minHeight: 32,
-               show:false
-       });
-
-       mainWindow.setMenu(null);
-       mainWindow.once('ready-to-show', () => {
-    mainWindow.show()
-  })
-
-       // and load the index.html of the app.
-       mainWindow.loadURL('http://localhost:' + appConfig.serverPort);
-
-       mainWindow.on('closed', function () {
-               mainWindow = null;
-       });
-
-       // Check if window was closed maximized and restore it
-       if (mainWindowState.maximized) {
-               mainWindow.maximize();
-       }
 
-       // Save current window state
-       mainWindow.on('close', () => {
-               mainWindowState.saveState(mainWindow);
-       });
+       if (!(process.argv.indexOf("-s")>-1 || process.argv.indexOf("--server")>-1)){
+               // Create the browser window.
+               mainWindow = new BrowserWindow({
+                       width: mainWindowState.width,
+                       height: mainWindowState.height,
+                       x: mainWindowState.x,
+                       y: mainWindowState.y,
+                       alwaysOnTop: false,
+                       frame: false,
+                       icon: __dirname + "/icon.png",
+                       minWidth: 415,
+                       minHeight: 32,
+                       backgroundColor: "#3F51B5"
+               });
+
+               mainWindow.setMenu(null);
+
+               // and load the index.html of the app.
+               mainWindow.loadURL('http://localhost:' + appConfig.serverPort);
+
+               mainWindow.on('closed', function () {
+                       mainWindow = null;
+               });
+
+               // Check if window was closed maximized and restore it
+               if (mainWindowState.maximized) {
+                       mainWindow.maximize();
+               }
+
+               // Save current window state
+               mainWindow.on('close', () => {
+                       mainWindowState.saveState(mainWindow);
+               });
+       }
 }
 
 app.on('ready', createWindow);
index 12f46af41e87287e56b46ec5c13bbd66f5b895cf..d85b7cbee8ea50876b9c531be7de311835e45a1d 100644 (file)
@@ -1,7 +1,7 @@
 {
        "name": "deezloader-rmx",
        "productName": "Deezloader Remix",
-       "version": "4.0.2",
+       "version": "4.0.3",
        "description": "Download music from Deezer",
        "main": "main.js",
        "author": "RemixDevs",
                "electron-context-menu": "latest",
                "electron-window-state-manager": "latest",
                "express": "latest",
-               "flac-metadata": "latest",
                "fs-extra": "latest",
                "package.json": "latest",
                "request": "latest",
                "requestretry": "latest",
                "socket.io": "latest",
-               "spotify-web-api-node": "latest"
+               "spotify-web-api-node": "latest",
+               "dateformat": "latest",
+               "queue": "latest"
        }
 }
index 3719bb94e3035e46dba37396e560cddedf25cfcc..1af60d30a1e220572ee6b3e9b380ae476d0365f7 100644 (file)
@@ -237,3 +237,6 @@ a[href^="ftp://"] {
     -webkit-user-drag: auto;
     user-drag: auto; /* Technically not supported in Electron yet */
 }
+.toast{
+       justify-content: normal !important;
+}
index a03bc03e810ae24d4ab171c2f580e9e38c45152c..3def02a33e019f850bde835d3df28c96e2e7374a 100644 (file)
Binary files a/app/public/favicon.ico and b/app/public/favicon.ico differ
diff --git a/app/public/img/noCover.jpg b/app/public/img/noCover.jpg
new file mode 100644 (file)
index 0000000..62b7527
Binary files /dev/null and b/app/public/img/noCover.jpg differ
index 40603659a20d900029ab2f0a9d4143c7a81dcb79..95eec6551cdb3482ca7cbe194e7ab8f24a68f498 100644 (file)
@@ -11,7 +11,7 @@
   <meta charset="utf-8"/>
 </head>
 <body>
-<span id="appVersionFallback" hidden>4.0.2</span>
+<span id="appVersionFallback" hidden>4.0.3</span>
 
 <div id="title-bar">
        <div class="resize-padding" style="width: 100%; height: 3px;"></div>
                                <label for="modal_settings_input_albumNameTemplate">Album names <i class="material-icons valignicon tiny tooltipped" data-position="right" data-delay="50" data-tooltip="Supported variables are: %year%, %artist%, %type% and %album%">info_outline</i></label>
                        </div>
                </div>
-    <div class="row">
-                       <div class="input-field col s12">
-                               <i class="material-icons prefix">label</i>
-                               <input type="text" id="modal_settings_input_spotifyUser"/>
-                               <label for="modal_settings_input_spotifyUser">Spotify Username</label>
-                       </div>
-               </div>
                <div class="row">
-                       <div class="input-field col s12">
+                       <div class="input-field col s6">
                                <i class="material-icons prefix">label</i>
                                <select name="artworkSize" id="modal_settings_select_artworkSize">
                                        <option value="/500x500.jpg">500x500</option>
-                                       <option value="/800x800.jpg" selected>800x800</option>
+                                       <option value="/800x800.jpg">800x800</option>
                                        <option value="/1000x1000.jpg">1000x1000</option>
                                        <option value="/1200x1200.jpg">1200x1200</option>
                                        <option value="/1400x1400.jpg">1400x1400</option>
                                </select>
                                <label for="modal_settings_select_artworkSize">Artwork size</label>
                        </div>
+      <div class="input-field col s6">
+                               <i class="material-icons prefix">label</i>
+                               <select name="artworkSize" id="modal_settings_select_multitagSeparator">
+                                       <option value="null">Null Character</option>
+                                       <option value=",">,</option>
+                                       <option value="/">/</option>
+                                       <option value=";">;</option>
+                               </select>
+                               <label for="modal_settings_select_multitagSeparator">Multitag Separator</label>
+                       </div>
                </div>
     <div class="row">
                        <div class="input-field col s12">
                                <label for="modal_settings_input_downloadTracksLocation">Download location</label>
                        </div>
                </div>
+    <div class="row">
+                       <div class="input-field col s6">
+                               <i class="material-icons prefix">label</i>
+                               <input type="number" id="modal_settings_number_queueConcurrency" min="1"/>
+                               <label for="modal_settings_number_queueConcurrency">Queue Concurrency</label>
+                       </div>
+                       <div class="input-field col s6">
+                               <i class="material-icons prefix">label</i>
+                               <input type="text" id="modal_settings_input_spotifyUser"/>
+                               <label for="modal_settings_input_spotifyUser">Spotify Username</label>
+                       </div>
+               </div>
                <div class="row">
                        <p class="col s4">
         <label>
                        <p class="col s4">
         <label>
                                <input type="checkbox" id="modal_settings_cbox_hifi" class="filled-in"/>
-                               <span class="tooltipped" data-position="top" data-delay="500" data-tooltip="Downloads a FLAC file if it is available">Enable HIFI</span>
+                               <span class="tooltipped" data-position="top" data-delay="500" data-tooltip="Downloads a FLAC file if it is available">Download FLACs</span>
         </label>
       </p>
                        <p class="col s4">
                        <p class="col s4">
         <label>
                                <input type="checkbox" id="modal_settings_cbox_partOfSet" class="filled-in"/>
-                               <span class="tooltipped" data-position="top" data-delay="500" data-tooltip="Track number and disc number will be formatted as 1/16 instead of just 1">Add part of set to tracks</span>
+                               <span class="tooltipped" data-position="top" data-delay="500" data-tooltip="Track number and disc number will be formatted as 1/16 instead of just 1">Add "out of" tag</span>
+        </label>
+      </p>
+      <p class="col s4">
+        <label>
+                               <input type="checkbox" id="modal_settings_cbox_saveArtwork" class="filled-in"/>
+                               <span>Save Artwork</span>
+        </label>
+      </p>
+      <p class="col s4">
+        <label>
+                               <input type="checkbox" id="modal_settings_cbox_logErrors" class="filled-in"/>
+                               <span>Create log file for Errors</span>
         </label>
       </p>
                </div>
                <div class="row">
-                       <div class="center input-field col s12">
+                       <div class="col s12">
+        <img id="modal_settings_picture" src="" alt="Profile Picture" class="circle left" style="height:125px; margin-right: 12px;"/>
                                <p>You are logged in as <b id="modal_settings_username"></b></p>
                                <a href="#" class="modal-close waves-effect waves-light btn" id="modal_settings_btn_logout">Logout</a>
                        </div>
                                                        <b>Version:</b> <span id="application_version_about"></span><br/>
                                                        <b>Original Programmer:</b> <a href="https://boerse.to/members/zzmtv.3378614/">ZzMTV</a><br/>
                                                        <b>Former Maintainers:</b> <a href="https://www.reddit.com/user/ParadoxalManiak/">ParadoxalManiak</a>, <a href="https://www.reddit.com/user/_snwflake/">snwflake</a>, <a href="https://www.reddit.com/user/ExtendLord/">ExtendLord</a><br>
-                                                       <b>Interface:</b> <a href="http://materializecss.com/">materializecss</a><br/>
+              <b>Repo: </b> <a href="https://notabug.org/RemixDevs/DeezloaderRemix">Official Repo</a>
+              <b>Interface:</b> <a href="http://materializecss.com/">materializecss</a><br/>
               <b>Based on: </b>Deezloader Reborn</br>
                                                </p>
                                        </div>
                                        <div class="collapsible-header waves-effect"><i class="material-icons">feedback</i>Feedback</div>
                                        <div class="collapsible-body">
                                                <p>
-                                                       Do you have some feedback or want to ask for some new features? Join the <a href="https://t.me/joinchat/BJf2dUoyFbTi9HLUp0SWkw">official group</a>.
+                                                       Do you have some feedback or want to ask for some new features? Open a <a href="https://notabug.org/RemixDevs/DeezloaderRemix/issues/new">New Issue</a> and add the Feature label.
                                                </p>
                                        </div>
                                </li>
                                        <div class="collapsible-header waves-effect"><i class="material-icons">bug_report</i>Bugs</div>
                                        <div class="collapsible-body">
                                                <p>
-                                                       If you experience any bugs, please send a bug report to <a href="https://t.me/joinchat/BJf2dUoyFbTi9HLUp0SWkw">this group</a>.
+                                                       If you experience any bugs, open a <a href="https://notabug.org/RemixDevs/DeezloaderRemix/issues/new">New Issue</a> and send the bug report there, thankyou.
                                                </p>
                                        </div>
                                </li>
+        <li>
+          <div class="collapsible-header waves-effect"><i class="material-icons">group</i>Contacts</div>
+          <div class="collapsible-body">
+            <p>
+              If you want to talk about the app and help you can do that in this <a href="https://t.me/joinchat/BJf2dUoyFbTi9HLUp0SWkw">Telegram Group</a>!
+            </p>
+          </div>
+        </li>
                                <li>
                                        <div class="collapsible-header waves-effect"><i class="material-icons">warning</i>Attention!</div>
                                        <div class="collapsible-body">
                                        <div class="collapsible-header waves-effect"><i class="material-icons">history</i>Changelog</div>
                                        <div class="collapsible-body">
                                                <p>
+              <b>Version 4.0.3</b><br/>
+              - Added concurrent download! (Default value is 3)<br/>
+              - Enhanced logs system (Now with colors!)<br/>
+              - Added argument -s and --server to start without GUI<br/>
+              - Slimmed down request number<br/>
+              - Fixed BPM tag<br/>
+              - Added placeholder for missing covers<br/>
+              - Improved login algorithm<br/>
+              - Fixed some problems with settings change during downloads<br/>
+              - Added option to save artworks<br/>
+              - Added option to save logs of failed downloads<br/>
+              - Changing default chart country in settings automaticaly updates the charts tab<br/>
+              - Changing search type will automaticaly do a search<br/>
+              - Added choice of separator on MP3 Files (FLAC uses native support)<br/>
+              - Added track preview in album and playlist tracklist modal<br/>
+              <br/>
               <b>Version 4.0.2</b><br/>
               - Changed API token retrive (some users were not able to login)<br/>
               - Now the Artist modal will stay under the Album modal (you can browse artist albums without reopening the artist modal each time)<br/>
                                                </td>
                                        </tr>
                                        </tbody>
-                                       <tbody id="tab_search_table_results_tbody_results"></tbody>
+                                       <tbody id="tab_search_table_results_tbody_results" class="animated fadeInUp"></tbody>
                                        <tbody id="tab_search_table_results_tbody_noResults" class="animated fadeInUp hide">
                                        <tr>
                                                <td class="center">Nothing found!</td>
index 3a6c213ae2469bb988c02fb48655ba7897be798c..8926b8972aa974ee7936457a60c0324c255d4b72 100644 (file)
@@ -25,19 +25,18 @@ const version = (typeof packageFile === 'undefined') ? $("#appVersionFallback").
 
        // Open DevTools when F12 is pressed
        // Reload page when F5 is pressed
-       document.addEventListener("keydown", function (e) {
-               if (e.which === 123) {
-                       if(typeof require !== "undefined"){
-                               remote.getCurrentWindow().toggleDevTools();
-                       }
-               }
-
-               if (e.which === 116) {
-                       if(typeof require !== "undefined"){
-                               remote.getCurrentWindow().reload();
-                       }
+       if (typeof require !== "undefined"){
+               if (remote.process.env.NODE_ENV == 'development'){
+                       document.addEventListener("keydown", function (e) {
+                               if (e.which === 123) {
+                                       remote.getCurrentWindow().toggleDevTools();
+                               }
+                               if (e.which === 116) {
+                                       remote.getCurrentWindow().reload();
+                               }
+                       });
                }
-       });
+       }
 
        // Function to make title-bar work
        function initTitleBar() {
index d1c58d9ae005d27140a2e89c05caffc877dc7ed3..82730146a04c76c834d5abcd7389734ec59fe7c2 100644 (file)
@@ -7,20 +7,10 @@ if(typeof mainApp !== "undefined"){
        var defaultDownloadLocation = mainApp.defaultDownloadDir;
 }
 let userSettings = [];
-let Username = "";
 
 let preview_track = document.getElementById('preview-track');
 let preview_stopped = true;
 
-function sleep(milliseconds) {
-  var start = new Date().getTime();
-  for (var i = 0; i < 1e7; i++) {
-    if ((new Date().getTime() - start) > milliseconds){
-      break;
-    }
-  }
-}
-
 socket.emit("autologin");
 
 socket.on("message", function(title, msg){
@@ -35,7 +25,6 @@ $('#modal_login_btn_login').click(function () {
        var password = $('#modal_login_input_password').val();
        var autologin = $('#modal_login_input_autologin').prop("checked");
        //Send to the software
-       Username = username;
        socket.emit('login', username, password,autologin);
 });
 
@@ -49,22 +38,22 @@ socket.on("autologin",function(username,password){
        M.updateTextFields();
        socket.emit('login', username, password,false);
 });
-socket.on("login", function (errmsg) {
-       if (errmsg == "none") {
-               $("#modal_settings_username").html(Username);
+
+socket.on("login", function (data) {
+       if (!data.error) {
+               $("#modal_settings_username").html(data.username);
+               $("#modal_settings_picture").attr("src",data.picture)
                $('#initializing').addClass('animated fadeOut').on('webkitAnimationEnd', function () {
                        $(this).css('display', 'none');
                        $(this).removeClass('animated fadeOut');
                });
-
-       // Load top charts list for countries
-       socket.emit("getChartsCountryList", {selected: userSettings.chartsCountry});
-       socket.emit("getChartsTrackListByCountry", {country: userSettings.chartsCountry});
-       socket.emit("getMePlaylistList", {});
-
+               // Load top charts list for countries
+               socket.emit("getChartsCountryList", {selected: userSettings.chartsCountry});
+               socket.emit("getChartsTrackListByCountry", {country: userSettings.chartsCountry});
+               socket.emit("getMePlaylistList", {});
        }else{
-                       $('#login-res-text').text(errmsg);
-                       setTimeout(function(){$('#login-res-text').text("");},1000);
+                       $('#login-res-text').text(data.error);
+                       setTimeout(function(){$('#login-res-text').text("");},3000);
        }
        $('#modal_login_btn_login').attr("disabled", false);
        $('#modal_login_btn_login').html("Login");
@@ -114,15 +103,15 @@ $(document).ready(function () {
                        preview_stopped = true;
                        $("*").removeProp("playing");
                        $('.preview_controls').text("play_arrow");
+                       $('.preview_playlist_controls').text("play_arrow");
                }
        });
 
        $('.modal').modal();
-
+       socket.emit("getUserSettings");
 });
 
 // Load settings
-socket.emit("getUserSettings");
 socket.on('getUserSettings', function (data) {
        userSettings = data.settings;
        console.log('Settings refreshed');
@@ -167,7 +156,11 @@ $('#modal_settings_btn_saveSettings').click(function () {
                extendedTags: $('#modal_settings_cbox_extendedTags').is(':checked'),
                partOfSet: $('#modal_settings_cbox_partOfSet').is(':checked'),
                chartsCountry: $('#modal_settings_select_chartsCounrty').val(),
-               spotifyUser: $('#modal_settings_input_spotifyUser').val()
+               spotifyUser: $('#modal_settings_input_spotifyUser').val(),
+               saveArtwork: $('#modal_settings_cbox_saveArtwork').is(':checked'),
+               logErrors: $('#modal_settings_cbox_logErrors').is(':checked'),
+               queueConcurrency: parseInt($('#modal_settings_number_queueConcurrency').val()),
+               multitagSeparator: $('#modal_settings_select_multitagSeparator').val()
        };
 
        // Send updated settings to be saved into config file
@@ -222,6 +215,10 @@ function fillSettingsModal(settings) {
        $('#modal_settings_cbox_partOfSet').prop('checked', settings.partOfSet);
        $('#modal_settings_select_chartsCounrty').val(settings.chartsCountry).formSelect();
        $('#modal_settings_input_spotifyUser').val(settings.spotifyUser);
+       $('#modal_settings_cbox_saveArtwork').prop('checked', settings.saveArtwork);
+       $('#modal_settings_cbox_logErrors').prop('checked', settings.logErrors);
+       $('#modal_settings_number_queueConcurrency').val(settings.queueConcurrency);
+       $('#modal_settings_select_multitagSeparator').val(settings.multitagSeparator).formSelect();
 
        M.updateTextFields()
 }
@@ -229,55 +226,15 @@ function fillSettingsModal(settings) {
 
 //#############################################MODAL_MSG##############################################\\
 function message(title, message) {
-
        $('#modal_msg_title').html(title);
-
        $('#modal_msg_message').html(message);
-
        $('#modal_msg').modal('open');
-
 }
 
 //****************************************************************************************************\\
 //************************************************TABS************************************************\\
 //****************************************************************************************************\\
 
-//###############################################TAB_URL##############################################\\
-$('#tab_url_form_url').submit(function (ev) {
-
-       ev.preventDefault();
-
-       var urls = $("#song_url").val().split(";");
-       console.log(urls);
-       for(var i = 0; i < urls.length; i++){
-               var url = urls[i];
-               console.log(url);
-
-               if (url.length == 0) {
-                       message('Blank URL Field', 'You need to insert an URL to download it!');
-                       return false;
-               }
-
-               //Validate URL
-               if (url.indexOf('deezer.com/') < 0 && url.indexOf('open.spotify.com/') < 0 && url.indexOf('spotify:') < 0) {
-                       message('Wrong URL', 'The URL seems to be wrong. Please check it and try it again.');
-                       return false;
-               }
-
-               if (url.indexOf('?') > -1) {
-                       url = url.substring(0, url.indexOf("?"));
-               }
-
-               if (url.indexOf('open.spotify.com/') >= 0 ||  url.indexOf('spotify:') >= 0){
-                       if (url.indexOf('user') < 0 || url.indexOf('playlist') < 0){
-                               message('Playlist not found', 'Spotify for now can only download playlists.');
-                               return false;
-                       }
-               }
-               addToQueue(url);
-       }
-});
-
 //#############################################TAB_SEARCH#############################################\\
 $('#tab_search_form_search').submit(function (ev) {
 
@@ -287,7 +244,6 @@ $('#tab_search_form_search').submit(function (ev) {
        var mode = $('#tab_search_form_search').find('input[name=searchMode]:checked').val();
 
        if (searchString.length == 0) {
-               message('Search can\'t be empty', 'You tried to search for nothing. But if you search nothing, you\'ll find nothing. So don\'t try it again.');
                return;
        }
 
@@ -300,6 +256,10 @@ $('#tab_search_form_search').submit(function (ev) {
 
 });
 
+$('input[name=searchMode][type=radio]').change(()=>{
+       $('#tab_search_form_search').submit();
+})
+
 socket.on('search', function (data) {
 
        $('#tab_search_table_results_tbody_loadingIndicator').addClass('hide');
@@ -329,7 +289,7 @@ function showResults_table_track(tracks) {
                var currentResultTrack = tracks[i];
                $(tableBody).append(
                        '<tr>' +
-                       '<td><a href="#" class="circle single-cover" preview="'+currentResultTrack['preview']+'"><i class="material-icons preview_controls white-text">play_arrow</i><img class="circle" src="' + currentResultTrack['album']['cover_small'] + '"/></a></td>' +
+                       '<td><a href="#" class="circle single-cover" preview="'+currentResultTrack['preview']+'"><i class="material-icons preview_controls white-text">play_arrow</i><img style="width:56px" class="circle" src="' + (currentResultTrack['album']['cover_small'] ? currentResultTrack['album']['cover_small'] : "img/noCover.jpg" ) + '"/></a></td>' +
                        '<td>' + currentResultTrack['title'] + (currentResultTrack.explicit_lyrics ? ' <i class="material-icons valignicon tiny materialize-red-text">error_outline</i>' : '')+ '</td>' +
                        '<td>' + currentResultTrack['artist']['name'] + '</td>' +
                        '<td>' + currentResultTrack['album']['title'] + '</td>' +
@@ -362,6 +322,7 @@ function showResults_table_track(tracks) {
                                $("*").removeProp("playing");
                                $(this).prop("playing","playing");
                                $('.preview_controls').text("play_arrow");
+                               $('.preview_playlist_controls').text("play_arrow");
                                $('.preview_controls').css({opacity:0});
                                $(this).children('i').text("pause");
                                $(this).children('i').css({opacity: 1});
@@ -384,7 +345,7 @@ function showResults_table_album(albums) {
                var currentResultAlbum = albums[i];
                $(tableBody).append(
                                '<tr>' +
-                               '<td><img src="' + currentResultAlbum['cover_small'] + '" class="circle" /></td>' +
+                               '<td><img style="width:56px" src="' + (currentResultAlbum['cover_small'] ? currentResultAlbum['cover_small'] : "img/noCover.jpg") + '" class="circle" /></td>' +
                                (currentResultAlbum.explicit_lyrics ? '<td><i class="material-icons valignicon tiny materialize-red-text tooltipped" data-tooltip="Explicit">error_outline</i> ' : '<td> ') + currentResultAlbum['title'] + '</td>' +
                                '<td>' + currentResultAlbum['artist']['name'] + '</td>' +
                                '<td>' + currentResultAlbum['nb_tracks'] + '</td>' +
@@ -404,7 +365,7 @@ function showResults_table_artist(artists) {
                var currentResultArtist = artists[i];
                $(tableBody).append(
                                '<tr>' +
-                               '<td><img src="' + currentResultArtist['picture_small'] + '" class="circle" /></td>' +
+                               '<td><img style="width:56px" src="' + (currentResultArtist['picture_small'] ? currentResultArtist['picture_small'] : "img/noCover.jpg")  + '" class="circle" /></td>' +
                                '<td>' + currentResultArtist['name'] + '</td>' +
                                '<td>' + currentResultArtist['nb_album'] + '</td>' +
                                '</tr>');
@@ -421,7 +382,7 @@ function showResults_table_playlist(playlists) {
                var currentResultPlaylist = playlists[i];
                $(tableBody).append(
                                '<tr>' +
-                               '<td><img src="' + currentResultPlaylist['picture_small'] + '" class="circle" /></td>' +
+                               '<td><img style="width:56px" src="' + (currentResultPlaylist['picture_small'] ? currentResultPlaylist['picture_small'] : "img/noCover.jpg") + '" class="circle" /></td>' +
                                '<td>' + currentResultPlaylist['title'] + '</td>' +
                                '<td>' + currentResultPlaylist['nb_tracks'] + '</td>' +
                                '</tr>');
@@ -548,6 +509,7 @@ socket.on("getTrackList", function (data) {
                        trackListSelectiveModalApp.title = 'Playlist';
 
                        trackListSelectiveModalApp.head = [
+                               {title: '<i class="material-icons">music_note</i>'},
                                {title: '#'},
                                {title: 'Song'},
                                {title: 'Artist'},
@@ -558,16 +520,49 @@ socket.on("getTrackList", function (data) {
                        $('.selectAll').prop('checked', false);
 
                        for (var i = 0; i < trackList.length; i++) {
-                               $(tableBody).append('<tr><td>' + (i + 1) + '</td>' +
-                                               (trackList[i].explicit_lyrics ? '<td><i class="material-icons valignicon tiny materialize-red-text tooltipped" data-tooltip="Explicit">error_outline</i> ' : '<td> ') + trackList[i].title + '</td>' +
-                                               '<td>' + trackList[i].artist.name + '</td>' +
-                                               '<td>' + convertDuration(trackList[i].duration) + '</td>' +
-                                               '<td><div class="valign-wrapper"><label><input class="trackCheckbox valign" type="checkbox" id="trackChk'+ i +'" value="' + trackList[i].link + '"><span></span></label></div></tr>');
+                               $(tableBody).append(
+                                       '<tr><td><i class="material-icons preview_playlist_controls" preview="'+trackList[i].preview+'">play_arrow</i></td>'+
+                                       '<td>' + (i + 1) + '</td>' +
+                                       (trackList[i].explicit_lyrics ? '<td><i class="material-icons valignicon tiny materialize-red-text tooltipped" data-tooltip="Explicit">error_outline</i> ' : '<td> ') + trackList[i].title + '</td>' +
+                                       '<td>' + trackList[i].artist.name + '</td>' +
+                                       '<td>' + convertDuration(trackList[i].duration) + '</td>' +
+                                       '<td><div class="valign-wrapper"><label><input class="trackCheckbox valign" type="checkbox" id="trackChk'+ i +'" value="' + trackList[i].link + '"><span></span></label></div></tr>'
+                               );
+                               tableBody.children('tr:last').find('.preview_playlist_controls').click(function (e) {
+                                       e.preventDefault();
+                                       if ($(this).prop("playing")){
+                                               if (preview_track.paused){
+                                                       preview_track.play();
+                                                       preview_stopped = false;
+                                                       $(this).text("pause");
+                                                       $(preview_track).animate({volume: 1}, 500);
+                                               }else{
+                                                       preview_stopped = true;
+                                                       $(this).text("play_arrow");
+                                                       $(preview_track).animate({volume: 0}, 250, "swing", ()=>{ preview_track.pause() });
+                                               }
+                                       }else{
+                                               $("*").removeProp("playing");
+                                               $(this).prop("playing","playing");
+                                               $('.preview_controls').text("play_arrow");
+                                               $('.preview_playlist_controls').text("play_arrow");
+                                               $('.preview_controls').css({opacity:0});
+                                               $(this).text("pause");
+                                               $(this).css({opacity: 1});
+                                               preview_stopped = false;
+                                               $(preview_track).animate({volume: 0}, 250, "swing", ()=>{
+                                                       preview_track.pause();
+                                                       $('#preview-track_source').prop("src", $(this).attr("preview"));
+                                                       preview_track.load();
+                                               });
+                                       }
+                               });
                        }
                } else if(data.reqType == 'album') {
                        trackListSelectiveModalApp.title = 'Tracklist';
 
                        trackListSelectiveModalApp.head = [
+                               {title: '<i class="material-icons">music_note</i>'},
                                {title: '#'},
                                {title: 'Song'},
                                {title: 'Artist'},
@@ -589,15 +584,48 @@ socket.on("getTrackList", function (data) {
                                        $(tableBody).append('<tr><td colspan="4" style="opacity: 0.54;"><i class="material-icons valignicon tiny">album</i> '+discNum+'</td></tr>');
                                        baseDisc = discNum;
                                }
-                               $(tableBody).append('<tr><td>' + trackList[i].track_position + '</td>' +
-                                               (trackList[i].explicit_lyrics ? '<td><i class="material-icons valignicon tiny materialize-red-text tooltipped" data-tooltip="Explicit">error_outline</i> ' : '<td> ') + trackList[i].title + '</td>' +
-                                               '<td>' + trackList[i].artist.name + '</td>' +
-                                               '<td>' + convertDuration(trackList[i].duration) + '</td>' +
-                                               '<td><div class="valign-wrapper"><label><input class="trackCheckbox valign" type="checkbox" id="trackChk'+ i +'" value="' + trackList[i].link + '"><span></span></label></div></tr>');
+                               $(tableBody).append(
+                                       '<tr><td><i class="material-icons preview_playlist_controls" preview="'+trackList[i].preview+'">play_arrow</i></td>'+
+                                       '<td>' + trackList[i].track_position + '</td>' +
+                                       (trackList[i].explicit_lyrics ? '<td><i class="material-icons valignicon tiny materialize-red-text tooltipped" data-tooltip="Explicit">error_outline</i> ' : '<td> ') + trackList[i].title + '</td>' +
+                                       '<td>' + trackList[i].artist.name + '</td>' +
+                                       '<td>' + convertDuration(trackList[i].duration) + '</td>' +
+                                       '<td><div class="valign-wrapper"><label><input class="trackCheckbox valign" type="checkbox" id="trackChk'+ i +'" value="' + trackList[i].link + '"><span></span></label></div></tr>'
+                               );
+                               tableBody.children('tr:last').find('.preview_playlist_controls').click(function (e) {
+                                       e.preventDefault();
+                                       if ($(this).prop("playing")){
+                                               if (preview_track.paused){
+                                                       preview_track.play();
+                                                       preview_stopped = false;
+                                                       $(this).text("pause");
+                                                       $(preview_track).animate({volume: 1}, 500);
+                                               }else{
+                                                       preview_stopped = true;
+                                                       $(this).text("play_arrow");
+                                                       $(preview_track).animate({volume: 0}, 250, "swing", ()=>{ preview_track.pause() });
+                                               }
+                                       }else{
+                                               $("*").removeProp("playing");
+                                               $(this).prop("playing","playing");
+                                               $('.preview_controls').text("play_arrow");
+                                               $('.preview_playlist_controls').text("play_arrow");
+                                               $('.preview_controls').css({opacity:0});
+                                               $(this).text("pause");
+                                               $(this).css({opacity: 1});
+                                               preview_stopped = false;
+                                               $(preview_track).animate({volume: 0}, 250, "swing", ()=>{
+                                                       preview_track.pause();
+                                                       $('#preview-track_source').prop("src", $(this).attr("preview"));
+                                                       preview_track.load();
+                                               });
+                                       }
+                               });
                        }
                } else {
                        trackListModalApp.title = 'Tracklist';
                        trackListModalApp.head = [
+                               {title: '<i class="material-icons">music_note</i>'},
                                {title: '#'},
                                {title: 'Song'},
                                {title: 'Artist'},
@@ -643,6 +671,12 @@ socket.on("getChartsCountryList", function (data) {
        $('select').formSelect();
 });
 
+socket.on("setChartsCountry", function (data) {
+       $('#tab_charts_select_country').find('option[value="' + data.selected + '"]').attr("selected", true);
+       $('#modal_settings_select_chartsCounrty').find('option[value="' + data.selected + '"]').attr("selected", true);
+       $('select').formSelect();
+});
+
 $('#tab_charts_select_country').on('change', function () {
 
        var country = $(this).find('option:selected').val();
@@ -670,7 +704,7 @@ socket.on("getChartsTrackListByCountry", function (data) {
                $(chartsTableBody).append(
                                '<tr>' +
                                '<td>' + (i + 1) + '</td>' +
-                               '<td><a href="#" class="circle single-cover" preview="'+currentChartTrack['preview']+'"><i class="material-icons preview_controls white-text">play_arrow</i><img src="' + currentChartTrack['album']['cover_small'] + '" class="circle" /></a></td>' +
+                               '<td><a href="#" class="circle single-cover" preview="'+currentChartTrack['preview']+'"><i class="material-icons preview_controls white-text">play_arrow</i><img style="width:56px" src="' + (currentChartTrack['album']['cover_small'] ? currentChartTrack['album']['cover_small'] : "img/noCover.jpg") + '" class="circle" /></a></td>' +
                                '<td>' + currentChartTrack['title'] + '</td>' +
                                '<td>' + currentChartTrack['artist']['name'] + '</td>' +
                                '<td>' + currentChartTrack['album']['title'] + '</td>' +
@@ -704,6 +738,7 @@ socket.on("getChartsTrackListByCountry", function (data) {
                                $("*").removeProp("playing");
                                $(this).prop("playing","playing");
                                $('.preview_controls').text("play_arrow");
+                               $('.preview_playlist_controls').text("play_arrow");
                                $('.preview_controls').css({opacity:0});
                                $(this).children('i').text("pause");
                                $(this).children('i').css({opacity: 1});
@@ -723,7 +758,7 @@ socket.on("getChartsTrackListByCountry", function (data) {
 
 });
 
-//############################################
+//#############################################TAB_PLAYLISTS############################################\\
 socket.on("getMePlaylistList", function (data) {
        var tableBody = $('#table_personal_playlists');
        $(tableBody).html('');
@@ -741,13 +776,48 @@ socket.on("getMePlaylistList", function (data) {
        $('.tooltipped').tooltip({delay: 100});
 });
 
+//###############################################TAB_URL##############################################\\
+$('#tab_url_form_url').submit(function (ev) {
+
+       ev.preventDefault();
+       var urls = $("#song_url").val().split(";");
+       console.log(urls);
+       for(var i = 0; i < urls.length; i++){
+               var url = urls[i];
+               console.log(url);
+
+               if (url.length == 0) {
+                       message('Blank URL Field', 'You need to insert an URL to download it!');
+                       return false;
+               }
+
+               //Validate URL
+               if (url.indexOf('deezer.com/') < 0 && url.indexOf('open.spotify.com/') < 0 && url.indexOf('spotify:') < 0) {
+                       message('Wrong URL', 'The URL seems to be wrong. Please check it and try it again.');
+                       return false;
+               }
+
+               if (url.indexOf('?') > -1) {
+                       url = url.substring(0, url.indexOf("?"));
+               }
+
+               if (url.indexOf('open.spotify.com/') >= 0 ||  url.indexOf('spotify:') >= 0){
+                       if (url.indexOf('user') < 0 || url.indexOf('playlist') < 0){
+                               message('Playlist not found', 'Spotify for now can only download playlists.');
+                               return false;
+                       }
+               }
+               addToQueue(url);
+       }
+});
+
 //############################################TAB_DOWNLOADS###########################################\\
 function addToQueue(url) {
        var type = getTypeFromLink(url), id = getIDFromLink(url);
 
        if (type == 'spotifyplaylist'){
                [user, id] = getPlayUserFromURI(url)
-               userSettings.spotifyUser = user;
+               userSettings.currentSpotifyUser = user;
        }
 
        if (type == 'track') {
@@ -765,7 +835,7 @@ function addToQueue(url) {
        }
 
        if (alreadyInQueue(id)) {
-               M.toast({html: '<i class="material-icons">playlist_add_check</i>Already in download-queue!', displayLength: 5000, classes: 'rounded'});
+               M.toast({html: '<i class="material-icons left">playlist_add_check</i> Already in download-queue!', displayLength: 5000, classes: 'rounded'});
 
                return false;
        }
@@ -776,7 +846,7 @@ function addToQueue(url) {
        }
        socket.emit("download" + type, {id: id, settings: userSettings});
 
-       M.toast({html: '<i class="material-icons">add</i>Added to download-queue', displayLength: 5000, classes: 'rounded'});
+       M.toast({html: '<i class="material-icons left">add</i>Added to download-queue', displayLength: 5000, classes: 'rounded'});
 
 }
 
@@ -846,33 +916,35 @@ socket.on('updateQueue', function (data) {
        if (data.failed == 0 && ((data.downloaded + data.failed) >= data.size)) {
                $('#' + data.queueId).find('.eventBtn').html('<i class="material-icons">done</i>');
                $('#' + data.queueId).addClass('finished');
-               M.toast({html: '<i class="material-icons">done</i>One download completed!', displayLength: 5000, classes: 'rounded'})
+               M.toast({html: `<i class="material-icons left">done</i>${quoteattr(data.name)} - Completed!`, displayLength: 5000, classes: 'rounded'})
        } else if (data.downloaded == 0 && ((data.downloaded + data.failed) >= data.size)) {
                $('#' + data.queueId).find('.eventBtn').html('<i class="material-icons">error</i>');
                $('#' + data.queueId).addClass('error');
-               M.toast({html: '<i class="material-icons">error</i>One download failed!', displayLength: 5000, classes: 'rounded'})
+               M.toast({html: `<i class="material-icons left">error</i>${quoteattr(data.name)} - Failed!`, displayLength: 5000, classes: 'rounded'})
+       } else if ((data.downloaded + data.failed) >= data.size) {
+               $('#' + data.queueId).find('.eventBtn').html('<i class="material-icons">warning</i>');
+               $('#' + data.queueId).addClass('error');
+               M.toast({html: `<i class="material-icons left">warning</i>${quoteattr(data.name)} - Completed with errors!`, displayLength: 5000, classes: 'rounded'})
        }
-
 });
 
 socket.on("downloadProgress", function (data) {
        //data.queueId -> id (string)
        //data.percentage -> float/double, percentage
        //updated in 1% steps
-
        $('#' + data.queueId).find('.determinate').css('width', data.percentage + '%');
 
 });
 
 socket.on("emptyDownloadQueue", function () {
-       M.toast({html: '<i class="material-icons">done_all</i>All downloads completed!', displayLength: 5000, classes: 'rounded'});
+       M.toast({html: '<i class="material-icons left">done_all</i>All downloads completed!', displayLength: 5000, classes: 'rounded'});
 });
 
 socket.on("cancelDownload", function (data) {
        //data.queueId          -> queueId of item which was canceled
        $('#' + data.queueId).addClass('animated fadeOutRight').on('webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend', function () {
                $(this).remove();
-               M.toast({html: '<i class="material-icons">clear</i>One download removed!', displayLength: 5000, classes: 'rounded'})
+               M.toast({html: '<i class="material-icons left">clear</i>One download removed!', displayLength: 5000, classes: 'rounded'})
        });
 });
 
@@ -886,7 +958,29 @@ $('#clearTracksTable').click(function (ev) {
 //****************************************************************************************************\\
 //******************************************HELPER-FUNCTIONS******************************************\\
 //****************************************************************************************************\\
-
+/**
+ * Replaces special characters with HTML friendly counterparts
+ * @param s string
+ * @param preserveCR preserves the new line character
+ * @returns {string}
+ */
+function quoteattr(s, preserveCR) {
+  preserveCR = preserveCR ? '&#13;' : '\n';
+  return ('' + s) /* Forces the conversion to string. */
+       .replace(/&/g, '&amp;') /* This MUST be the 1st replacement. */
+    .replace(/'/g, '&apos;') /* The 4 other predefined entities, required. */
+    .replace(/"/g, '&quot;')
+    .replace(/</g, '&lt;')
+    .replace(/>/g, '&gt;')
+    /*
+    You may add other replacements here for HTML only
+    (but it's not necessary).
+    Or for XML, only if the named entities are defined in its DTD.
+    */
+    .replace(/\r\n/g, preserveCR) /* Must be before the next replacement. */
+    .replace(/[\r\n]/g, preserveCR);
+    ;
+}
 /**
  * Given a spotify playlist URL or URI it returns the username of the owner of the playlist and the ID of the playlist
  * @param url URL or URI
@@ -955,3 +1049,12 @@ function convertDuration(duration) {
        }
        return mm + ":" + ss;
 }
+
+function sleep(milliseconds) {
+  var start = new Date().getTime();
+  for (var i = 0; i < 1e7; i++) {
+    if ((new Date().getTime() - start) > milliseconds){
+      break;
+    }
+  }
+}
index a4035b743e02e5ccc9792c98884bccc6abaf4e3c..0887505c4265c7900ab52f57adf167b485e5cca2 100644 (file)
Binary files a/build/icon.icns and b/build/icon.icns differ
index a03bc03e810ae24d4ab171c2f580e9e38c45152c..3def02a33e019f850bde835d3df28c96e2e7374a 100644 (file)
Binary files a/build/icon.ico and b/build/icon.ico differ
index b0aba20337e4acd0691dd91504bc409d44b3bbd2..3ab3cdc238650c0ccb9b57185b26993e3a8c9133 100644 (file)
@@ -3,13 +3,14 @@
        "description": "Download music from Deezer",
        "private": true,
        "devDependencies": {
+               "cross-env": "^5.1.6",
                "electron": "latest",
                "electron-builder": "latest"
        },
        "scripts": {
                "dist": "electron-builder",
                "pack": "electron-builder --dir",
-               "start": "npm install && electron ./app",
+               "start": "cross-env NODE_ENV=development electron ./app",
                "postinstall": "electron-builder install-app-deps"
        },
        "build": {
@@ -44,8 +45,5 @@
                        "artifactName": "${productName} ${version} - Portable.${ext}",
                        "requestExecutionLevel": "user"
                }
-       },
-       "dependencies": {
-               "ajv": "^6.5.1"
        }
 }