display yt-dlp version, multiple downloads enabled.

code refactoring
preparations for optimistic ui updates for new downloads
This commit is contained in:
2024-03-22 13:22:38 +01:00
parent 48c9258088
commit 43e5c94b58
10 changed files with 137 additions and 28 deletions

View File

@@ -1,7 +1,7 @@
--- ---
languages: languages:
english: english:
urlInput: Video URL urlInput: Video URL (one per line)
statusTitle: Status statusTitle: Status
statusReady: Ready statusReady: Ready
selectFormatButton: Select format selectFormatButton: Select format
@@ -150,7 +150,7 @@ languages:
logsTitle: 'Logs' logsTitle: 'Logs'
awaitingLogs: 'Awaiting logs...' awaitingLogs: 'Awaiting logs...'
italian: italian:
urlInput: URL Video urlInput: URL Video (uno per linea)
statusTitle: Stato statusTitle: Stato
startButton: Inizia startButton: Inizia
statusReady: Pronto statusReady: Pronto

View File

@@ -1,6 +1,12 @@
import { atom } from 'recoil' import { atom } from 'recoil'
import { RPCResult } from '../types'
export const loadingAtom = atom({ export const loadingAtom = atom({
key: 'loadingAtom', key: 'loadingAtom',
default: true default: true
}) })
export const optimisticDownloadsState = atom<RPCResult[]>({
key: 'optimisticDownloadsState',
default: []
})

View File

