From 4c7faa1b46865b6f02c35b6a737fd627cc2b3caa Mon Sep 17 00:00:00 2001 From: marcobaobao Date: Wed, 11 Jan 2023 23:19:37 +0100 Subject: [PATCH] converted dir tree --- frontend/src/App.tsx | 27 +---- frontend/src/Home.tsx | 29 +++-- frontend/src/components/StackableResult.tsx | 124 ++++++++++---------- frontend/src/features/status/statusSlice.ts | 33 ++++-- frontend/src/rpcClient.ts | 18 ++- frontend/src/types.d.ts | 1 + frontend/src/utils.ts | 4 + package.json | 1 + pnpm-lock.yaml | 7 ++ server/internal/stack.go | 37 ++++++ server/process.go | 5 + server/server.go | 3 + server/service.go | 6 + server/sys/fs.go | 41 +++++++ 14 files changed, 229 insertions(+), 107 deletions(-) create mode 100644 server/internal/stack.go diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 7894f46..870f5f2 100755 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -23,18 +23,16 @@ import { BrowserRouter as Router, Link, Route, Routes } from 'react-router-dom'; -import { io } from "socket.io-client"; import ArchivedDownloads from "./Archived"; import { AppBar } from "./components/AppBar"; import { Drawer } from "./components/Drawer"; import Home from "./Home"; import Settings from "./Settings"; import { RootState, store } from './stores/store'; -import { getWebSocketEndpoint } from "./utils"; +import { formatGiB, getWebSocketEndpoint } from "./utils"; function AppContent() { const [open, setOpen] = useState(false); - const [freeDiskSpace, setFreeDiskSpace] = useState(''); const settings = useSelector((state: RootState) => state.settings) const status = useSelector((state: RootState) => state.status) @@ -58,11 +56,6 @@ function AppContent() { setOpen(!open); }; - /* Get disk free space */ - useEffect(() => { - - }, []) - return ( @@ -96,14 +89,14 @@ function AppContent() { yt-dlp WebUI { - freeDiskSpace ? + status.freeSpace ?
-  {freeDiskSpace}  +  {formatGiB(status.freeSpace)} 
: null } @@ -145,20 +138,6 @@ function AppContent() { - {/* Next release: list downloaded files */} - {/* - - - - - - - */} { socket.onopen = () => { 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) }, []) + useEffect(() => { + client.freeSpace() + .then(bytes => dispatch(setFreeSpace(bytes.result))) + }, []) + useEffect(() => { socket.onmessage = (event) => { const res = client.decode(event.data) @@ -106,10 +107,9 @@ export default function Home({ socket }: Props) { }, []) useEffect(() => { - fetch(`${window.location.protocol}//${settings.serverAddr}:${settings.serverPort}/tree`) - .then(res => res.json()) + client.directoryTree() .then(data => { - setAvailableDownloadPaths(data.flat) + setAvailableDownloadPaths(data.result) }) }, []) @@ -150,10 +150,15 @@ export default function Home({ socket }: Props) { setPickedVideoFormat('') setPickedBestFormat('') - setTimeout(() => { - resetInput() - setShowBackdrop(true) - }, 250) + setShowBackdrop(true) + + client.formats(url) + ?.then(formats => { + console.log(formats) + setDownloadFormats(formats.result) + setShowBackdrop(false) + resetInput() + }) } /** diff --git a/frontend/src/components/StackableResult.tsx b/frontend/src/components/StackableResult.tsx index 3dfa0c9..29ee858 100644 --- a/frontend/src/components/StackableResult.tsx +++ b/frontend/src/components/StackableResult.tsx @@ -4,73 +4,73 @@ import { IMessage } from "../interfaces"; import { ellipsis } from "../utils"; type Props = { - title: string, - thumbnail: string, - resolution: string - percentage: string, - size: number, - speed: number, - stopCallback: VoidFunction, + title: string, + thumbnail: string, + resolution: string + percentage: string, + size: number, + speed: number, + stopCallback: VoidFunction, } export function StackableResult({ - title, - thumbnail, - resolution, - percentage, - speed, - size, - stopCallback + title, + thumbnail, + resolution, + percentage, + speed, + size, + stopCallback }: Props) { - const guessResolution = (xByY: string): any => { - if (!xByY) return null; - if (xByY.includes('4320')) return (); - if (xByY.includes('2160')) return (); - if (xByY.includes('1080')) return (); - if (xByY.includes('720')) return (); - return null; - } + const guessResolution = (xByY: string): any => { + if (!xByY) return null; + if (xByY.includes('4320')) return (); + if (xByY.includes('2160')) return (); + if (xByY.includes('1080')) return (); + if (xByY.includes('720')) return (); + 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 ( - - - {thumbnail !== '' ? - : - - } - - {title !== '' ? - - {ellipsis(title, 54)} - : - - } - - - {percentage} - {speed} - {roundMB(size ?? 0)} - {guessResolution(resolution)} - - {percentage ? - : - null - } - - - - - - - ) + return ( + + + {thumbnail !== '' ? + : + + } + + {title !== '' ? + + {ellipsis(title, 54)} + : + + } + + + {percentage} + {speed} + {roundMB(size ?? 0)} + {guessResolution(resolution)} + + {percentage ? + : + null + } + + + + + + + ) } \ No newline at end of file diff --git a/frontend/src/features/status/statusSlice.ts b/frontend/src/features/status/statusSlice.ts index 9486cc7..a0d55af 100644 --- a/frontend/src/features/status/statusSlice.ts +++ b/frontend/src/features/status/statusSlice.ts @@ -1,30 +1,47 @@ -import { createSlice } from "@reduxjs/toolkit" +import { createSlice, PayloadAction } from "@reduxjs/toolkit" export interface StatusState { connected: boolean, updated: boolean, downloading: boolean, + freeSpace: number, } const initialState: StatusState = { connected: false, updated: false, downloading: false, + freeSpace: 0, } export const statusSlice = createSlice({ name: 'status', initialState, reducers: { - connected: (state) => { state.connected = true }, - disconnected: (state) => { state.connected = false }, - updated: (state) => { state.updated = true }, - alreadyUpdated: (state) => { state.updated = false }, - downloading: (state) => { state.downloading = true }, - finished: (state) => { state.downloading = false }, + connected: (state) => { + state.connected = true + }, + disconnected: (state) => { + state.connected = 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) => { + 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 \ No newline at end of file diff --git a/frontend/src/rpcClient.ts b/frontend/src/rpcClient.ts index 94d5b83..c75b337 100644 --- a/frontend/src/rpcClient.ts +++ b/frontend/src/rpcClient.ts @@ -1,6 +1,8 @@ import type { RPCRequest, RPCResponse } from "./types" import type { IDLMetadata } from './interfaces' +import { getHttpRPCEndpoint } from './utils' + export class RPCClient { private socket: WebSocket private seq: number @@ -20,7 +22,7 @@ export class RPCClient { private sendHTTP(req: RPCRequest) { return new Promise>((resolve, reject) => { - fetch('/rpc-http', { + fetch(getHttpRPCEndpoint(), { method: 'POST', body: JSON.stringify(req) }) @@ -75,6 +77,20 @@ export class RPCClient { }) } + public freeSpace() { + return this.sendHTTP({ + method: 'Service.FreeSpace', + params: [], + }) + } + + public directoryTree() { + return this.sendHTTP({ + method: 'Service.DirectoryTree', + params: [], + }) + } + public decode(data: any): RPCResponse { return JSON.parse(data) } diff --git a/frontend/src/types.d.ts b/frontend/src/types.d.ts index e8559ee..be65a37 100644 --- a/frontend/src/types.d.ts +++ b/frontend/src/types.d.ts @@ -5,6 +5,7 @@ export type RPCMethods = | "Service.KillAll" | "Service.FreeSpace" | "Service.Formats" + | "Service.DirectoryTree" export type RPCRequest = { method: RPCMethods, diff --git a/frontend/src/utils.ts b/frontend/src/utils.ts index f0db68b..454441b 100644 --- a/frontend/src/utils.ts +++ b/frontend/src/utils.ts @@ -111,4 +111,8 @@ export function getWebSocketEndpoint() { export function getHttpRPCEndpoint() { 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` } \ No newline at end of file diff --git a/package.json b/package.json index b5b8901..195ad3f 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "koa-router": "^10.1.1", "koa-static": "^5.0.0", "mime-types": "^2.1.35", + "radash": "^10.6.0", "react": "^18.2.0", "react-dom": "^18.2.0", "react-redux": "^8.0.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2321e9f..0b0e568 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -24,6 +24,7 @@ specifiers: mime-types: ^2.1.35 path-browserify: ^1.0.1 process: ^0.11.10 + radash: ^10.6.0 react: ^18.2.0 react-dom: ^18.2.0 react-redux: ^8.0.1 @@ -46,6 +47,7 @@ dependencies: koa-router: 10.1.1 koa-static: 5.0.0 mime-types: 2.1.35 + radash: 10.6.0 react: 18.2.0 react-dom: 18.2.0_react@18.2.0 react-redux: 8.0.4_5uumaiclxbdbzaqafclbf6maf4 @@ -1830,6 +1832,11 @@ packages: react-is: 16.13.1 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: resolution: {integrity: sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==} peerDependencies: diff --git a/server/internal/stack.go b/server/internal/stack.go new file mode 100644 index 0000000..c7738c1 --- /dev/null +++ b/server/internal/stack.go @@ -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 +} diff --git a/server/process.go b/server/process.go index faab93b..c209a9d 100644 --- a/server/process.go +++ b/server/process.go @@ -157,7 +157,12 @@ func (p *Process) GetFormatsSync() (DownloadFormats, error) { cmd.Wait() info := DownloadFormats{URL: p.url} + best := Format{} + json.Unmarshal(stdout, &info) + json.Unmarshal(stdout, &best) + + info.Best = best return info, nil } diff --git a/server/server.go b/server/server.go index 2cb6b3a..ac275f8 100644 --- a/server/server.go +++ b/server/server.go @@ -10,6 +10,7 @@ import ( "net/rpc" "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v2/middleware/cors" "github.com/gofiber/fiber/v2/middleware/filesystem" "github.com/gofiber/websocket/v2" ) @@ -29,6 +30,8 @@ func RunBlocking(ctx context.Context) { app := fiber.New() + app.Use(cors.New()) + app.Use("/", filesystem.New(filesystem.Config{ Root: http.FS(fe), })) diff --git a/server/service.go b/server/service.go index 1889572..2803ae9 100644 --- a/server/service.go +++ b/server/service.go @@ -84,3 +84,9 @@ func (t *Service) FreeSpace(args NoArgs, free *uint64) error { *free = freeSpace return err } + +func (t *Service) DirectoryTree(args NoArgs, tree *[]string) error { + dfsTree, err := sys.DirectoryTree("downloads") + *tree = *dfsTree + return err +} diff --git a/server/sys/fs.go b/server/sys/fs.go index 00ef630..c086316 100644 --- a/server/sys/fs.go +++ b/server/sys/fs.go @@ -2,7 +2,9 @@ package sys import ( "os" + "path/filepath" + "github.com/marcopeocchi/yt-dlp-web-ui/server/internal" "golang.org/x/sys/unix" ) @@ -18,3 +20,42 @@ func FreeSpace() (uint64, error) { unix.Statfs(wd+"/downloads", &stat) 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 +}