refactor: Improve code readability and structure
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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:",
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
@@ -378,4 +378,14 @@ export const ar = {
|
||||
history: 'سجل',
|
||||
downloading: "جاري التنزيل...",
|
||||
poweredBy: "مدعوم من MyTube",
|
||||
existingVideoDetected: "تم اكتشاف فيديو موجود",
|
||||
videoAlreadyDownloaded: "تم تنزيل هذا الفيديو بالفعل.",
|
||||
viewVideo: "عرض الفيديو",
|
||||
downloadAgain: "تنزيل مرة أخرى",
|
||||
downloadedOn: "تم التنزيل في",
|
||||
deletedOn: "تم الحذف في",
|
||||
existingVideo: "فيديو موجود",
|
||||
skipped: "تم التخطي",
|
||||
videoSkippedExists: "الفيديو موجود بالفعل، تم تخطي التنزيل",
|
||||
videoSkippedDeleted: "تم حذف الفيديو سابقًا، تم تخطي التنزيل",
|
||||
};
|
||||
|
||||
@@ -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",
|
||||
};
|
||||
|
||||
@@ -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",
|
||||
};
|
||||
|
||||
@@ -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é",
|
||||
};
|
||||
|
||||
@@ -376,4 +376,14 @@ export const ja = {
|
||||
history: '履歴',
|
||||
downloading: "ダウンロード中...",
|
||||
poweredBy: "MyTubeによって提供",
|
||||
existingVideoDetected: "既存の動画が検出されました",
|
||||
videoAlreadyDownloaded: "この動画は既にダウンロードされています。",
|
||||
viewVideo: "動画を見る",
|
||||
downloadAgain: "再ダウンロード",
|
||||
downloadedOn: "ダウンロード日",
|
||||
deletedOn: "削除日",
|
||||
existingVideo: "既存の動画",
|
||||
skipped: "スキップ",
|
||||
videoSkippedExists: "動画は既に存在するため、ダウンロードをスキップしました",
|
||||
videoSkippedDeleted: "動画は以前削除されたため、ダウンロードをスキップしました",
|
||||
};
|
||||
|
||||
@@ -379,4 +379,14 @@ export const ko = {
|
||||
history: '기록',
|
||||
downloading: "다운로드 중...",
|
||||
poweredBy: "MyTube 제공",
|
||||
existingVideoDetected: "기존 동영상 감지됨",
|
||||
videoAlreadyDownloaded: "이 동영상은 이미 다운로드되었습니다.",
|
||||
viewVideo: "동영상 보기",
|
||||
downloadAgain: "다시 다운로드",
|
||||
downloadedOn: "다운로드 날짜",
|
||||
deletedOn: "삭제 날짜",
|
||||
existingVideo: "기존 동영상",
|
||||
skipped: "건너뜀",
|
||||
videoSkippedExists: "동영상이 이미 존재하여 다운로드를 건너뛰었습니다",
|
||||
videoSkippedDeleted: "동영상이 이전에 삭제되어 다운로드를 건너뛰었습니다",
|
||||
};
|
||||
|
||||
@@ -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",
|
||||
};
|
||||
|
||||
@@ -380,4 +380,14 @@ export const ru = {
|
||||
history: 'История',
|
||||
downloading: "Скачивание...",
|
||||
poweredBy: "Работает на MyTube",
|
||||
existingVideoDetected: "Обнаружено существующее видео",
|
||||
videoAlreadyDownloaded: "Это видео уже загружено.",
|
||||
viewVideo: "Посмотреть видео",
|
||||
downloadAgain: "Скачать снова",
|
||||
downloadedOn: "Скачано",
|
||||
deletedOn: "Удалено",
|
||||
existingVideo: "Существующее видео",
|
||||
skipped: "Пропущено",
|
||||
videoSkippedExists: "Видео уже существует, загрузка пропущена",
|
||||
videoSkippedDeleted: "Видео было ранее удалено, загрузка пропущена",
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user