From 495b6f212911a5b36afecb1ac181ab8079b74178 Mon Sep 17 00:00:00 2001 From: Subhamoy Biswas Date: Mon, 21 Jul 2025 19:27:39 +0530 Subject: [PATCH] feat: added cancel search button and improved search error handling --- src/App.tsx | 29 ++++++++++++++++++--------- src/pages/downloader.tsx | 43 ++++++++++++++++++++++++++++++++++------ src/services/store.ts | 4 ++++ src/types/store.ts | 4 ++++ 4 files changed, 65 insertions(+), 15 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 57333a3..43dd402 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -43,6 +43,8 @@ export default function App({ children }: { children: React.ReactNode }) { const tempDownloadDirPath = useBasePathsStore((state) => state.tempDownloadDirPath); const downloadDirPath = useBasePathsStore((state) => state.downloadDirPath); + const setSearchPid = useCurrentVideoMetadataStore((state) => state.setSearchPid); + // const isUsingDefaultSettings = useSettingsPageStatesStore((state) => state.isUsingDefaultSettings); const setIsUsingDefaultSettings = useSettingsPageStatesStore((state) => state.setIsUsingDefaultSettings); const setSettingsKey = useSettingsPageStatesStore((state) => state.setSettingsKey); @@ -121,14 +123,19 @@ export default function App({ children }: { children: React.ReactNode }) { jsonOutput += line; }); - command.on('close', async () => { - try { - const data: RawVideoInfo = JSON.parse(jsonOutput); - resolve(data); - } - catch (e) { - console.error(`Failed to parse JSON: ${e}`); + command.on('close', async (data) => { + if (data.code !== 0) { + console.error(`yt-dlp failed to fetch metadata with code ${data.code}`); resolve(null); + } else { + try { + const parsedData: RawVideoInfo = JSON.parse(jsonOutput); + resolve(parsedData); + } + catch (e) { + console.error(`Failed to parse JSON: ${e}`); + resolve(null); + } } }); @@ -137,7 +144,9 @@ export default function App({ children }: { children: React.ReactNode }) { resolve(null); }); - command.spawn().catch(e => { + command.spawn().then(child => { + setSearchPid(child.pid); + }).catch(e => { console.error(`Failed to spawn command: ${e}`); resolve(null); }); @@ -265,7 +274,7 @@ export default function App({ children }: { children: React.ReactNode }) { console.log('Starting download with args:', args); const command = Command.sidecar('binaries/yt-dlp', args); - command.on('close', async data => { + command.on('close', async (data) => { if (data.code !== 0) { console.error(`Download failed with code ${data.code}`); if (!isErrorExpected) { @@ -303,6 +312,8 @@ export default function App({ children }: { children: React.ReactNode }) { command.on('error', error => { console.error(`Error: ${error}`); + setIsErrored(true); + setErroredDownloadId(downloadId); }); command.stdout.on('data', line => { diff --git a/src/pages/downloader.tsx b/src/pages/downloader.tsx index 2d91385..a77de8a 100644 --- a/src/pages/downloader.tsx +++ b/src/pages/downloader.tsx @@ -9,7 +9,7 @@ import { useToast } from "@/hooks/use-toast"; import { useAppContext } from "@/providers/appContextProvider"; import { useCurrentVideoMetadataStore, useDownloaderPageStatesStore, useSettingsPageStatesStore } from "@/services/store"; import { determineFileType, fileFormatFilter, formatBitrate, formatDurationString, formatFileSize, formatReleaseDate, formatYtStyleCount, isObjEmpty, sortByBitrate } from "@/utils"; -import { Calendar, Clock, DownloadCloud, Eye, Info, Loader2, Music, ThumbsUp, Video, File, ListVideo, PackageSearch, AlertCircleIcon } from "lucide-react"; +import { Calendar, Clock, DownloadCloud, Eye, Info, Loader2, Music, ThumbsUp, Video, File, ListVideo, PackageSearch, AlertCircleIcon, X } from "lucide-react"; import { FormatSelectionGroup, FormatSelectionGroupItem } from "@/components/custom/formatSelectionGroup"; import { useEffect, useRef } from "react"; import { ToggleGroup, ToggleGroupItem } from "@/components/custom/legacyToggleGroup"; @@ -23,6 +23,7 @@ import { Form, FormControl, FormField, FormItem, FormMessage } from "@/component import { config } from "@/config"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; +import { invoke } from "@tauri-apps/api/core"; const searchFormSchema = z.object({ url: z.string().min(1, { message: "URL is required" }) @@ -38,11 +39,14 @@ export default function DownloaderPage() { const isMetadataLoading = useCurrentVideoMetadataStore((state) => state.isMetadataLoading); const requestedUrl = useCurrentVideoMetadataStore((state) => state.requestedUrl); const autoSubmitSearch = useCurrentVideoMetadataStore((state) => state.autoSubmitSearch); + const searchPid = useCurrentVideoMetadataStore((state) => state.searchPid); const setVideoUrl = useCurrentVideoMetadataStore((state) => state.setVideoUrl); const setVideoMetadata = useCurrentVideoMetadataStore((state) => state.setVideoMetadata); const setIsMetadataLoading = useCurrentVideoMetadataStore((state) => state.setIsMetadataLoading); const setRequestedUrl = useCurrentVideoMetadataStore((state) => state.setRequestedUrl); const setAutoSubmitSearch = useCurrentVideoMetadataStore((state) => state.setAutoSubmitSearch); + const setSearchPid = useCurrentVideoMetadataStore((state) => state.setSearchPid); + const setShowSearchError = useCurrentVideoMetadataStore((state) => state.setShowSearchError); const activeDownloadModeTab = useDownloaderPageStatesStore((state) => state.activeDownloadModeTab); const isStartingDownload = useDownloaderPageStatesStore((state) => state.isStartingDownload); @@ -205,19 +209,25 @@ export default function DownloaderPage() { function handleSearchSubmit(values: z.infer) { setVideoMetadata(null); + setSearchPid(null); + setShowSearchError(true); setIsMetadataLoading(true); setSelectedDownloadFormat('best'); setSelectedCombinableVideoFormat(''); setSelectedCombinableAudioFormat(''); setSelectedSubtitles([]); setSelectedPlaylistVideoIndex('1'); + fetchVideoMetadata(values.url).then((metadata) => { if (!metadata || (metadata._type !== 'video' && metadata._type !== 'playlist') || (metadata && metadata._type === 'video' && metadata.formats.length <= 0) || (metadata && metadata._type === 'playlist' && metadata.entries.length <= 0)) { - toast({ - title: 'Opps! No results found', - description: 'The provided URL does not contain any downloadable content. Please check the URL and try again.', - variant: "destructive" - }); + const showSearchError = useCurrentVideoMetadataStore.getState().showSearchError; + if (showSearchError) { + toast({ + title: 'Oops! No results found', + description: 'The provided URL does not contain any downloadable content or you are not connected to the internet. Please check the URL, your network connection and try again.', + variant: "destructive" + }); + } } if (metadata && (metadata._type === 'video' || metadata._type === 'playlist') && ((metadata._type === 'video' && metadata.formats.length > 0) || (metadata._type === 'playlist' && metadata.entries.length > 0))) setVideoMetadata(metadata); if (metadata) console.log(metadata); @@ -225,6 +235,16 @@ export default function DownloaderPage() { }); } + const cancelSearch = async (pid: number | null) => { + setShowSearchError(false); + if (pid) { + console.log("Killing process with PID:", pid); + await invoke('kill_all_process', { pid: pid }); + } + setVideoMetadata(null); + setIsMetadataLoading(false); + }; + useEffect(() => { const updateBottomBarWidth = (): void => { if (containerRef.current && bottomBarRef.current) { @@ -338,6 +358,17 @@ export default function DownloaderPage() { )} /> + {isMetadataLoading && ( + + )}