converted dir tree
This commit is contained in:
@@ -23,18 +23,16 @@ import {
|
|||||||
BrowserRouter as Router, Link, Route,
|
BrowserRouter as Router, Link, Route,
|
||||||
Routes
|
Routes
|
||||||
} from 'react-router-dom';
|
} from 'react-router-dom';
|
||||||
import { io } from "socket.io-client";
|
|
||||||
import ArchivedDownloads from "./Archived";
|
import ArchivedDownloads from "./Archived";
|
||||||
import { AppBar } from "./components/AppBar";
|
import { AppBar } from "./components/AppBar";
|
||||||
import { Drawer } from "./components/Drawer";
|
import { Drawer } from "./components/Drawer";
|
||||||
import Home from "./Home";
|
import Home from "./Home";
|
||||||
import Settings from "./Settings";
|
import Settings from "./Settings";
|
||||||
import { RootState, store } from './stores/store';
|
import { RootState, store } from './stores/store';
|
||||||
import { getWebSocketEndpoint } from "./utils";
|
import { formatGiB, getWebSocketEndpoint } from "./utils";
|
||||||
|
|
||||||
function AppContent() {
|
function AppContent() {
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const [freeDiskSpace, setFreeDiskSpace] = useState('');
|
|
||||||
|
|
||||||
const settings = useSelector((state: RootState) => state.settings)
|
const settings = useSelector((state: RootState) => state.settings)
|
||||||
const status = useSelector((state: RootState) => state.status)
|
const status = useSelector((state: RootState) => state.status)
|
||||||
@@ -58,11 +56,6 @@ function AppContent() {
|
|||||||
setOpen(!open);
|
setOpen(!open);
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Get disk free space */
|
|
||||||
useEffect(() => {
|
|
||||||
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ThemeProvider theme={theme}>
|
<ThemeProvider theme={theme}>
|
||||||
<Router>
|
<Router>
|
||||||
@@ -96,14 +89,14 @@ function AppContent() {
|
|||||||
yt-dlp WebUI
|
yt-dlp WebUI
|
||||||
</Typography>
|
</Typography>
|
||||||
{
|
{
|
||||||
freeDiskSpace ?
|
status.freeSpace ?
|
||||||
<div style={{
|
<div style={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
flexWrap: 'wrap',
|
flexWrap: 'wrap',
|
||||||
}}>
|
}}>
|
||||||
<Storage />
|
<Storage />
|
||||||
<span> {freeDiskSpace} </span>
|
<span> {formatGiB(status.freeSpace)} </span>
|
||||||
</div>
|
</div>
|
||||||
: null
|
: null
|
||||||
}
|
}
|
||||||
@@ -145,20 +138,6 @@ function AppContent() {
|
|||||||
<ListItemText primary="Home" />
|
<ListItemText primary="Home" />
|
||||||
</ListItemButton>
|
</ListItemButton>
|
||||||
</Link>
|
</Link>
|
||||||
{/* Next release: list downloaded files */}
|
|
||||||
{/* <Link to={'/downloaded'} style={
|
|
||||||
{
|
|
||||||
textDecoration: 'none',
|
|
||||||
color: mode === 'dark' ? '#ffffff' : '#000000DE'
|
|
||||||
}
|
|
||||||
}>
|
|
||||||
<ListItemButton disabled={status.downloading}>
|
|
||||||
<ListItemIcon>
|
|
||||||
<Download />
|
|
||||||
</ListItemIcon>
|
|
||||||
<ListItemText primary="Downloaded" />
|
|
||||||
</ListItemButton>
|
|
||||||
</Link> */}
|
|
||||||
<Link to={'/settings'} style={
|
<Link to={'/settings'} style={
|
||||||
{
|
{
|
||||||
textDecoration: 'none',
|
textDecoration: 'none',
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import { useDispatch, useSelector } from "react-redux";
|
|||||||
import { CliArguments } from "./classes";
|
import { CliArguments } from "./classes";
|
||||||
import { StackableResult } from "./components/StackableResult";
|
import { StackableResult } from "./components/StackableResult";
|
||||||
import { serverStates } from "./events";
|
import { serverStates } from "./events";
|
||||||
import { connected } from "./features/status/statusSlice";
|
import { connected, setFreeSpace } from "./features/status/statusSlice";
|
||||||
import { I18nBuilder } from "./i18n";
|
import { I18nBuilder } from "./i18n";
|
||||||
import { IDLMetadata, IMessage } from "./interfaces";
|
import { IDLMetadata, IMessage } from "./interfaces";
|
||||||
import { RPCClient } from "./rpcClient";
|
import { RPCClient } from "./rpcClient";
|
||||||
@@ -73,10 +73,6 @@ export default function Home({ socket }: Props) {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
socket.onopen = () => {
|
socket.onopen = () => {
|
||||||
dispatch(connected())
|
dispatch(connected())
|
||||||
console.log('oke')
|
|
||||||
socket.send('fetch-jobs')
|
|
||||||
socket.send('disk-space')
|
|
||||||
socket.send('retrieve-jobs')
|
|
||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
@@ -85,6 +81,11 @@ export default function Home({ socket }: Props) {
|
|||||||
return () => clearInterval(interval)
|
return () => clearInterval(interval)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
client.freeSpace()
|
||||||
|
.then(bytes => dispatch(setFreeSpace(bytes.result)))
|
||||||
|
}, [])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
socket.onmessage = (event) => {
|
socket.onmessage = (event) => {
|
||||||
const res = client.decode(event.data)
|
const res = client.decode(event.data)
|
||||||
@@ -106,10 +107,9 @@ export default function Home({ socket }: Props) {
|
|||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetch(`${window.location.protocol}//${settings.serverAddr}:${settings.serverPort}/tree`)
|
client.directoryTree()
|
||||||
.then(res => res.json())
|
|
||||||
.then(data => {
|
.then(data => {
|
||||||
setAvailableDownloadPaths(data.flat)
|
setAvailableDownloadPaths(data.result)
|
||||||
})
|
})
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
@@ -150,10 +150,15 @@ export default function Home({ socket }: Props) {
|
|||||||
setPickedVideoFormat('')
|
setPickedVideoFormat('')
|
||||||
setPickedBestFormat('')
|
setPickedBestFormat('')
|
||||||
|
|
||||||
setTimeout(() => {
|
setShowBackdrop(true)
|
||||||
resetInput()
|
|
||||||
setShowBackdrop(true)
|
client.formats(url)
|
||||||
}, 250)
|
?.then(formats => {
|
||||||
|
console.log(formats)
|
||||||
|
setDownloadFormats(formats.result)
|
||||||
|
setShowBackdrop(false)
|
||||||
|
resetInput()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -4,73 +4,73 @@ import { IMessage } from "../interfaces";
|
|||||||
import { ellipsis } from "../utils";
|
import { ellipsis } from "../utils";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
title: string,
|
title: string,
|
||||||
thumbnail: string,
|
thumbnail: string,
|
||||||
resolution: string
|
resolution: string
|
||||||
percentage: string,
|
percentage: string,
|
||||||
size: number,
|
size: number,
|
||||||
speed: number,
|
speed: number,
|
||||||
stopCallback: VoidFunction,
|
stopCallback: VoidFunction,
|
||||||
}
|
}
|
||||||
|
|
||||||
export function StackableResult({
|
export function StackableResult({
|
||||||
title,
|
title,
|
||||||
thumbnail,
|
thumbnail,
|
||||||
resolution,
|
resolution,
|
||||||
percentage,
|
percentage,
|
||||||
speed,
|
speed,
|
||||||
size,
|
size,
|
||||||
stopCallback
|
stopCallback
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const guessResolution = (xByY: string): any => {
|
const guessResolution = (xByY: string): any => {
|
||||||
if (!xByY) return null;
|
if (!xByY) return null;
|
||||||
if (xByY.includes('4320')) return (<EightK color="primary" />);
|
if (xByY.includes('4320')) return (<EightK color="primary" />);
|
||||||
if (xByY.includes('2160')) return (<FourK color="primary" />);
|
if (xByY.includes('2160')) return (<FourK color="primary" />);
|
||||||
if (xByY.includes('1080')) return (<Hd color="primary" />);
|
if (xByY.includes('1080')) return (<Hd color="primary" />);
|
||||||
if (xByY.includes('720')) return (<Sd color="primary" />);
|
if (xByY.includes('720')) return (<Sd color="primary" />);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const percentageToNumber = () => Number(percentage.replace('%', ''))
|
const percentageToNumber = () => Number(percentage.replace('%', ''))
|
||||||
|
|
||||||
const roundMB = (bytes: number) => `${(bytes / 1_000_000).toFixed(2)}MiB`
|
const roundMB = (bytes: number) => `${(bytes / 1_000_000).toFixed(2)}MiB`
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card>
|
<Card>
|
||||||
<CardActionArea>
|
<CardActionArea>
|
||||||
{thumbnail !== '' ?
|
{thumbnail !== '' ?
|
||||||
<CardMedia
|
<CardMedia
|
||||||
component="img"
|
component="img"
|
||||||
height={180}
|
height={180}
|
||||||
image={thumbnail}
|
image={thumbnail}
|
||||||
/> :
|
/> :
|
||||||
<Skeleton variant="rectangular" height={180} />
|
<Skeleton variant="rectangular" height={180} />
|
||||||
}
|
}
|
||||||
<CardContent>
|
<CardContent>
|
||||||
{title !== '' ?
|
{title !== '' ?
|
||||||
<Typography gutterBottom variant="h6" component="div">
|
<Typography gutterBottom variant="h6" component="div">
|
||||||
{ellipsis(title, 54)}
|
{ellipsis(title, 54)}
|
||||||
</Typography> :
|
</Typography> :
|
||||||
<Skeleton />
|
<Skeleton />
|
||||||
}
|
}
|
||||||
<Stack direction="row" spacing={1} py={2}>
|
<Stack direction="row" spacing={1} py={2}>
|
||||||
<Chip label={'Downloading'} color="primary" />
|
<Chip label={'Downloading'} color="primary" />
|
||||||
<Typography>{percentage}</Typography>
|
<Typography>{percentage}</Typography>
|
||||||
<Typography>{speed}</Typography>
|
<Typography>{speed}</Typography>
|
||||||
<Typography>{roundMB(size ?? 0)}</Typography>
|
<Typography>{roundMB(size ?? 0)}</Typography>
|
||||||
{guessResolution(resolution)}
|
{guessResolution(resolution)}
|
||||||
</Stack>
|
</Stack>
|
||||||
{percentage ?
|
{percentage ?
|
||||||
<LinearProgress variant="determinate" value={percentageToNumber()} /> :
|
<LinearProgress variant="determinate" value={percentageToNumber()} /> :
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</CardActionArea>
|
</CardActionArea>
|
||||||
<CardActions>
|
<CardActions>
|
||||||
<Button variant="contained" size="small" color="primary" onClick={stopCallback}>
|
<Button variant="contained" size="small" color="primary" onClick={stopCallback}>
|
||||||
Stop
|
Stop
|
||||||
</Button>
|
</Button>
|
||||||
</CardActions>
|
</CardActions>
|
||||||
</Card>
|
</Card>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -1,30 +1,47 @@
|
|||||||
import { createSlice } from "@reduxjs/toolkit"
|
import { createSlice, PayloadAction } from "@reduxjs/toolkit"
|
||||||
|
|
||||||
export interface StatusState {
|
export interface StatusState {
|
||||||
connected: boolean,
|
connected: boolean,
|
||||||
updated: boolean,
|
updated: boolean,
|
||||||
downloading: boolean,
|
downloading: boolean,
|
||||||
|
freeSpace: number,
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialState: StatusState = {
|
const initialState: StatusState = {
|
||||||
connected: false,
|
connected: false,
|
||||||
updated: false,
|
updated: false,
|
||||||
downloading: false,
|
downloading: false,
|
||||||
|
freeSpace: 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const statusSlice = createSlice({
|
export const statusSlice = createSlice({
|
||||||
name: 'status',
|
name: 'status',
|
||||||
initialState,
|
initialState,
|
||||||
reducers: {
|
reducers: {
|
||||||
connected: (state) => { state.connected = true },
|
connected: (state) => {
|
||||||
disconnected: (state) => { state.connected = false },
|
state.connected = true
|
||||||
updated: (state) => { state.updated = true },
|
},
|
||||||
alreadyUpdated: (state) => { state.updated = false },
|
disconnected: (state) => {
|
||||||
downloading: (state) => { state.downloading = true },
|
state.connected = false
|
||||||
finished: (state) => { state.downloading = false },
|
},
|
||||||
|
updated: (state) => {
|
||||||
|
state.updated = true
|
||||||
|
},
|
||||||
|
alreadyUpdated: (state) => {
|
||||||
|
state.updated = false
|
||||||
|
},
|
||||||
|
downloading: (state) => {
|
||||||
|
state.downloading = true
|
||||||
|
},
|
||||||
|
finished: (state) => {
|
||||||
|
state.downloading = false
|
||||||
|
},
|
||||||
|
setFreeSpace: (state, action: PayloadAction<number>) => {
|
||||||
|
state.freeSpace = action.payload
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
export const { connected, disconnected, updated, alreadyUpdated, downloading, finished } = statusSlice.actions
|
export const { connected, disconnected, updated, alreadyUpdated, downloading, finished, setFreeSpace } = statusSlice.actions
|
||||||
|
|
||||||
export default statusSlice.reducer
|
export default statusSlice.reducer
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
import type { RPCRequest, RPCResponse } from "./types"
|
import type { RPCRequest, RPCResponse } from "./types"
|
||||||
import type { IDLMetadata } from './interfaces'
|
import type { IDLMetadata } from './interfaces'
|
||||||
|
|
||||||
|
import { getHttpRPCEndpoint } from './utils'
|
||||||
|
|
||||||
export class RPCClient {
|
export class RPCClient {
|
||||||
private socket: WebSocket
|
private socket: WebSocket
|
||||||
private seq: number
|
private seq: number
|
||||||
@@ -20,7 +22,7 @@ export class RPCClient {
|
|||||||
|
|
||||||
private sendHTTP<T>(req: RPCRequest) {
|
private sendHTTP<T>(req: RPCRequest) {
|
||||||
return new Promise<RPCResponse<T>>((resolve, reject) => {
|
return new Promise<RPCResponse<T>>((resolve, reject) => {
|
||||||
fetch('/rpc-http', {
|
fetch(getHttpRPCEndpoint(), {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify(req)
|
body: JSON.stringify(req)
|
||||||
})
|
})
|
||||||
@@ -75,6 +77,20 @@ export class RPCClient {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public freeSpace() {
|
||||||
|
return this.sendHTTP<number>({
|
||||||
|
method: 'Service.FreeSpace',
|
||||||
|
params: [],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
public directoryTree() {
|
||||||
|
return this.sendHTTP<string[]>({
|
||||||
|
method: 'Service.DirectoryTree',
|
||||||
|
params: [],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
public decode(data: any): RPCResponse<any> {
|
public decode(data: any): RPCResponse<any> {
|
||||||
return JSON.parse(data)
|
return JSON.parse(data)
|
||||||
}
|
}
|
||||||
|
|||||||
1
frontend/src/types.d.ts
vendored
1
frontend/src/types.d.ts
vendored
@@ -5,6 +5,7 @@ export type RPCMethods =
|
|||||||
| "Service.KillAll"
|
| "Service.KillAll"
|
||||||
| "Service.FreeSpace"
|
| "Service.FreeSpace"
|
||||||
| "Service.Formats"
|
| "Service.Formats"
|
||||||
|
| "Service.DirectoryTree"
|
||||||
|
|
||||||
export type RPCRequest = {
|
export type RPCRequest = {
|
||||||
method: RPCMethods,
|
method: RPCMethods,
|
||||||
|
|||||||
@@ -112,3 +112,7 @@ export function getWebSocketEndpoint() {
|
|||||||
export function getHttpRPCEndpoint() {
|
export function getHttpRPCEndpoint() {
|
||||||
return `${window.location.protocol}//${localStorage.getItem('server-addr') || window.location.hostname}:${localStorage.getItem('server-port') || window.location.port}/http-rpc`
|
return `${window.location.protocol}//${localStorage.getItem('server-addr') || window.location.hostname}:${localStorage.getItem('server-port') || window.location.port}/http-rpc`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function formatGiB(bytes: number) {
|
||||||
|
return `${(bytes / 1_000_000_000).toFixed(0)}GiB`
|
||||||
|
}
|
||||||
@@ -28,6 +28,7 @@
|
|||||||
"koa-router": "^10.1.1",
|
"koa-router": "^10.1.1",
|
||||||
"koa-static": "^5.0.0",
|
"koa-static": "^5.0.0",
|
||||||
"mime-types": "^2.1.35",
|
"mime-types": "^2.1.35",
|
||||||
|
"radash": "^10.6.0",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-redux": "^8.0.1",
|
"react-redux": "^8.0.1",
|
||||||
|
|||||||
7
pnpm-lock.yaml
generated
7
pnpm-lock.yaml
generated
@@ -24,6 +24,7 @@ specifiers:
|
|||||||
mime-types: ^2.1.35
|
mime-types: ^2.1.35
|
||||||
path-browserify: ^1.0.1
|
path-browserify: ^1.0.1
|
||||||
process: ^0.11.10
|
process: ^0.11.10
|
||||||
|
radash: ^10.6.0
|
||||||
react: ^18.2.0
|
react: ^18.2.0
|
||||||
react-dom: ^18.2.0
|
react-dom: ^18.2.0
|
||||||
react-redux: ^8.0.1
|
react-redux: ^8.0.1
|
||||||
@@ -46,6 +47,7 @@ dependencies:
|
|||||||
koa-router: 10.1.1
|
koa-router: 10.1.1
|
||||||
koa-static: 5.0.0
|
koa-static: 5.0.0
|
||||||
mime-types: 2.1.35
|
mime-types: 2.1.35
|
||||||
|
radash: 10.6.0
|
||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
react-dom: 18.2.0_react@18.2.0
|
react-dom: 18.2.0_react@18.2.0
|
||||||
react-redux: 8.0.4_5uumaiclxbdbzaqafclbf6maf4
|
react-redux: 8.0.4_5uumaiclxbdbzaqafclbf6maf4
|
||||||
@@ -1830,6 +1832,11 @@ packages:
|
|||||||
react-is: 16.13.1
|
react-is: 16.13.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/radash/10.6.0:
|
||||||
|
resolution: {integrity: sha512-L0PD+kBVaPGxn0UO9yVLJUKUkuu7bLqroZbieecPUGuSEtByCtMedDSyw+arA8pnLtZduYTgHnMjRfN90gozpQ==}
|
||||||
|
engines: {node: '>=14.18.0'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/react-dom/18.2.0_react@18.2.0:
|
/react-dom/18.2.0_react@18.2.0:
|
||||||
resolution: {integrity: sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==}
|
resolution: {integrity: sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
|
|||||||
37
server/internal/stack.go
Normal file
37
server/internal/stack.go
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
package internal
|
||||||
|
|
||||||
|
type Node[T any] struct {
|
||||||
|
Value T
|
||||||
|
}
|
||||||
|
|
||||||
|
type Stack[T any] struct {
|
||||||
|
Nodes []*Node[T]
|
||||||
|
count int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Stack[T]) Push(n *Node[T]) {
|
||||||
|
if s.count >= len(s.Nodes) {
|
||||||
|
Nodes := make([]*Node[T], len(s.Nodes)*2)
|
||||||
|
copy(Nodes, s.Nodes)
|
||||||
|
s.Nodes = Nodes
|
||||||
|
}
|
||||||
|
s.Nodes[s.count] = n
|
||||||
|
s.count++
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Stack[T]) Pop() *Node[T] {
|
||||||
|
if s.count == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
node := s.Nodes[s.count-1]
|
||||||
|
s.count--
|
||||||
|
return node
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Stack[T]) IsEmpty() bool {
|
||||||
|
return s.count == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Stack[T]) IsNotEmpty() bool {
|
||||||
|
return s.count != 0
|
||||||
|
}
|
||||||
@@ -157,7 +157,12 @@ func (p *Process) GetFormatsSync() (DownloadFormats, error) {
|
|||||||
cmd.Wait()
|
cmd.Wait()
|
||||||
|
|
||||||
info := DownloadFormats{URL: p.url}
|
info := DownloadFormats{URL: p.url}
|
||||||
|
best := Format{}
|
||||||
|
|
||||||
json.Unmarshal(stdout, &info)
|
json.Unmarshal(stdout, &info)
|
||||||
|
json.Unmarshal(stdout, &best)
|
||||||
|
|
||||||
|
info.Best = best
|
||||||
|
|
||||||
return info, nil
|
return info, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"net/rpc"
|
"net/rpc"
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"github.com/gofiber/fiber/v2/middleware/cors"
|
||||||
"github.com/gofiber/fiber/v2/middleware/filesystem"
|
"github.com/gofiber/fiber/v2/middleware/filesystem"
|
||||||
"github.com/gofiber/websocket/v2"
|
"github.com/gofiber/websocket/v2"
|
||||||
)
|
)
|
||||||
@@ -29,6 +30,8 @@ func RunBlocking(ctx context.Context) {
|
|||||||
|
|
||||||
app := fiber.New()
|
app := fiber.New()
|
||||||
|
|
||||||
|
app.Use(cors.New())
|
||||||
|
|
||||||
app.Use("/", filesystem.New(filesystem.Config{
|
app.Use("/", filesystem.New(filesystem.Config{
|
||||||
Root: http.FS(fe),
|
Root: http.FS(fe),
|
||||||
}))
|
}))
|
||||||
|
|||||||
@@ -84,3 +84,9 @@ func (t *Service) FreeSpace(args NoArgs, free *uint64) error {
|
|||||||
*free = freeSpace
|
*free = freeSpace
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *Service) DirectoryTree(args NoArgs, tree *[]string) error {
|
||||||
|
dfsTree, err := sys.DirectoryTree("downloads")
|
||||||
|
*tree = *dfsTree
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,7 +2,9 @@ package sys
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/marcopeocchi/yt-dlp-web-ui/server/internal"
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -18,3 +20,42 @@ func FreeSpace() (uint64, error) {
|
|||||||
unix.Statfs(wd+"/downloads", &stat)
|
unix.Statfs(wd+"/downloads", &stat)
|
||||||
return (stat.Bavail * uint64(stat.Bsize)), nil
|
return (stat.Bavail * uint64(stat.Bsize)), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func DirectoryTree(rootPath string) (*[]string, error) {
|
||||||
|
type Node struct {
|
||||||
|
path string
|
||||||
|
children []Node
|
||||||
|
}
|
||||||
|
|
||||||
|
stack := internal.Stack[Node]{
|
||||||
|
Nodes: make([]*internal.Node[Node], 5),
|
||||||
|
}
|
||||||
|
flattened := make([]string, 0)
|
||||||
|
|
||||||
|
root := Node{path: rootPath}
|
||||||
|
stack.Push(&internal.Node[Node]{
|
||||||
|
Value: root,
|
||||||
|
})
|
||||||
|
flattened = append(flattened, rootPath)
|
||||||
|
|
||||||
|
for stack.IsNotEmpty() {
|
||||||
|
current := stack.Pop().Value
|
||||||
|
children, err := os.ReadDir(current.path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, entry := range children {
|
||||||
|
childPath := filepath.Join(current.path, entry.Name())
|
||||||
|
childNode := Node{path: childPath}
|
||||||
|
|
||||||
|
if entry.IsDir() {
|
||||||
|
current.children = append(current.children, childNode)
|
||||||
|
stack.Push(&internal.Node[Node]{
|
||||||
|
Value: childNode,
|
||||||
|
})
|
||||||
|
flattened = append(flattened, childNode.path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &flattened, nil
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user