Skip to content

Commit a88f3ff

Browse files
authored
Merge pull request #412 from DialmasterOrg/feat/allow-yt-dlp-update-in-app
feat: add in-app yt-dlp update functionality
2 parents 93cc851 + 8900fb9 commit a88f3ff

File tree

10 files changed

+1488
-11
lines changed

10 files changed

+1488
-11
lines changed

client/src/App.tsx

Lines changed: 74 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import './App.css';
22
import packageJson from '../package.json';
3-
import React, { useState, useEffect, useRef, useMemo } from 'react';
3+
import React, { useState, useEffect, useRef, useMemo, useCallback } from 'react';
44
import toplogo from './Youtarr_text.png';
55
import axios from 'axios';
66
import {
@@ -34,6 +34,7 @@ import { ThemeProvider } from '@mui/material/styles';
3434
import CloseIcon from '@mui/icons-material/Close';
3535
import MenuIcon from '@mui/icons-material/Menu';
3636
import WarningAmberIcon from '@mui/icons-material/WarningAmber';
37+
import Tooltip from '@mui/material/Tooltip';
3738
import SettingsIcon from '@mui/icons-material/Settings';
3839
import SubscriptionsIcon from '@mui/icons-material/Subscriptions';
3940
import DownloadIcon from '@mui/icons-material/Download';
@@ -55,6 +56,7 @@ import { useConfig } from './hooks/useConfig';
5556
import ErrorBoundary from './components/ErrorBoundary';
5657
import DatabaseErrorOverlay from './components/DatabaseErrorOverlay';
5758
import { lightTheme, darkTheme } from './theme';
59+
import { YTDLP_UPDATED_EVENT } from './components/Configuration/hooks/useYtDlpUpdate';
5860

5961
// Event name for database error detection
6062
const DB_ERROR_EVENT = 'db-error-detected';
@@ -76,6 +78,8 @@ function AppContent() {
7678
const [dbErrors, setDbErrors] = useState<string[]>([]);
7779
const [dbRecovered, setDbRecovered] = useState(false);
7880
const [countdown, setCountdown] = useState(15);
81+
const [ytDlpUpdateAvailable, setYtDlpUpdateAvailable] = useState(false);
82+
const [ytDlpLatestVersion, setYtDlpLatestVersion] = useState('');
7983
const location = useLocation();
8084
const theme = useTheme();
8185
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
@@ -359,6 +363,53 @@ function AppContent() {
359363
});
360364
}, []);
361365

