feat: Add functionality to clear finished tasks

This commit is contained in:
Peifan Li
2025-12-27 21:55:25 -05:00
parent 68d4b8a00f
commit 604ff713b1
14 changed files with 124 additions and 4 deletions

View File

@@ -121,6 +121,18 @@ export const deleteContinuousDownloadTask = async (
res.status(200).json(successMessage("Task deleted"));
};
/**
* Clear all finished continuous download tasks
* Errors are automatically handled by asyncHandler middleware
*/
export const clearFinishedTasks = async (
req: Request,
res: Response
): Promise<void> => {
await continuousDownloadService.clearFinishedTasks();
res.status(200).json(successMessage("Finished tasks cleared"));
};
/**
* Create a continuous download task for a playlist
* Errors are automatically handled by asyncHandler middleware

View File

@@ -133,6 +133,11 @@ router.get(
"/subscriptions/tasks",
asyncHandler(subscriptionController.getContinuousDownloadTasks)
);
// Specific routes must come before parameterized routes (:id)
router.delete(
"/subscriptions/tasks/clear-finished",
asyncHandler(subscriptionController.clearFinishedTasks)
);
router.delete(
"/subscriptions/tasks/:id",
asyncHandler(subscriptionController.cancelContinuousDownloadTask)

View File

@@ -170,6 +170,26 @@ export class ContinuousDownloadService {
await this.taskRepository.deleteTask(id);
}
/**
* Clear all finished tasks (completed or cancelled)
*/
async clearFinishedTasks(): Promise<void> {
const tasks = await this.getAllTasks();
const finishedTasks = tasks.filter(
(task) => task.status === "completed" || task.status === "cancelled"
);
logger.info(`Clearing ${finishedTasks.length} finished tasks`);
for (const task of finishedTasks) {
try {
await this.deleteTask(task.id);
} catch (error) {
logger.error(`Error deleting task ${task.id} during cleanup:`, error);
}
}
}
/**
* Process a continuous download task
*/

View File