@@ -80,7 +80,6 @@ const DownloadDialog: FC<Props> = ({ open, onClose, onDownloadStart }) => {
) )
const [url, setUrl] = useState('') const [url, setUrl] = useState('')
const [workingUrl, setWorkingUrl] = useState('')
const [isPlaylist, setIsPlaylist] = useState(false) const [isPlaylist, setIsPlaylist] = useState(false)
@@ -103,35 +102,36 @@ const DownloadDialog: FC<Props> = ({ open, onClose, onDownloadStart }) => {
/** /**
* Retrive url from input, cli-arguments from checkboxes and emits via WebSocket * Retrive url from input, cli-arguments from checkboxes and emits via WebSocket
*/ */
const sendUrl = (immediate?: string) => { const sendUrl = async (immediate?: string) => {
for (const line of url.split('\n')) {
const codes = new Array<string>() const codes = new Array<string>()
if (pickedVideoFormat !== '') codes.push(pickedVideoFormat) if (pickedVideoFormat !== '') codes.push(pickedVideoFormat)
if (pickedAudioFormat !== '') codes.push(pickedAudioFormat) if (pickedAudioFormat !== '') codes.push(pickedAudioFormat)
if (pickedBestFormat !== '') codes.push(pickedBestFormat) if (pickedBestFormat !== '') codes.push(pickedBestFormat)
client.download({ await new Promise(r => setTimeout(r, 200))
url: immediate || url || workingUrl, await client.download({
url: immediate || line,
args: `${argsBuilder.toString()} ${toFormatArgs(codes)} ${downloadTemplate}`, args: `${argsBuilder.toString()} ${toFormatArgs(codes)} ${downloadTemplate}`,
pathOverride: downloadPath ?? '', pathOverride: downloadPath ?? '',
renameTo: settings.fileRenaming ? filenameTemplate : '', renameTo: settings.fileRenaming ? filenameTemplate : '',
playlist: isPlaylist, playlist: isPlaylist,
}) })
setUrl('')
setWorkingUrl('')
setTimeout(() => { setTimeout(() => {
resetInput() resetInput()
setDownloadFormats(undefined) setDownloadFormats(undefined)
onDownloadStart(immediate || url || workingUrl) onDownloadStart(immediate || line)
}, 250) }, 250)
} }
setUrl('')
}
/** /**
* Retrive url from input and display the formats selection view * Retrive url from input and display the formats selection view
*/ */
const sendUrlFormatSelection = () => { const sendUrlFormatSelection = () => {
setWorkingUrl(url)
setUrl('') setUrl('')
setPickedAudioFormat('') setPickedAudioFormat('')
setPickedVideoFormat('') setPickedVideoFormat('')
@@ -220,6 +220,7 @@ const DownloadDialog: FC<Props> = ({ open, onClose, onDownloadStart }) => {
> >
<Grid container> <Grid container>
<TextField <TextField
multiline
fullWidth fullWidth
ref={urlInputRef} ref={urlInputRef}
label={i18n.t('urlInput')} label={i18n.t('urlInput')}

View File

@@ -6,7 +6,7 @@ import { settingsState } from '../atoms/settings'
import { connectedState } from '../atoms/status' import { connectedState } from '../atoms/status'
import { useI18n } from '../hooks/useI18n' import { useI18n } from '../hooks/useI18n'
import FreeSpaceIndicator from './FreeSpaceIndicator' import FreeSpaceIndicator from './FreeSpaceIndicator'
import GitHubIcon from '@mui/icons-material/GitHub' import VersionIndicator from './VersionIndicator'
const Footer: React.FC = () => { const Footer: React.FC = () => {
const settings = useRecoilValue(settingsState) const settings = useRecoilValue(settingsState)
@@ -30,8 +30,12 @@ const Footer: React.FC = () => {
fontSize: 14, fontSize: 14,
display: 'flex', gap: 1, justifyContent: 'space-between' display: 'flex', gap: 1, justifyContent: 'space-between'
}}> }}>
<div>v3.0.6</div> <div style={{ display: 'flex', gap: 2 }}>
<div style={{ display: 'flex', gap: 1 }}> <div>RPC v3.0.6</div>
<div></div>
<VersionIndicator />
</div>
<div style={{ display: 'flex', gap: 1, 'alignItems': 'center' }}>
<div style={{ <div style={{
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',

View File

@@ -1,6 +1,6 @@
import { Suspense, useState } from 'react' import { Suspense, useState } from 'react'
import { useRecoilState } from 'recoil' import { useRecoilState } from 'recoil'
import { loadingAtom } from '../atoms/ui' import { loadingAtom, optimisticDownloadsState } from '../atoms/ui'
import { useToast } from '../hooks/toast' import { useToast } from '../hooks/toast'
import DownloadDialog from './DownloadDialog' import DownloadDialog from './DownloadDialog'
import HomeSpeedDial from './HomeSpeedDial' import HomeSpeedDial from './HomeSpeedDial'
@@ -8,12 +8,32 @@ import TemplatesEditor from './TemplatesEditor'
const HomeActions: React.FC = () => { const HomeActions: React.FC = () => {
const [, setIsLoading] = useRecoilState(loadingAtom) const [, setIsLoading] = useRecoilState(loadingAtom)
const [optimistic, setOptimistic] = useRecoilState(optimisticDownloadsState)
const [openDownload, setOpenDownload] = useState(false) const [openDownload, setOpenDownload] = useState(false)
const [openEditor, setOpenEditor] = useState(false) const [openEditor, setOpenEditor] = useState(false)
const { pushMessage } = useToast() const { pushMessage } = useToast()
// it's stupid because it will be overriden on the next server tick
const handleOptimisticUpdate = (url: string) => setOptimistic([
...optimistic, {
id: url,
info: {
created_at: new Date().toISOString(),
thumbnail: '',
title: url,
url: url
},
progress: {
eta: Number.MAX_SAFE_INTEGER,
percentage: '0%',
process_status: 0,
speed: 0
}
}
])
return ( return (
<> <>
<HomeSpeedDial <HomeSpeedDial
@@ -27,7 +47,9 @@ const HomeActions: React.FC = () => {
setOpenDownload(false) setOpenDownload(false)
setIsLoading(true) setIsLoading(true)
}} }}
// TODO: handle optimistic UI update
onDownloadStart={(url) => { onDownloadStart={(url) => {
handleOptimisticUpdate(url)
pushMessage(`Requested ${url}`, 'info') pushMessage(`Requested ${url}`, 'info')
setOpenDownload(false) setOpenDownload(false)
setIsLoading(true) setIsLoading(true)

View File

@@ -52,7 +52,7 @@ const SocketSubscriber: React.FC<Props> = () => {
.filter(f => !!f.info.url).sort((a, b) => datetimeCompareFunc( .filter(f => !!f.info.url).sort((a, b) => datetimeCompareFunc(
b.info.created_at, b.info.created_at,
a.info.created_at, a.info.created_at,
)) )),
) )
) )
} }

View File

@@ -0,0 +1,34 @@
import { useEffect, useState } from 'react'
import { useRecoilValue } from 'recoil'
import { serverURL } from '../atoms/settings'
import { CircularProgress } from '@mui/material'
import { useToast } from '../hooks/toast'
const VersionIndicator: React.FC = () => {
const serverAddr = useRecoilValue(serverURL)
const [version, setVersion] = useState('')
const { pushMessage } = useToast()
const fetchVersion = async () => {
const res = await fetch(`${serverAddr}/api/v1/version`)
if (!res.ok) {
return pushMessage(await res.text(), 'error')
}
setVersion(await res.json())
}
useEffect(() => {
fetchVersion()
}, [])
return (
version
? <div>yt-dlp v{version}</div>
: <CircularProgress size={15} />
)
}
export default VersionIndicator

View File

@@ -26,6 +26,7 @@ func ApplyRouter(db *sql.DB, mdb *internal.MemoryDB, mq *internal.MessageQueue)
} }
r.Post("/exec", h.Exec()) r.Post("/exec", h.Exec())
r.Get("/running", h.Running()) r.Get("/running", h.Running())
r.Get("/version", h.GetVersion())
r.Post("/cookies", h.SetCookies()) r.Post("/cookies", h.SetCookies())
r.Post("/template", h.AddTemplate()) r.Post("/template", h.AddTemplate())
r.Get("/template/all", h.GetTemplates()) r.Get("/template/all", h.GetTemplates())

View File

@@ -158,3 +158,21 @@ func (h *Handler) DeleteTemplate() http.HandlerFunc {
} }
} }
} }
func (h *Handler) GetVersion() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
w.Header().Set("Content-Type", "application/json")
version, err := h.service.GetVersion(r.Context())
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if err := json.NewEncoder(w).Encode(version); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}
}

View File

@@ -6,8 +6,11 @@ import (
"errors" "errors"
"log/slog" "log/slog"
"os" "os"
"os/exec"
"time"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/marcopeocchi/yt-dlp-web-ui/server/config"
"github.com/marcopeocchi/yt-dlp-web-ui/server/internal" "github.com/marcopeocchi/yt-dlp-web-ui/server/internal"
) )
@@ -118,3 +121,23 @@ func (s *Service) DeleteTemplate(ctx context.Context, id string) error {
return err return err
} }
func (s *Service) GetVersion(ctx context.Context) (string, error) {
ch := make(chan string, 1)
c, cancel := context.WithTimeout(ctx, time.Second*10)
defer cancel()
cmd := exec.CommandContext(c, config.Instance().DownloaderPath, "--version")
go func() {
stdout, _ := cmd.Output()
ch <- string(stdout)
}()
select {
case <-c.Done():
return "", errors.New("requesting yt-dlp version took too long")
case res := <-ch:
return res, nil
}
}