From b6ff444526667e71b6b6e79c1238653a2a2c32fa Mon Sep 17 00:00:00 2001 From: marcobaobao Date: Wed, 15 Jun 2022 12:02:50 +0200 Subject: [PATCH] File upload and code refactoring --- frontend/src/Home.tsx | 60 ++++++++++++++++--- frontend/src/Settings.tsx | 37 +++++++----- frontend/src/classes.ts | 5 ++ .../src/features/settings/settingsSlice.ts | 4 +- frontend/src/utils.ts | 20 ++++++- server/src/core/Process.ts | 2 +- 6 files changed, 100 insertions(+), 28 deletions(-) diff --git a/frontend/src/Home.tsx b/frontend/src/Home.tsx index a2775d6..561a1a2 100644 --- a/frontend/src/Home.tsx +++ b/frontend/src/Home.tsx @@ -6,8 +6,11 @@ import { CircularProgress, Container, Grid, + IconButton, + InputAdornment, Paper, Snackbar, + styled, TextField, Typography, } from "@mui/material"; @@ -17,7 +20,9 @@ import { StackableResult } from "./components/StackableResult"; import { connected, downloading, finished } from "./features/status/statusSlice"; import { IDLInfo, IDLInfoBase, IDownloadInfo, IMessage } from "./interfaces"; import { RootState } from "./stores/store"; -import { toFormatArgs, updateInStateMap, } from "./utils"; +import { isValidURL, toFormatArgs, updateInStateMap, } from "./utils"; +import { FileUpload } from "@mui/icons-material"; +import { Buffer } from 'buffer'; type Props = { socket: Socket @@ -74,7 +79,7 @@ export default function Home({ socket }: Props) { socket.on('info', (data: IDLInfo) => { setShowBackdrop(false) dispatch(downloading()) - updateInStateMap(data.pid, data.info, downloadInfoMap, setDownloadInfoMap); + updateInStateMap(data.pid, data.info, downloadInfoMap, setDownloadInfoMap); }) }, []) @@ -83,15 +88,19 @@ export default function Home({ socket }: Props) { socket.on('progress', (data: IMessage) => { if (data.status === 'Done!' || data.status === 'Aborted') { setShowBackdrop(false) - updateInStateMap(data.pid, 'Done!', messageMap, setMessageMap); - updateInStateMap(data.pid, 0, progressMap, setProgressMap); + updateInStateMap(data.pid, 'Done!', messageMap, setMessageMap); + updateInStateMap(data.pid, 0, progressMap, setProgressMap); socket.emit('disk-space') dispatch(finished()) return; } - updateInStateMap(data.pid, data, messageMap, setMessageMap); + updateInStateMap(data.pid, data, messageMap, setMessageMap); if (data.progress) { - updateInStateMap(data.pid, Math.ceil(Number(data.progress.replace('%', ''))), progressMap, setProgressMap) + updateInStateMap(data.pid, + Math.ceil(Number(data.progress.replace('%', ''))), + progressMap, + setProgressMap + ); } }) }, []) @@ -101,14 +110,14 @@ export default function Home({ socket }: Props) { /** * Retrive url from input, cli-arguments from checkboxes and emits via WebSocket */ - const sendUrl = () => { + const sendUrl = (immediate?: string) => { const codes = new Array(); if (pickedVideoFormat !== '') codes.push(pickedVideoFormat); if (pickedAudioFormat !== '') codes.push(pickedAudioFormat); if (pickedBestFormat !== '') codes.push(pickedBestFormat); socket.emit('send-url', { - url: url || workingUrl, + url: immediate || url || workingUrl, params: settings.cliArgs.toString() + toFormatArgs(codes), }) setUrl('') @@ -163,6 +172,27 @@ export default function Home({ socket }: Props) { socket.emit('abort-all') } + const parseUrlListFile = (event: any) => { + const urlList = event.target.files + const reader = new FileReader() + reader.addEventListener('load', $event => { + const base64 = $event.target?.result!.toString().split(',')[1] + Buffer.from(base64!, 'base64') + .toString() + .trimEnd() + .split('\n') + .filter(_url => isValidURL(_url)) + .forEach(_url => sendUrl(_url)) + }) + reader.readAsDataURL(urlList[0]) + } + + /* -------------------- styled components -------------------- */ + + const Input = styled('input')({ + display: 'none', + }); + return ( + + + ), + }} /> diff --git a/frontend/src/Settings.tsx b/frontend/src/Settings.tsx index 2500bb5..a147bac 100644 --- a/frontend/src/Settings.tsx +++ b/frontend/src/Settings.tsx @@ -19,6 +19,7 @@ import { } from "@mui/material"; import React, { useState } from "react"; import { useDispatch, useSelector } from "react-redux"; +import { debounceTime, distinctUntilChanged, map, of } from "rxjs"; import { Socket } from "socket.io-client"; import { LanguageUnion, setCliArgs, setFormatSelection, setLanguage, setServerAddr, setTheme, ThemeUnion } from "./features/settings/settingsSlice"; import { alreadyUpdated, updated } from "./features/status/statusSlice"; @@ -38,20 +39,28 @@ export default function Settings({ socket }: Props) { /** * Update the server ip address state and localstorage whenever the input value changes. - * Validate the ip-addr then set. + * Validate the ip-addr then set.s * @param e Input change event */ - const handleAddrChange = (e: React.ChangeEvent) => { - const input = e.target.value; - if (validateIP(input)) { - setInvalidIP(false) - dispatch(setServerAddr(input)) - } else if (validateDomain(input)) { - setInvalidIP(false) - dispatch(setServerAddr(input)) - } else { - setInvalidIP(true) - } + const handleAddrChange = (event: any) => { + const $serverAddr = of(event) + .pipe( + map(event => event.target.value), + debounceTime(500), + distinctUntilChanged() + ) + .subscribe(addr => { + if (validateIP(addr)) { + setInvalidIP(false) + dispatch(setServerAddr(addr)) + } else if (validateDomain(addr)) { + setInvalidIP(false) + dispatch(setServerAddr(addr)) + } else { + setInvalidIP(true) + } + }) + return $serverAddr.unsubscribe() } /** @@ -79,7 +88,6 @@ export default function Settings({ socket }: Props) { return ( - {/* Chart */} ws://, }} @@ -165,6 +173,7 @@ export default function Settings({ socket }: Props) { dispatch(setCliArgs(settings.cliArgs.toggleExtractAudio()))} + disabled={settings.formatSelection} /> } label={settings.i18n.t('extractAudioCheckbox')} diff --git a/frontend/src/classes.ts b/frontend/src/classes.ts index 0ab92c3..80321b7 100644 --- a/frontend/src/classes.ts +++ b/frontend/src/classes.ts @@ -16,6 +16,11 @@ export class CliArguments { return this; } + public disableExtractAudio() { + this._extractAudio = false; + return this; + } + public get noMTime(): boolean { return this._noMTime; } diff --git a/frontend/src/features/settings/settingsSlice.ts b/frontend/src/features/settings/settingsSlice.ts index 0eb21d3..c92d41d 100644 --- a/frontend/src/features/settings/settingsSlice.ts +++ b/frontend/src/features/settings/settingsSlice.ts @@ -15,10 +15,10 @@ export interface SettingsState { } const initialState: SettingsState = { - serverAddr: localStorage.getItem("server-addr") || "localhost", + serverAddr: localStorage.getItem("server-addr") || window.location.hostname, language: (localStorage.getItem("language") || "english") as LanguageUnion, theme: (localStorage.getItem("theme") || "light") as ThemeUnion, - cliArgs: localStorage.getItem("cli-args") ? new CliArguments().fromString(localStorage.getItem("cli-args")) : new CliArguments(false, true), + cliArgs: localStorage.getItem("cli-args") ? new CliArguments().fromString(localStorage.getItem("cli-args") ?? "") : new CliArguments(false, true), i18n: new I18nBuilder((localStorage.getItem("language") || "english")), formatSelection: localStorage.getItem("format-selection") === "true", } diff --git a/frontend/src/utils.ts b/frontend/src/utils.ts index dc909d1..e01e772 100644 --- a/frontend/src/utils.ts +++ b/frontend/src/utils.ts @@ -24,6 +24,22 @@ export function validateDomain(domainName: string): boolean { return domainRegex.test(domainName) || domainName === 'localhost' } +/** + * Validate a domain via regex. + * Exapmples + * - http://example.com + * - https://example.com + * - http://www.example.com + * - https://www.example.com + * - http://10.0.0.1/[something]/[something-else] + * @param url + * @returns url validity test + */ +export function isValidURL(url: string): boolean { + let urlRegex = /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()!@:%_\+.~#?&\/\/=]*)/ + return urlRegex.test(url) +} + export function ellipsis(str: string, lim: number): string { if (str) { return str.length > lim ? `${str.substring(0, lim)}...` : str @@ -37,7 +53,7 @@ export function ellipsis(str: string, lim: number): string { * @returns download speed in KiB/s */ export function detectSpeed(str: string): number { - let effective = str.match(/[\d,]+(\.\d+)?/)[0] + let effective = str.match(/[\d,]+(\.\d+)?/)![0] const unit = str.replace(effective, '') switch (unit) { case 'MiB/s': @@ -57,7 +73,7 @@ export function detectSpeed(str: string): number { * @param callback calls React's StateAction function with the newly created Map * @param remove -optional- is it an update or a deletion operation? */ -export const updateInStateMap = (k: number, v: any, target: Map, callback: Function, remove: boolean = false) => { +export function updateInStateMap(k: K, v: any, target: Map, callback: Function, remove: boolean = false) { if (remove) { const _target = target _target.delete(k) diff --git a/server/src/core/Process.ts b/server/src/core/Process.ts index e1861ba..89ad016 100644 --- a/server/src/core/Process.ts +++ b/server/src/core/Process.ts @@ -110,7 +110,7 @@ class Process { */ async kill() { spawn('kill', [String(this.pid)]).on('exit', () => { - log.info('db', `Deleted ${this.pid} because SIGKILL`) + log.info('proc', `Stopped ${this.pid} because SIGKILL`) }); }