Refactoring and small optimizations
This commit is contained in:
@@ -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"> Extract audio</label>
|
<label htmlFor="-x"> Extract audio</label>
|
||||||
|
<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"> Don't set file modification time</label>
|
<label htmlFor="-x"> 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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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],
|
||||||
|
|||||||
@@ -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)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
}
|
||||||
@@ -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) => {
|
||||||
|
|||||||
@@ -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`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
})
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user