first proposal download path selection
This commit is contained in:
@@ -5,10 +5,13 @@ import {
|
|||||||
ButtonGroup,
|
ButtonGroup,
|
||||||
CircularProgress,
|
CircularProgress,
|
||||||
Container,
|
Container,
|
||||||
|
FormControl,
|
||||||
Grid,
|
Grid,
|
||||||
IconButton,
|
IconButton,
|
||||||
InputAdornment,
|
InputAdornment,
|
||||||
|
MenuItem,
|
||||||
Paper,
|
Paper,
|
||||||
|
Select,
|
||||||
Snackbar,
|
Snackbar,
|
||||||
styled,
|
styled,
|
||||||
TextField,
|
TextField,
|
||||||
@@ -44,6 +47,9 @@ export default function Home({ socket }: Props) {
|
|||||||
const [pickedAudioFormat, setPickedAudioFormat] = useState('');
|
const [pickedAudioFormat, setPickedAudioFormat] = useState('');
|
||||||
const [pickedBestFormat, setPickedBestFormat] = useState('');
|
const [pickedBestFormat, setPickedBestFormat] = useState('');
|
||||||
|
|
||||||
|
const [downloadPath, setDownloadPath] = useState<number>(0);
|
||||||
|
const [availableDownloadPaths, setAvailableDownloadPaths] = useState<string[]>([]);
|
||||||
|
|
||||||
const [url, setUrl] = useState('');
|
const [url, setUrl] = useState('');
|
||||||
const [workingUrl, setWorkingUrl] = useState('');
|
const [workingUrl, setWorkingUrl] = useState('');
|
||||||
const [showBackdrop, setShowBackdrop] = useState(false);
|
const [showBackdrop, setShowBackdrop] = useState(false);
|
||||||
@@ -106,6 +112,14 @@ export default function Home({ socket }: Props) {
|
|||||||
})
|
})
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetch(`${window.location.protocol}//${settings.serverAddr}:${settings.serverPort}/tree`)
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(data => {
|
||||||
|
setAvailableDownloadPaths(data.flat)
|
||||||
|
})
|
||||||
|
}, [])
|
||||||
|
|
||||||
/* -------------------- component functions -------------------- */
|
/* -------------------- component functions -------------------- */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -119,6 +133,7 @@ export default function Home({ socket }: Props) {
|
|||||||
|
|
||||||
socket.emit('send-url', {
|
socket.emit('send-url', {
|
||||||
url: immediate || url || workingUrl,
|
url: immediate || url || workingUrl,
|
||||||
|
path: availableDownloadPaths[downloadPath],
|
||||||
params: settings.cliArgs.toString() + toFormatArgs(codes),
|
params: settings.cliArgs.toString() + toFormatArgs(codes),
|
||||||
})
|
})
|
||||||
setUrl('')
|
setUrl('')
|
||||||
@@ -211,25 +226,44 @@ export default function Home({ socket }: Props) {
|
|||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<TextField
|
<Grid container spacing={1}>
|
||||||
id="urlInput"
|
<Grid item xs={10}>
|
||||||
label={settings.i18n.t('urlInput')}
|
<TextField
|
||||||
variant="outlined"
|
fullWidth
|
||||||
onChange={handleUrlChange}
|
id="urlInput"
|
||||||
disabled={!status.connected || (settings.formatSelection && downloadFormats != null)}
|
label={settings.i18n.t('urlInput')}
|
||||||
InputProps={{
|
variant="outlined"
|
||||||
endAdornment: (
|
onChange={handleUrlChange}
|
||||||
<InputAdornment position="end">
|
disabled={!status.connected || (settings.formatSelection && downloadFormats != null)}
|
||||||
<label htmlFor="icon-button-file">
|
InputProps={{
|
||||||
<Input id="icon-button-file" type="file" accept=".txt" onChange={parseUrlListFile} />
|
endAdornment: (
|
||||||
<IconButton color="primary" aria-label="upload file" component="span">
|
<InputAdornment position="end">
|
||||||
<FileUpload />
|
<label htmlFor="icon-button-file">
|
||||||
</IconButton>
|
<Input id="icon-button-file" type="file" accept=".txt" onChange={parseUrlListFile} />
|
||||||
</label>
|
<IconButton color="primary" aria-label="upload file" component="span">
|
||||||
</InputAdornment>
|
<FileUpload />
|
||||||
),
|
</IconButton>
|
||||||
}}
|
</label>
|
||||||
/>
|
</InputAdornment>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={2}>
|
||||||
|
<FormControl fullWidth>
|
||||||
|
<Select
|
||||||
|
defaultValue={0}
|
||||||
|
value={availableDownloadPaths[downloadPath]}
|
||||||
|
onChange={(e) => setDownloadPath(e.target.value)}
|
||||||
|
>
|
||||||
|
|
||||||
|
{availableDownloadPaths.map((val: string, idx: number) => (
|
||||||
|
<MenuItem key={idx} value={idx}>{val}</MenuItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
<Grid container spacing={1} pt={2}>
|
<Grid container spacing={1} pt={2}>
|
||||||
<Grid item>
|
<Grid item>
|
||||||
<Button
|
<Button
|
||||||
@@ -365,7 +399,7 @@ export default function Home({ socket }: Props) {
|
|||||||
<Grid container spacing={{ xs: 2, md: 2 }} columns={{ xs: 4, sm: 8, md: 12 }} pt={2}>
|
<Grid container spacing={{ xs: 2, md: 2 }} columns={{ xs: 4, sm: 8, md: 12 }} pt={2}>
|
||||||
{ /*Super big brain flatMap moment*/
|
{ /*Super big brain flatMap moment*/
|
||||||
Array
|
Array
|
||||||
.from(messageMap)
|
.from<any>(messageMap)
|
||||||
.filter(flattened => [...flattened][0])
|
.filter(flattened => [...flattened][0])
|
||||||
.filter(flattened => [...flattened][1].toString() !== serverStates.PROG_DONE)
|
.filter(flattened => [...flattened][1].toString() !== serverStates.PROG_DONE)
|
||||||
.flatMap(message => (
|
.flatMap(message => (
|
||||||
|
|||||||
@@ -41,6 +41,12 @@ class Process {
|
|||||||
public async start(callback?: Function): Promise<this> {
|
public async start(callback?: Function): Promise<this> {
|
||||||
const sanitizedParams = this.params.filter((param: string) => availableParams.includes(param));
|
const sanitizedParams = this.params.filter((param: string) => availableParams.includes(param));
|
||||||
|
|
||||||
|
if (this.settings?.download_path) {
|
||||||
|
if (this.settings.download_path.charAt(this.settings.download_path.length - 1) !== '/') {
|
||||||
|
this.settings.download_path = `${this.settings.download_path}/`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const ytldp = spawn(this.exePath,
|
const ytldp = spawn(this.exePath,
|
||||||
['-o', `${this.settings?.download_path || 'downloads/'}%(title)s.%(ext)s`]
|
['-o', `${this.settings?.download_path || 'downloads/'}%(title)s.%(ext)s`]
|
||||||
.concat(sanitizedParams)
|
.concat(sanitizedParams)
|
||||||
|
|||||||
@@ -61,6 +61,9 @@ export async function download(socket: Socket, payload: IPayload) {
|
|||||||
payload.params.split(' ') :
|
payload.params.split(' ') :
|
||||||
payload.params;
|
payload.params;
|
||||||
|
|
||||||
|
const scopedSettings = { ...settings }
|
||||||
|
scopedSettings.download_path = payload.path;
|
||||||
|
|
||||||
let p = new Process(url, params, settings);
|
let p = new Process(url, params, settings);
|
||||||
|
|
||||||
p.start().then(downloader => {
|
p.start().then(downloader => {
|
||||||
@@ -111,7 +114,10 @@ function streamProcess(process: Process, socket: Socket) {
|
|||||||
map(stdout => formatter(String(stdout), process.getPid()))
|
map(stdout => formatter(String(stdout), process.getPid()))
|
||||||
)
|
)
|
||||||
.subscribe({
|
.subscribe({
|
||||||
next: (stdout) => socket.emit('progress', stdout),
|
next: (stdout) => {
|
||||||
|
socket.emit('progress', stdout)
|
||||||
|
log.info(`proc-${stdout.pid}`, `${stdout.progress}\t${stdout.dlSpeed}`)
|
||||||
|
},
|
||||||
complete: () => {
|
complete: () => {
|
||||||
process.kill().then(() => {
|
process.kill().then(() => {
|
||||||
emitAbort();
|
emitAbort();
|
||||||
|
|||||||
1
server/src/interfaces/IPayload.d.ts
vendored
1
server/src/interfaces/IPayload.d.ts
vendored
@@ -5,6 +5,7 @@
|
|||||||
export interface IPayload {
|
export interface IPayload {
|
||||||
url: string
|
url: string
|
||||||
params: Array<string> | string,
|
params: Array<string> | string,
|
||||||
|
path: string,
|
||||||
title?: string,
|
title?: string,
|
||||||
thumbnail?: string,
|
thumbnail?: string,
|
||||||
size?: string,
|
size?: string,
|
||||||
|
|||||||
@@ -2,7 +2,13 @@ import { splash } from './utils/logger';
|
|||||||
import { join } from 'path';
|
import { join } from 'path';
|
||||||
import { Server } from 'socket.io';
|
import { Server } from 'socket.io';
|
||||||
import { ytdlpUpdater } from './utils/updater';
|
import { ytdlpUpdater } from './utils/updater';
|
||||||
import { download, abortDownload, retrieveDownload, abortAllDownloads, getFormatsAndMetadata } from './core/downloader';
|
import {
|
||||||
|
download,
|
||||||
|
abortDownload,
|
||||||
|
retrieveDownload,
|
||||||
|
abortAllDownloads,
|
||||||
|
getFormatsAndMetadata
|
||||||
|
} from './core/downloader';
|
||||||
import { getFreeDiskSpace } from './utils/procUtils';
|
import { getFreeDiskSpace } from './utils/procUtils';
|
||||||
import { listDownloaded } from './core/downloadArchive';
|
import { listDownloaded } from './core/downloadArchive';
|
||||||
import { createServer } from 'http';
|
import { createServer } from 'http';
|
||||||
@@ -13,6 +19,7 @@ import * as serve from 'koa-static';
|
|||||||
import * as cors from '@koa/cors';
|
import * as cors from '@koa/cors';
|
||||||
import Logger from './utils/BetterLogger';
|
import Logger from './utils/BetterLogger';
|
||||||
import { ISettings } from './interfaces/ISettings';
|
import { ISettings } from './interfaces/ISettings';
|
||||||
|
import { directoryTree } from './utils/directoryUtils';
|
||||||
|
|
||||||
const app = new Koa();
|
const app = new Koa();
|
||||||
const server = createServer(app.callback());
|
const server = createServer(app.callback());
|
||||||
@@ -56,6 +63,10 @@ router.get('/archive', (ctx, next) => {
|
|||||||
router.get('/stream/:filepath', (ctx, next) => {
|
router.get('/stream/:filepath', (ctx, next) => {
|
||||||
streamer(ctx, next)
|
streamer(ctx, next)
|
||||||
})
|
})
|
||||||
|
router.get('/tree', (ctx, next) => {
|
||||||
|
ctx.body = directoryTree()
|
||||||
|
next()
|
||||||
|
})
|
||||||
|
|
||||||
// WebSocket listeners
|
// WebSocket listeners
|
||||||
io.on('connection', socket => {
|
io.on('connection', socket => {
|
||||||
|
|||||||
62
server/src/utils/directoryUtils.ts
Normal file
62
server/src/utils/directoryUtils.ts
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
import { readdirSync, statSync } from "fs";
|
||||||
|
import { ISettings } from "../interfaces/ISettings";
|
||||||
|
|
||||||
|
let settings: ISettings;
|
||||||
|
|
||||||
|
class Node {
|
||||||
|
public path: string
|
||||||
|
public children: Node[]
|
||||||
|
|
||||||
|
constructor(path: string) {
|
||||||
|
this.path = path
|
||||||
|
this.children = []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildTreeDFS(rootPath: string, directoryOnly: boolean) {
|
||||||
|
const root = new Node(rootPath)
|
||||||
|
const stack: Node[] = []
|
||||||
|
const flattened: string[] = []
|
||||||
|
|
||||||
|
stack.push(root)
|
||||||
|
flattened.push(rootPath)
|
||||||
|
|
||||||
|
while (stack.length) {
|
||||||
|
const current = stack.pop()
|
||||||
|
if (current) {
|
||||||
|
const children = readdirSync(current.path)
|
||||||
|
for (const it of children) {
|
||||||
|
const childPath = `${current.path}/${it}`
|
||||||
|
const childNode = new Node(childPath)
|
||||||
|
|
||||||
|
if (directoryOnly) {
|
||||||
|
if (statSync(childPath).isDirectory()) {
|
||||||
|
current.children.push(childNode)
|
||||||
|
stack.push(childNode)
|
||||||
|
flattened.push(childNode.path)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
current.children.push(childNode)
|
||||||
|
if (statSync(childPath).isDirectory()) {
|
||||||
|
stack.push(childNode)
|
||||||
|
flattened.push(childNode.path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
tree: root,
|
||||||
|
flat: flattened
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
settings = require('../../settings.json');
|
||||||
|
} catch (e) { }
|
||||||
|
|
||||||
|
export function directoryTree() {
|
||||||
|
const tree = buildTreeDFS(settings.download_path || 'downloads', true)
|
||||||
|
return tree
|
||||||
|
}
|
||||||
@@ -17,8 +17,8 @@ export const splash = () => {
|
|||||||
const reset = "\u001b[0m"
|
const reset = "\u001b[0m"
|
||||||
console.log(`${fg} __ ____ __ __ ______`)
|
console.log(`${fg} __ ____ __ __ ______`)
|
||||||
console.log(" __ __/ /________/ / /__ _ _____ / / / / / / _/")
|
console.log(" __ __/ /________/ / /__ _ _____ / / / / / / _/")
|
||||||
console.log(" / // / __/___/ _ / / _ \ | |/|/ / -_) _ \/ /_/ // / ")
|
console.log(" / // / __/___/ _ / / _ \\ | |/|/ / -_) _ \\/ /_/ // / ")
|
||||||
console.log(" \_, /\__/ \_,_/_/ .__/ |__,__/\__/_.__/\____/___/ ")
|
console.log(" \\_, /\\__/ \\_,_/_/ .__/ |__,__/\\__/_.__/\\____/___/ ")
|
||||||
console.log(`/___/ /_/ \n${reset}`)
|
console.log(`/___/ /_/ \n${reset}`)
|
||||||
console.log(" yt-dlp-webUI - A web-ui for yt-dlp, simply enough")
|
console.log(" yt-dlp-webUI - A web-ui for yt-dlp, simply enough")
|
||||||
console.log("---------------------------------------------------\n")
|
console.log("---------------------------------------------------\n")
|
||||||
|
|||||||
Reference in New Issue
Block a user