mirror of
https://github.com/neosubhamoy/neodlp.git
synced 2025-12-20 04:19:34 +05:30
refactor: added errored state and improved error detection
This commit is contained in:
63
src/App.tsx
63
src/App.tsx
@@ -52,14 +52,12 @@ export default function App({ children }: { children: React.ReactNode }) {
|
|||||||
color_scheme: APP_COLOR_SCHEME,
|
color_scheme: APP_COLOR_SCHEME,
|
||||||
} = useSettingsPageStatesStore(state => state.settings);
|
} = useSettingsPageStatesStore(state => state.settings);
|
||||||
|
|
||||||
const isErrored = useDownloaderPageStatesStore((state) => state.isErrored);
|
const erroredDownloadIds = useDownloaderPageStatesStore((state) => state.erroredDownloadIds);
|
||||||
const isErrorExpected = useDownloaderPageStatesStore((state) => state.isErrorExpected);
|
const expectedErrorDownloadIds = useDownloaderPageStatesStore((state) => state.expectedErrorDownloadIds);
|
||||||
const erroredDownloadId = useDownloaderPageStatesStore((state) => state.erroredDownloadId);
|
const removeErroredDownload = useDownloaderPageStatesStore((state) => state.removeErroredDownload);
|
||||||
const setIsErrored = useDownloaderPageStatesStore((state) => state.setIsErrored);
|
const removeExpectedErrorDownload = useDownloaderPageStatesStore((state) => state.removeExpectedErrorDownload);
|
||||||
const setIsErrorExpected = useDownloaderPageStatesStore((state) => state.setIsErrorExpected);
|
|
||||||
const setErroredDownloadId = useDownloaderPageStatesStore((state) => state.setErroredDownloadId);
|
|
||||||
|
|
||||||
const appWindow = getCurrentWebviewWindow()
|
const appWindow = getCurrentWebviewWindow();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const LOG = useLogger();
|
const LOG = useLogger();
|
||||||
const currentPlatform = platform();
|
const currentPlatform = platform();
|
||||||
@@ -79,6 +77,7 @@ export default function App({ children }: { children: React.ReactNode }) {
|
|||||||
const hasRunYtDlpAutoUpdateRef = useRef(false);
|
const hasRunYtDlpAutoUpdateRef = useRef(false);
|
||||||
const hasRunAppUpdateCheckRef = useRef(false);
|
const hasRunAppUpdateCheckRef = useRef(false);
|
||||||
const isRegisteredToMacOsRef = useRef(false);
|
const isRegisteredToMacOsRef = useRef(false);
|
||||||
|
const pendingErrorUpdatesRef = useRef<Set<string>>(new Set());
|
||||||
|
|
||||||
const { fetchVideoMetadata, startDownload, pauseDownload, resumeDownload, cancelDownload, processQueuedDownloads } = useDownloader();
|
const { fetchVideoMetadata, startDownload, pauseDownload, resumeDownload, cancelDownload, processQueuedDownloads } = useDownloader();
|
||||||
|
|
||||||
@@ -328,38 +327,56 @@ export default function App({ children }: { children: React.ReactNode }) {
|
|||||||
|
|
||||||
// show a toast and pause the download when yt-dlp exits unexpectedly
|
// show a toast and pause the download when yt-dlp exits unexpectedly
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isErrored && !isErrorExpected) {
|
const unexpectedErrors = Array.from(erroredDownloadIds).filter(id => !expectedErrorDownloadIds.has(id));
|
||||||
|
const processedUnexpectedErrors = unexpectedErrors.filter(id => !pendingErrorUpdatesRef.current.has(id));
|
||||||
|
if (unexpectedErrors.length === 0) return;
|
||||||
|
|
||||||
|
processedUnexpectedErrors.forEach((downloadId) => {
|
||||||
|
const downloadState = globalDownloadStates.find(d => d.download_id === downloadId);
|
||||||
toast.error("Download Failed", {
|
toast.error("Download Failed", {
|
||||||
description: "yt-dlp exited unexpectedly. Please try again later",
|
description: `The download for "${downloadState?.title}" failed because yt-dlp exited unexpectedly. Please try again later.`,
|
||||||
});
|
});
|
||||||
if (erroredDownloadId) {
|
});
|
||||||
downloadStatusUpdater.mutate({ download_id: erroredDownloadId, download_status: 'paused' }, {
|
|
||||||
|
const timeoutIds: NodeJS.Timeout[] = [];
|
||||||
|
unexpectedErrors.forEach((downloadId) => {
|
||||||
|
pendingErrorUpdatesRef.current.add(downloadId);
|
||||||
|
|
||||||
|
const timeoutId = setTimeout(() => {
|
||||||
|
downloadStatusUpdater.mutate({ download_id: downloadId, download_status: 'errored' }, {
|
||||||
onSuccess: (data) => {
|
onSuccess: (data) => {
|
||||||
console.log("Download status updated successfully:", data);
|
console.log("Download status updated successfully:", data);
|
||||||
queryClient.invalidateQueries({ queryKey: ['download-states'] });
|
queryClient.invalidateQueries({ queryKey: ['download-states'] });
|
||||||
|
removeErroredDownload(downloadId);
|
||||||
|
pendingErrorUpdatesRef.current.delete(downloadId);
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
console.error("Failed to update download status:", error);
|
console.error("Failed to update download status:", error);
|
||||||
|
removeErroredDownload(downloadId);
|
||||||
|
pendingErrorUpdatesRef.current.delete(downloadId);
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
setErroredDownloadId(null);
|
}, 500);
|
||||||
}
|
timeoutIds.push(timeoutId);
|
||||||
setIsErrored(false);
|
});
|
||||||
setIsErrorExpected(false);
|
|
||||||
}
|
return () => {
|
||||||
}, [isErrored, isErrorExpected, erroredDownloadId, setIsErrored, setIsErrorExpected, setErroredDownloadId]);
|
timeoutIds.forEach(id => clearTimeout(id));
|
||||||
|
};
|
||||||
|
}, [erroredDownloadIds, expectedErrorDownloadIds]);
|
||||||
|
|
||||||
// auto reset error states after 3 seconds of expecting an error
|
// auto reset error states after 3 seconds of expecting an error
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isErrorExpected) {
|
if (expectedErrorDownloadIds.size > 0) {
|
||||||
const timeoutId = setTimeout(() => {
|
const timeoutId = setTimeout(() => {
|
||||||
setIsErrored(false);
|
expectedErrorDownloadIds.forEach((downloadId) => {
|
||||||
setIsErrorExpected(false);
|
removeErroredDownload(downloadId);
|
||||||
setErroredDownloadId(null);
|
removeExpectedErrorDownload(downloadId);
|
||||||
|
});
|
||||||
}, 3000);
|
}, 3000);
|
||||||
return () => clearTimeout(timeoutId);
|
return () => clearTimeout(timeoutId);
|
||||||
}
|
}
|
||||||
}, [isErrorExpected, setIsErrorExpected]);
|
}, [expectedErrorDownloadIds]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppContext.Provider value={{ fetchVideoMetadata, startDownload, pauseDownload, resumeDownload, cancelDownload }}>
|
<AppContext.Provider value={{ fetchVideoMetadata, startDownload, pauseDownload, resumeDownload, cancelDownload }}>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { toast } from "sonner";
|
|||||||
import { useAppContext } from "@/providers/appContextProvider";
|
import { useAppContext } from "@/providers/appContextProvider";
|
||||||
import { useDownloadActionStatesStore, useSettingsPageStatesStore } from "@/services/store";
|
import { useDownloadActionStatesStore, useSettingsPageStatesStore } from "@/services/store";
|
||||||
import { formatFileSize, formatSecToTimeString, formatSpeed } from "@/utils";
|
import { formatFileSize, formatSecToTimeString, formatSpeed } from "@/utils";
|
||||||
import { ArrowUpRightIcon, CircleCheck, File, Loader2, Music, Pause, Play, Video, X } from "lucide-react";
|
import { ArrowUpRightIcon, CircleCheck, File, Info, Loader2, Music, Pause, Play, RotateCw, Video, X } from "lucide-react";
|
||||||
import { DownloadState } from "@/types/download";
|
import { DownloadState } from "@/types/download";
|
||||||
import { Empty, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle } from "@/components/ui/empty";
|
import { Empty, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle } from "@/components/ui/empty";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
@@ -72,7 +72,7 @@ export function IncompleteDownload({ state }: IncompleteDownloadProps) {
|
|||||||
{((state.download_status === 'starting') || (state.download_status === 'downloading' && state.status === 'finished')) && (
|
{((state.download_status === 'starting') || (state.download_status === 'downloading' && state.status === 'finished')) && (
|
||||||
<IndeterminateProgress indeterminate={true} className="w-full" />
|
<IndeterminateProgress indeterminate={true} className="w-full" />
|
||||||
)}
|
)}
|
||||||
{(state.download_status === 'downloading' || state.download_status === 'paused') && state.progress && state.status !== 'finished' && (
|
{(state.download_status === 'downloading' || state.download_status === 'paused' || state.download_status === 'errored') && state.progress && state.status !== 'finished' && (
|
||||||
<div className="w-full flex items-center gap-2">
|
<div className="w-full flex items-center gap-2">
|
||||||
<span className="text-sm text-nowrap">{state.progress}%</span>
|
<span className="text-sm text-nowrap">{state.progress}%</span>
|
||||||
<Progress value={state.progress} />
|
<Progress value={state.progress} />
|
||||||
@@ -84,7 +84,21 @@ export function IncompleteDownload({ state }: IncompleteDownloadProps) {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="text-xs text-muted-foreground">
|
<div className="text-xs text-muted-foreground">
|
||||||
{state.download_status && state.download_status === 'downloading' && state.status === 'finished' ? 'Processing' : state.download_status.charAt(0).toUpperCase() + state.download_status.slice(1)} {debugMode && state.download_id ? <><span className="text-primary">•</span> ID: {state.download_id.toUpperCase()}</> : ""} {state.download_status === 'downloading' && state.status !== 'finished' && state.speed ? <><span className="text-primary">•</span> Speed: {formatSpeed(state.speed)}</> : ""} {state.download_status === 'downloading' && state.eta ? <><span className="text-primary">•</span> ETA: {formatSecToTimeString(state.eta)}</> : ""}
|
{state.download_status && state.download_status === 'downloading' && state.status === 'finished' ? (
|
||||||
|
<span>Processing</span>
|
||||||
|
) : state.download_status && state.download_status === 'errored' ? (
|
||||||
|
<span className="text-destructive"><Info className="inline size-3 mb-1 mr-0.5" /> Errored</span>
|
||||||
|
) : (
|
||||||
|
<span>{state.download_status.charAt(0).toUpperCase() + state.download_status.slice(1)}</span>
|
||||||
|
)} {
|
||||||
|
(debugMode && state.download_id) || (state.download_status === 'errored' && state.download_id) && (
|
||||||
|
<><span className="text-primary">•</span> ID: {state.download_id.toUpperCase()}</>
|
||||||
|
)} {
|
||||||
|
state.download_status === 'downloading' && state.status !== 'finished' && state.speed && (
|
||||||
|
<><span className="text-primary">•</span> Speed: {formatSpeed(state.speed)}</>
|
||||||
|
)} {state.download_status === 'downloading' && state.eta && (
|
||||||
|
<><span className="text-primary">•</span> ETA: {formatSecToTimeString(state.eta)}</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full flex items-center gap-2 mt-2">
|
<div className="w-full flex items-center gap-2 mt-2">
|
||||||
@@ -102,7 +116,7 @@ export function IncompleteDownload({ state }: IncompleteDownloadProps) {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
toast.error("Failed to Resume Download", {
|
toast.error("Failed to Resume Download", {
|
||||||
description: "An error occurred while trying to resume the download.",
|
description: `An error occurred while trying to resume the download for "${state.title}".`,
|
||||||
})
|
})
|
||||||
} finally {
|
} finally {
|
||||||
setIsResumingDownload(state.download_id, false);
|
setIsResumingDownload(state.download_id, false);
|
||||||
@@ -122,6 +136,37 @@ export function IncompleteDownload({ state }: IncompleteDownloadProps) {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
|
) : state.download_status === 'errored' ? (
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
className="w-fill"
|
||||||
|
onClick={async () => {
|
||||||
|
setIsResumingDownload(state.download_id, true);
|
||||||
|
try {
|
||||||
|
await resumeDownload(state);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
toast.error("Failed to Restart Download", {
|
||||||
|
description: `An error occurred while trying to restart the download for "${state.title}".`,
|
||||||
|
})
|
||||||
|
} finally {
|
||||||
|
setIsResumingDownload(state.download_id, false);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
disabled={itemActionStates.isResuming || itemActionStates.isCanceling}
|
||||||
|
>
|
||||||
|
{itemActionStates.isResuming ? (
|
||||||
|
<>
|
||||||
|
<Loader2 className="h-4 w-4 animate-spin" />
|
||||||
|
Retrying
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<RotateCw className="w-4 h-4" />
|
||||||
|
Retry
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
) : (
|
) : (
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
@@ -136,7 +181,7 @@ export function IncompleteDownload({ state }: IncompleteDownloadProps) {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
toast.error("Failed to Pause Download", {
|
toast.error("Failed to Pause Download", {
|
||||||
description: "An error occurred while trying to pause the download."
|
description: `An error occurred while trying to pause the download for "${state.title}".`,
|
||||||
})
|
})
|
||||||
} finally {
|
} finally {
|
||||||
setIsPausingDownload(state.download_id, false);
|
setIsPausingDownload(state.download_id, false);
|
||||||
@@ -165,12 +210,12 @@ export function IncompleteDownload({ state }: IncompleteDownloadProps) {
|
|||||||
try {
|
try {
|
||||||
await cancelDownload(state)
|
await cancelDownload(state)
|
||||||
toast.success("Canceled Download", {
|
toast.success("Canceled Download", {
|
||||||
description: "Download canceled successfully.",
|
description: `The download for "${state.title}" has been canceled.`,
|
||||||
})
|
})
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
toast.error("Failed to Cancel Download", {
|
toast.error("Failed to Cancel Download", {
|
||||||
description: "An error occurred while trying to cancel the download.",
|
description: `An error occurred while trying to cancel the download for "${state.title}".`,
|
||||||
})
|
})
|
||||||
} finally {
|
} finally {
|
||||||
setIsCancelingDownload(state.download_id, false);
|
setIsCancelingDownload(state.download_id, false);
|
||||||
|
|||||||
@@ -63,10 +63,11 @@ export default function useDownloader() {
|
|||||||
download_completion_notification: DOWNLOAD_COMPLETION_NOTIFICATION
|
download_completion_notification: DOWNLOAD_COMPLETION_NOTIFICATION
|
||||||
} = useSettingsPageStatesStore(state => state.settings);
|
} = useSettingsPageStatesStore(state => state.settings);
|
||||||
|
|
||||||
const isErrorExpected = useDownloaderPageStatesStore((state) => state.isErrorExpected);
|
const expectedErrorDownloadIds = useDownloaderPageStatesStore((state) => state.expectedErrorDownloadIds);
|
||||||
const setIsErrored = useDownloaderPageStatesStore((state) => state.setIsErrored);
|
const addErroredDownload = useDownloaderPageStatesStore((state) => state.addErroredDownload);
|
||||||
const setIsErrorExpected = useDownloaderPageStatesStore((state) => state.setIsErrorExpected);
|
const removeErroredDownload = useDownloaderPageStatesStore((state) => state.removeErroredDownload);
|
||||||
const setErroredDownloadId = useDownloaderPageStatesStore((state) => state.setErroredDownloadId);
|
const addExpectedErrorDownload = useDownloaderPageStatesStore((state) => state.addExpectedErrorDownload);
|
||||||
|
const removeExpectedErrorDownload = useDownloaderPageStatesStore((state) => state.removeExpectedErrorDownload);
|
||||||
|
|
||||||
const LOG = useLogger();
|
const LOG = useLogger();
|
||||||
const currentPlatform = platform();
|
const currentPlatform = platform();
|
||||||
@@ -210,10 +211,6 @@ export default function useDownloader() {
|
|||||||
const startDownload = async (params: StartDownloadParams) => {
|
const startDownload = async (params: StartDownloadParams) => {
|
||||||
const { url, selectedFormat, downloadConfig, selectedSubtitles, resumeState, playlistItems } = params;
|
const { url, selectedFormat, downloadConfig, selectedSubtitles, resumeState, playlistItems } = params;
|
||||||
LOG.info('NEODLP', `Initiating yt-dlp download for URL: ${url}`);
|
LOG.info('NEODLP', `Initiating yt-dlp download for URL: ${url}`);
|
||||||
// set error states to default
|
|
||||||
setIsErrored(false);
|
|
||||||
setIsErrorExpected(false);
|
|
||||||
setErroredDownloadId(null);
|
|
||||||
|
|
||||||
console.log('Starting download:', { url, selectedFormat, downloadConfig, selectedSubtitles, resumeState, playlistItems });
|
console.log('Starting download:', { url, selectedFormat, downloadConfig, selectedSubtitles, resumeState, playlistItems });
|
||||||
if (!ffmpegPath || !tempDownloadDirPath || !downloadDirPath) {
|
if (!ffmpegPath || !tempDownloadDirPath || !downloadDirPath) {
|
||||||
@@ -258,6 +255,11 @@ export default function useDownloader() {
|
|||||||
const videoId = resumeState?.video_id || generateVideoId(videoMetadata.id, videoMetadata.webpage_url_domain);
|
const videoId = resumeState?.video_id || generateVideoId(videoMetadata.id, videoMetadata.webpage_url_domain);
|
||||||
const playlistId = isPlaylist ? (resumeState?.playlist_id || generateVideoId(videoMetadata.playlist_id, videoMetadata.webpage_url_domain)) : null;
|
const playlistId = isPlaylist ? (resumeState?.playlist_id || generateVideoId(videoMetadata.playlist_id, videoMetadata.webpage_url_domain)) : null;
|
||||||
const downloadId = resumeState?.download_id || ulid() /*generateDownloadId(videoMetadata.id, videoMetadata.webpage_url_domain)*/;
|
const downloadId = resumeState?.download_id || ulid() /*generateDownloadId(videoMetadata.id, videoMetadata.webpage_url_domain)*/;
|
||||||
|
|
||||||
|
// Clear any existing errored/expected error states for this download
|
||||||
|
removeErroredDownload(downloadId);
|
||||||
|
removeExpectedErrorDownload(downloadId);
|
||||||
|
|
||||||
// const tempDownloadPathForYtdlp = await join(tempDownloadDirPath, `${downloadId}_${selectedFormat}.%(ext)s`);
|
// const tempDownloadPathForYtdlp = await join(tempDownloadDirPath, `${downloadId}_${selectedFormat}.%(ext)s`);
|
||||||
// const tempDownloadPath = await join(tempDownloadDirPath, `${downloadId}_${selectedFormat}.${videoMetadata.ext}`);
|
// const tempDownloadPath = await join(tempDownloadDirPath, `${downloadId}_${selectedFormat}.${videoMetadata.ext}`);
|
||||||
// let downloadFilePath = resumeState?.filepath || await join(downloadDirPath, sanitizeFilename(`${videoMetadata.title}_${videoMetadata.resolution || 'unknown'}[${videoMetadata.id}].${videoMetadata.ext}`));
|
// let downloadFilePath = resumeState?.filepath || await join(downloadDirPath, sanitizeFilename(`${videoMetadata.title}_${videoMetadata.resolution || 'unknown'}[${videoMetadata.id}].${videoMetadata.ext}`));
|
||||||
@@ -446,10 +448,7 @@ export default function useDownloader() {
|
|||||||
if (data.code !== 0) {
|
if (data.code !== 0) {
|
||||||
console.error(`Download failed with code ${data.code}`);
|
console.error(`Download failed with code ${data.code}`);
|
||||||
LOG.error(`YT-DLP Download ${downloadId}`, `yt-dlp exited with code ${data.code} (ignore if you manually paused or cancelled the download)`);
|
LOG.error(`YT-DLP Download ${downloadId}`, `yt-dlp exited with code ${data.code} (ignore if you manually paused or cancelled the download)`);
|
||||||
if (!isErrorExpected) {
|
if (!expectedErrorDownloadIds.has(downloadId)) addErroredDownload(downloadId);
|
||||||
setIsErrored(true);
|
|
||||||
setErroredDownloadId(downloadId);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
LOG.info(`YT-DLP Download ${downloadId}`, `yt-dlp exited with code ${data.code}`);
|
LOG.info(`YT-DLP Download ${downloadId}`, `yt-dlp exited with code ${data.code}`);
|
||||||
}
|
}
|
||||||
@@ -458,8 +457,7 @@ export default function useDownloader() {
|
|||||||
command.on('error', error => {
|
command.on('error', error => {
|
||||||
console.error(`Error: ${error}`);
|
console.error(`Error: ${error}`);
|
||||||
LOG.error(`YT-DLP Download ${downloadId}`, `Error occurred: ${error}`);
|
LOG.error(`YT-DLP Download ${downloadId}`, `Error occurred: ${error}`);
|
||||||
setIsErrored(true);
|
addErroredDownload(downloadId);
|
||||||
setErroredDownloadId(downloadId);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
command.stdout.on('data', line => {
|
command.stdout.on('data', line => {
|
||||||
@@ -681,7 +679,7 @@ export default function useDownloader() {
|
|||||||
try {
|
try {
|
||||||
LOG.info('NEODLP', `Pausing yt-dlp download with id: ${downloadState.download_id} (as per user request)`);
|
LOG.info('NEODLP', `Pausing yt-dlp download with id: ${downloadState.download_id} (as per user request)`);
|
||||||
if ((downloadState.download_status === 'downloading' && downloadState.process_id) || (downloadState.download_status === 'starting' && downloadState.process_id)) {
|
if ((downloadState.download_status === 'downloading' && downloadState.process_id) || (downloadState.download_status === 'starting' && downloadState.process_id)) {
|
||||||
setIsErrorExpected(true); // Set error expected to true to handle UI state
|
addExpectedErrorDownload(downloadState.download_id); // Mark as error expected to handle UI state
|
||||||
console.log("Killing process with PID:", downloadState.process_id);
|
console.log("Killing process with PID:", downloadState.process_id);
|
||||||
await invoke('kill_all_process', { pid: downloadState.process_id });
|
await invoke('kill_all_process', { pid: downloadState.process_id });
|
||||||
}
|
}
|
||||||
@@ -706,12 +704,13 @@ export default function useDownloader() {
|
|||||||
reject(error);
|
reject(error);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, 1000);
|
}, 500);
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(`Failed to pause download: ${e}`);
|
console.error(`Failed to pause download: ${e}`);
|
||||||
LOG.error('NEODLP', `Failed to pause download with id: ${downloadState.download_id} with error: ${e}`);
|
LOG.error('NEODLP', `Failed to pause download with id: ${downloadState.download_id} with error: ${e}`);
|
||||||
isProcessingQueueRef.current = false;
|
isProcessingQueueRef.current = false;
|
||||||
|
removeExpectedErrorDownload(downloadState.download_id);
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -726,6 +725,7 @@ export default function useDownloader() {
|
|||||||
output_format: null,
|
output_format: null,
|
||||||
embed_metadata: null,
|
embed_metadata: null,
|
||||||
embed_thumbnail: null,
|
embed_thumbnail: null,
|
||||||
|
square_crop_thumbnail: null,
|
||||||
sponsorblock: null,
|
sponsorblock: null,
|
||||||
custom_command: null
|
custom_command: null
|
||||||
},
|
},
|
||||||
@@ -744,31 +744,39 @@ export default function useDownloader() {
|
|||||||
try {
|
try {
|
||||||
LOG.info('NEODLP', `Cancelling yt-dlp download with id: ${downloadState.download_id} (as per user request)`);
|
LOG.info('NEODLP', `Cancelling yt-dlp download with id: ${downloadState.download_id} (as per user request)`);
|
||||||
if ((downloadState.download_status === 'downloading' && downloadState.process_id) || (downloadState.download_status === 'starting' && downloadState.process_id)) {
|
if ((downloadState.download_status === 'downloading' && downloadState.process_id) || (downloadState.download_status === 'starting' && downloadState.process_id)) {
|
||||||
setIsErrorExpected(true); // Set error expected to true to handle UI state
|
addExpectedErrorDownload(downloadState.download_id); // Mark as error expected to handle UI state
|
||||||
console.log("Killing process with PID:", downloadState.process_id);
|
console.log("Killing process with PID:", downloadState.process_id);
|
||||||
await invoke('kill_all_process', { pid: downloadState.process_id });
|
await invoke('kill_all_process', { pid: downloadState.process_id });
|
||||||
}
|
}
|
||||||
downloadStateDeleter.mutate(downloadState.download_id, {
|
|
||||||
onSuccess: (data) => {
|
|
||||||
console.log("Download State deleted successfully:", data);
|
|
||||||
queryClient.invalidateQueries({ queryKey: ['download-states'] });
|
|
||||||
// Reset processing flag and trigger queue processing
|
|
||||||
isProcessingQueueRef.current = false;
|
|
||||||
|
|
||||||
// Process the queue after a short delay
|
return new Promise<void>((resolve, reject) => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
processQueuedDownloads();
|
downloadStateDeleter.mutate(downloadState.download_id, {
|
||||||
}, 1000);
|
onSuccess: (data) => {
|
||||||
},
|
console.log("Download State deleted successfully:", data);
|
||||||
onError: (error) => {
|
queryClient.invalidateQueries({ queryKey: ['download-states'] });
|
||||||
console.error("Failed to delete download state:", error);
|
// Reset processing flag and trigger queue processing
|
||||||
isProcessingQueueRef.current = false;
|
isProcessingQueueRef.current = false;
|
||||||
}
|
|
||||||
|
// Process the queue after a short delay
|
||||||
|
setTimeout(() => {
|
||||||
|
processQueuedDownloads();
|
||||||
|
}, 1000);
|
||||||
|
resolve();
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
console.error("Failed to delete download state:", error);
|
||||||
|
isProcessingQueueRef.current = false;
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, 500);
|
||||||
});
|
});
|
||||||
return Promise.resolve();
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(`Failed to cancel download: ${e}`);
|
console.error(`Failed to cancel download: ${e}`);
|
||||||
LOG.error('NEODLP', `Failed to cancel download with id: ${downloadState.download_id} with error: ${e}`);
|
LOG.error('NEODLP', `Failed to cancel download with id: ${downloadState.download_id} with error: ${e}`);
|
||||||
|
isProcessingQueueRef.current = false;
|
||||||
|
removeExpectedErrorDownload(downloadState.download_id);
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -826,6 +834,7 @@ export default function useDownloader() {
|
|||||||
output_format: null,
|
output_format: null,
|
||||||
embed_metadata: null,
|
embed_metadata: null,
|
||||||
embed_thumbnail: null,
|
embed_thumbnail: null,
|
||||||
|
square_crop_thumbnail: null,
|
||||||
sponsorblock: null,
|
sponsorblock: null,
|
||||||
custom_command: null
|
custom_command: null
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,13 +3,22 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
|||||||
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
|
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
|
||||||
|
|
||||||
const TanstackProvider = ({children}: {children: React.ReactNode}) => {
|
const TanstackProvider = ({children}: {children: React.ReactNode}) => {
|
||||||
const queryClient = new QueryClient();
|
const queryClient = new QueryClient({
|
||||||
return (
|
defaultOptions: {
|
||||||
<QueryClientProvider client={queryClient}>
|
queries: {
|
||||||
{children}
|
networkMode: 'always',
|
||||||
<ReactQueryDevtools initialIsOpen={false} />
|
},
|
||||||
</QueryClientProvider>
|
mutations: {
|
||||||
)
|
networkMode: 'always',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return (
|
||||||
|
<QueryClientProvider client={queryClient}>
|
||||||
|
{children}
|
||||||
|
<ReactQueryDevtools initialIsOpen={false} />
|
||||||
|
</QueryClientProvider>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default TanstackProvider
|
export default TanstackProvider
|
||||||
|
|||||||
@@ -62,9 +62,8 @@ export const useDownloaderPageStatesStore = create<DownloaderPageStatesStore>((s
|
|||||||
sponsorblock: null,
|
sponsorblock: null,
|
||||||
custom_command: null
|
custom_command: null
|
||||||
},
|
},
|
||||||
isErrored: false,
|
erroredDownloadIds: new Set(),
|
||||||
isErrorExpected: false,
|
expectedErrorDownloadIds: new Set(),
|
||||||
erroredDownloadId: null,
|
|
||||||
videoPanelSizes: [35, 65],
|
videoPanelSizes: [35, 65],
|
||||||
playlistPanelSizes: [45, 55],
|
playlistPanelSizes: [45, 55],
|
||||||
setActiveDownloadModeTab: (tab) => set(() => ({ activeDownloadModeTab: tab })),
|
setActiveDownloadModeTab: (tab) => set(() => ({ activeDownloadModeTab: tab })),
|
||||||
@@ -92,9 +91,23 @@ export const useDownloaderPageStatesStore = create<DownloaderPageStatesStore>((s
|
|||||||
custom_command: null
|
custom_command: null
|
||||||
}
|
}
|
||||||
})),
|
})),
|
||||||
setIsErrored: (isErrored) => set(() => ({ isErrored: isErrored })),
|
addErroredDownload: (downloadId) => set((state) => ({
|
||||||
setIsErrorExpected: (isErrorExpected) => set(() => ({ isErrorExpected: isErrorExpected })),
|
erroredDownloadIds: new Set(state.erroredDownloadIds).add(downloadId)
|
||||||
setErroredDownloadId: (downloadId) => set(() => ({ erroredDownloadId: downloadId })),
|
})),
|
||||||
|
removeErroredDownload: (downloadId) => set((state) => {
|
||||||
|
const newSet = new Set(state.erroredDownloadIds);
|
||||||
|
newSet.delete(downloadId);
|
||||||
|
return { erroredDownloadIds: newSet };
|
||||||
|
}),
|
||||||
|
addExpectedErrorDownload: (downloadId) => set((state) => ({
|
||||||
|
expectedErrorDownloadIds: new Set(state.expectedErrorDownloadIds).add(downloadId)
|
||||||
|
})),
|
||||||
|
removeExpectedErrorDownload: (downloadId) => set((state) => {
|
||||||
|
const newSet = new Set(state.expectedErrorDownloadIds);
|
||||||
|
newSet.delete(downloadId);
|
||||||
|
return { expectedErrorDownloadIds: newSet };
|
||||||
|
}),
|
||||||
|
clearErrorStates: () => set({ erroredDownloadIds: new Set(), expectedErrorDownloadIds: new Set() }),
|
||||||
setVideoPanelSizes: (sizes) => set(() => ({ videoPanelSizes: sizes })),
|
setVideoPanelSizes: (sizes) => set(() => ({ videoPanelSizes: sizes })),
|
||||||
setPlaylistPanelSizes: (sizes) => set(() => ({ playlistPanelSizes: sizes }))
|
setPlaylistPanelSizes: (sizes) => set(() => ({ playlistPanelSizes: sizes }))
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -45,9 +45,8 @@ export interface DownloaderPageStatesStore {
|
|||||||
selectedSubtitles: string[];
|
selectedSubtitles: string[];
|
||||||
selectedPlaylistVideoIndex: string;
|
selectedPlaylistVideoIndex: string;
|
||||||
downloadConfiguration: DownloadConfiguration;
|
downloadConfiguration: DownloadConfiguration;
|
||||||
isErrored: boolean;
|
erroredDownloadIds: Set<string>;
|
||||||
isErrorExpected: boolean;
|
expectedErrorDownloadIds: Set<string>;
|
||||||
erroredDownloadId: string | null;
|
|
||||||
videoPanelSizes: number[];
|
videoPanelSizes: number[];
|
||||||
playlistPanelSizes: number[];
|
playlistPanelSizes: number[];
|
||||||
setActiveDownloadModeTab: (tab: string) => void;
|
setActiveDownloadModeTab: (tab: string) => void;
|
||||||
@@ -61,9 +60,11 @@ export interface DownloaderPageStatesStore {
|
|||||||
setDownloadConfigurationKey: (key: string, value: unknown) => void;
|
setDownloadConfigurationKey: (key: string, value: unknown) => void;
|
||||||
setDownloadConfiguration: (config: DownloadConfiguration) => void;
|
setDownloadConfiguration: (config: DownloadConfiguration) => void;
|
||||||
resetDownloadConfiguration: () => void;
|
resetDownloadConfiguration: () => void;
|
||||||
setIsErrored: (isErrored: boolean) => void;
|
addErroredDownload: (downloadId: string) => void;
|
||||||
setIsErrorExpected: (isErrorExpected: boolean) => void;
|
removeErroredDownload: (downloadId: string) => void;
|
||||||
setErroredDownloadId: (downloadId: string | null) => void;
|
addExpectedErrorDownload: (downloadId: string) => void;
|
||||||
|
removeExpectedErrorDownload: (downloadId: string) => void;
|
||||||
|
clearErrorStates: () => void;
|
||||||
setVideoPanelSizes: (sizes: number[]) => void;
|
setVideoPanelSizes: (sizes: number[]) => void;
|
||||||
setPlaylistPanelSizes: (sizes: number[]) => void;
|
setPlaylistPanelSizes: (sizes: number[]) => void;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user