Refactoring and small optimizations

This commit is contained in:
2022-01-30 12:18:36 +01:00
parent 9dcfade3fd
commit 33d567aaed
10 changed files with 91 additions and 34 deletions

View File

@@ -9,7 +9,7 @@ import {
Button, Button,
ButtonGroup, ButtonGroup,
} from "react-bootstrap"; } from "react-bootstrap";
import { X } from "react-bootstrap-icons"; import { X, HddFill } from "react-bootstrap-icons";
import { buildMessage, updateInStateMap, validateDomain, validateIP } from "./utils"; import { buildMessage, updateInStateMap, validateDomain, validateIP } from "./utils";
import { IDLInfo, IDLInfoBase, IMessage } from "./interfaces"; import { IDLInfo, IDLInfoBase, IMessage } from "./interfaces";
import { MessageToast } from "./components/MessageToast"; import { MessageToast } from "./components/MessageToast";
@@ -31,6 +31,7 @@ export function App() {
const [invalidIP, setInvalidIP] = useState(false); const [invalidIP, setInvalidIP] = useState(false);
const [updatedBin, setUpdatedBin] = useState(false); const [updatedBin, setUpdatedBin] = useState(false);
const [showSettings, setShowSettings] = useState(false); const [showSettings, setShowSettings] = useState(false);
const [freeDiskSpace, setFreeDiskSpace] = useState('');
const [darkMode, setDarkMode] = useState(localStorage.getItem('theme') === 'dark'); const [darkMode, setDarkMode] = useState(localStorage.getItem('theme') === 'dark');
const xaInput = useRef(null); const xaInput = useRef(null);
@@ -53,6 +54,7 @@ export function App() {
socket.on('connect', () => { socket.on('connect', () => {
setShowToast(true) setShowToast(true)
socket.emit('fetch-jobs') socket.emit('fetch-jobs')
socket.emit('disk-space')
}) })
return () => { return () => {
socket.disconnect() socket.disconnect()
@@ -80,6 +82,7 @@ export function App() {
setHalt(false); setHalt(false);
updateInStateMap(data.pid, 'Done!', messageMap, setMessageMap); updateInStateMap(data.pid, 'Done!', messageMap, setMessageMap);
updateInStateMap(data.pid, 0, progressMap, setProgressMap); updateInStateMap(data.pid, 0, progressMap, setProgressMap);
socket.emit('disk-space')
return; return;
} }
updateInStateMap(data.pid, buildMessage(data), messageMap, setMessageMap); updateInStateMap(data.pid, buildMessage(data), messageMap, setMessageMap);
@@ -104,6 +107,13 @@ export function App() {
document.body.classList.remove('dark'); document.body.classList.remove('dark');
}, [darkMode]) }, [darkMode])
/* Get disk free space */
useEffect(() => {
socket.on('free-space', (res: string) => {
setFreeDiskSpace(res)
})
}, [])
/* -------------------- component functions -------------------- */ /* -------------------- component functions -------------------- */
/** /**
@@ -282,6 +292,10 @@ export function App() {
<Button onClick={() => sendUrl()} disabled={false}>Start</Button> <Button onClick={() => sendUrl()} disabled={false}>Start</Button>
<Button active onClick={() => abort()}>Abort all</Button> <Button active onClick={() => abort()}>Abort all</Button>
</ButtonGroup> </ButtonGroup>
<span className="text-muted float-end pt-3">
<HddFill></HddFill> {' '}
<small>{freeDiskSpace ? freeDiskSpace : '-'}</small>
</span>
</div> </div>
@@ -304,21 +318,22 @@ export function App() {
/> />
<InputGroup.Text>:3022</InputGroup.Text> <InputGroup.Text>:3022</InputGroup.Text>
</InputGroup> </InputGroup>
<Button onClick={() => updateBinary()} disabled={halt}>
Update yt-dlp binary
</Button>{' '}
<Button variant={darkMode ? 'light' : 'dark'} onClick={() => toggleTheme()}>
{darkMode ? 'Light theme' : 'Dark theme'}
</Button>
<div className="pt-2"> <div className="pt-2">
<input type="checkbox" name="-x" defaultChecked={cliArgs.extractAudio} ref={xaInput} <input type="checkbox" name="-x" defaultChecked={cliArgs.extractAudio} ref={xaInput}
onClick={setExtractAudio} /> onClick={setExtractAudio} />
<label htmlFor="-x">&nbsp;Extract audio</label> <label htmlFor="-x">&nbsp;Extract audio</label>
&nbsp;&nbsp; <div></div>
<input type="checkbox" name="-nomtime" defaultChecked={cliArgs.noMTime} ref={mtInput} <input type="checkbox" name="-nomtime" defaultChecked={cliArgs.noMTime} ref={mtInput}
onClick={setNoMTime} /> onClick={setNoMTime} />
<label htmlFor="-x">&nbsp;Don't set file modification time</label> <label htmlFor="-x">&nbsp;Don't set file modification time</label>
</div> </div>
<br />
<Button size="sm" onClick={() => updateBinary()} disabled={halt}>
Update yt-dlp binary
</Button>{' '}
<Button size="sm" variant={darkMode ? 'light' : 'dark'} onClick={() => toggleTheme()}>
{darkMode ? 'Light theme' : 'Dark theme'}
</Button>
</div> : </div> :
null null
} }

View File

@@ -3,8 +3,8 @@
"version": "1.1.0", "version": "1.1.0",
"description": "A terrible webUI for yt-dlp, all-in-one solution.", "description": "A terrible webUI for yt-dlp, all-in-one solution.",
"scripts": { "scripts": {
"start": "node server.js", "start": "node dist/main.js",
"dev": "nodemon app.js", "dev": "tsc --build -w",
"build": "parcel build ./frontend/index.html --dist-dir ./dist/frontend", "build": "parcel build ./frontend/index.html --dist-dir ./dist/frontend",
"build-server": "tsc --build", "build-server": "tsc --build",
"build-all": "tsc --build && npm run build && npm run fetch", "build-all": "tsc --build && npm run build && npm run fetch",

View File

@@ -52,7 +52,14 @@ class Process {
log.info('proc', `Spawned a new process, pid: ${this.pid}`) log.info('proc', `Spawned a new process, pid: ${this.pid}`)
await insertDownload(this.url, this.info?.title, this.info?.thumbnail, null, this.pid); await insertDownload(
this.url,
this.info?.title,
this.info?.thumbnail,
null,
this.params.reduce((prev, next) => `${prev} ${next}`),
this.pid
);
return this; return this;
} }

View File

@@ -95,7 +95,9 @@ export async function retriveDownload(socket: Socket) {
if (coldRestart) { if (coldRestart) {
coldRestart = false; coldRestart = false;
let downloads = await pruneDownloads(); let downloads = await pruneDownloads();
downloads = [... new Set(downloads)]; console.log(downloads)
// sanitize
downloads = [... new Set(downloads.filter(el => el !== undefined))];
log.info('dl', `Cold restart, retrieving ${downloads.length} jobs`) log.info('dl', `Cold restart, retrieving ${downloads.length} jobs`)
for (const entry of downloads) { for (const entry of downloads) {
if (entry) { if (entry) {
@@ -180,7 +182,7 @@ const formatter = (stdout: string, pid: number) => {
switch (status) { switch (status) {
case 'download': case 'download':
return { return {
status: cleanStdout[0].replace(/\[|\]|\r/g, ''), status: 'download',
progress: cleanStdout[1], progress: cleanStdout[1],
size: cleanStdout[3], size: cleanStdout[3],
dlSpeed: cleanStdout[5], dlSpeed: cleanStdout[5],

View File

@@ -1,5 +1,6 @@
import { v1 } from 'uuid'; import { v1 } from 'uuid';
import { existsInProc } from '../utils/procUtils'; import { existsInProc } from '../utils/procUtils';
import { IRecord } from '../interfaces/IRecord';
import Logger from '../utils/BetterLogger'; import Logger from '../utils/BetterLogger';
const db = require('better-sqlite3')('downloads.db'); const db = require('better-sqlite3')('downloads.db');
@@ -17,7 +18,8 @@ export async function init() {
thumbnail text, thumbnail text,
created date, created date,
size text, size text,
process_pid int NOT NULL, params text,
pid int NOT NULL,
PRIMARY KEY (uid) PRIMARY KEY (uid)
)`) )`)
} catch (e) { } catch (e) {
@@ -39,21 +41,22 @@ export async function get_db(): Promise<any> {
* @param {string} title the title fetched by the info process * @param {string} title the title fetched by the info process
* @param {string} thumbnail the thumbnail url fetched by the info process * @param {string} thumbnail the thumbnail url fetched by the info process
* @param {string} size optional - the download size * @param {string} size optional - the download size
* @param {string} params optional - the download parameters, cli arguments
* @param {number} PID the pid of the downloader * @param {number} PID the pid of the downloader
* @returns {Promise<string>} the download UUID * @returns {Promise<string>} the download UUID
*/ */
export async function insertDownload(url: string, title: string, thumbnail: string, size: string, PID: number): Promise<string> { export async function insertDownload(url: string, title: string, thumbnail: string, size: string, params: string, PID: number): Promise<string> {
const uid = v1() const uid = v1()
try { try {
db db
.prepare(` .prepare(`
INSERT INTO downloads INSERT INTO downloads
(uid, url, title, thumbnail, size, process_pid) (uid, url, title, thumbnail, size, params, pid)
VALUES (?, ?, ?, ?, ?, ?)` VALUES (?, ?, ?, ?, ?, ?, ?)`
) )
.run(uid, url, title, thumbnail, size, PID) .run(uid, url, title, thumbnail, size, params, PID)
} catch (error) { } catch (error) {
log.err('db', 'some error occourred') log.err('db', error)
} }
return uid return uid
@@ -63,7 +66,7 @@ export async function insertDownload(url: string, title: string, thumbnail: stri
* Retrieve all downloads from the database * Retrieve all downloads from the database
* @returns {ArrayLike} a collection of results * @returns {ArrayLike} a collection of results
*/ */
export async function retrieveAll(): Promise<any> { export async function retrieveAll(): Promise<Array<IRecord>> {
return db return db
.prepare('SELECT * FROM downloads') .prepare('SELECT * FROM downloads')
.all() .all()
@@ -81,20 +84,20 @@ export async function deleteDownloadById(uid: string) {
* Delete a download by its pid * Delete a download by its pid
* @param {string} pid the to-be-deleted download pid * @param {string} pid the to-be-deleted download pid
*/ */
export async function deleteDownloadByPID(PID) { export async function deleteDownloadByPID(pid: number) {
db.prepare(`DELETE FROM downloads WHERE process_pid=${PID}`).run() db.prepare(`DELETE FROM downloads WHERE pid=${pid}`).run()
} }
/** /**
* Deletes the downloads that aren't active anymore * Deletes the downloads that aren't active anymore
* @returns {Promise<ArrayLike>} * @returns {Promise<ArrayLike>}
*/ */
export async function pruneDownloads(): Promise<any> { export async function pruneDownloads(): Promise<Array<IRecord>> {
const all = await retrieveAll() const all = await retrieveAll()
return all.map(job => { return all.map(job => {
if (existsInProc(job.process_pid)) { if (existsInProc(job.pid)) {
return job return job
} }
deleteDownloadByPID(job.process_pid) deleteDownloadByPID(job.pid)
}) })
} }

View File

@@ -1,3 +1,7 @@
/**
* Represent a download payload sent by the frontend
*/
export interface IPayload { export interface IPayload {
url: string url: string
params: Array<string> | string, params: Array<string> | string,

View File

@@ -0,0 +1,14 @@
/**
* Represent a download db record
*/
export interface IRecord {
uid: string,
url: string,
title: string,
thumbnail: string,
created: Date,
size: string,
pid: number,
params: string,
}

View File

@@ -9,6 +9,7 @@ import { ytdlpUpdater } from './utils/updater';
import { download, abortDownload, retriveDownload, abortAllDownloads } from './core/downloader'; import { download, abortDownload, retriveDownload, abortAllDownloads } from './core/downloader';
import Logger from './utils/BetterLogger'; import Logger from './utils/BetterLogger';
import { retrieveAll, init } from './db/db'; import { retrieveAll, init } from './db/db';
import { getFreeDiskSpace } from './utils/procUtils';
const app = new Koa() const app = new Koa()
const log = new Logger() const log = new Logger()
@@ -45,6 +46,9 @@ io.on('connection', socket => {
socket.on('retrieve-jobs', () => { socket.on('retrieve-jobs', () => {
retriveDownload(socket) retriveDownload(socket)
}) })
socket.on('disk-space', () => {
getFreeDiskSpace(socket)
})
}) })
io.on('disconnect', (socket) => { io.on('disconnect', (socket) => {

View File

@@ -12,9 +12,9 @@ class Logger {
* @param {string} proto the context/protocol/section outputting the message * @param {string} proto the context/protocol/section outputting the message
* @param {string} args the acutal message * @param {string} args the acutal message
*/ */
info(proto: string, args: string) { public info(proto: string, args: string) {
process.stdout.write( process.stdout.write(
this.#__formatter(proto, args) this.formatter(proto, args)
) )
} }
/** /**
@@ -22,9 +22,9 @@ class Logger {
* @param {string} proto the context/protocol/section outputting the message * @param {string} proto the context/protocol/section outputting the message
* @param {string} args the acutal message * @param {string} args the acutal message
*/ */
warn(proto: string, args: string) { public warn(proto: string, args: string) {
process.stdout.write( process.stdout.write(
`${ansi.yellow}${this.#__formatter(proto, args)}${ansi.reset}` `${ansi.yellow}${this.formatter(proto, args)}${ansi.reset}`
) )
} }
/** /**
@@ -32,13 +32,13 @@ class Logger {
* @param {string} proto the context/protocol/section outputting the message * @param {string} proto the context/protocol/section outputting the message
* @param {string} args the acutal message * @param {string} args the acutal message
*/ */
err(proto: string, args: string) { public err(proto: string, args: string) {
process.stdout.write( process.stdout.write(
`${ansi.red}${this.#__formatter(proto, args)}${ansi.reset}` `${ansi.red}${this.formatter(proto, args)}${ansi.reset}`
) )
} }
#__formatter(proto: any, args: any) { private formatter(proto: any, args: any) {
return `[${proto}]\t${args}\n` return `[${proto}]\t${args}\n`
} }
} }

View File

@@ -1,6 +1,6 @@
import { spawn } from 'child_process'; import { exec, spawn } from 'child_process';
import fs = require('fs'); import fs = require('fs');
import net = require('net'); // import net = require('net');
import { logger } from './logger'; import { logger } from './logger';
/** /**
@@ -39,3 +39,11 @@ export async function killProcess(pid: number) {
logger('proc', `Successfully killed yt-dlp process, pid: ${pid}`) logger('proc', `Successfully killed yt-dlp process, pid: ${pid}`)
}) })
} }
export function getFreeDiskSpace(socket: any) {
let buffer: string = '';
let message: string = 'free-space';
exec("df -h / | tail -1 | awk '{print $4}'", (_, stdout) => {
socket.emit(message, stdout)
})
}