diff --git a/server/package.json b/server/package.json index 0cd8ad5..9a12347 100644 --- a/server/package.json +++ b/server/package.json @@ -15,7 +15,7 @@ "dependencies": { "cookie-parser": "1.4.5", "debug": "2.6.9", - "deemix": "0.0.2", + "deemix": "0.0.3", "deezer-js": "0.0.10", "dotenv": "8.2.0", "express": "4.17.1", diff --git a/server/src/main.ts b/server/src/main.ts index 5c58977..65c97fc 100644 --- a/server/src/main.ts +++ b/server/src/main.ts @@ -1,12 +1,110 @@ // @ts-expect-error import deemix from 'deemix' +import fs from 'fs' +import {sep} from 'path' +import { wss } from './app' +import WebSocket from 'ws' -export const loadSettings = deemix.settings.load +const Downloader = deemix.downloader.Downloader +const { Single, Collection, Convertable } = deemix.types.downloadObjects export const defaultSettings: any = deemix.settings.DEFAULTS -export let settings: any = loadSettings() +export const configFolder: string = deemix.utils.localpaths.getConfigFolder() +export let settings: any = deemix.settings.load(configFolder) export let sessionDZ: any = {} +let deemixPlugins = {} + +export const listener = { + send: function(key:string, data:any){ + console.log(key, data) + wss.clients.forEach(client => { + if (client.readyState === WebSocket.OPEN) { + client.send(JSON.stringify({key, data})) + } + }) + } +} + export function saveSettings(newSettings: any) { - deemix.settings.save(newSettings) + deemix.settings.save(newSettings, configFolder) settings = newSettings } + +export let queueOrder: string[] = [] +export let queue: any = {} +export let currentJob: any = null + +export async function addToQueue(dz: any, url: string, bitrate: number){ + if (!dz.logged_in) throw new NotLoggedIn + console.log(`Adding ${url} to queue`) + let downloadObj = await deemix.generateDownloadObject(dz, url, bitrate, deemixPlugins, listener) + + // Check if element is already in queue + if (queueOrder.includes(downloadObj.uuid)) + throw new AlreadyInQueue(downloadObj.getEssentialDict()) + + // Save queue status when adding something to the queue + if (!fs.existsSync(configFolder+'queue')) fs.mkdirSync(configFolder+'queue') + + queueOrder.push(downloadObj.uuid) + fs.writeFileSync(configFolder+`queue${sep}order.json`, JSON.stringify(queueOrder)) + queue[downloadObj.uuid] = downloadObj.getEssentialDict() + fs.writeFileSync(configFolder+`queue${sep}${downloadObj.uuid}.json`, JSON.stringify(downloadObj.toDict())) + listener.send('addedToQueue', downloadObj.getSlimmedDict()) + + startQueue(dz) + return queue[downloadObj.uuid] +} + +async function startQueue(dz: any): Promise{ + do { + if (currentJob !== null || queueOrder.length === 0){ + // Should not start another download + return null + } + currentJob = true // lock currentJob + + let currentUUID: string | undefined = queueOrder.shift() + let currentItem: any = JSON.parse(fs.readFileSync(configFolder+`queue${sep}${currentUUID}.json`).toString()) + let downloadObject: any + switch (currentItem.__type__) { + case 'Single': + downloadObject = new Single(currentItem) + break; + case 'Collection': + downloadObject = new Collection(currentItem) + break; + case 'Convertable': + downloadObject = new Convertable(currentItem) + // Convert object here + break; + } + currentJob = new Downloader(dz, downloadObject, settings, listener) + listener.send('startDownload', currentUUID) + await currentJob.start() + currentJob = null + } while (queueOrder.length) +} + +class QueueError extends Error { + constructor(message: string) { + super(message) + this.name = "QueueError" + } +} + +class AlreadyInQueue extends QueueError { + item: any + constructor(dwObj: any) { + super(`${dwObj.artist} - ${dwObj.title} is already in queue.`) + this.name = "AlreadyInQueue" + this.item = dwObj + } +} + +class NotLoggedIn extends QueueError { + constructor() { + super(`You must be logged in to start a download.`) + this.name = "NotLoggedIn" + } +} diff --git a/server/src/routes/api/post/addToQueue.ts b/server/src/routes/api/post/addToQueue.ts index e69de29..7f29440 100644 --- a/server/src/routes/api/post/addToQueue.ts +++ b/server/src/routes/api/post/addToQueue.ts @@ -0,0 +1,38 @@ +// @ts-expect-error +import { Deezer } from 'deezer-js' +import { ApiHandler } from '../../../types' +import { sessionDZ, addToQueue, settings } from '../../../main' + +const path: ApiHandler['path'] = '/addToQueue' + +const handler: ApiHandler['handler'] = async (req, res) => { + if (!sessionDZ[req.session.id]) sessionDZ[req.session.id] = new Deezer() + const dz = sessionDZ[req.session.id] + + const url = req.query.url + let bitrate = req.query.bitrate + if (bitrate === 'null') bitrate = settings.maxBitrate + let obj: any; + + try { + obj = await addToQueue(dz, url, bitrate) + } catch (e){ + switch (e.name) { + case 'AlreadyInQueue': + res.send({result: false, errid: e.name, data: {url, bitrate, obj: e.data}}) + break + default: + console.error(e) + case 'NotLoggedIn': + res.send({result: false, errid: e.name, data: {url, bitrate}}) + break + } + return + } + + res.send({result: true, data: {url, bitrate, obj}}) +} + +const apiHandler: ApiHandler = { path, handler } + +export default apiHandler diff --git a/server/src/routes/api/post/index.ts b/server/src/routes/api/post/index.ts index 569ec2e..0312a5c 100644 --- a/server/src/routes/api/post/index.ts +++ b/server/src/routes/api/post/index.ts @@ -1,3 +1,4 @@ import loginArl from './login-arl' +import addToQueue from './addToQueue' -export default [loginArl] +export default [loginArl, addToQueue] diff --git a/server/src/websocket/index.ts b/server/src/websocket/index.ts index 29541a3..76e1018 100644 --- a/server/src/websocket/index.ts +++ b/server/src/websocket/index.ts @@ -6,14 +6,6 @@ import wsModules from './modules' // ? Is this needed? // ? https://github.com/websockets/ws#how-to-detect-and-close-broken-connections -export const broadcast = function(wss:WsServer, key:string, data:any) { - wss.clients.forEach(client => { - if (client.readyState === WebSocket.OPEN) { - client.send(JSON.stringify({key, data})) - } - }) -} - export const registerWebsocket = (wss: WsServer) => { wss.on('connection', ws => { ws.on('message', (message)=>{ diff --git a/server/src/websocket/modules/saveSettings.ts b/server/src/websocket/modules/saveSettings.ts index af75b97..64adf8d 100644 --- a/server/src/websocket/modules/saveSettings.ts +++ b/server/src/websocket/modules/saveSettings.ts @@ -1,7 +1,6 @@ import { Server as WsServer } from 'ws' import { consoleInfo } from '../../helpers/errors' -import { saveSettings } from '../../main' -import { broadcast } from '../index' +import { saveSettings, listener } from '../../main' const eventName = 'saveSettings' @@ -9,7 +8,7 @@ const cb = (data: any, ws: any, wss: WsServer) => { const {settings, spotifySettings} = data saveSettings(settings) consoleInfo('Settings saved') - broadcast(wss, 'updateSettings', {settings, spotifySettings}) + listener.send('updateSettings', {settings, spotifySettings}) } export default { eventName, cb } diff --git a/server/yarn.lock b/server/yarn.lock index 679cc84..73f3306 100644 --- a/server/yarn.lock +++ b/server/yarn.lock @@ -1878,10 +1878,10 @@ decompress-response@^6.0.0: dependencies: mimic-response "^3.1.0" -deemix@0.0.2: - version "0.0.2" - resolved "https://registry.yarnpkg.com/deemix/-/deemix-0.0.2.tgz#3b375834327d4d6bd1d1db072db976ced86746ee" - integrity sha512-NhTAM2LcoNxit6GiQYlUUO0YaGUMWtN8brGt9HO5mkvbn2cKojF1k9BulmUt/38NoiVvfkqTEtSQD8RtD4qPIA== +deemix@0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/deemix/-/deemix-0.0.3.tgz#a32b1b3c8e99fe26ea7a326658e49b4892c96dd5" + integrity sha512-cUsfliHRXWLOYB7K+UCN0Lmx3d4ueWfaQ/Xinhg8/cHetifJC4/CxD8YDdBArI7Fl3StkJVGt45BajU/z00k6Q== dependencies: async "^3.2.0" browser-id3-writer "^4.4.0"