Implemented create folder for playlists
[DeezloaderRemix.git] / app / app.js
1 /*
2 *   _____                _                 _             _____                _
3 *  |  __ \              | |               | |           |  __ \              (_)
4 *  | |  | | ___  ___ ___| | ___   __ _  __| | ___ _ __  | |__) |___ _ __ ___  ___  __
5 *  | |  | |/ _ \/ _ \_  / |/ _ \ / _` |/ _` |/ _ \ '__| |  _  // _ \ '_ ` _ \| \ \/ /
6 *  | |__| |  __/  __// /| | (_) | (_| | (_| |  __/ |    | | \ \  __/ | | | | | |>  <
7 *  |_____/ \___|\___/___|_|\___/ \__,_|\__,_|\___|_|    |_|  \_\___|_| |_| |_|_/_/\_\
8 *
9 **/
10
11 // Server stuff
12 const express = require('express')
13 const app = express()
14 const server = require('http').createServer(app)
15 const io = require('socket.io').listen(server, {log: false, wsEngine: 'ws'})
16 // Music tagging stuff
17 const mflac = require('./lib/flac-metadata')
18 const ID3Writer = require('./lib/browser-id3-writer')
19 const deezerApi = require('deezer-api')
20 const spotifyApi = require('spotify-web-api-node')
21 // App stuff
22 const fs = require('fs-extra')
23 const async = require('async')
24 const request = require('request-promise')
25 const requestOld = require('request')
26 const os = require('os')
27 const path = require('path')
28 const logger = require('./utils/logger.js')
29 const queue = require('queue')
30 const localpaths = require('./utils/localpaths.js')
31 const package = require('./package.json')
32 const stq = require('sequential-task-queue')
33
34 // Main Constants
35 // Files
36 const configFileLocation = localpaths.user+"config.json"
37 // Folders
38 var coverArtFolder = 'deezloader-imgs' + path.sep
39 var defaultDownloadFolder = path.sep + 'Deezloader Music' + path.sep
40
41 if(process.platform == "android"){
42         coverArtFolder = localpaths.user + coverArtFolder
43         fs.ensureFileSync(coverArtFolder + '.nomedia');
44         defaultDownloadFolder = localpaths.home + path.sep + 'Music' + defaultDownloadFolder
45 }else{
46         coverArtFolder = os.tmpdir() + path.sep + coverArtFolder
47         defaultDownloadFolder = localpaths.home + defaultDownloadFolder
48 }
49
50 // Default settings
51 const defaultSettings = require('./default.json').userDefined
52 // Spotify Files
53 const spotifySupport = fs.existsSync(localpaths.user+"authCredentials.js")
54 if (spotifySupport){
55         var authCredentials = require(localpaths.user+'authCredentials.js')
56         var Spotify = new spotifyApi(authCredentials)
57 }
58
59 // Setup the folders START
60 var mainFolder = defaultDownloadFolder
61
62 // First run, create config file
63 if(!fs.existsSync(configFileLocation)){
64         logger.info("Can't find config.json, creating one now!")
65         fs.outputFileSync(configFileLocation, fs.readFileSync(__dirname+path.sep+"default.json",'utf8'))
66 }
67
68 // See if all settings are there after update
69 var configFile = require(configFileLocation);
70 for (let x in defaultSettings){
71         if (typeof configFile.userDefined[x] != typeof defaultSettings[x]){
72                 configFile.userDefined[x] = defaultSettings[x]
73         }
74 }
75 // Set default download folder if not userDefined
76 if (configFile.userDefined.downloadLocation != "") {
77         mainFolder = configFile.userDefined.downloadLocation
78 }
79
80 initFolders();
81
82 // Route and create server
83 app.use('/', express.static(__dirname + '/public/'))
84 server.listen(configFile.serverPort)
85 logger.info('Server is running @ localhost:' + configFile.serverPort)
86
87 var dqueue = new stq.SequentialTaskQueue()
88 var downloadQueue = {}
89 var trackQueue = queue({
90         autostart: true
91 })
92 trackQueue.concurrency = configFile.userDefined.queueConcurrency
93
94 // START sockets clusterfuck
95 io.sockets.on('connection', function (s) {
96         logger.info("Connection received!")
97
98         // Check for updates
99         request({
100                 url: "https://notabug.org/RemixDevs/DeezloaderRemix/raw/master/update.json",
101                 rejectUnauthorized: false,
102                 json: true
103         })
104         .then(body=>{
105                 logger.info("Checking for updates")
106                 let [currentVersion_MAJOR, currentVersion_MINOR, currentVersion_PATCH] = package.version.split(".").map(x=>parseInt(x))
107                 let [lastVersion_MAJOR, lastVersion_MINOR, lastVersion_PATCH] = body.version.split(".").map(x=>parseInt(x))
108                 if (
109                         lastVersion_MAJOR>currentVersion_MAJOR ||
110                         lastVersion_MAJOR==currentVersion_MAJOR && lastVersion_MINOR>currentVersion_MINOR ||
111                         lastVersion_MAJOR==currentVersion_MAJOR && lastVersion_MINOR==currentVersion_MINOR && lastVersion_PATCH>currentVersion_PATCH
112                 ){
113                         logger.info("Update Available")
114                         s.emit("messageUpdate", {title: `Version ${lastVersion_MAJOR}.${lastVersion_MINOR}.${lastVersion_PATCH} is available!`, msg: body.changelog, lastVersion: body.version})
115                 }else{
116                         logger.info("Running the latest version!")
117                 }
118         })
119         .catch(error=>{
120                 logger.error(`UpdateCheck failed: ${error.stack ? error.stack : error}`)
121         })
122
123         // Connection dependet variables
124         s.Deezer = new deezerApi()
125         s.spotifyUser = null
126
127         s.emit("checkAutologin")
128         s.emit("getDefaultSettings", defaultSettings, defaultDownloadFolder)
129         s.emit("populateDownloadQueue", downloadQueue)
130
131         if(process.platform != "android"){
132                 const captcha = require('./utils/captcha');
133                 captcha.callbackResponse = function (data) {
134                         s.emit("getCaptcha", data)
135                 };
136         }
137
138         // Function for logging in
139         s.on("login", async function (username, password, captchaResponse) {
140                 try{
141                         logger.info("Logging in");
142                         await s.Deezer.login(username, password, captchaResponse)
143                         s.emit("login", {user: s.Deezer.user})
144                         logger.info("Logged in successfully")
145                         // Save session login so next time login is not needed
146                         // This is the same method used by the official website
147                         s.emit('getCookies', s.Deezer.getCookies())
148                 }catch(err){
149                         s.emit("login", {error: err.message})
150                         logger.error(`Login failed: ${err.message}`)
151                 }
152         })
153
154         // Function for login with userToken
155         s.on("loginViaUserToken", async function (userToken) {
156                 try{
157                         logger.info("Logging in");
158                         await s.Deezer.loginViaArl(userToken)
159                         s.emit("login", {user: s.Deezer.user})
160                         logger.info("Logged in successfully")
161                         s.emit('getCookies', s.Deezer.getCookies())
162                 }catch(err){
163                         s.emit("login", {error: err.message})
164                         logger.error(`Login failed: ${err.message}`)
165                 }
166         });
167
168         // Function for autologin
169         s.on("autologin", async function(jar, email){
170                 try{
171                         await s.Deezer.loginViaCookies(jar, email)
172                         s.emit('login', {user: s.Deezer.user})
173                 }catch(err){
174                         s.emit('login', {error: err.message})
175                         logger.error(`Autologin failed: ${err.message}`)
176                         return
177                 }
178         })
179
180         // Function for logout
181         s.on("logout", function(){
182                 logger.info("Logged out")
183                 // Creating new object to clear the cookies
184                 s.Deezer = new deezerApi()
185                 return
186         })
187
188         // Returns list of charts available
189         s.on("getChartsCountryList", async function (data) {
190                 try{
191                         let charts = await s.Deezer.legacyGetChartsTopCountry()
192                         charts = charts.data || []
193                         let countries = []
194                         for (let i = 0; i < charts.length; i++) {
195                                 let obj = {
196                                         country: charts[i].title.replace("Top ", ""),
197                                         picture_small: charts[i].picture_small,
198                                         picture_medium: charts[i].picture_medium,
199                                         picture_big: charts[i].picture_big,
200                                         playlistId: charts[i].id
201                                 }
202                                 countries.push(obj)
203                         }
204                         s.emit("getChartsCountryList", {countries: countries, selected: data.selected})
205                 }catch(err){
206                         logger.error(`getChartsCountryList failed: ${err.stack}`)
207                         return
208                 }
209         })
210
211         // Returns chart tracks from Playlist ID
212         async function getChartsTrackListById(playlistId){
213                 if (typeof playlistId === 'undefined') {
214                         s.emit("getChartsTrackListByCountry", {err: "Can't find that playlist"})
215                         return
216                 }
217                 try{
218                         let tracks = await s.Deezer.legacyGetPlaylistTracks(playlistId)
219                         s.emit("getChartsTrackListByCountry", {
220                                 playlistId: playlistId,
221                                 tracks: tracks.data
222                         })
223                 }catch(err){
224                         s.emit("getChartsTrackListByCountry", {err: err})
225                         logger.error(`getChartsTrackListById failed: ${err.stack}`)
226                         return
227                 }
228         }
229
230         // Returns chart tracks from country name
231         async function getChartsTrackListByCountry(country){
232                 if (typeof country === 'undefined') {
233                         s.emit("getChartsTrackListByCountry", {err: "No country passed"})
234                         return
235                 }
236                 try{
237                         let charts = await s.Deezer.legacyGetChartsTopCountry()
238                         charts = charts.data || []
239                         let countries = []
240                         for (let i = 0; i < charts.length; i++) {
241                                 countries.push(charts[i].title.replace("Top ", ""))
242                         }
243                         if (countries.indexOf(country) == -1) {
244                                 s.emit("getChartsTrackListByCountry", {err: "Country not found"})
245                                 return
246                         }
247                         let playlistId = charts[countries.indexOf(country)].id
248                         await getChartsTrackListById(playlistId)
249                 }catch(err){
250                         logger.error(`getChartsTrackListByCountry failed: ${err.stack}`)
251                         return
252                 }
253         }
254         s.on("getChartsTrackListByCountry", function (data) {getChartsTrackListByCountry(data.country)})
255
256         // Returns list of playlists
257         async function getMyPlaylistList(spotUser=null){
258                 if (spotUser && s.spotifyUser != spotUser) s.spotifyUser = spotUser
259                 try{
260                         logger.info("Loading Personal Playlists")
261                         let data = await s.Deezer.legacyGetUserPlaylists(s.Deezer.user.id)
262                         data = data.data || []
263                         let playlists = []
264                         for (let i = 0; i < data.length; i++) {
265                                 let obj = {
266                                         title: data[i].title,
267                                         image: data[i].picture_small,
268                                         songs: data[i].nb_tracks,
269                                         link: data[i].link
270                                 }
271                                 playlists.push(obj)
272                         }
273                         if (s.spotifyUser && spotifySupport){
274                                 try{
275                                         let creds = await Spotify.clientCredentialsGrant()
276                                         Spotify.setAccessToken(creds.body['access_token'])
277                                         let first = true
278                                         let offset = 0
279                                         do{
280                                                 let data = await Spotify.getUserPlaylists(s.spotifyUser, {fields: "items(images,name,owner.id,tracks.total,uri),total", offset: offset*20})
281                                                 if (first){
282                                                         var total = data.body.total
283                                                         var numPages=Math.floor((total-1)/20)
284                                                         var playlistList = new Array(total)
285                                                         first = false
286                                                 }
287                                                 data.body.items.forEach((playlist, i) => {
288                                                         playlistList[(offset*20)+i] = {
289                                                                 title: playlist.name,
290                                                                 image: (playlist.images[0] ? playlist.images[0].url : ""),
291                                                                 songs: playlist.tracks.total,
292                                                                 link: playlist.uri,
293                                                                 spotify: true
294                                                         }
295                                                 })
296                                                 offset++
297                                         }while(offset<=numPages)
298                                         playlists = playlists.concat(playlistList)
299                                 }catch(err){
300                                         logger.error(`Spotify playlist failed loading: ${err}`)
301                                 }
302                         }
303                         logger.info(`Loaded ${playlists.length} Playlist${playlists.length>1 ? "s" : ""}`)
304                         s.emit("getMyPlaylistList", {playlists: playlists})
305                 }catch(err){
306                         logger.error(`getMyPlaylistList failed: ${err}`)
307                         return
308                 }
309         }
310         s.on("getMyPlaylistList", function (d) {getMyPlaylistList(d.spotifyUser)})
311
312         // Returns search results from a query
313         s.on("search", async function (data) {
314                 data.type = data.type || "track"
315                 if (["track", "playlist", "album", "artist"].indexOf(data.type) == -1) data.type = "track"
316
317                 // Remove "feat." "ft." and "&" (causes only problems)
318                 data.text = data.text
319                         .replace(/ feat[\.]? /g, " ")
320                         .replace(/ ft[\.]? /g, " ")
321                         .replace(/\(feat[\.]? /g, " ")
322                         .replace(/\(ft[\.]? /g, " ")
323                         .replace(/\&/g, "")
324                         .replace(/–/g, "-")
325                         .replace(/—/g, "-")
326
327                 try {
328                         let searchObject = await s.Deezer.legacySearch(encodeURIComponent(data.text), data.type)
329                         s.emit("search", {type: data.type, items: searchObject.data})
330                 } catch (err) {
331                         s.emit("search", {type: data.type, items: []})
332                         logger.error(`search failed: ${err.stack}`)
333                         return
334                 }
335         })
336
337         // Returns list of tracks from an album/playlist or the list of albums from an artist
338         s.on("getTrackList", async function (data) {
339                 if (!data.type || (["playlist", "album", "artist", "spotifyplaylist"].indexOf(data.type) == -1) || !data.id) {
340                         s.emit("getTrackList", {err: -1, response: {}, id: data.id, reqType: data.type})
341                         return
342                 }
343                 if (data.type == 'artist') {
344                         try{
345                                 let response = await s.Deezer.legacyGetArtist(data.id)
346                                 let tracks = await s.Deezer.legacyGetArtistAlbums(data.id)
347                                 response.data = tracks.data
348                                 s.emit("getTrackList", {response: response, id: data.id, reqType: data.type})
349                         }catch(err){
350                                 s.emit("getTrackList", {err: "wrong artist id", response: {}, id: data.id, reqType: data.type})
351                                 logger.error(`getTrackList failed: ${err.stack}`)
352                                 return
353                         }
354                 }else if(data.type == "spotifyplaylist" && spotifySupport){
355                         try{
356                                 let creds = await Spotify.clientCredentialsGrant()
357                                 Spotify.setAccessToken(creds.body.access_token)
358                                 let first = true
359                                 let offset = 0
360                                 var response = {}
361                                 let resp0 = await Spotify.getPlaylist(data.id, {fields: "images,name,owner"})
362                                 response.title = resp0.body.name
363                                 response.image = resp0.body.images[0].url
364                                 response.owner = resp0.body.owner.display_name
365                                 do{
366                                         let resp = await Spotify.getPlaylistTracks(data.id, {fields: "items(track(artists,name,duration_ms,preview_url,explicit)),total", offset: offset*100})
367                                         if (first){
368                                                 var numPages=Math.floor((resp.body.total-1)/100)
369                                                 response.data = new Array(resp.body.total)
370                                                 first = false
371                                         }
372                                         resp.body.items.forEach((t, index) => {
373                                                 response.data[index+offset*100]={
374                                                         explicit_lyrics: t.track.explicit,
375                                                         preview: t.track.preview_url,
376                                                         title: t.track.name,
377                                                         artist: {
378                                                                 name: t.track.artists[0].name
379                                                         },
380                                                         duration: Math.floor(t.track.duration_ms/1000)
381                                                 }
382                                         })
383                                         offset++
384                                 }while(offset<=numPages)
385                                 s.emit("getTrackList", {response: response, id: data.id, reqType: data.type})
386                         }catch(err){
387                                 logger.error(`getTrackList failed: ${err.stack}`)
388                         }
389                 }else{
390                         let reqType = data.type.charAt(0).toUpperCase() + data.type.slice(1)
391                         try{
392                                 let response = await s.Deezer["legacyGet" + reqType](data.id)
393                                 let tracks = await s.Deezer["legacyGet" + reqType + "Tracks"](data.id)
394                                 response.data = tracks.data
395                                 s.emit("getTrackList", {response: response, id: data.id, reqType: data.type})
396                         }catch(err){
397                                 s.emit("getTrackList", {err: "wrong id "+reqType, response: {}, id: data.id, reqType: data.type})
398                                 logger.error(`getTrackList failed: ${err.stack}`)
399                                 return
400                         }
401                 }
402         })
403
404         // Sends settings saved by the user to the frontend
405         s.on("getUserSettings", function () {
406                 let settings = configFile.userDefined
407                 if (!settings.downloadLocation) {
408                         settings.downloadLocation = mainFolder
409                 }
410                 s.emit('getUserSettings', {settings: settings})
411         });
412
413         // Saves locally the settings comming from the frontend
414         s.on("saveSettings", function (settings, spotifyUser) {
415                 if (settings.userDefined.downloadLocation == defaultDownloadFolder) {
416                         settings.userDefined.downloadLocation = ""
417                 } else {
418                         settings.userDefined.downloadLocation = path.resolve(settings.userDefined.downloadLocation + path.sep) + path.sep
419                         mainFolder = settings.userDefined.downloadLocation
420                 }
421
422                 if (settings.userDefined.queueConcurrency < 1) settings.userDefined.queueConcurrency = 1
423
424                 if (settings.userDefined.queueConcurrency != trackQueue.concurrency){
425                         trackQueue.concurrency = settings.userDefined.queueConcurrency
426                 }
427
428                 if (spotifyUser != s.spotifyUser){
429                         s.spotifyUser = spotifyUser
430                         getMyPlaylistList(spotifyUser)
431                 }
432
433                 configFile.userDefined = settings.userDefined;
434                 fs.outputFile(configFileLocation, JSON.stringify(configFile, null, 2), function (err) {
435                         if (err) return
436                         logger.info("Settings updated")
437                         initFolders()
438                 });
439         });
440
441         s.on("analyzetrack", async (id)=>{
442                 s.emit("analyzetrack", await s.Deezer.legacyGetTrack(id))
443         })
444
445         s.on("analyzealbum", async (id)=>{
446                 s.emit("analyzealbum", await s.Deezer.legacyGetAlbum(id))
447         })
448
449         /*
450          * Downloading section of the app
451         */
452
453         // Gets data from the frontend and creates the track object
454         async function downloadTrack(data){
455                 logger.info(`Added to Queue ${data.id}`)
456                 try{
457                         var track = await s.Deezer.getTrack(data.id)
458                         data.settings.filename = data.settings.trackNameTemplate
459                         data.settings.foldername = data.settings.albumNameTemplate
460                         let _track = {
461                                 name: track.title,
462                                 artist: track.artist.name,
463                                 size: 1,
464                                 downloaded: 0,
465                                 failed: 0,
466                                 queueId: `id${Math.random().toString(36).substring(2)}`,
467                                 id: `${track.id}:${data.bitrate}`,
468                                 bitrate: data.bitrate+"",
469                                 type: 'track',
470                                 settings: data.settings || {},
471                                 obj: track,
472                         }
473                         addToQueue(_track)
474                 }catch(err){
475                         logger.error(`downloadTrack failed: ${err.stack ? err.stack : err}`)
476                         message = ""
477                         if (err.message){
478                                 switch (err.message){
479                                         case "DataException: no data":
480                                                 message = "Not Found"
481                                         break
482                                         default:
483                                                 message = err.message
484                                         break
485                                 }
486                         }
487                         s.emit("toast", `Track ${data.id} download failed: ${message}`)
488                         s.emit("silentlyCancelDownload", `${data.id}:${data.bitrate}`)
489                         return
490                 }
491         }
492         s.on("downloadtrack", async data=>{await downloadTrack(data)})
493
494         // Gets data from the frontend and creates the album object
495         async function downloadAlbum(data){
496                 logger.info(`Added to Queue ${data.id}`)
497                 try{
498                         var album = await s.Deezer.legacyGetAlbum(data.id)
499                         if (data.settings.tags.discTotal || data.settings.createCDFolder){
500                                 var discTotal = await s.Deezer.getAlbum(data.id)
501                                 album.discTotal = discTotal.discTotal
502                         }
503                         if (album.nb_tracks == 1 && data.settings.downloadSinglesAsTracks){
504                                 var track = await s.Deezer.getTrack(album.tracks.data[0].id)
505                                 data.settings.filename = data.settings.trackNameTemplate
506                                 data.settings.foldername = data.settings.albumNameTemplate
507                                 let _track = {
508                                         name: track.title,
509                                         artist: track.artist.name,
510                                         size: 1,
511                                         downloaded: 0,
512                                         failed: 0,
513                                         queueId: `id${Math.random().toString(36).substring(2)}`,
514                                         id: `${track.id}:${data.bitrate}`,
515                                         urlId: data.id,
516                                         bitrate: data.bitrate+"",
517                                         type: 'track',
518                                         settings: data.settings || {},
519                                         obj: track,
520                                 }
521                                 addToQueue(_track)
522                         }else{
523                                 album.tracks = await s.Deezer.getAlbumTracks(data.id)
524                                 data.settings.filename = data.settings.albumTrackNameTemplate
525                                 data.settings.foldername = data.settings.albumNameTemplate
526                                 let _album = {
527                                         name: album.title,
528                                         artist: album.artist.name,
529                                         size: album.tracks.length,
530                                         downloaded: 0,
531                                         failed: 0,
532                                         queueId: `id${Math.random().toString(36).substring(2)}`,
533                                         id: `${album.id}:${data.bitrate}`,
534                                         bitrate: data.bitrate+"",
535                                         type: 'album',
536                                         settings: data.settings || {},
537                                         obj: album,
538                                 }
539                                 addToQueue(_album)
540                         }
541                         return
542                 }catch(err){
543                         logger.error(`downloadAlbum failed: ${err.stack ? err.stack : err}`)
544                         message = ""
545                         if (err.message){
546                                 switch (err.message){
547                                         case "DataException: no data":
548                                                 message = "Not Found"
549                                         break
550                                         default:
551                                                 message = err.message
552                                         break
553                                 }
554                         }
555                         s.emit("toast", `Album ${data.id} download failed: ${message}`)
556                         s.emit("silentlyCancelDownload", `${data.id}:${data.bitrate}`)
557                         return
558                 }
559         }
560         s.on("downloadalbum", async data=>{await downloadAlbum(data)});
561
562         // Gets data from the frontend and creates for each album an album object
563         async function downloadArtist(data){
564                 logger.info(`Added to Queue ${data.id}`)
565                 try{
566                         var albums = await s.Deezer.legacyGetArtistAlbums(data.id);
567                         (function sendAllAlbums(i) {
568                                 setTimeout(function () {
569                                         data.id = albums.data[albums.data.length-1-i].id
570                                         downloadAlbum(JSON.parse(JSON.stringify(data)))
571                                         if (--i+1) sendAllAlbums(i)
572                                 }, 100)
573                         })(albums.data.length-1)
574                 }catch(err){
575                         logger.error(`downloadArtist failed: ${err.stack ? err.stack : err}`)
576                         message = ""
577                         if (err.message){
578                                 switch (err.message){
579                                         case "DataException: no data":
580                                                 message = "Not Found"
581                                         break
582                                         default:
583                                                 message = err.message
584                                         break
585                                 }
586                         }
587                         s.emit("toast", `Artist ${data.id} download failed: ${message}`)
588                         s.emit("silentlyCancelDownload", `${data.id}:${data.bitrate}`)
589                         return
590                 }
591         }
592         s.on("downloadartist", async data=>{ await downloadArtist(data)});
593
594         // Gets data from the frontend and creates the playlist object
595         async function downloadPlaylist(data){
596                 logger.info(`Added to Queue ${data.id}`)
597                 try{
598                         var playlist = await s.Deezer.legacyGetPlaylist(data.id)
599                         data.settings.filename = data.settings.playlistTrackNameTemplate
600                         data.settings.foldername = data.settings.albumNameTemplate
601                         playlist.tracks = await s.Deezer.getPlaylistTracks(data.id)
602                         let _playlist = {
603                                 name: playlist.title,
604                                 artist: playlist.creator.name,
605                                 size: playlist.tracks.length,
606                                 downloaded: 0,
607                                 failed: 0,
608                                 queueId: `id${Math.random().toString(36).substring(2)}`,
609                                 id: `${playlist.id}:${data.bitrate}`,
610                                 bitrate: data.bitrate+"",
611                                 type: "playlist",
612                                 settings: data.settings || {},
613                                 obj: playlist,
614                         }
615                         addToQueue(_playlist)
616                 }catch(err){
617                         logger.error(`downloadPlaylist failed: ${err.stack ? err.stack : err}`)
618                         message = ""
619                         if (err.message){
620                                 switch (err.message){
621                                         case "DataException: no data":
622                                                 message = "Not Found"
623                                         break
624                                         default:
625                                                 message = err.message
626                                         break
627                                 }
628                         }
629                         s.emit("toast", `Playlist ${data.id} download failed: ${message}`)
630                         s.emit("silentlyCancelDownload", `${data.id}:${data.bitrate}`)
631                         return
632                 }
633         }
634         s.on("downloadplaylist", data=>{downloadPlaylist(data)});
635
636         // Gets data from the frontend and creates the object fot the artist top tracks
637         async function downloadArtistTop(data){
638                 logger.info(`Added to Queue ${data.id}`)
639                 try{
640                         var artist = await s.Deezer.legacyGetArtist(data.id)
641                         data.settings.filename = data.settings.playlistTrackNameTemplate
642                         data.settings.foldername = data.settings.albumNameTemplate
643                         artist.tracks = await s.Deezer.getArtistTopTracks(data.id)
644                         let _playlist = {
645                                 name: artist.name + " Most played tracks",
646                                 artist: artist.name,
647                                 size: artist.tracks.length,
648                                 downloaded: 0,
649                                 failed: 0,
650                                 queueId: `id${Math.random().toString(36).substring(2)}`,
651                                 id: `${artist.id}:${data.bitrate}`,
652                                 bitrate: data.bitrate+"",
653                                 type: "playlist",
654                                 settings: data.settings || {},
655                                 obj: artist,
656                         }
657                         addToQueue(_playlist)
658                 }catch(err){
659                         logger.error(`downloadArtistTop failed: ${err.stack ? err.stack : err}`)
660                         message = ""
661                         if (err.message){
662                                 switch (err.message){
663                                         case "DataException: no data":
664                                                 message = "Not Found"
665                                         break
666                                         default:
667                                                 message = err.message
668                                         break
669                                 }
670                         }
671                         s.emit("toast", `ArtistTop ${data.id} download failed: ${message}`)
672                         s.emit("silentlyCancelDownload", `${data.id}:${data.bitrate}`)
673                         return
674                 }
675         }
676         s.on("downloadartisttop", data=>{downloadArtistTop(data)});
677
678         // Gets data from the frontend and creates the spotify playlist object
679         async function downloadSpotifyPlaylist(data){
680                 logger.info(`Added to Queue ${data.id}`)
681                 if (spotifySupport){
682                         try{
683                                 let creds = await Spotify.clientCredentialsGrant()
684                                 Spotify.setAccessToken(creds.body['access_token'])
685                                 var offset = 0
686                                 data.settings.filename = data.settings.playlistTrackNameTemplate
687                                 data.settings.foldername = data.settings.albumNameTemplate
688                                 var resp = await Spotify.getPlaylist(data.id, {fields: "id,name,owner,images,tracks(total)"})
689                                 var _playlist = {
690                                         name: resp.body.name,
691                                         artist: (resp.body.owner.display_name ? resp.body.owner.display_name : resp.body.owner.id),
692                                         size: resp.body.tracks.total,
693                                         downloaded: 0,
694                                         failed: 0,
695                                         queueId: `id${Math.random().toString(36).substring(2)}`,
696                                         settings: data.settings || {},
697                                         id: `${resp.body.id}:${data.bitrate}`,
698                                         bitrate: data.bitrate+"",
699                                         type: "spotifyplaylist",
700                                         obj: resp.body
701                                 }
702                                 var numPages=Math.floor((_playlist.size-1)/100)
703                                 var trackList = new Array(_playlist.size)
704                                 do{
705                                         var resp = await Spotify.getPlaylistTracks(data.id, {fields: "items(track(artists,name,album,external_ids))", offset: offset*100})
706                                         resp.body.items.forEach((track, i) => {
707                                                 trackList[i+(offset*100)] = track.track
708                                         })
709                                         offset++
710                                 }while(offset<=numPages)
711                                 _playlist.obj.tracks = trackList
712                                 addToQueue(_playlist)
713                         }catch(err){
714                                 logger.error(`downloadSpotifyPlaylist failed: ${err.stack ? err.stack : err}`)
715                                 if (err.message && err.message == "Bad Request"){
716                                         s.emit("message", {title: "You setted it up wrong!", msg: "It seems like you setted the authCredentials.js file wrong...<br>Make sure you keep the ' around the IDs and that the Secret and Client ID are copied correctly<br>After that you should restart the app to make it work.<br><br>If you need the guide again <a href=\"https://notabug.org/RemixDevs/DeezloaderRemix/wiki/Spotify+Features\">Here it is</a>"})
717                                 }else{
718                                         s.emit("toast", `SpotifyPlaylist ${data.id} failed: ${err.message ? err.message : err}`)
719                                 }
720                                 s.emit("silentlyCancelDownload", `${data.id}:${data.bitrate}`)
721                                 return
722                         }
723                 }else{
724                         s.emit("message", {title: "Spotify Support is not enabled", msg: "You should add authCredentials.js in your config files and then restart the app to use this feature<br>You can see how to do that in <a href=\"https://notabug.org/RemixDevs/DeezloaderRemix/wiki/Spotify+Features\">this guide</a>"})
725                 }
726         }
727         s.on("downloadspotifyplaylist", data=>{downloadSpotifyPlaylist(data)})
728
729         // Gets data from the frontend and creates data for the deezer track object
730         async function downloadSpotifyTrack(data){
731                 logger.info(`Added to Queue ${data.id}`)
732                 if (spotifySupport){
733                         try{
734                                 let creds = await Spotify.clientCredentialsGrant()
735                                 Spotify.setAccessToken(creds.body['access_token'])
736                                 var resp = await Spotify.getTrack(data.id, {fields: "external_ids,artists,album,name"})
737                                 deezerId = await convertSpotify2Deezer(resp.body)
738                                 if (deezerId != 0){
739                                         data.id = deezerId
740                                         downloadTrack(data)
741                                 }else{
742                                         s.emit("toast", "Can't find the track on Deezer!")
743                                         s.emit("silentlyCancelDownload", `${data.id}:${data.bitrate}`)
744                                         logger.error(`Can't find the track on Deezer!`)
745                                 }
746                         }catch(err){
747                                 logger.error(`downloadSpotifyTrack failed: ${err.stack ? err.stack : err}`)
748                                 return
749                         }
750                 }else{
751                         s.emit("message", {title: "Spotify Support is not enabled", msg: "You should add authCredentials.js in your config files and then restart the app to use this feature<br>You can see how to do that in <a href=\"https://notabug.org/RemixDevs/DeezloaderRemix/wiki/Spotify+Features\">this guide</a>"})
752                 }
753         }
754         s.on("downloadspotifytrack", data=>{downloadSpotifyTrack(data)})
755
756         // Gets data from the frontend and creates data for the deezer track object
757         async function downloadSpotifyAlbum(data){
758                 logger.info(`Added to Queue ${data.id}`)
759                 if (spotifySupport){
760                         try{
761                                 let creds = await Spotify.clientCredentialsGrant()
762                                 Spotify.setAccessToken(creds.body['access_token'])
763                                 var resp = await Spotify.getAlbum(data.id, {fields: "external_ids,artists,name"})
764                                 deezerId = await convertSpotifyAlbum2Deezer(resp.body)
765                                 if (deezerId != 0){
766                                         data.id = deezerId
767                                         downloadAlbum(data)
768                                 }else{
769                                         s.emit("toast", "Can't find the album on Deezer!")
770                                         s.emit("silentlyCancelDownload", `${data.id}:${data.bitrate}`)
771                                         logger.error(`Can't find the album on Deezer!`)
772                                 }
773                         }catch(err){
774                                 logger.error(`downloadSpotifyAlbum failed: ${err.stack ? err.stack : err}`)
775                                 return
776                         }
777                 }else{
778                         s.emit("message", {title: "Spotify Support is not enabled", msg: "You should add authCredentials.js in your config files and then restart the app to use this feature<br>You can see how to do that in <a href=\"https://notabug.org/RemixDevs/DeezloaderRemix/wiki/Spotify+Features\">this guide</a>"})
779                 }
780         }
781         s.on("downloadspotifyalbum", data=>{downloadSpotifyAlbum(data)})
782
783         // Converts the spotify track to a deezer one
784         // It tries first with the isrc (best way of conversion)
785         // Fallbacks to the old way, using search
786         async function convertSpotify2Deezer(track){
787                 if (!track) return 0
788                 try{
789                         if (track.external_ids.isrc){
790                                 let resp = await s.Deezer.legacyGetTrackByISRC(track.external_ids.isrc)
791                                 if (resp.title)
792                                         return resp.id
793                                 else
794                                         logger.warn("ISRC track is not on Deezer, falling back to old method")
795                         }
796                 }catch(err){
797                         logger.warn("ISRC not found, falling back to old method")
798                 }
799                 return convertMetadata2Deezer(track.artists[0].name, track.name, track.album.name)
800         }
801
802         // Tries to get track id from pure luck
803         async function convertMetadata2Deezer(artist, track, album){
804                 let resp
805                 artist = artist.replace(/–/g,"-").replace(/’/g, "'")
806                 track = track.replace(/–/g,"-").replace(/’/g, "'")
807                 album = album.replace(/–/g,"-").replace(/’/g, "'")
808                 try{
809                         resp = await s.Deezer.legacySearch(`artist:"${artist}" track:"${track}" album:"${album}"`, "track", 1)
810                 }catch(err){logger.err(`ConvertFromMetadata: ${err.stack ? err.stack : err}`)}
811                 if (resp.data[0]) return resp.data[0].id
812                 try{
813                         resp = await s.Deezer.legacySearch(encodeURIComponent(`artist:"${artist}" track:"${track}"`), "track", 1)
814                 }catch(err){logger.err(`ConvertFromMetadata: ${err.stack ? err.stack : err}`)}
815                 if (resp.data[0]) return resp.data[0].id
816                 if (track.indexOf("(") < track.indexOf(")")){
817                         try{
818                                 resp = await s.Deezer.legacySearch(encodeURIComponent(`artist:"${artist}" track:"${track.split("(")[0]}"`), "track", 1)
819                         }catch(err){logger.err(`ConvertFromMetadata: ${err.stack ? err.stack : err}`)}
820                         if (resp.data[0]) return resp.data[0].id
821                 }else if (track.indexOf(" - ")>0){
822                         try{
823                                 resp = await s.Deezer.legacySearch(encodeURIComponent(`artist:"${artist}" track:"${track.split(" - ")[0]}"`), "track", 1)
824                         }catch(err){logger.err(`ConvertFromMetadata: ${err.stack ? err.stack : err}`)}
825                         if (resp.data[0]) return resp.data[0].id
826                 }else{
827                         return 0
828                 }
829                 return 0
830         }
831
832         // Converts the spotify album to a deezer one
833         // It tries first with the upc (best way of conversion)
834         // Fallbacks to the old way, using search
835         async function convertSpotifyAlbum2Deezer(album){
836                 if (!album) return 0
837                 try{
838                         if (album.external_ids.upc){
839                                 if (! isNaN(album.external_ids.upc)) album.external_ids.upc = parseInt(album.external_ids.upc)
840                                 let resp = await s.Deezer.legacyGetAlbumByUPC(album.external_ids.upc)
841                                 if (resp.title)
842                                         return resp.id
843                                 else
844                                         logger.warn("UPC album is not on Deezer, falling back to old method")
845                         }
846                 }catch(err){
847                         logger.warn("UPC not found, falling back to old method")
848                 }
849                 return convertAlbumMetadata2Deezer(album.artists[0].name, album.name)
850         }
851
852         // Tries to get album id from pure luck
853         async function convertAlbumMetadata2Deezer(artist, album){
854                 let resp
855                 artist = artist.replace(/–/g,"-").replace(/’/g, "'")
856                 album = album.replace(/–/g,"-").replace(/’/g, "'")
857                 try{
858                         resp = await s.Deezer.legacySearch(`artist:"${artist}" album:"${album}"`, "album", 1)
859                 }catch(err){logger.err(`ConvertAlbumFromMetadata: ${err.stack ? err.stack : err}`)}
860                 if (resp.data[0]) return resp.data[0].id
861                 return 0
862         }
863
864         // All the above functions call this function
865         // It adds the object to an array and adds the promise for the download to the object itself
866         function addToQueue(object) {
867                 downloadQueue[object.queueId] = object
868                 io.sockets.emit('addToQueue', object)
869                 downloadQueue[object.queueId].downloadQueuePromise = dqueue.push(addNextDownload, { args: object })
870         }
871
872         // Wrapper for queue download
873         function addNextDownload(obj, token){
874                 return new Promise(async (resolve, reject) => {
875                         await queueDownload(obj)
876                         resolve()
877                 }).then(() => new Promise((resolve, reject) => {
878                         if (token.cancelled)
879                                 reject()
880                         else
881                                 resolve()
882                 }))
883         }
884
885         // Cancels download
886         // TODO: Might check this one, could be a little buggy
887         function cancelDownload(queueId, cleanAll=false){
888                 if (!queueId) return
889                 let cancel = false
890                 let cancelSuccess
891                 if (downloadQueue[queueId]){
892                         cancel = true;
893                         if (downloadQueue[queueId].downloadQueuePromise) downloadQueue[queueId].downloadQueuePromise.cancel()
894                         if (downloadQueue[Object.keys(downloadQueue)[0]].queueId == queueId) {
895                                 trackQueue = queue({
896                                         autostart: true,
897                                         concurrency: trackQueue.concurrency
898                                 })
899                         }
900                         delete downloadQueue[queueId]
901                 }
902
903                 if (cancel) {
904                         io.sockets.emit("cancelDownload", {queueId: queueId, cleanAll: cleanAll});
905                 }
906         }
907         s.on("cancelDownload", function (data) {cancelDownload(data.queueId)});
908
909         s.on("cancelAllDownloads", function(data){
910                 data.queueList.forEach(x=>{
911                         cancelDownload(x, true);
912                 })
913                 io.sockets.emit("cancelAllDownloads")
914         })
915
916         /*function getNextDownload() {
917                 if (s.currentItem != null || Object.keys(downloadQueue).length == 0) {
918                         if (Object.keys(downloadQueue).length == 0 && s.currentItem == null) {
919                                 s.emit("emptyDownloadQueue", {})
920                         }
921                         return null
922                 }
923                 s.currentItem = downloadQueue[Object.keys(downloadQueue)[0]]
924                 return s.currentItem
925         }*/
926
927         //downloadQueue: the tracks in the queue to be downloaded
928         //queueId: random number generated when user clicks download on something
929         async function queueDownload(downloading) {
930                 if (!downloading) return
931
932                 if (downloading.type != "spotifyplaylist"){
933                         io.sockets.emit("downloadStarted", {queueId: downloading.queueId})
934                 }
935
936                 downloading.errorLog = "";
937                 downloading.searchedLog = "";
938
939                 let filePath;
940                 logger.info(`Registered ${downloading.type}: ${downloading.id} | ${downloading.artist} - ${downloading.name}`);
941                 switch(downloading.type){
942                         /*
943                         *  TRACK DOWNLOAD
944                         */
945                         case "track":
946                                 var downloadPromise = new Promise(async (resolve,reject)=>{
947                                         try{
948                                                 await downloadTrackObject(downloading.obj, downloading.queueId, downloading.settings)
949                                                 downloading.downloaded++
950                                         }catch(err){
951                                                 logger.error(`[${downloading.obj.artist.name} - ${downloading.obj.title}] ${err}`)
952                                                 downloading.errorLog += `${t.id} | ${t.artist.name} - ${t.title} | ${err}\r\n`
953                                                 downloading.failed++
954                                         }
955                                         io.sockets.emit("updateQueue", {
956                                                 name: downloading.name,
957                                                 artist: downloading.artist,
958                                                 size: downloading.size,
959                                                 downloaded: downloading.downloaded,
960                                                 failed: downloading.failed,
961                                                 queueId: downloading.queueId,
962                                                 id: downloading.id,
963                                                 type: downloading.type,
964                                                 errorLog: downloading.errorLog,
965                                         })
966                                         io.sockets.emit("downloadProgress", {
967                                                 queueId: downloading.queueId,
968                                                 percentage: 100
969                                         })
970                                         resolve()
971                                 })
972                                 try{
973                                         await downloadPromise
974                                 }catch(err){
975                                         if (err) logger.error(`queueDownload:track failed: ${err.stack ? err.stack : err}`)
976                                         logger.info("Downloading Stopped")
977                                 }
978                         break
979                         /*
980                         *  ALBUM DOWNLOAD
981                         */
982                         case "album":
983                                 downloading.settings.albName = downloading.name;
984                                 downloading.settings.artName = downloading.artist;
985                                 downloading.playlistArr = Array(downloading.size);
986                                 filePath = mainFolder;
987                                 downloading.obj.genresString = []
988                                 downloading.obj.genres.data.map((x)=>{
989                                         downloading.obj.genresString.push(x.name)
990                                 })
991                                 let ajson = {
992                                         artist : downloading.obj.artist,
993                                         nb_tracks : downloading.obj.nb_tracks,
994                                         upc : downloading.obj.upc,
995                                         record_type : downloading.obj.record_type,
996                                         explicit_lyrics : downloading.obj.explicit_lyrics,
997                                         label : downloading.obj.label,
998                                         release_date : downloading.obj.release_date,
999                                         genres : downloading.obj.genres,
1000                                         discTotal: downloading.obj.discTotal ? downloading.obj.discTotal : null
1001                                 }
1002                                 let tempDate = {
1003                                         day: ajson.release_date.slice(8,10),
1004                                         month: ajson.release_date.slice(5,7),
1005                                         year: ajson.release_date.slice(0, 4),
1006                                         slicedYear: (downloading.settings.dateFormatYear == "2" ? ajson.release_date.slice(2, 4) : ajson.release_date.slice(0, 4))
1007                                 }
1008                                 let date
1009                                 switch (downloading.settings.dateFormat){
1010                                         case "0": date = `${tempDate.slicedYear}-${tempDate.month}-${tempDate.day}`; break;
1011                                         case "1": date = `${tempDate.day}-${tempDate.month}-${tempDate.slicedYear}`; break;
1012                                         case "2": date = `${tempDate.month}-${tempDate.day}-${tempDate.slicedYear}`; break;
1013                                         case "3": date = `${tempDate.slicedYear}-${tempDate.day}-${tempDate.month}`; break;
1014                                         default: date = `${tempDate.slicedYear}-${tempDate.month}-${tempDate.day}`; break;
1015                                 }
1016                                 let albumObj = {
1017                                         title: downloading.name,
1018                                         artist: {name: downloading.artist},
1019                                         year: tempDate.year,
1020                                         date: date,
1021                                         recordType: ajson.record_type,
1022                                         label: ajson.label,
1023                                         explicit: ajson.explicit_lyrics,
1024                                         genres: downloading.obj.genresString
1025                                 }
1026                                 if (downloading.settings.createArtistFolder || downloading.settings.createAlbumFolder) {
1027                                         if (downloading.settings.createArtistFolder) {
1028                                                 filePath += antiDot(fixName(downloading.settings.artName)) + path.sep;
1029                                         }
1030                                         if (downloading.settings.createAlbumFolder) {
1031                                                 filePath += antiDot(settingsRegexAlbum(albumObj, downloading.settings.foldername)) + path.sep;
1032                                         }
1033                                 } else if (downloading.settings.artName) {
1034                                         filePath += antiDot(settingsRegexAlbum(albumObj, downloading.settings.foldername)) + path.sep;
1035                                 }
1036                                 downloading.downloadPromise = new Promise((resolve,reject)=>{
1037                                         downloading.obj.tracks.every(function (t) {
1038                                                 trackQueue.push(async cb=>{
1039                                                         if (!downloadQueue[downloading.queueId]) {
1040                                                                 reject()
1041                                                                 return false
1042                                                         }
1043                                                         t.ajson = ajson
1044                                                         logger.info(`Now downloading: ${t.artist.name} - ${t.title}`)
1045                                                         try{
1046                                                                 await downloadTrackObject(t, downloading.queueId, downloading.settings)
1047                                                                 downloading.downloaded++
1048                                                                 downloading.playlistArr[t.playlistData[0]] = t.playlistData[1].split(filePath)[1]
1049                                                                 if (t.searched) downloading.searchedLog += `${t.artist.name} - ${t.title}\r\n`
1050                                                         }catch(err){
1051                                                                 downloading.failed++
1052                                                                 downloading.errorLog += `${t.id} | ${t.artist.name} - ${t.title} | ${err}\r\n`
1053                                                                 logger.error(`[${t.artist.name} - ${t.title}] ${err}`)
1054                                                         }
1055                                                         io.sockets.emit("downloadProgress", {
1056                                                                 queueId: downloading.queueId,
1057                                                                 percentage: ((downloading.downloaded+downloading.failed) / downloading.size) * 100
1058                                                         });
1059                                                         io.sockets.emit("updateQueue", {
1060                                                                 name: downloading.name,
1061                                                                 artist: downloading.artist,
1062                                                                 size: downloading.size,
1063                                                                 downloaded: downloading.downloaded,
1064                                                                 failed: downloading.failed,
1065                                                                 queueId: downloading.queueId,
1066                                                                 id: downloading.id,
1067                                                                 type: downloading.type,
1068                                                                 errorLog: downloading.errorLog,
1069                                                         })
1070                                                         if (downloading.downloaded + downloading.failed >= downloading.size) resolve()
1071                                                         cb()
1072                                                 })
1073                                                 return true
1074                                         })
1075                                 })
1076                                 try{
1077                                         await downloading.downloadPromise
1078                                         logger.info("Album finished downloading: "+downloading.name);
1079                                         io.sockets.emit("downloadProgress", {
1080                                                 queueId: downloading.queueId,
1081                                                 percentage: 100
1082                                         });
1083                                         if (downloading.settings.logErrors){
1084                                                 if (downloading.errorLog != ""){
1085                                                         if (!fs.existsSync(filePath)) fs.mkdirSync(filePath);
1086                                                         fs.writeFileSync(filePath+"notFound.txt",downloading.errorLog)
1087                                                 }else{
1088                                                         if (fs.existsSync(filePath+"notFound.txt")) fs.unlinkSync(filePath+"notFound.txt");
1089                                                 }
1090                                         }
1091                                         if (downloading.settings.logSearched){
1092                                                 if (downloading.searchedLog != ""){
1093                                                         if (!fs.existsSync(filePath)) fs.mkdirSync(filePath);
1094                                                         fs.writeFileSync(filePath+"alternativeSongs.txt",downloading.searchedLog)
1095                                                 }else{
1096                                                         if (fs.existsSync(filePath+"alternativeSongs.txt")) fs.unlinkSync(filePath+"alternativeSongs.txt");
1097                                                 }
1098                                         }
1099                                         if (downloading.settings.createM3UFile){
1100                                                 fs.writeFileSync(filePath+"playlist.m3u8", downloading.playlistArr.join("\r\n"));
1101                                         }
1102                                 }catch(err){
1103                                         if (err) logger.error(`queueDownload:album failed: ${err.stack ? err.stack : err}`)
1104                                         logger.info("Stopping the album queue");
1105                                 }
1106                         break
1107                         /*
1108                         *  PLAYLIST DOWNLOAD
1109                         */
1110                         case "playlist":
1111                                 downloading.settings.plName = downloading.name;
1112                                 downloading.playlistArr = Array(downloading.size);
1113                                 downloading.settings.playlist = {
1114                                         fullSize: downloading.obj.tracks.length
1115                                 };
1116                                 filePath = mainFolder+antiDot(fixName(downloading.settings.plName)) + path.sep
1117                                 downloading.downloadPromise = new Promise((resolve,reject)=>{
1118                                         downloading.obj.tracks.every(function (t, index) {
1119                                                 trackQueue.push(async cb=>{
1120                                                         if (!downloadQueue[downloading.queueId]) {
1121                                                                 reject()
1122                                                                 return false
1123                                                         }
1124                                                         try{
1125                                                                 await downloadTrackObject(t, downloading.queueId, downloading.settings)
1126                                                                 downloading.downloaded++
1127                                                                 downloading.playlistArr[t.playlistData[0]] = t.playlistData[1].split(filePath)[1]
1128                                                                 if (t.searched) downloading.searchedLog += `${t.artist.name} - ${t.title}\r\n`
1129                                                         }catch(err){
1130                                                                 downloading.failed++
1131                                                                 downloading.errorLog += `${t.id} | ${t.artist.name} - ${t.title} | ${err}\r\n`
1132                                                                 logger.error(`[${t.artist.name} - ${t.title}] ${err}`)
1133                                                         }
1134                                                         io.sockets.emit("downloadProgress", {
1135                                                                 queueId: downloading.queueId,
1136                                                                 percentage: ((downloading.downloaded+downloading.failed) / downloading.size) * 100
1137                                                         });
1138                                                         io.sockets.emit("updateQueue", {
1139                                                                 name: downloading.name,
1140                                                                 artist: downloading.artist,
1141                                                                 size: downloading.size,
1142                                                                 downloaded: downloading.downloaded,
1143                                                                 failed: downloading.failed,
1144                                                                 queueId: downloading.queueId,
1145                                                                 id: downloading.id,
1146                                                                 type: downloading.type,
1147                                                                 errorLog: downloading.errorLog,
1148                                                         })
1149                                                         if (downloading.downloaded + downloading.failed >= downloading.size) resolve()
1150                                                         cb()
1151                                                 })
1152                                                 return true
1153                                         })
1154                                 })
1155                                 try{
1156                                         await downloading.downloadPromise
1157                                         logger.info("Playlist finished "+downloading.name);
1158                                         io.sockets.emit("downloadProgress", {
1159                                                 queueId: downloading.queueId,
1160                                                 percentage: 100
1161                                         });
1162                                         if (downloading.settings.logErrors){
1163                                                 if (downloading.errorLog != ""){
1164                                                         if (!fs.existsSync(filePath)) fs.mkdirSync(filePath);
1165                                                         fs.writeFileSync(filePath+"notFound.txt",downloading.errorLog)
1166                                                 }else{
1167                                                         if (fs.existsSync(filePath+"notFound.txt")) fs.unlinkSync(filePath+"notFound.txt");
1168                                                 }
1169                                         }
1170                                         if (downloading.settings.logSearched){
1171                                                 if (downloading.searchedLog != ""){
1172                                                         if (!fs.existsSync(filePath)) fs.mkdirSync(filePath);
1173                                                         fs.writeFileSync(filePath+"alternativeSongs.txt",downloading.searchedLog)
1174                                                 }else{
1175                                                         if (fs.existsSync(filePath+"alternativeSongs.txt")) fs.unlinkSync(filePath+"alternativeSongs.txt");
1176                                                 }
1177                                         }
1178                                         if (downloading.settings.createM3UFile){
1179                                                 fs.writeFileSync(filePath + "playlist.m3u8", downloading.playlistArr.join("\r\n"));
1180                                         }
1181                                         if (downloading.settings.saveArtwork){
1182                                                 if (!fs.existsSync(filePath)) fs.mkdirSync(filePath);
1183                                                 let imgPath = filePath + antiDot(settingsRegexCover(downloading.settings.coverImageTemplate,downloading.artist,downloading.name))+(downloading.settings.PNGcovers ? ".png" : ".jpg");
1184                                                 if (downloading.cover){
1185                                                         downloading.cover = downloading.cover.replace("56x56",`${downloading.settings.artworkSize}x${downloading.settings.artworkSize}`)
1186                                                         request.get(downloading.cover, {strictSSL: false,encoding: 'binary'}, function(error,response,body){
1187                                                                 if(error){
1188                                                                         logger.error(error.stack);
1189                                                                         return;
1190                                                                 }
1191                                                                 fs.outputFile(imgPath,body,'binary',function(err){
1192                                                                         if(err){
1193                                                                                 logger.error(err.stack);
1194                                                                                 return;
1195                                                                         }
1196                                                                         logger.info(`Cover downloaded for: ${downloading.settings.plName}`)
1197                                                                 })
1198                                                         });
1199                                                 }
1200                                         }
1201                                 }catch(err){
1202                                         if (err) logger.error(`queueDownload:playlist failed: ${err.stack ? err.stack : err}`)
1203                                         logger.info("Stopping the playlist queue")
1204                                 }
1205                         break
1206                         /*
1207                         *  SPOTIFY PLAYLIST DOWNLOAD
1208                         */
1209                         case "spotifyplaylist":
1210                                 downloading.settings.plName = downloading.name
1211                                 downloading.playlistArr = Array(downloading.size)
1212                                 downloading.playlistContent = new Array(downloading.size)
1213                                 logger.info("Waiting for all tracks to be converted");
1214                                 const convert = async () =>{
1215                                         await asyncForEach(downloading.obj.tracks, async (t,i)=>{
1216                                                 if (!downloadQueue[downloading.queueId]) return false
1217                                                 try{
1218                                                         downloading.playlistContent[i] = await convertSpotify2Deezer(t)
1219                                                 }catch(err){
1220                                                         logger.error(`queueDownload:spotifyplaylist failed during conversion: ${err.stack ? err.stack : err}`)
1221                                                 }
1222                                         })
1223                                 }
1224                                 await convert()
1225                                 if (!downloadQueue[downloading.queueId]) {
1226                                         logger.info("Stopping the playlist queue")
1227                                         break
1228                                 }
1229                                 downloading.trackList = await s.Deezer.getTracks(downloading.playlistContent)
1230                                 logger.info("All tracks converted, starting download")
1231                                 io.sockets.emit("downloadStarted", {queueId: downloading.queueId})
1232                                 downloading.settings.playlist = {
1233                                         fullSize: downloading.trackList.length
1234                                 }
1235                                 filePath = `${mainFolder}${antiDot(fixName(downloading.settings.plName))}${path.sep}`
1236                                 downloading.downloadPromise = new Promise((resolve,reject)=>{
1237                                         downloading.trackList.every(function (t, index) {
1238                                                 trackQueue.push(async cb=>{
1239                                                         if (!downloadQueue[downloading.queueId]) {
1240                                                                 reject()
1241                                                                 return false
1242                                                         }
1243                                                         t.position = index
1244                                                         if (t.id==0 && downloading.obj.tracks[t.position] != null){
1245                                                                 t.title = downloading.obj.tracks[t.position].name
1246                                                                 t.album = {id: 0, title: downloading.obj.tracks[t.position].album.name}
1247                                                                 t.artist = {id: 0, name: downloading.obj.tracks[t.position].artists[0].name}
1248                                                         }
1249                                                         try{
1250                                                                 await downloadTrackObject(t, downloading.queueId, downloading.settings)
1251                                                                 downloading.downloaded++
1252                                                                 downloading.playlistArr[t.playlistData[0]] = t.playlistData[1].split(filePath)[1]
1253                                                                 if (t.searched) downloading.searchedLog += `${t.artist.name} - ${t.title}\r\n`
1254                                                         }catch(err){
1255                                                                 downloading.failed++
1256                                                                 downloading.errorLog += `${t.id} | ${t.artist.name} - ${t.title} | ${err}\r\n`
1257                                                                 logger.error(`[${t.artist.name} - ${t.title}] ${err}`)
1258                                                         }
1259                                                         io.sockets.emit("downloadProgress", {
1260                                                                 queueId: downloading.queueId,
1261                                                                 percentage: ((downloading.downloaded+downloading.failed) / downloading.size) * 100
1262                                                         });
1263                                                         io.sockets.emit("updateQueue", {
1264                                                                 name: downloading.name,
1265                                                                 artist: downloading.artist,
1266                                                                 size: downloading.size,
1267                                                                 downloaded: downloading.downloaded,
1268                                                                 failed: downloading.failed,
1269                                                                 queueId: downloading.queueId,
1270                                                                 id: downloading.id,
1271                                                                 type: downloading.type,
1272                                                                 errorLog: downloading.errorLog,
1273                                                         })
1274                                                         if (downloading.downloaded + downloading.failed >= downloading.size) resolve()
1275                                                         cb()
1276                                                 })
1277                                                 return true
1278                                         })
1279                                 })
1280                                 try{
1281                                         await downloading.downloadPromise
1282                                         logger.info("Playlist finished "+downloading.name);
1283                                         io.sockets.emit("downloadProgress", {
1284                                                 queueId: downloading.queueId,
1285                                                 percentage: 100
1286                                         });
1287                                         if (downloading.settings.logErrors){
1288                                                 if (downloading.errorLog != ""){
1289                                                         if (!fs.existsSync(filePath)) fs.mkdirSync(filePath);
1290                                                         fs.writeFileSync(filePath+"notFound.txt",downloading.errorLog)
1291                                                 }else{
1292                                                         if (fs.existsSync(filePath+"notFound.txt")) fs.unlinkSync(filePath+"notFound.txt");
1293                                                 }
1294                                         }
1295                                         if (downloading.settings.logSearched){
1296                                                 if (downloading.searchedLog != ""){
1297                                                         if (!fs.existsSync(filePath)) fs.mkdirSync(filePath);
1298                                                         fs.writeFileSync(filePath+"alternativeSongs.txt",downloading.searchedLog)
1299                                                 }else{
1300                                                         if (fs.existsSync(filePath+"alternativeSongs.txt")) fs.unlinkSync(filePath+"alternativeSongs.txt");
1301                                                 }
1302                                         }
1303                                         if (downloading.settings.createM3UFile){
1304                                                 fs.writeFileSync(filePath + "playlist.m3u8", downloading.playlistArr.join("\r\n"));
1305                                         }
1306                                         if (downloading.settings.saveArtwork){
1307                                                 if (!fs.existsSync(filePath)) fs.mkdirSync(filePath);
1308                                                 let imgPath = filePath + antiDot(settingsRegexCover(downloading.settings.coverImageTemplate,downloading.artist,downloading.name))+(downloading.settings.PNGcovers ? ".png" : ".jpg");
1309                                                 if (downloading.obj.images){
1310                                                         downloading.cover = downloading.obj.images[0].url.replace("56x56",`${downloading.settings.artworkSize}x${downloading.settings.artworkSize}`)
1311                                                         request.get(downloading.cover, {strictSSL: false,encoding: 'binary'}, function(error,response,body){
1312                                                                 if(error){
1313                                                                         logger.error(error.stack);
1314                                                                         return;
1315                                                                 }
1316                                                                 fs.outputFile(imgPath,body,'binary',function(err){
1317                                                                         if(err){
1318                                                                                 logger.error(err.stack);
1319                                                                                 return;
1320                                                                         }
1321                                                                         logger.info(`Cover downloaded for: ${downloading.settings.plName}`)
1322                                                                 })
1323                                                         });
1324                                                 }
1325                                         }
1326                                 }catch(err){
1327                                         if (err) logger.error(`queueDownload:spotifyplaylist failed: ${err.stack ? err.stack : err}`)
1328                                         logger.info("Stopping the playlist queue")
1329                                 }
1330                         break
1331                 }
1332                 if (downloading && downloadQueue[Object.keys(downloadQueue)[0]] && (Object.keys(downloadQueue)[0] == downloading.queueId)) delete downloadQueue[Object.keys(downloadQueue)[0]]
1333                 if (Object.keys(downloadQueue).length == 0) {
1334                         io.sockets.emit("emptyDownloadQueue", {})
1335                 }
1336         }
1337
1338         // This function takes the track object and does all the stuff to download it
1339         async function downloadTrackObject(track, queueId, settings) {
1340                 if (!downloadQueue[queueId]) {
1341                         logger.error(`[${track.artist.name} - ${track.title}] Failed to download: Not in queue`)
1342                         throw new Error("Not in queue")
1343                         return false
1344                 }
1345                 if (parseInt(track.id) == 0){
1346                         logger.error(`[${track.artist.name} - ${track.title}] Failed to download: Song not Found`)
1347                         throw new Error("Song not Found")
1348                         return false
1349                 }
1350
1351                 /* Album information is necessary for the following tags:
1352                  * album_artist
1353                  * album_artist_picture
1354                  * trackTotal
1355                  * recordType
1356                  * barcode
1357                  * explicit
1358                  * label
1359                  * genres
1360                  * date
1361                 */
1362                 if (parseInt(track.id)>0){
1363                         var ajson
1364                         if (!track.ajson){
1365                                 try{
1366                                         logger.info(`[${track.artist.name} - ${track.title}] Getting album info`)
1367                                         ajson = await s.Deezer.legacyGetAlbum(track.album.id)
1368                                 }catch(err){
1369                                         logger.warn(`[${track.artist.name} - ${track.title}] Album not found, trying to reach deeper`)
1370                                         try{
1371                                                 ajson = await s.Deezer.getAlbum(track.album.id)
1372                                                 ajson.fromNewAPI = true
1373                                         } catch(err){
1374                                                 if(track.fallbackId){
1375                                                         logger.warn(`[${track.artist.name} - ${track.title}] Failed to download track, falling on alternative`)
1376                                                         track = await s.Deezer.getTrack(track.fallbackId)
1377                                                         return downloadTrackObject(track, queueId, settings)
1378                                                 }else if(!track.searched && settings.fallbackSearch){
1379                                                         logger.warn(`[${track.artist.name} - ${track.title}] Failed to download track, searching for alternative`)
1380                                                         var _trackID = await convertMetadata2Deezer(track.artist.name, track.title, track.album.title)
1381                                                         if (_trackID != "0"){
1382                                                                 track = await s.Deezer.getTrack()
1383                                                                 track.searched = true
1384                                                                 return downloadTrackObject(track, queueId, settings)
1385                                                         }else{
1386                                                                 logger.error(`[${track.artist.name} - ${track.title}] Failed to download: Alternative not found`)
1387                                                                 return
1388                                                         }
1389                                                 }else{
1390                                                         logger.error(`[${track.artist.name} - ${track.title}] Failed to download: ${err}`)
1391                                                         return
1392                                                 }
1393                                         }
1394                                 }
1395                         }else{
1396                                 ajson = track.ajson
1397                         }
1398                         if (!ajson.fromNewAPI){
1399                                 // Aquiring discTotal (only if necessary)
1400                                 if (settings.tags.discTotal || settings.createCDFolder){
1401                                         if (!ajson.discTotal){
1402                                                 logger.info(`[${track.artist.name} - ${track.title}] Getting total disc number`);
1403                                                 var discTotal = await s.Deezer.getAlbum(ajson.id)
1404                                                 track.album.discTotal = discTotal.discTotal
1405                                         }else{
1406                                                 track.album.discTotal = ajson.discTotal
1407                                         }
1408                                 }
1409                                 track.album.artist = {
1410                                         id: ajson.artist.id,
1411                                         name: ajson.artist.name,
1412                                         picture: ajson.artist.picture_small.substring(46,ajson.artist.picture_small.length-24),
1413                                 }
1414                                 track.album.trackTotal = ajson.nb_tracks
1415                                 track.album.barcode = ajson.upc
1416                                 if (ajson.record_type){
1417                                         track.album.recordType = ajson.record_type
1418                                 }else{
1419                                         track.album.recordType = switchReleaseType(track.album.recordType)
1420                                 }
1421                                 if (ajson.explicit_lyrics)
1422                                         track.album.explicit = ajson.explicit_lyrics;
1423                                 if(ajson.label)
1424                                         track.album.label = ajson.label;
1425                                 if (ajson.release_date) {
1426                                         track.date = {
1427                                                 day: ajson.release_date.slice(8,10),
1428                                                 month: ajson.release_date.slice(5,7),
1429                                                 year: ajson.release_date.slice(0, 4),
1430                                                 slicedYear: (settings.dateFormatYear == "2" ? ajson.release_date.slice(2, 4) : ajson.release_date.slice(0, 4))
1431                                         }
1432                                 }else if(!track.date){
1433                                         track.date = {
1434                                                 day: 0,
1435                                                 month: 0,
1436                                                 year: 0,
1437                                                 slicedYear: 0
1438                                         }
1439                                 }
1440                                 if(ajson.genres && ajson.genres.data[0] && ajson.genres.data[0].name){
1441                                         track.album.genre = []
1442                                         ajson.genres.data.forEach(function(genre){
1443                                                 if (track.album.genre.indexOf(genre.name) == -1)
1444                                                         track.album.genre.push(genre.name)
1445                                         })
1446                                 }
1447                         }else{
1448                                 // Missing barcode, genre
1449                                 track.album = ajson
1450                                 track.date = track.album.date
1451                                 track.date.slicedYear = (settings.dateFormatYear == "2" ? track.date.year.slice(2, 4) : track.date.year.slice(0, 4))
1452                                 track.trackTotal = track.album.trackTotal
1453                                 track.album.recordType = "Album"
1454                                 // TODO: Make a loop for each artist
1455                         }
1456
1457                         if (!track.date.slicedYear){
1458                                 track.date.slicedYear = settings.dateFormatYear == "2" ? track.date.year.slice(2, 4) : track.date.year.slice(0, 4)
1459                         }
1460
1461                         // Acquiring bpm (only if necessary)
1462                         if (settings.tags.bpm){
1463                                 logger.info(`[${track.artist.name} - ${track.title}] Getting BPM`);
1464                                 track.legacyTrack = await s.Deezer.legacyGetTrack(track.id)
1465                                 try{
1466                                         track.bpm = track.legacyTrack.bpm
1467                                 }catch(err){
1468                                         track.bpm = 0
1469                                 }
1470                         }else{
1471                                 track.bpm = 0
1472                         }
1473
1474                         // Acquiring ReplayGain value (only if necessary)
1475                         if (settings.tags.replayGain){
1476                                 logger.info(`[${track.artist.name} - ${track.title}] Getting track gain`);
1477                                 if (!track.legacyTrack) track.legacyTrack = await s.Deezer.legacyGetTrack(track.id)
1478                                 try{
1479                                         track.replayGain = track.legacyTrack.gain
1480                                 }catch(err){
1481                                         track.replayGain = 0
1482                                 }
1483                         }else{
1484                                 track.replayGain = 0
1485                         }
1486
1487                         // Acquiring discNumber value (only if necessary)
1488                         if (settings.tags.discNumber && !track.discNumber){
1489                                 logger.info(`[${track.artist.name} - ${track.title}] Getting disc number`);
1490                                 if (!track.legacyTrack) track.legacyTrack = await s.Deezer.legacyGetTrack(track.id)
1491                                 track.discNumber = track.legacyTrack.disk_number
1492                         }
1493
1494                         let separator = settings.multitagSeparator
1495                         if (separator == "null") separator = String.fromCharCode(0)
1496
1497                         // Autoremoves (Album Version) from the title
1498                         if (settings.removeAlbumVersion){
1499                                 if(track.title.indexOf("Album Version")>-1){
1500                                         track.title = track.title.replace(/ ?\(Album Version\)/g,"")
1501                                 }
1502                         }
1503
1504                         // See if you already have the artist picture
1505                         if (!track.album.artist.picture && !settings.plName){
1506                                 if (track.artist.name == track.album.artist.name && !track.album.artist.picture){
1507                                         track.album.artist.picture = track.artist.picture
1508                                 }else{
1509                                         let found = false
1510                                         if (track.artists){
1511                                                 track.artists.forEach(x=>{
1512                                                         if(!found && x.name == track.album.artist.name){
1513                                                                 track.album.artist.picture = x.picture
1514                                                                 found = true
1515                                                         }
1516                                                 })
1517                                         }
1518                                         if(settings.saveArtworkArtist && !found){
1519                                                 artist = await s.Deezer.legacyGetArtist(track.album.artist.id)
1520                                                 track.album.artist.picture = artist.picture_small.substring(46,ajson.artist.picture_small.length-24)
1521                                         }
1522                                 }
1523                         }
1524                         if (!track.album.artist.picture) track.album.artist.picture = ""
1525                         track.album.artist.pictureUrl = `${s.Deezer.artistPicturesHost}${track.album.artist.picture}/${settings.artworkSize}x${settings.artworkSize}-000000-80-0-0${(settings.PNGcovers ? ".png" : ".jpg")}`
1526                         track.album.pictureUrl = `${s.Deezer.albumPicturesHost}${track.album.picture}/${settings.artworkSize}x${settings.artworkSize}-000000-80-0-0${(settings.PNGcovers ? ".png" : ".jpg")}`
1527
1528                         // Auto detect aviable track format from settings
1529                         switch(downloadQueue[queueId].bitrate.toString()){
1530                                 case "9":
1531                                         track.selectedFormat = 9
1532                                         track.selectedFilesize = track.filesize.flac
1533                                         if (track.filesize.flac>0) break
1534                                         if (!settings.fallbackBitrate) throw new Error("Song not found at desired bitrate.")
1535                                 case "3":
1536                                         track.selectedFormat = 3
1537                                         track.selectedFilesize = track.filesize.mp3_320
1538                                         if (track.filesize.mp3_320>0) break
1539                                         if (!settings.fallbackBitrate) throw new Error("Song not found at desired bitrate.")
1540                                 case "1":
1541                                         track.selectedFormat = 1
1542                                         track.selectedFilesize = track.filesize.mp3_128
1543                                         if (track.filesize.mp3_128>0) break
1544                                         if (!settings.fallbackBitrate) throw new Error("Song not found at desired bitrate.")
1545                                 default:
1546                                         track.selectedFormat = 8
1547                                         track.selectedFilesize = track.filesize.default
1548                         }
1549                         track.album.bitrate = track.selectedFormat
1550
1551                         if(track.contributor){
1552                                 if(track.contributor.composer){
1553                                         track.composerString = uniqueArray(track.contributor.composer)
1554                                         if (!(track.selectedFormat == 9 && separator==String.fromCharCode(0))) track.composerString = track.composerString.join(separator)
1555                                 }
1556                                 if(track.contributor.musicpublisher){
1557                                         track.musicpublisherString = uniqueArray(track.contributor.musicpublisher)
1558                                         if (!(track.selectedFormat == 9 && separator==String.fromCharCode(0))) track.musicpublisherString = track.musicpublisherString.join(separator)
1559                                 }
1560                                 if(track.contributor.producer){
1561                                         track.producerString = uniqueArray(track.contributor.producer)
1562                                         if (!(track.selectedFormat == 9 && separator==String.fromCharCode(0))) track.producerString = track.producerString.join(separator)
1563                                 }
1564                                 if(track.contributor.engineer){
1565                                         track.engineerString = uniqueArray(track.contributor.engineer)
1566                                         if (!(track.selectedFormat == 9 && separator==String.fromCharCode(0))) track.engineerString = track.engineerString.join(separator)
1567                                 }
1568                                 if(track.contributor.writer){
1569                                         track.writerString = uniqueArray(track.contributor.writer)
1570                                         if (!(track.selectedFormat == 9 && separator==String.fromCharCode(0))) track.writerString = track.writerString.join(separator)
1571                                 }
1572                                 if(track.contributor.author){
1573                                         track.authorString = uniqueArray(track.contributor.author)
1574                                         if (!(track.selectedFormat == 9 && separator==String.fromCharCode(0))) track.authorString = track.authorString.join(separator)
1575                                 }
1576                                 if(track.contributor.mixer){
1577                                         track.mixerString = uniqueArray(track.contributor.mixer)
1578                                         if (!(track.selectedFormat == 9 && separator==String.fromCharCode(0))) track.mixerString = track.mixerString.join(separator)
1579                                 }
1580                         }
1581
1582                         if(track.artists || track.artistsString){
1583                                 if (!track.artistsString){
1584                                         track.artistsString = []
1585                                         artistArray = []
1586                                         track.artists.forEach(function(artist){
1587                                                 artistArray.push(artist.name)
1588                                         })
1589                                 }else{
1590                                         if (! Array.isArray(track.artistsString)){
1591                                                 track.artistsString = [track.artistsString,]
1592                                         }
1593                                         artistArray = track.artistsString
1594                                 }
1595                                 track.artistsString = uniqueArray(artistArray)
1596                                 let posMainArtist = track.artistsString.indexOf(track.album.artist.name)
1597                                 if (posMainArtist !== -1 && posMainArtist !== 0){
1598                                         let element = track.artistsString[posMainArtist]
1599                                         track.artistsString.splice(posMainArtist, 1)
1600                                         track.artistsString.splice(0, 0, element)
1601                                 }
1602                                 if (!(track.selectedFormat == 9 && separator==String.fromCharCode(0))) track.artistsString = track.artistsString.join(separator)
1603                         }
1604                         if (track.album.genre){
1605                                 if (!(track.selectedFormat == 9 && separator==String.fromCharCode(0)))
1606                                         track.album.genreString = track.album.genre.join(separator)
1607                                 else
1608                                         track.album.genreString = track.album.genre
1609                         }
1610
1611                         if (track.date){
1612                                 let date
1613                                 switch (settings.dateFormat){
1614                                         case "0": date = `${track.date.slicedYear}-${track.date.month}-${track.date.day}`; break;
1615                                         case "1": date = `${track.date.day}-${track.date.month}-${track.date.slicedYear}`; break;
1616                                         case "2": date = `${track.date.month}-${track.date.day}-${track.date.slicedYear}`; break;
1617                                         case "3": date = `${track.date.slicedYear}-${track.date.day}-${track.date.month}`; break;
1618                                         default: date = `${track.date.slicedYear}-${track.date.month}-${track.date.day}`; break;
1619                                 }
1620                                 track.dateString = date;
1621                                 track.id3dateString = `${track.date.day}${track.date.month}`;
1622                         }
1623                 }else{
1624                         track.date = {year: 0,day: 0,month: 0}
1625                         track.selectedFilesize = track.filesize
1626                         track.selectedFormat = 3
1627                         track.album.bitrate = 3
1628                 }
1629                 track.album.date = track.dateString
1630                 track.album.year = track.date.year
1631
1632                 // TODO: Move to a separate function
1633                 // Generating file name
1634                 if (settings.saveFullArtists && settings.multitagSeparator != null){
1635                         let filename = antiDot(fixName(`${track.artistsString} - ${track.title}`));
1636                 }else{
1637                         let filename = antiDot(fixName(`${track.artist.name} - ${track.title}`));
1638                 }
1639                 if (settings.filename) {
1640                         filename = antiDot(fixName(settingsRegex(track, settings.filename, settings.playlist)))
1641                 }
1642
1643                 filename = antiDot(fixName(filename))
1644
1645                 // TODO: Move to a separate function
1646                 // Generating file path
1647                 let filepath = mainFolder;
1648                 let artistPath;
1649                 if ((settings.createArtistFolder || settings.createAlbumFolder) && (!settings.plName || settings.createFoldersPlaylist && settings.plName)) {
1650
1651                         if(settings.plName && settings.createFoldersPlaylist){
1652                                 filepath += antiDot(fixName(settings.plName)) + path.sep;
1653                         }
1654
1655                         if (settings.createArtistFolder) {
1656                                 if(settings.artName){
1657                                         filepath += antiDot(fixName(settings.artName)) + path.sep;
1658                                 }else{
1659                                         filepath += antiDot(fixName(track.album.artist.name)) + path.sep;
1660                                 }
1661                                 artistPath = filepath;
1662                         }
1663
1664                         if (settings.createAlbumFolder) {
1665                                 if(settings.artName){
1666                                         filepath += antiDot(fixName(settingsRegexAlbum(track.album, settings.foldername))) + path.sep;
1667                                 }else{
1668                                         filepath += antiDot(fixName(settingsRegexAlbum(track.album, settings.foldername))) + path.sep;
1669                                 }
1670                         }
1671                 } else if (settings.plName) {
1672                         filepath += antiDot(fixName(settings.plName)) + path.sep;
1673                 } else if (settings.artName) {
1674                         filepath += antiDot(fixName(settingsRegexAlbum(track.album, settings.foldername))) + path.sep;
1675                 }
1676                 let coverpath = filepath;
1677                 if (track.album.discTotal > 1 && (settings.artName || settings.createAlbumFolder) && settings.createCDFolder && (!settings.plName || settings.createFoldersPlaylist && settings.plName)){
1678                         filepath += `CD${track.discNumber + path.sep}`
1679                 }
1680
1681                 let writePath;
1682                 if(track.selectedFormat == 9){
1683                         writePath = filepath + filename + '.flac';
1684                 }else{
1685                         writePath = filepath + filename + '.mp3';
1686                 }
1687
1688                 if ((settings.syncedlyrics || settings.tags.unsynchronisedLyrics) && track.lyricsId>0){
1689                         let lyr = await s.Deezer.getLyrics(track.id)
1690                         track.syncLyrics = lyr.syncLyrics
1691                         track.unsyncLyrics = lyr.unsyncLyrics
1692                 }
1693
1694                 if(track.syncLyrics && settings.syncedlyrics){
1695                         fs.outputFile(writePath.substring(0,writePath.lastIndexOf('.'))+".lrc",track.syncLyrics,function(){});
1696                 }
1697
1698                 track.playlistData = [0,""]
1699                 if (settings.createM3UFile && (settings.plName || settings.albName)) {
1700                         if (typeof track.position != 'undefined'){
1701                                 track.playlistData = [parseInt(track.position), writePath];
1702                         }else{
1703                                 track.playlistData = [track.trackNumber-1, writePath];
1704                         }
1705                 }
1706                 if (fs.existsSync(writePath)) {
1707                         logger.info(`[${track.artist.name} - ${track.title}] Already downloaded`);
1708                         return;
1709                 }else{
1710                         logger.info(`[${track.artist.name} - ${track.title}] Downloading file to ${writePath}`);
1711                 }
1712                 // Get cover image
1713                 if (track.album.pictureUrl) {
1714                         let imgPath;
1715                         //If its not from an album but a playlist.
1716                         if(settings.albName || settings.createAlbumFolder){
1717                                 if (settings.saveArtwork && ! settings.plName)
1718                                         imgPath = coverpath + settingsRegexCover(settings.coverImageTemplate,track.album.artist.name,track.album.title)+(settings.PNGcovers ? ".png" : ".jpg")
1719                                 else
1720                                         imgPath = coverArtFolder + fixName(track.album.barcode ? fixName(track.album.barcode) : fixName(`${track.album.artist.name} - ${track.album.title}`))+(settings.PNGcovers ? ".png" : ".jpg")
1721                         }else{
1722                                 imgPath = coverArtFolder + (track.album.barcode ? fixName(track.album.barcode) : fixName(`${track.album.artist.name} - ${track.album.title}`))+(settings.PNGcovers ? ".png" : ".jpg")
1723                         }
1724                         if(fs.existsSync(imgPath)){
1725                                 track.album.picturePath = (imgPath).replace(/\\/g, "/")
1726                                 logger.info(`[${track.artist.name} - ${track.title}] Starting the download process CODE:1`)
1727                         }else{
1728                                 try{
1729                                         var body = await request.get(track.album.pictureUrl, {strictSSL: false,encoding: 'binary'})
1730                                         fs.outputFileSync(imgPath,body,'binary')
1731                                         track.album.picturePath = (imgPath).replace(/\\/g, "/")
1732                                         logger.info(`[${track.artist.name} - ${track.title}] Starting the download process CODE:2`)
1733                                 }catch(error){
1734                                         logger.error(`[${track.artist.name} - ${track.title}] Cannot download Album Image: ${error}`)
1735                                         logger.error(`Album art link: ${track.album.pictureUrl}`)
1736                                         track.album.pictureUrl = undefined
1737                                         track.album.picturePath = undefined
1738                                 }
1739                         }
1740                 }else{
1741                         track.album.pictureUrl = undefined
1742                         logger.info(`[${track.artist.name} - ${track.title}] Starting the download process CODE:3`)
1743                 }
1744
1745                 // Get Artist Image
1746                 if (parseInt(track.id)>0 && track.album.artist.pictureUrl && settings.saveArtworkArtist) {
1747                         let imgPath;
1748                         if(settings.createArtistFolder && artistPath){
1749                                 imgPath = artistPath + antiDot(settingsRegexArtistCover(settings.artistImageTemplate,track.album.artist.name))+(settings.PNGcovers ? ".png" : ".jpg");
1750                                 if(!fs.existsSync(imgPath)){
1751                                         try{
1752                                                 var body = await request.get(track.album.artist.pictureUrl, {strictSSL: false,encoding: 'binary'})
1753                                                 if (body.indexOf("unauthorized")>-1) throw new Error("Unauthorized")
1754                                                 fs.outputFileSync(imgPath,body,'binary')
1755                                                 logger.info(`[${track.artist.name} - ${track.title}] Saved Artist Image`)
1756                                         }catch(err){
1757                                                 logger.error(`[${track.artist.name} - ${track.title}] Cannot download Artist Image: ${err}`)
1758                                         }
1759                                 }
1760                         }
1761                 }
1762
1763                 let tempPath
1764                 if(parseInt(track.id)>0)
1765                         tempPath = `${filepath}${track.id}_${track.selectedFormat}.temp`
1766                 else
1767                         tempPath = writePath
1768
1769                 logger.info(`[${track.artist.name} - ${track.title}] Downloading track`)
1770                 var downloadingPromise = new Promise((resolve, reject)=>{
1771                         let req = requestOld.get({url: track.getDownloadUrl(track.selectedFormat), strictSSL: false, headers: s.Deezer.httpHeaders, encoding: 'binary'}, function (error, response, body) {
1772                                 if (error){
1773                                         logger.error(`[${track.artist.name} - ${track.title}] Downloading error: ${error}`)
1774                                         reject("Downloading error: "+error)
1775                                         return false
1776                                 }
1777                                 if (!downloadQueue[queueId]){
1778                                         fs.remove(tempPath)
1779                                         reject("Not in Queue")
1780                                         return false
1781                                 }
1782                                 if (body.length == 0){
1783                                         fs.remove(tempPath)
1784                                         reject("Track is Empty")
1785                                         return false
1786                                 }
1787                                 logger.info(`[${track.artist.name} - ${track.title}] Decrypting track`)
1788                                 var decryptedSource = s.Deezer.decryptDownload(Buffer.from(body, 'binary'), track.id)
1789                                 try{
1790                                         fs.outputFileSync(tempPath,decryptedSource)
1791                                         resolve()
1792                                 }catch(err){
1793                                         logger.error(`[${track.artist.name} - ${track.title}] Decryption error: ${err}`)
1794                                         reject(err)
1795                                         return false
1796                                 }
1797                         }).on("data", function(data) {
1798                                 if (!downloadQueue[queueId]){
1799                                         reject("Not in Queue")
1800                                         return false
1801                                 }
1802                         })
1803                         if((downloadQueue[queueId]) && downloadQueue[queueId].type == "track"){
1804                                 let chunkLength = 0
1805                                 req.on("data", function(data) {
1806                                         if (!downloadQueue[queueId]){
1807                                                 reject("Not in Queue")
1808                                         }
1809                                         chunkLength += data.length
1810                                         try{
1811                                                 if (!downloadQueue[queueId].percentage) {
1812                                                         downloadQueue[queueId].percentage = 0
1813                                                 }
1814                                                 let complete = track.selectedFilesize
1815                                                 let percentage = (chunkLength / complete) * 100;
1816                                                 if ((percentage - downloadQueue[queueId].percentage > 1) || (chunkLength == complete)) {
1817                                                         downloadQueue[queueId].percentage = percentage
1818                                                         io.sockets.emit("downloadProgress", {
1819                                                                 queueId: queueId,
1820                                                                 percentage: downloadQueue[queueId].percentage-5
1821                                                         })
1822                                                 }
1823                                         }catch(err){}
1824                                 })
1825                         }
1826                 })
1827
1828                 try{
1829                         await downloadingPromise
1830                 }catch(err){
1831                         if (err==="Track is Empty"){
1832                                 if(track.fallbackId && track.fallbackId != "0"){
1833                                         logger.warn(`[${track.artist.name} - ${track.title}] Track is empty, falling on alternative`)
1834                                         var _track = await s.Deezer.getTrack(track.fallbackId)
1835                                         track.id = _track.id
1836                                         track.fallbackId = _track.fallbackId
1837                                         track.filesize = _track.filesize
1838                                         track.duration = _track.duration
1839                                         track.MD5 = _track.MD5
1840                                         track.mediaVersion = _track.mediaVersion
1841                                         return downloadTrackObject(track, queueId, settings)
1842                                 }else if(!track.searched && settings.fallbackSearch){
1843                                         logger.warn(`[${track.artist.name} - ${track.title}] Track is empty, searching for alternative`)
1844                                         _trackId = await convertMetadata2Deezer(track.artist.name, track.title, track.album.title)
1845                                         if (_trackId != "0"){
1846                                                 _track = await s.Deezer.getTrack(_trackId)
1847                                                 track.id = _track.id
1848                                                 track.fallbackId = _track.fallbackId
1849                                                 track.filesize = _track.filesize
1850                                                 track.duration = _track.duration
1851                                                 track.MD5 = _track.MD5
1852                                                 track.mediaVersion = _track.mediaVersion
1853                                                 track.searched = true
1854                                                 return downloadTrackObject(track, queueId, settings)
1855                                         }else{
1856                                                 logger.error(`[${track.artist.name} - ${track.title}] No alternative found`)
1857                                                 throw new Error("No Alternative Found")
1858                                                 return
1859                                         }
1860                                 }else{
1861                                         logger.error(`[${track.artist.name} - ${track.title}] Downloading error: Track is Empty`)
1862                                         throw new Error("Track is Empty")
1863                                         return
1864                                 }
1865                         }else{
1866                                 throw new Error(err)
1867                                 return
1868                         }
1869                 }
1870
1871                 logger.info(`[${track.artist.name} - ${track.title}] Adding Tags`)
1872                 if (parseInt(track.id)>0){
1873                         if(track.selectedFormat == 9){
1874                                 let flacComments = [];
1875                                 if (settings.tags.title)
1876                                         flacComments.push('TITLE=' + track.title);
1877                                 if (settings.tags.album)
1878                                         flacComments.push('ALBUM=' + track.album.title);
1879                                 if (settings.tags.albumArtist)
1880                                         flacComments.push('ALBUMARTIST=' + track.album.artist.name);
1881                                 if (settings.tags.trackNumber)
1882                                         flacComments.push('TRACKNUMBER=' + track.trackNumber);
1883                                 if (settings.tags.discNumber)
1884                                         flacComments.push('DISCNUMBER=' + track.discNumber);
1885                                 if (settings.tags.trackTotal)
1886                                         flacComments.push('TRACKTOTAL=' + track.album.trackTotal);
1887                                 if (settings.tags.explicit)
1888                                         flacComments.push('ITUNESADVISORY=' + track.explicit);
1889                                 if (settings.tags.isrc)
1890                                         flacComments.push('ISRC=' + track.ISRC);
1891                                 if (settings.tags.artist && track.artistsString)
1892                                         if (Array.isArray(track.artistsString)){
1893                                                 track.artistsString.forEach(x=>{
1894                                                         flacComments.push('ARTIST=' + x);
1895                                                 });
1896                                         }else{
1897                                                 flacComments.push('ARTIST=' + track.artistsString);
1898                                         }
1899                                 if (settings.tags.discTotal)
1900                                         flacComments.push('DISCTOTAL='+track.album.discTotal);
1901                                 if (settings.tags.length)
1902                                         flacComments.push('LENGTH=' + track.duration);
1903                                 if (settings.tags.barcode && track.album.barcode)
1904                                         flacComments.push('BARCODE=' + track.album.barcode);
1905                                 if (track.unsyncLyrics && settings.tags.unsynchronisedLyrics)
1906                                         flacComments.push('LYRICS='+track.unsyncLyrics.lyrics);
1907                                 if (track.album.genreString && settings.tags.genre)
1908                                         if (Array.isArray(track.album.genreString)){
1909                                                 track.album.genreString.forEach(x=>{
1910                                                         flacComments.push('GENRE=' + x);
1911                                                 });
1912                                         }else{
1913                                                 flacComments.push('GENRE=' + track.album.genreString);
1914                                         }
1915                                 if (track.copyright && settings.tags.copyright)
1916                                         flacComments.push('COPYRIGHT=' + track.copyright);
1917                                 if (0 < parseInt(track.date.year)){
1918                                         if (settings.tags.date)
1919                                                 flacComments.push('DATE=' + track.dateString);
1920                                         else if (settings.tags.year)
1921                                                 flacComments.push('DATE=' + track.date.year);
1922                                 }
1923                                 if (0 < parseInt(track.bpm) && settings.tags.bpm)
1924                                         flacComments.push('BPM=' + track.bpm);
1925                                 if(track.album.label && settings.tags.publisher)
1926                                         flacComments.push('PUBLISHER=' + track.album.label);
1927                                 if(track.composerString && settings.tags.composer)
1928                                         if (Array.isArray(track.composerString)){
1929                                                 track.composerString.forEach(x=>{
1930                                                         flacComments.push('COMPOSER=' + x);
1931                                                 });
1932                                         }else{
1933                                                 flacComments.push('COMPOSER=' + track.composerString);
1934                                         }
1935                                 if(track.musicpublisherString && settings.tags.musicpublisher)
1936                                         if (Array.isArray(track.musicpublisherString)){
1937                                                 track.musicpublisherString.forEach(x=>{
1938                                                         flacComments.push('ORGANIZATION=' + x);
1939                                                 });
1940                                         }else{
1941                                                 flacComments.push('ORGANIZATION=' + track.musicpublisherString);
1942                                         }
1943                                 if(track.mixerString && settings.tags.mixer)
1944                                         if (Array.isArray(track.mixerString)){
1945                                                 track.mixerString.forEach(x=>{
1946                                                         flacComments.push('MIXER=' + x);
1947                                                 });
1948                                         }else{
1949                                                 flacComments.push('MIXER=' + track.mixerString);
1950                                         }
1951                                 if(track.authorString && settings.tags.author)
1952                                         if (Array.isArray(track.authorString)){
1953                                                 track.authorString.forEach(x=>{
1954                                                         flacComments.push('AUTHOR=' + x);
1955                                                 });
1956                                         }else{
1957                                                 flacComments.push('AUTHOR=' + track.authorString);
1958                                         }
1959                                 if(track.writerString && settings.tags.writer)
1960                                         if (Array.isArray(track.writerString)){
1961                                                 track.writerString.forEach(x=>{
1962                                                         flacComments.push('WRITER=' + x);
1963                                                 });
1964                                         }else{
1965                                                 flacComments.push('WRITER=' + track.writerString);
1966                                         }
1967                                 if(track.engineerString && settings.tags.engineer)
1968                                         if (Array.isArray(track.engineerString)){
1969                                                 track.engineerString.forEach(x=>{
1970                                                         flacComments.push('ENGINEER=' + x);
1971                                                 });
1972                                         }else{
1973                                                 flacComments.push('ENGINEER=' + track.engineerString);
1974                                         }
1975                                 if(track.producerString && settings.tags.producer)
1976                                         if (Array.isArray(track.producerString)){
1977                                                 track.producerString.forEach(x=>{
1978                                                         flacComments.push('PRODUCER=' + x);
1979                                                 });
1980                                         }else{
1981                                                 flacComments.push('PRODUCER=' + track.producerString);
1982                                         }
1983                                 if(track.replayGain && settings.tags.replayGain)
1984                                         flacComments.push('REPLAYGAIN_TRACK_GAIN=' + track.replayGain);
1985
1986                                 const reader = fs.createReadStream(tempPath);
1987                                 const writer = fs.createWriteStream(writePath);
1988                                 let processor = new mflac.Processor({parseMetaDataBlocks: true});
1989                                 let vendor = 'reference libFLAC 1.2.1 20070917';
1990                                 let cover = null;
1991                                 if(track.album.picturePath && settings.tags.cover){
1992                                         cover = fs.readFileSync(track.album.picturePath)
1993                                 }
1994                                 let mdbVorbisPicture
1995                                 let mdbVorbisComment
1996                                 processor.on('preprocess', (mdb) => {
1997                                         // Remove existing VORBIS_COMMENT and PICTURE blocks, if any.
1998                                         if (mflac.Processor.MDB_TYPE_VORBIS_COMMENT === mdb.type) {
1999                                                 mdb.remove();
2000                                         } else if (mflac.Processor.MDB_TYPE_PICTURE === mdb.type) {
2001                                                 mdb.remove();
2002                                         }
2003                                         if (mdb.isLast) {
2004                                                 if(cover){
2005                                                         mdbVorbisPicture = mflac.data.MetaDataBlockPicture.create(true, 3, `image/${(settings.PNGcovers ? "png" : "jpeg")}`, '', settings.artworkSize, settings.artworkSize, 24, 0, cover);
2006                                                 }
2007                                                 mdbVorbisComment = mflac.data.MetaDataBlockVorbisComment.create(!cover, vendor, flacComments);
2008                                                 mdb.isLast = false;
2009                                         }
2010                                 });
2011                                 processor.on('postprocess', (mdb) => {
2012                                         if (mflac.Processor.MDB_TYPE_VORBIS_COMMENT === mdb.type && null !== mdb.vendor) {
2013                                                 vendor = mdb.vendor;
2014                                         }
2015                                         if (mdbVorbisPicture && mdbVorbisComment) {
2016                                                         processor.push(mdbVorbisComment.publish());
2017                                                         processor.push(mdbVorbisPicture.publish());
2018                                                 }else if(mdbVorbisComment){
2019                                                         processor.push(mdbVorbisComment.publish());
2020                                         }
2021                                 });
2022                                 reader.on('end', () => {
2023                                         fs.remove(tempPath);
2024                                 });
2025                                 await reader.pipe(processor).pipe(writer);
2026                         }else{
2027                                 const songBuffer = fs.readFileSync(tempPath);
2028                                 const writer = new ID3Writer(songBuffer);
2029                                 if (settings.tags.title)
2030                                         writer.setFrame('TIT2', track.title)
2031                                 if (settings.tags.artist)
2032                                         writer.setFrame('TPE1', [track.artistsString])
2033                                 if (settings.tags.album)
2034                                         writer.setFrame('TALB', track.album.title)
2035                                 if (settings.tags.albumArtist && track.album.artist)
2036                                         writer.setFrame('TPE2', track.album.artist.name)
2037                                 if (settings.tags.trackNumber)
2038                                         writer.setFrame('TRCK', (settings.tags.trackTotal ? track.trackNumber+"/"+track.album.trackTotal : track.trackNumber))
2039                                 if (settings.tags.discNumber)
2040                                         writer.setFrame('TPOS', (settings.tags.discTotal ? track.discNumber+"/"+track.album.discTotal : track.discNumber))
2041                                 if (settings.tags.isrc)
2042                                         writer.setFrame('TSRC', track.ISRC);
2043
2044                                 if (settings.tags.length)
2045                                         writer.setFrame('TLEN', track.duration);
2046                                 if (settings.tags.barcode && track.album.barcode)
2047                                         writer.setFrame('TXXX', {
2048                                                 description: 'BARCODE',
2049                                                 value: track.album.barcode
2050                                         });
2051                                 if(track.album.picturePath && settings.tags.cover){
2052                                         const coverBuffer = fs.readFileSync(track.album.picturePath);
2053                                         writer.setFrame('APIC', {
2054                                                 type: 3,
2055                                                 data: coverBuffer,
2056                                                 description: ''
2057                                         });
2058                                 }
2059                                 if(track.unsyncLyrics && settings.tags.unsynchronisedLyrics)
2060                                         writer.setFrame('USLT', track.unsyncLyrics);
2061                                 if(track.album.label && settings.tags.publisher)
2062                                         writer.setFrame('TPUB', track.album.label);
2063                                 if(track.album.genreString && settings.tags.genre)
2064                                         writer.setFrame('TCON', [track.album.genreString]);
2065                                 if(track.copyright && settings.tags.copyright)
2066                                         writer.setFrame('TCOP', track.copyright);
2067                                 if (0 < parseInt(track.date.year)) {
2068                                         if (settings.tags.date)
2069                                                 writer.setFrame('TDAT', track.id3dateString);
2070                                         if (settings.tags.year)
2071                                                 writer.setFrame('TYER', track.date.year);
2072                                 }
2073                                 if (0 < parseInt(track.bpm) && settings.tags.bpm)
2074                                         writer.setFrame('TBPM', track.bpm);
2075                                 if(track.composerString && settings.tags.composer)
2076                                         writer.setFrame('TCOM', [track.composerString]);
2077                                 if(track.replayGain && settings.tags.replayGain)
2078                                         writer.setFrame('TXXX', {
2079                                                 description: 'REPLAYGAIN_TRACK_GAIN',
2080                                                 value: track.replayGain
2081                                         });
2082                                 writer.addTag();
2083                                 const taggedSongBuffer = Buffer.from(writer.arrayBuffer);
2084                                 fs.writeFileSync(writePath, taggedSongBuffer);
2085                                 fs.remove(tempPath);
2086                         }
2087                 }
2088                 logger.info(`[${track.artist.name} - ${track.title}] Downloaded`)
2089         }
2090 })
2091
2092 // Helper functions
2093
2094 /**
2095  * Updates individual parameters in the settings file
2096  * @param config
2097  * @param value
2098  */
2099 function updateSettingsFile(config, value) {
2100         configFile.userDefined[config] = value;
2101
2102         fs.outputFile(configFileLocation, JSON.stringify(configFile, null, 2), function (err) {
2103                 if (err) return;
2104                 logger.info("Settings updated");
2105
2106                 // FIXME: Endless Loop, due to call from initFolders()...crashes soon after startup
2107                 // initFolders();
2108         });
2109 }
2110
2111 function fixName (txt) {
2112         txt = txt+""
2113         const regEx = /[\0\/\\:*?"<>|]/g;
2114         txt = txt.replace(regEx, '_');
2115         txt = txt.slice(0,200);
2116         return txt;
2117 }
2118
2119 function antiDot(str){
2120         while(str[str.length-1] == "." || str[str.length-1] == " " || str[str.length-1] == "\n"){
2121                 str = str.substring(0,str.length-1);
2122         }
2123         if(str.length < 1){
2124                 str = "dot";
2125         }
2126         return str;
2127 }
2128
2129 /**
2130  * Initialize the temp folder for covers and main folder for downloads
2131  */
2132 function initFolders() {
2133         // Check if main folder exists
2134         if (!fs.existsSync(mainFolder)) {
2135                 mainFolder = defaultDownloadFolder;
2136                 updateSettingsFile('downloadLocation', defaultDownloadFolder);
2137         }
2138         //fs.removeSync(coverArtFolder);
2139         //fs.ensureFolderSync(coverArtFolder);
2140 }
2141
2142 /**
2143  * Creates the name of the tracks replacing wildcards to correct metadata
2144  * @param track
2145  * @param filename
2146  * @param playlist
2147  * @returns {XML|string|*}
2148  */
2149 function settingsRegex(track, filename, playlist) {
2150         try{
2151                 filename = filename.replace(/%title%/g, fixName(track.title));
2152                 filename = filename.replace(/%album%/g, fixName(track.album.title));
2153                 filename = filename.replace(/%artist%/g, fixName(((configFile.userDefined.saveFullArtists && configFile.userDefined.multitagSeparator != null) ? track.artistsString : track.artist.name)));
2154                 filename = filename.replace(/%year%/g, fixName(track.date.year));
2155                 filename = filename.replace(/%label%/g, fixName(track.album.label));
2156                 if(typeof track.trackNumber != 'undefined'){
2157                         if(configFile.userDefined.padtrck){
2158                                  filename = filename.replace(/%number%/g, fixName(pad(track.trackNumber, (parseInt(configFile.userDefined.paddingSize)>0 ? parseInt(configFile.userDefined.paddingSize) : track.album.trackTotal))));
2159                         }else{
2160                                 filename = filename.replace(/%number%/g, fixName(track.trackNumber));
2161                         }
2162                 } else {
2163                         filename = filename.replace(/%number%/g, '');
2164                 }
2165                 if (playlist && typeof track.position != 'undefined'){
2166                         if(configFile.userDefined.padtrck){
2167                                  filename = filename.replace(/%position%/g, fixName(pad(track.position+1, (parseInt(configFile.userDefined.paddingSize)>0 ? parseInt(configFile.userDefined.paddingSize) : playlist.fullSize))));
2168                         }else{
2169                                 filename = filename.replace(/%position%/g, fixName(track.position+1));
2170                         }
2171                 } else {
2172                         filename = filename.replace(/%position%/g, '');
2173                 }
2174                 filename = filename.replace(/%disc%/g, fixName(track.discNumber));
2175                 filename = filename.replace(/%explicit%/g, fixName((track.explicit==="1" ? (filename.indexOf(/[^%]explicit/g)>-1 ? "" : "(Explicit Version)") : "")));
2176                 filename = filename.replace(/%genre%/g, fixName(track.album.genre ? (Array.isArray(track.album.genre) ? track.album.genre[0] : track.album.genre) : "Unknown"));
2177                 filename = filename.replace(/[/\\]/g, path.sep)
2178                 return filename.trim();
2179         }catch(e){
2180                 logger.error("settingsRegex failed: "+e)
2181         }
2182 }
2183
2184 /**
2185  * Creates the name of the albums folder replacing wildcards to correct metadata
2186  * @param metadata
2187  * @param foldername
2188  * @returns {XML|string|*}
2189  */
2190 function settingsRegexAlbum(album, foldername) {
2191         try{
2192                 foldername = foldername.replace(/%album%/g, fixName(album.title))
2193                 foldername = foldername.replace(/%artist%/g, fixName(album.artist.name))
2194                 foldername = foldername.replace(/%year%/g, fixName(album.year))
2195                 foldername = foldername.replace(/%date%/g, fixName(album.date))
2196                 if (album.recordType){
2197                         foldername = foldername.replace(/%type%/g, fixName(album.recordType[0].toUpperCase() + album.recordType.substring(1)))
2198                 }else{
2199                         foldername = foldername.replace(/%type%/g, "")
2200                 }
2201                 foldername = foldername.replace(/%label%/g, fixName(album.label))
2202                 foldername = foldername.replace(/%explicit%/g, fixName((album.explicit ? (foldername.indexOf(/[^%]explicit/g)>-1 ? "" : "(Explicit) ") : "")))
2203                 foldername = foldername.replace(/%genre%/g, fixName(album.genres ? (Array.isArray(album.genres) ? album.genres[0] : album.genres) : "Unknown"))
2204                 foldername = foldername.replace(/[/\\]/g, path.sep)
2205                 return foldername.trim();
2206         }catch(e){
2207                 logger.error("settingsRegexAlbum failed: "+e)
2208         }
2209
2210 }
2211
2212 function settingsRegexCover(foldername, artist, name) {
2213         foldername = foldername.replace(/%name%/g, fixName(name));
2214         foldername = foldername.replace(/%artist%/g, fixName(artist));
2215         foldername = foldername.replace(/[/\\]/g, path.sep)
2216         return foldername;
2217 }
2218
2219 function settingsRegexArtistCover(foldername, artist) {
2220         foldername = foldername.replace(/%artist%/g, fixName(artist));
2221         foldername = foldername.replace(/[/\\]/g, path.sep)
2222         return foldername;
2223 }
2224
2225 /**
2226  * Pad number with 0s so max and str have the same nuber of characters
2227  * @param str
2228  * @param max
2229  * @returns {String|string|*}
2230  */
2231 function pad(str, max) {
2232         str = str.toString();
2233         max = max.toString();
2234         return str.length < max.length || str.length == 1 ? pad("0" + str, max) : str;
2235 }
2236
2237 /**
2238  * Splits the %number%
2239  * @param string str
2240  * @return string
2241  */
2242 function splitNumber(str,total){
2243         str = str.toString();
2244         let i = str.indexOf("/");
2245         if(total && i > 0){
2246                 return str.slice(i+1, str.length);
2247         }else if(i > 0){
2248                 return str.slice(0, i);
2249         }else{
2250                 return str;
2251         }
2252         return i > 0 ? str.slice(0, i) : str;
2253 }
2254
2255 function switchReleaseType(id){
2256         switch (id.toString()) {
2257                 case "0":
2258                         return "Album";
2259                 case "1":
2260                         return "Single";
2261                 case "3":
2262                         return "EP";
2263                 default:
2264                         return "Album";
2265         }
2266 }
2267
2268 function uniqueArray(origin, removeDupes=true){
2269         destination = []
2270         Array.from(new Set(origin)).forEach(function(x){
2271                 if(destination.indexOf(x) == -1)
2272                         destination.push(x);
2273         })
2274         if (removeDupes){
2275                 destination.forEach((name,index)=>{
2276                         destination.forEach((name2,index2)=>{
2277                                 if(!(index===index2) && (name.toLowerCase().indexOf(name2.toLowerCase())!== -1)){
2278                                         destination.splice(index, 1);
2279                                 }
2280                         })
2281                 })
2282         }
2283         return destination
2284 }
2285
2286 async function asyncForEach(array, callback) {
2287         for (let index = 0; index < array.length; index++) {
2288                 await callback(array[index], index, array);
2289         }
2290 }
2291
2292 // Show crash error in console for debugging
2293 process.on('unhandledRejection', function (err) {
2294         if (err) logger.error(err.stack ? err.stack : err)
2295
2296 })
2297 process.on('uncaughtException', function (err) {
2298         if (err) logger.error(err.stack ? err.stack : err)
2299 })
2300
2301 // Exporting vars
2302 module.exports.mainFolder = mainFolder
2303 module.exports.defaultSettings = defaultSettings
2304 module.exports.defaultDownloadFolder = defaultDownloadFolder