mirror of
https://github.com/neosubhamoy/neodlp.git
synced 2025-12-20 00:49:33 +05:30
feat: added cancel search button and improved search error handling
This commit is contained in:
29
src/App.tsx
29
src/App.tsx
@@ -43,6 +43,8 @@ export default function App({ children }: { children: React.ReactNode }) {
|
|||||||
const tempDownloadDirPath = useBasePathsStore((state) => state.tempDownloadDirPath);
|
const tempDownloadDirPath = useBasePathsStore((state) => state.tempDownloadDirPath);
|
||||||
const downloadDirPath = useBasePathsStore((state) => state.downloadDirPath);
|
const downloadDirPath = useBasePathsStore((state) => state.downloadDirPath);
|
||||||
|
|
||||||
|
const setSearchPid = useCurrentVideoMetadataStore((state) => state.setSearchPid);
|
||||||
|
|
||||||
// const isUsingDefaultSettings = useSettingsPageStatesStore((state) => state.isUsingDefaultSettings);
|
// const isUsingDefaultSettings = useSettingsPageStatesStore((state) => state.isUsingDefaultSettings);
|
||||||
const setIsUsingDefaultSettings = useSettingsPageStatesStore((state) => state.setIsUsingDefaultSettings);
|
const setIsUsingDefaultSettings = useSettingsPageStatesStore((state) => state.setIsUsingDefaultSettings);
|
||||||
const setSettingsKey = useSettingsPageStatesStore((state) => state.setSettingsKey);
|
const setSettingsKey = useSettingsPageStatesStore((state) => state.setSettingsKey);
|
||||||
@@ -121,14 +123,19 @@ export default function App({ children }: { children: React.ReactNode }) {
|
|||||||
jsonOutput += line;
|
jsonOutput += line;
|
||||||
});
|
});
|
||||||
|
|
||||||
command.on('close', async () => {
|
command.on('close', async (data) => {
|
||||||
try {
|
if (data.code !== 0) {
|
||||||
const data: RawVideoInfo = JSON.parse(jsonOutput);
|
console.error(`yt-dlp failed to fetch metadata with code ${data.code}`);
|
||||||
resolve(data);
|
|
||||||
}
|
|
||||||
catch (e) {
|
|
||||||
console.error(`Failed to parse JSON: ${e}`);
|
|
||||||
resolve(null);
|
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);
|
resolve(null);
|
||||||
});
|
});
|
||||||
|
|
||||||
command.spawn().catch(e => {
|
command.spawn().then(child => {
|
||||||
|
setSearchPid(child.pid);
|
||||||
|
}).catch(e => {
|
||||||
console.error(`Failed to spawn command: ${e}`);
|
console.error(`Failed to spawn command: ${e}`);
|
||||||
resolve(null);
|
resolve(null);
|
||||||
});
|
});
|
||||||
@@ -265,7 +274,7 @@ export default function App({ children }: { children: React.ReactNode }) {
|
|||||||
console.log('Starting download with args:', args);
|
console.log('Starting download with args:', args);
|
||||||
const command = Command.sidecar('binaries/yt-dlp', args);
|
const command = Command.sidecar('binaries/yt-dlp', args);
|
||||||
|
|
||||||
command.on('close', async data => {
|
command.on('close', async (data) => {
|
||||||
if (data.code !== 0) {
|
if (data.code !== 0) {
|
||||||
console.error(`Download failed with code ${data.code}`);
|
console.error(`Download failed with code ${data.code}`);
|
||||||
if (!isErrorExpected) {
|
if (!isErrorExpected) {
|
||||||
@@ -303,6 +312,8 @@ export default function App({ children }: { children: React.ReactNode }) {
|
|||||||
|
|
||||||
command.on('error', error => {
|
command.on('error', error => {
|
||||||
console.error(`Error: ${error}`);
|
console.error(`Error: ${error}`);
|
||||||
|
setIsErrored(true);
|
||||||
|
setErroredDownloadId(downloadId);
|
||||||
});
|
});
|
||||||
|
|
||||||
command.stdout.on('data', line => {
|
command.stdout.on('data', line => {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { useToast } from "@/hooks/use-toast";
|
|||||||
import { useAppContext } from "@/providers/appContextProvider";
|
import { useAppContext } from "@/providers/appContextProvider";
|
||||||
import { useCurrentVideoMetadataStore, useDownloaderPageStatesStore, useSettingsPageStatesStore } from "@/services/store";
|
import { useCurrentVideoMetadataStore, useDownloaderPageStatesStore, useSettingsPageStatesStore } from "@/services/store";
|
||||||
import { determineFileType, fileFormatFilter, formatBitrate, formatDurationString, formatFileSize, formatReleaseDate, formatYtStyleCount, isObjEmpty, sortByBitrate } from "@/utils";
|
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 { FormatSelectionGroup, FormatSelectionGroupItem } from "@/components/custom/formatSelectionGroup";
|
||||||
import { useEffect, useRef } from "react";
|
import { useEffect, useRef } from "react";
|
||||||
import { ToggleGroup, ToggleGroupItem } from "@/components/custom/legacyToggleGroup";
|
import { ToggleGroup, ToggleGroupItem } from "@/components/custom/legacyToggleGroup";
|
||||||
@@ -23,6 +23,7 @@ import { Form, FormControl, FormField, FormItem, FormMessage } from "@/component
|
|||||||
import { config } from "@/config";
|
import { config } from "@/config";
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||||
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
|
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
|
||||||
|
import { invoke } from "@tauri-apps/api/core";
|
||||||
|
|
||||||
const searchFormSchema = z.object({
|
const searchFormSchema = z.object({
|
||||||
url: z.string().min(1, { message: "URL is required" })
|
url: z.string().min(1, { message: "URL is required" })
|
||||||
@@ -38,11 +39,14 @@ export default function DownloaderPage() {
|
|||||||
const isMetadataLoading = useCurrentVideoMetadataStore((state) => state.isMetadataLoading);
|
const isMetadataLoading = useCurrentVideoMetadataStore((state) => state.isMetadataLoading);
|
||||||
const requestedUrl = useCurrentVideoMetadataStore((state) => state.requestedUrl);
|
const requestedUrl = useCurrentVideoMetadataStore((state) => state.requestedUrl);
|
||||||
const autoSubmitSearch = useCurrentVideoMetadataStore((state) => state.autoSubmitSearch);
|
const autoSubmitSearch = useCurrentVideoMetadataStore((state) => state.autoSubmitSearch);
|
||||||
|
const searchPid = useCurrentVideoMetadataStore((state) => state.searchPid);
|
||||||
const setVideoUrl = useCurrentVideoMetadataStore((state) => state.setVideoUrl);
|
const setVideoUrl = useCurrentVideoMetadataStore((state) => state.setVideoUrl);
|
||||||
const setVideoMetadata = useCurrentVideoMetadataStore((state) => state.setVideoMetadata);
|
const setVideoMetadata = useCurrentVideoMetadataStore((state) => state.setVideoMetadata);
|
||||||
const setIsMetadataLoading = useCurrentVideoMetadataStore((state) => state.setIsMetadataLoading);
|
const setIsMetadataLoading = useCurrentVideoMetadataStore((state) => state.setIsMetadataLoading);
|
||||||
const setRequestedUrl = useCurrentVideoMetadataStore((state) => state.setRequestedUrl);
|
const setRequestedUrl = useCurrentVideoMetadataStore((state) => state.setRequestedUrl);
|
||||||
const setAutoSubmitSearch = useCurrentVideoMetadataStore((state) => state.setAutoSubmitSearch);
|
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 activeDownloadModeTab = useDownloaderPageStatesStore((state) => state.activeDownloadModeTab);
|
||||||
const isStartingDownload = useDownloaderPageStatesStore((state) => state.isStartingDownload);
|
const isStartingDownload = useDownloaderPageStatesStore((state) => state.isStartingDownload);
|
||||||
@@ -205,19 +209,25 @@ export default function DownloaderPage() {
|
|||||||
|
|
||||||
function handleSearchSubmit(values: z.infer<typeof searchFormSchema>) {
|
function handleSearchSubmit(values: z.infer<typeof searchFormSchema>) {
|
||||||
setVideoMetadata(null);
|
setVideoMetadata(null);
|
||||||
|
setSearchPid(null);
|
||||||
|
setShowSearchError(true);
|
||||||
setIsMetadataLoading(true);
|
setIsMetadataLoading(true);
|
||||||
setSelectedDownloadFormat('best');
|
setSelectedDownloadFormat('best');
|
||||||
setSelectedCombinableVideoFormat('');
|
setSelectedCombinableVideoFormat('');
|
||||||
setSelectedCombinableAudioFormat('');
|
setSelectedCombinableAudioFormat('');
|
||||||
setSelectedSubtitles([]);
|
setSelectedSubtitles([]);
|
||||||
setSelectedPlaylistVideoIndex('1');
|
setSelectedPlaylistVideoIndex('1');
|
||||||
|
|
||||||
fetchVideoMetadata(values.url).then((metadata) => {
|
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)) {
|
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({
|
const showSearchError = useCurrentVideoMetadataStore.getState().showSearchError;
|
||||||
title: 'Opps! No results found',
|
if (showSearchError) {
|
||||||
description: 'The provided URL does not contain any downloadable content. Please check the URL and try again.',
|
toast({
|
||||||
variant: "destructive"
|
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 && (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);
|
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(() => {
|
useEffect(() => {
|
||||||
const updateBottomBarWidth = (): void => {
|
const updateBottomBarWidth = (): void => {
|
||||||
if (containerRef.current && bottomBarRef.current) {
|
if (containerRef.current && bottomBarRef.current) {
|
||||||
@@ -338,6 +358,17 @@ export default function DownloaderPage() {
|
|||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
{isMetadataLoading && (
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="destructive"
|
||||||
|
size="icon"
|
||||||
|
disabled={!isMetadataLoading}
|
||||||
|
onClick={() => cancelSearch(searchPid)}
|
||||||
|
>
|
||||||
|
<X className="size-4" />
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={!videoUrl || isMetadataLoading}
|
disabled={!videoUrl || isMetadataLoading}
|
||||||
|
|||||||
@@ -34,11 +34,15 @@ export const useCurrentVideoMetadataStore = create<CurrentVideoMetadataStore>((s
|
|||||||
isMetadataLoading: false,
|
isMetadataLoading: false,
|
||||||
requestedUrl: '',
|
requestedUrl: '',
|
||||||
autoSubmitSearch: false,
|
autoSubmitSearch: false,
|
||||||
|
searchPid: null,
|
||||||
|
showSearchError: true,
|
||||||
setVideoUrl: (url) => set(() => ({ videoUrl: url })),
|
setVideoUrl: (url) => set(() => ({ videoUrl: url })),
|
||||||
setVideoMetadata: (metadata) => set(() => ({ videoMetadata: metadata })),
|
setVideoMetadata: (metadata) => set(() => ({ videoMetadata: metadata })),
|
||||||
setIsMetadataLoading: (isLoading) => set(() => ({ isMetadataLoading: isLoading })),
|
setIsMetadataLoading: (isLoading) => set(() => ({ isMetadataLoading: isLoading })),
|
||||||
setRequestedUrl: (url) => set(() => ({ requestedUrl: url })),
|
setRequestedUrl: (url) => set(() => ({ requestedUrl: url })),
|
||||||
setAutoSubmitSearch: (autoSubmit) => set(() => ({ autoSubmitSearch: autoSubmit })),
|
setAutoSubmitSearch: (autoSubmit) => set(() => ({ autoSubmitSearch: autoSubmit })),
|
||||||
|
setSearchPid: (pid) => set(() => ({ searchPid: pid })),
|
||||||
|
setShowSearchError: (showError) => set(() => ({ showSearchError: showError }))
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const useDownloaderPageStatesStore = create<DownloaderPageStatesStore>((set) => ({
|
export const useDownloaderPageStatesStore = create<DownloaderPageStatesStore>((set) => ({
|
||||||
|
|||||||
@@ -23,11 +23,15 @@ export interface CurrentVideoMetadataStore {
|
|||||||
isMetadataLoading: boolean;
|
isMetadataLoading: boolean;
|
||||||
requestedUrl: string;
|
requestedUrl: string;
|
||||||
autoSubmitSearch: boolean;
|
autoSubmitSearch: boolean;
|
||||||
|
searchPid: number | null;
|
||||||
|
showSearchError: boolean;
|
||||||
setVideoUrl: (url: string) => void;
|
setVideoUrl: (url: string) => void;
|
||||||
setVideoMetadata: (metadata: RawVideoInfo | null) => void;
|
setVideoMetadata: (metadata: RawVideoInfo | null) => void;
|
||||||
setIsMetadataLoading: (isLoading: boolean) => void;
|
setIsMetadataLoading: (isLoading: boolean) => void;
|
||||||
setRequestedUrl: (url: string) => void;
|
setRequestedUrl: (url: string) => void;
|
||||||
setAutoSubmitSearch: (autoSubmit: boolean) => void;
|
setAutoSubmitSearch: (autoSubmit: boolean) => void;
|
||||||
|
setSearchPid: (pid: number | null) => void;
|
||||||
|
setShowSearchError: (showError: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DownloaderPageStatesStore {
|
export interface DownloaderPageStatesStore {
|
||||||
|
|||||||
Reference in New Issue
Block a user