@@ -64,6 +64,7 @@ const SubscriptionsPage: React.FC = () => {
const [selectedSubscription, setSelectedSubscription] = useState<{ id: string; author: string } | null>(null);
const [isCancelTaskModalOpen, setIsCancelTaskModalOpen] = useState(false);
const [isDeleteTaskModalOpen, setIsDeleteTaskModalOpen] = useState(false);
const [isClearFinishedModalOpen, setIsClearFinishedModalOpen] = useState(false);
const [selectedTask, setSelectedTask] = useState<ContinuousDownloadTask | null>(null);
// Use React Query for better caching and memory management
@@ -163,6 +164,23 @@ const SubscriptionsPage: React.FC = () => {
}
};
const handleClearFinishedClick = () => {
setIsClearFinishedModalOpen(true);
};
const handleConfirmClearFinished = async () => {
try {
await axios.delete(`${API_URL}/subscriptions/tasks/clear-finished`);
showSnackbar(t('tasksCleared'));
refetchTasks();
} catch (error) {
console.error('Error clearing finished tasks:', error);
showSnackbar(t('error'));
} finally {
setIsClearFinishedModalOpen(false);
}
};
const getTaskProgress = (task: ContinuousDownloadTask) => {
if (task.totalVideos === 0) return 0;
return Math.round((task.currentVideoIndex / task.totalVideos) * 100);
@@ -232,9 +250,22 @@ const SubscriptionsPage: React.FC = () => {
{tasks.length > 0 && (
<Box sx={{ mt: 4 }}>
<Typography variant="h5" component="h2" gutterBottom fontWeight="bold">
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 2 }}>
<Typography variant="h5" component="h2" fontWeight="bold">
{t('continuousDownloadTasks')}
</Typography>
{!visitorMode && (
<Button
variant="outlined"
color="error"
onClick={handleClearFinishedClick}
startIcon={<DeleteOutline />}
size="small"
>
{t('clearFinishedTasks')}
</Button>
)}
</Box>
<TableContainer component={Paper} sx={{ mt: 2 }}>
<Table>
<TableHead>
@@ -347,6 +378,16 @@ const SubscriptionsPage: React.FC = () => {
cancelText={t('cancel')}
isDanger
/>
<ConfirmationModal
isOpen={isClearFinishedModalOpen}
onClose={() => setIsClearFinishedModalOpen(false)}
onConfirm={handleConfirmClearFinished}
title={t('clearFinishedTasks')}
message={t('confirmClearFinishedTasks')}
confirmText={t('clear')}
cancelText={t('cancel')}
isDanger
/>
</Container >
);
};

View File

@@ -450,6 +450,10 @@ export const ar = {
deleteTask: "حذف المهمة",
confirmDeleteTask: "هل أنت متأكد أنك تريد حذف سجل المهمة لـ {author}؟ لا يمكن التراجع عن هذا الإجراء.",
taskDeleted: "تم حذف المهمة بنجاح",
clearFinishedTasks: "مسح المهام المنتهية",
tasksCleared: "تم مسح المهام المنتهية بنجاح",
confirmClearFinishedTasks: "هل أنت متأكد أنك تريد مسح جميع المهام المنتهية (المكتملة، الملغاة)؟ سيؤدي هذا إلى إزالتها من القائمة ولكن لن يحذف أي ملفات تم تنزيلها.",
clear: "مسح",
// Instruction Page
instructionSection1Title: "1. التنزيل وإدارة المهام",
instructionSection1Desc:

View File

@@ -423,6 +423,10 @@ export const de = {
deleteTask: "Aufgabe löschen",
confirmDeleteTask: "Sind Sie sicher, dass Sie den Aufgaben-Datensatz für {author} löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden.",
taskDeleted: "Aufgabe erfolgreich gelöscht",
clearFinishedTasks: "Beendete Aufgaben löschen",
tasksCleared: "Beendete Aufgaben erfolgreich gelöscht",
confirmClearFinishedTasks: "Sind Sie sicher, dass Sie alle beendeten Aufgaben (abgeschlossen, abgebrochen) löschen möchten? Dies entfernt sie aus der Liste, löscht aber keine heruntergeladenen Dateien.",
clear: "Löschen",
// Instruction Page
instructionSection1Title: "1. Download & Aufgabenverwaltung",
instructionSection1Desc:

View File

@@ -452,6 +452,11 @@ export const en = {
confirmDeleteTask:
"Are you sure you want to delete the task record for {author}? This action cannot be undone.",
taskDeleted: "Task deleted successfully",
clearFinishedTasks: "Clear Finished Tasks",
tasksCleared: "Finished tasks cleared successfully",
confirmClearFinishedTasks:
"Are you sure you want to clear all finished tasks (completed, cancelled)? This will remove them from the list but will not delete any downloaded files.",
clear: "Clear",
// Instruction Page
instructionSection1Title: "1. Download & Task Management",
instructionSection1Desc:

View File

@@ -438,6 +438,10 @@ export const es = {
deleteTask: "Eliminar tarea",
confirmDeleteTask: "¿Estás seguro de que quieres eliminar el registro de tarea para {author}? Esta acción no se puede deshacer.",
taskDeleted: "Tarea eliminada exitosamente",
clearFinishedTasks: "Borrar tareas finalizadas",
tasksCleared: "Tareas finalizadas borradas con éxito",
confirmClearFinishedTasks: "¿Está seguro de que desea borrar todas las tareas finalizadas (completadas, canceladas)? Esto las eliminará de la lista pero no borrará ningún archivo descargado.",
clear: "Borrar",
// Instruction Page
instructionSection1Title: "1. Descarga y Gestión de Tareas",
instructionSection1Desc:

View File

@@ -472,6 +472,10 @@ export const fr = {
confirmDeleteTask:
"Êtes-vous sûr de vouloir supprimer l'enregistrement de tâche pour {author} ? Cette action ne peut pas être annulée.",
taskDeleted: "Tâche supprimée avec succès",
clearFinishedTasks: "Effacer les tâches terminées",
tasksCleared: "Tâches terminées effacées avec succès",
confirmClearFinishedTasks: "Êtes-vous sûr de vouloir effacer toutes les tâches terminées (complétées, annulées) ? Cela les supprimera de la liste mais ne supprimera aucun fichier téléchargé.",
clear: "Effacer",
// Instruction Page
instructionSection1Title: "1. Téléchargement et Gestion des Tâches",
instructionSection1Desc:

View File

@@ -444,6 +444,10 @@ export const ja = {
deleteTask: "タスクを削除",
confirmDeleteTask: "{author} のタスクレコードを削除してもよろしいですか?この操作は元に戻せません。",
taskDeleted: "タスクが正常に削除されました",
clearFinishedTasks: "完了したタスクをクリア",
tasksCleared: "完了したタスクを正常にクリアしました",
confirmClearFinishedTasks: "完了したタスク(完了、キャンセル済み)をすべてクリアしてもよろしいですか?これによりリストからは削除されますが、ダウンロードされたファイルは削除されません。",
clear: "クリア",
// Instruction Page
instructionSection1Title: "1. ダウンロードとタスク管理",
instructionSection1Desc:

View File

@@ -440,6 +440,10 @@ export const ko = {
deleteTask: "작업 삭제",
confirmDeleteTask: "{author}님의 작업 기록을 삭제하시겠습니까? 이 작업은 취소할 수 없습니다.",
taskDeleted: "작업이 성공적으로 삭제되었습니다",
clearFinishedTasks: "완료된 작업 지우기",
tasksCleared: "완료된 작업이 성공적으로 지워졌습니다",
confirmClearFinishedTasks: "완료된 모든 작업(완료됨, 취소됨)을 지우시겠습니까? 목록에서 제거되지만 다운로드된 파일은 삭제되지 않습니다.",
clear: "지우기",
// Instruction Page
instructionSection1Title: "1. 다운로드 및 작업 관리",
instructionSection1Desc:

View File

@@ -453,6 +453,10 @@ export const pt = {
deleteTask: "Excluir tarefa",
confirmDeleteTask: "Tem certeza de que deseja excluir o registro da tarefa para {author}? Esta ação não pode ser desfeita.",
taskDeleted: "Tarefa excluída com sucesso",
clearFinishedTasks: "Limpar tarefas concluídas",
tasksCleared: "Tarefas concluídas limpas com sucesso",
confirmClearFinishedTasks: "Tem certeza de que deseja limpar todas as tarefas concluídas (concluídas, canceladas)? Isso as removerá da lista, mas não excluirá nenhum arquivo baixado.",
clear: "Limpar",
// Instruction Page
instructionSection1Title: "1. Download e Gerenciamento de Tarefas",
instructionSection1Desc:

View File

@@ -458,6 +458,10 @@ export const ru = {
deleteTask: "Удалить задачу",
confirmDeleteTask: "Вы уверены, что хотите удалить запись задачи для {author}? Это действие нельзя отменить.",
taskDeleted: "Задача успешно удалена",
clearFinishedTasks: "Очистить завершенные задачи",
tasksCleared: "Завершенные задачи успешно очищены",
confirmClearFinishedTasks: "Вы уверены, что хотите очистить все завершенные задачи (завершенные, отмененные)? Это удалит их из списка, но не удалит загруженные файлы.",
clear: "Очистить",
// Instruction Page
instructionSection1Title: "1. Загрузка и управление задачами",
instructionSection1Desc:

View File

@@ -180,7 +180,8 @@ export const zh = {
clearing: "清除中...",
clearThumbnailCacheSuccess: "缩略图缓存清除成功。下次访问时将重新生成。",
clearThumbnailCacheError: "清除缩略图缓存失败",
clearThumbnailCacheConfirmMessage: "这将清除所有云端视频的本地缩略图缓存。下次访问时将从云端重新生成。确定继续吗?",
clearThumbnailCacheConfirmMessage:
"这将清除所有云端视频的本地缩略图缓存。下次访问时将从云端重新生成。确定继续吗?",
// Manage
manageContent: "内容管理",
@@ -440,6 +441,10 @@ export const zh = {
deleteTask: "删除任务",
confirmDeleteTask: "您确定要删除 {author} 的任务记录吗?此操作无法撤销。",
taskDeleted: "任务已成功删除",
clearFinishedTasks: "清除已完成任务",
tasksCleared: "已成功清除已完成的任务",
confirmClearFinishedTasks: "您确定要清除所有已完成的任务(包括已完成和已取消)吗?这只会将其从列表中移除,不会删除任何已下载的文件。",
clear: "清除",
// Existing Video Detection
existingVideoDetected: "检测到已下载视频",