366+
const fetchYtDlpVersionInfo = useCallback(() => {
367+
if (!token) {
368+
setYtDlpUpdateAvailable(false);
369+
setYtDlpLatestVersion('');
370+
return;
371+
}
372+
373+
fetch('/api/ytdlp/latest-version', {
374+
headers: { 'x-access-token': token },
375+
})
376+
.then((response) => {
377+
if (response.ok) {
378+
return response.json();
379+
}
380+
throw new Error('Failed to fetch yt-dlp version');
381+
})
382+
.then((data) => {
383+
setYtDlpUpdateAvailable(data.updateAvailable || false);
384+
setYtDlpLatestVersion(data.latestVersion || '');
385+
if (data.currentVersion) {
386+
setYtDlpVersion(data.currentVersion);
387+
}
388+
})
389+
.catch((err) => {
390+
console.error('Failed to fetch yt-dlp version info:', err);
391+
setYtDlpUpdateAvailable(false);
392+
setYtDlpLatestVersion('');
393+
});
394+
}, [token]);
395+
396+
useEffect(() => {
397+
fetchYtDlpVersionInfo();
398+
}, [fetchYtDlpVersionInfo]);
399+
400+
// Listen for yt-dlp update events to refresh version display
401+
useEffect(() => {
402+
const handleYtDlpUpdated = () => {
403+
fetchYtDlpVersionInfo();
404+
};
405+
406+
window.addEventListener(YTDLP_UPDATED_EVENT, handleYtDlpUpdated);
407+
408+
return () => {
409+
window.removeEventListener(YTDLP_UPDATED_EVENT, handleYtDlpUpdated);
410+
};
411+
}, [fetchYtDlpVersionInfo]);
412+
362413
return (
363414
<ThemeProvider theme={selectedTheme}>
364415
<CssBaseline />
@@ -450,13 +501,28 @@ function AppContent() {
450501
{clientVersion}
451502
</Typography>
452503
{ytDlpVersion && (
453-
<Typography
454-
fontSize='x-small'
455-
color={'textSecondary'}
456-
style={{ opacity: 0.7 }}
457-
>
458-
yt-dlp: {ytDlpVersion}
459-
</Typography>
504+
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
505+
{ytDlpUpdateAvailable && (
506+
<Tooltip title={`yt-dlp update available (${ytDlpLatestVersion}). Go to Configuration to update.`}>
507+
<IconButton
508+
component={Link}
509+
to="/configuration"
510+
size="small"
511+
aria-label={`yt-dlp update available (${ytDlpLatestVersion}). Click to go to Configuration.`}
512+
sx={{ p: 0.25 }}
513+
>
514+
<WarningAmberIcon sx={{ fontSize: 14, color: 'warning.main' }} />
515+
</IconButton>
516+
</Tooltip>
517+
)}
518+
<Typography
519+
fontSize='x-small'
520+
color={'textSecondary'}
521+
style={{ opacity: 0.7 }}
522+
>
523+
yt-dlp: {ytDlpVersion}
524+
</Typography>
525+
</Box>
460526
)}
461527
</Box>
462528
{/* This is the matching invisible IconButton */}

client/src/components/Configuration.tsx

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import {
2727
usePlexConnection,
2828
useConfigSave,
2929
} from './Configuration/hooks';
30+
import { useYtDlpUpdate } from './Configuration/hooks/useYtDlpUpdate';
3031
import { useStorageStatus } from '../hooks/useStorageStatus';
3132
import { useConfig } from '../hooks/useConfig';
3233
import { TRACKABLE_CONFIG_KEYS } from '../config/configSchema';
@@ -129,6 +130,34 @@ function Configuration({ token }: ConfigurationProps) {
129130
checkPlexConnection,
130131
});
131132

133+
const {
134+
versionInfo: ytDlpVersionInfo,
135+
updateStatus: ytDlpUpdateStatus,
136+
errorMessage: ytDlpErrorMessage,
137+
successMessage: ytDlpSuccessMessage,
138+
performUpdate: performYtDlpUpdate,
139+
clearMessages: clearYtDlpMessages,
140+
} = useYtDlpUpdate(token);
141+
142+
// Show yt-dlp update snackbar messages
143+
React.useEffect(() => {
144+
if (ytDlpErrorMessage) {
145+
setSnackbar({
146+
open: true,
147+
message: ytDlpErrorMessage,
148+
severity: 'error',
149+
});
150+
clearYtDlpMessages();
151+
} else if (ytDlpSuccessMessage) {
152+
setSnackbar({
153+
open: true,
154+
message: ytDlpSuccessMessage,
155+
severity: 'success',
156+
});
157+
clearYtDlpMessages();
158+
}
159+
}, [ytDlpErrorMessage, ytDlpSuccessMessage, clearYtDlpMessages]);
160+
132161
const handleOpenConfirmDialog = () => {
133162
setOpenConfirmDialog(true);
134163
};
@@ -200,6 +229,9 @@ function Configuration({ token }: ConfigurationProps) {
200229
onConfigChange={handleConfigChange}
201230
onMobileTooltipClick={setMobileTooltip}
202231
token={token}
232+
ytDlpVersionInfo={ytDlpVersionInfo}
233+
ytDlpUpdateStatus={ytDlpUpdateStatus}
234+
onYtDlpUpdate={performYtDlpUpdate}
203235
/>
204236

205237
<PlexIntegrationSection

0 commit comments

Comments
 (0)