refactor: Improve code readability and structure

This commit is contained in:
Peifan Li
2025-12-09 14:50:27 -05:00
parent 250ab9a63e
commit 316c554033
14 changed files with 169 additions and 26 deletions

View File

@@ -1,7 +1,7 @@
import { extractSourceVideoId } from "../utils/helpers";
import { CloudStorageService } from "./CloudStorageService";
import { createDownloadTask } from "./downloadService";
import * as storageService from "./storageService";
import { extractSourceVideoId } from "../utils/helpers";
interface DownloadTask {
downloadFn: (registerCancel: (cancel: () => void) => void) => Promise<any>;
@@ -282,6 +282,7 @@ class DownloadManager {
thumbnailPath: videoData.thumbnailPath,
sourceUrl: videoData.sourceUrl || task.sourceUrl,
author: videoData.author,
videoId: videoData.id,
});
// Record video download for future duplicate detection

View File

@@ -2,22 +2,22 @@ import { and, desc, eq, lt } from "drizzle-orm";
import fs from "fs-extra";
import path from "path";
import {
DATA_DIR,
IMAGES_DIR,
STATUS_DATA_PATH,
SUBTITLES_DIR,
UPLOADS_DIR,
VIDEOS_DIR,
DATA_DIR,
IMAGES_DIR,
STATUS_DATA_PATH,
SUBTITLES_DIR,
UPLOADS_DIR,
VIDEOS_DIR,
} from "../config/paths";
import { db, sqlite } from "../db";
import {
collections,
collectionVideos,
downloadHistory,
downloads,
settings,
videoDownloads,
videos,
collections,
collectionVideos,
downloadHistory,
downloads,
settings,
videoDownloads,
videos,
} from "../db/schema";
import { formatVideoFilename } from "../utils/helpers";
@@ -347,6 +347,20 @@ export function initializeStorage(): void {
if (updatedCount > 0) {
console.log(`Populated fileSize for ${updatedCount} videos.`);
}
// Backfill video_id in download_history for existing records
try {
const result = sqlite.prepare(`
UPDATE download_history
SET video_id = (SELECT id FROM videos WHERE videos.source_url = download_history.source_url)
WHERE video_id IS NULL AND status = 'success' AND source_url IS NOT NULL
`).run();
if (result.changes > 0) {
console.log(`Backfilled video_id for ${result.changes} download history items.`);
}
} catch (error) {
console.error("Error backfilling video_id in download history:", error);
}
} catch (error) {
console.error(
"Error checking/migrating viewCount/progress/duration/fileSize columns:",

View File

@@ -404,7 +404,7 @@ const Header: React.FC<HeaderProps> = ({
</Typography>
{websiteName !== 'MyTube' && (
<Typography variant="caption" color="text.secondary" sx={{ fontSize: '0.6rem', lineHeight: 1 }}>
{t('poweredBy')}
Powered by MyTube
</Typography>
)}
</Box>

View File

@@ -329,7 +329,7 @@ const VideoCard: React.FC<VideoCardProps> = ({
</Box>
<CardContent sx={{ flexGrow: 1, p: 2 }}>
<CardContent sx={{ flexGrow: 1, p: 2, display: 'flex', flexDirection: 'column' }}>
<Typography gutterBottom variant="subtitle1" component="div" sx={{ fontWeight: 600, lineHeight: 1.2, mb: 1, display: '-webkit-box', WebkitLineClamp: 2, WebkitBoxOrient: 'vertical', overflow: 'hidden' }}>
{isFirstInAnyCollection ? (
<>
@@ -352,7 +352,7 @@ const VideoCard: React.FC<VideoCardProps> = ({
fontWeight: 500
}}
>
{video.author}
{video.author.length > 15 ? `${video.author.substring(0, 15)}...` : video.author}
</Typography>
<Box sx={{ display: 'flex', alignItems: 'center' }}>
<Typography variant="caption" color="text.secondary">

View File

@@ -136,14 +136,14 @@ const UpNextSidebar: React.FC<UpNextSidebarProps> = ({
>
<SidebarThumbnail video={relatedVideo} />
<CardContent sx={{ flex: '1 1 auto', minWidth: 0, p: 1, '&:last-child': { pb: 1 }, position: 'relative' }}>
<CardContent sx={{ flex: '1 1 auto', minWidth: 0, p: 1, '&:last-child': { pb: 1 }, position: 'relative', display: 'flex', flexDirection: 'column' }}>
<Typography variant="body2" fontWeight="bold" sx={{ lineHeight: 1.2, mb: 0.5, display: '-webkit-box', WebkitLineClamp: 2, WebkitBoxOrient: 'vertical', overflow: 'hidden' }}>
{relatedVideo.title}
</Typography>
<Typography variant="caption" display="block" color="text.secondary">
{relatedVideo.author}
</Typography>
<Box sx={{ display: 'flex', alignItems: 'center', flexWrap: 'wrap' }}>
<Box sx={{ display: 'flex', alignItems: 'center', flexWrap: 'wrap', mt: 'auto' }}>
<Typography variant="caption" color="text.secondary">
{formatDate(relatedVideo.date)}
</Typography>

View File

@@ -24,7 +24,9 @@ import {
Paper,
Tab,
Tabs,
Typography
Typography,
useMediaQuery,
useTheme
} from '@mui/material';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import axios from 'axios';
@@ -81,6 +83,8 @@ function CustomTabPanel(props: TabPanelProps) {
}
const DownloadPage: React.FC = () => {
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
const { t } = useLanguage();
const { showSnackbar } = useSnackbar();
const { activeDownloads, queuedDownloads, handleVideoSubmit } = useDownload();
@@ -231,7 +235,7 @@ const DownloadPage: React.FC = () => {
// Re-download deleted video
const handleReDownload = async (sourceUrl: string) => {
if (!sourceUrl) return;
try {
// Call download with forceDownload flag
const response = await axios.post(`${API_URL}/download`, {
@@ -338,7 +342,7 @@ const DownloadPage: React.FC = () => {
>
<ListItemText
primary={download.title}
secondaryTypographyProps={{ component: 'div' }}
slotProps={{ secondary: { component: 'div' } }}
secondary={
<Box sx={{ mt: 1 }}>
<LinearProgress variant="determinate" value={download.progress || 0} sx={{ mb: 1 }} />
@@ -413,6 +417,7 @@ const DownloadPage: React.FC = () => {
page={queuePage}
onChange={(_: React.ChangeEvent<unknown>, page: number) => setQueuePage(page)}
color="primary"
siblingCount={isMobile ? 0 : 1}
/>
</Box>
)}
@@ -448,14 +453,38 @@ const DownloadPage: React.FC = () => {
<DeleteIcon />
</IconButton>
}
sx={{
flexDirection: { xs: 'column', md: 'row' },
alignItems: { xs: 'flex-start', md: 'center' },
gap: { xs: 2, md: 0 },
pr: { xs: 6, md: 10 }, // Add padding to avoid overlap with delete button
position: 'relative'
}}
>
<ListItemText
primary={item.title}
secondaryTypographyProps={{ component: 'div' }}
slotProps={{ secondary: { component: 'div' } }}
sx={{
width: { xs: '100%', md: 'auto' },
flex: { md: 1 },
pr: { xs: 0, md: 2 }
}}
secondary={
<Box component="div" sx={{ display: 'flex', flexDirection: 'column', gap: 0.5 }}>
{item.sourceUrl && (
<Typography variant="caption" color="primary" component="a" href={item.sourceUrl} target="_blank" rel="noopener noreferrer" sx={{ textDecoration: 'none', '&:hover': { textDecoration: 'underline' } }}>
<Typography
variant="caption"
color="primary"
component="a"
href={item.sourceUrl}
target="_blank"
rel="noopener noreferrer"
sx={{
textDecoration: 'none',
'&:hover': { textDecoration: 'underline' },
wordBreak: 'break-all'
}}
>
{item.sourceUrl}
</Typography>
)}
@@ -485,7 +514,14 @@ const DownloadPage: React.FC = () => {
</Box>
}
/>
<Box sx={{ mr: 8, display: 'flex', alignItems: 'center', gap: 1 }}>
<Box sx={{
display: 'flex',
alignItems: 'center',
gap: 1,
flexWrap: 'wrap',
width: { xs: '100%', md: 'auto' },
justifyContent: { xs: 'flex-start', md: 'flex-end' }
}}>
{item.status === 'failed' && item.sourceUrl && (
<Button
variant="outlined"
@@ -510,6 +546,18 @@ const DownloadPage: React.FC = () => {
{t('viewVideo') || 'View Video'}
</Button>
)}
{item.status === 'success' && item.videoId && (
<Button
variant="outlined"
color="primary"
size="small"
startIcon={<PlayArrowIcon />}
onClick={() => window.location.href = `/video/${item.videoId}`}
sx={{ minWidth: '100px' }}
>
{t('viewVideo') || 'View Video'}
</Button>
)}
{item.status === 'deleted' && item.sourceUrl && (
<Button
variant="outlined"
@@ -517,7 +565,6 @@ const DownloadPage: React.FC = () => {
size="small"
startIcon={<ReplayIcon />}
onClick={() => handleReDownload(item.sourceUrl!)}
sx={{ minWidth: '120px' }}
>
{t('downloadAgain') || 'Download Again'}
</Button>
@@ -543,6 +590,7 @@ const DownloadPage: React.FC = () => {
page={historyPage}
onChange={(_: React.ChangeEvent<unknown>, page: number) => setHistoryPage(page)}
color="primary"
siblingCount={isMobile ? 0 : 1}
/>
</Box>
)}

View File

@@ -378,4 +378,14 @@ export const ar = {
history: 'سجل',
downloading: "جاري التنزيل...",
poweredBy: "مدعوم من MyTube",
existingVideoDetected: "تم اكتشاف فيديو موجود",
videoAlreadyDownloaded: "تم تنزيل هذا الفيديو بالفعل.",
viewVideo: "عرض الفيديو",
downloadAgain: "تنزيل مرة أخرى",
downloadedOn: "تم التنزيل في",
deletedOn: "تم الحذف في",
existingVideo: "فيديو موجود",
skipped: "تم التخطي",
videoSkippedExists: "الفيديو موجود بالفعل، تم تخطي التنزيل",
videoSkippedDeleted: "تم حذف الفيديو سابقًا، تم تخطي التنزيل",
};

View File

@@ -242,4 +242,14 @@ export const de = {
history: 'Verlauf',
downloading: "Herunterladen...",
poweredBy: "Bereitgestellt von MyTube",
existingVideoDetected: "Vorhandenes Video erkannt",
videoAlreadyDownloaded: "Dieses Video wurde bereits heruntergeladen.",
viewVideo: "Video ansehen",
downloadAgain: "Erneut herunterladen",
downloadedOn: "Heruntergeladen am",
deletedOn: "Gelöscht am",
existingVideo: "Vorhandenes Video",
skipped: "Übersprungen",
videoSkippedExists: "Video existiert bereits, Download übersprungen",
videoSkippedDeleted: "Video wurde zuvor gelöscht, Download übersprungen",
};

View File

@@ -246,4 +246,14 @@ export const es = {
history: 'Historial',
downloading: "Descargando...",
poweredBy: "Con tecnología de MyTube",
existingVideoDetected: "Video existente detectado",
videoAlreadyDownloaded: "Este video ya ha sido descargado.",
viewVideo: "Ver video",
downloadAgain: "Descargar de nuevo",
downloadedOn: "Descargado el",
deletedOn: "Eliminado el",
existingVideo: "Video existente",
skipped: "Omitido",
videoSkippedExists: "El video ya existe, descarga omitida",
videoSkippedDeleted: "El video fue eliminado anteriormente, descarga omitida",
};

View File

@@ -379,4 +379,14 @@ export const fr = {
history: 'Historique',
downloading: "Téléchargement...",
poweredBy: "Propulsé par MyTube",
existingVideoDetected: "Vidéo existante détectée",
videoAlreadyDownloaded: "Cette vidéo a déjà été téléchargée.",
viewVideo: "Voir la vidéo",
downloadAgain: "Télécharger à nouveau",
downloadedOn: "Téléchargé le",
deletedOn: "Supprimé le",
existingVideo: "Vidéo existante",
skipped: "Ignoré",
videoSkippedExists: "La vidéo existe déjà, téléchargement ignoré",
videoSkippedDeleted: "La vidéo a été supprimée précédemment, téléchargement ignoré",
};

View File

@@ -376,4 +376,14 @@ export const ja = {
history: '履歴',
downloading: "ダウンロード中...",
poweredBy: "MyTubeによって提供",
existingVideoDetected: "既存の動画が検出されました",
videoAlreadyDownloaded: "この動画は既にダウンロードされています。",
viewVideo: "動画を見る",
downloadAgain: "再ダウンロード",
downloadedOn: "ダウンロード日",
deletedOn: "削除日",
existingVideo: "既存の動画",
skipped: "スキップ",
videoSkippedExists: "動画は既に存在するため、ダウンロードをスキップしました",
videoSkippedDeleted: "動画は以前削除されたため、ダウンロードをスキップしました",
};

View File

@@ -379,4 +379,14 @@ export const ko = {
history: '기록',
downloading: "다운로드 중...",
poweredBy: "MyTube 제공",
existingVideoDetected: "기존 동영상 감지됨",
videoAlreadyDownloaded: "이 동영상은 이미 다운로드되었습니다.",
viewVideo: "동영상 보기",
downloadAgain: "다시 다운로드",
downloadedOn: "다운로드 날짜",
deletedOn: "삭제 날짜",
existingVideo: "기존 동영상",
skipped: "건너뜀",
videoSkippedExists: "동영상이 이미 존재하여 다운로드를 건너뛰었습니다",
videoSkippedDeleted: "동영상이 이전에 삭제되어 다운로드를 건너뛰었습니다",
};

View File

@@ -379,4 +379,14 @@ export const pt = {
history: 'Histórico',
downloading: "Baixando...",
poweredBy: "Com tecnologia de MyTube",
existingVideoDetected: "Vídeo existente detectado",
videoAlreadyDownloaded: "Este vídeo já foi baixado.",
viewVideo: "Ver vídeo",
downloadAgain: "Baixar novamente",
downloadedOn: "Baixado em",
deletedOn: "Excluído em",
existingVideo: "Vídeo existente",
skipped: "Pular",
videoSkippedExists: "Vídeo já existe, download pulado",
videoSkippedDeleted: "Vídeo foi excluído anteriormente, download pulado",
};

View File

@@ -380,4 +380,14 @@ export const ru = {
history: 'История',
downloading: "Скачивание...",
poweredBy: "Работает на MyTube",
existingVideoDetected: "Обнаружено существующее видео",
videoAlreadyDownloaded: "Это видео уже загружено.",
viewVideo: "Посмотреть видео",
downloadAgain: "Скачать снова",
downloadedOn: "Скачано",
deletedOn: "Удалено",
existingVideo: "Существующее видео",
skipped: "Пропущено",
videoSkippedExists: "Видео уже существует, загрузка пропущена",
videoSkippedDeleted: "Видео было ранее удалено, загрузка пропущена",
};