mirror of
https://github.com/neosubhamoy/neodlp.git
synced 2025-12-19 23:39:33 +05:30
feat: added neodlp logger and log viewer
This commit is contained in:
33
src/App.tsx
33
src/App.tsx
@@ -26,6 +26,7 @@ import { useMacOsRegisterer } from "@/helpers/use-macos-registerer";
|
|||||||
import useAppUpdater from "@/helpers/use-app-updater";
|
import useAppUpdater from "@/helpers/use-app-updater";
|
||||||
import { Toaster as Sonner } from "@/components/ui/sonner";
|
import { Toaster as Sonner } from "@/components/ui/sonner";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
|
import { useLogger } from "@/helpers/use-logger";
|
||||||
|
|
||||||
export default function App({ children }: { children: React.ReactNode }) {
|
export default function App({ children }: { children: React.ReactNode }) {
|
||||||
const { data: downloadStates, isSuccess: isSuccessFetchingDownloadStates } = useFetchAllDownloadStates();
|
const { data: downloadStates, isSuccess: isSuccessFetchingDownloadStates } = useFetchAllDownloadStates();
|
||||||
@@ -93,6 +94,7 @@ export default function App({ children }: { children: React.ReactNode }) {
|
|||||||
|
|
||||||
const appWindow = getCurrentWebviewWindow()
|
const appWindow = getCurrentWebviewWindow()
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const LOG = useLogger();
|
||||||
const { updateYtDlp } = useYtDlpUpdater();
|
const { updateYtDlp } = useYtDlpUpdater();
|
||||||
const { registerToMac } = useMacOsRegisterer();
|
const { registerToMac } = useMacOsRegisterer();
|
||||||
const { checkForAppUpdate } = useAppUpdater();
|
const { checkForAppUpdate } = useAppUpdater();
|
||||||
@@ -151,12 +153,14 @@ export default function App({ children }: { children: React.ReactNode }) {
|
|||||||
command.on('close', async (data) => {
|
command.on('close', async (data) => {
|
||||||
if (data.code !== 0) {
|
if (data.code !== 0) {
|
||||||
console.error(`yt-dlp failed to fetch metadata with code ${data.code}`);
|
console.error(`yt-dlp failed to fetch metadata with code ${data.code}`);
|
||||||
|
LOG.error('NEODLP', `yt-dlp exited with code ${data.code} while fetching metadata for URL: ${url} (ignore if you manually cancelled)`);
|
||||||
resolve(null);
|
resolve(null);
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
const matchedJson = jsonOutput.match(/{.*}/);
|
const matchedJson = jsonOutput.match(/{.*}/);
|
||||||
if (!matchedJson) {
|
if (!matchedJson) {
|
||||||
console.error(`Failed to match JSON: ${jsonOutput}`);
|
console.error(`Failed to match JSON: ${jsonOutput}`);
|
||||||
|
LOG.error('NEODLP', `Failed to parse metadata JSON for URL: ${url})`);
|
||||||
resolve(null);
|
resolve(null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -165,6 +169,7 @@ export default function App({ children }: { children: React.ReactNode }) {
|
|||||||
}
|
}
|
||||||
catch (e) {
|
catch (e) {
|
||||||
console.error(`Failed to parse JSON: ${e}`);
|
console.error(`Failed to parse JSON: ${e}`);
|
||||||
|
LOG.error('NEODLP', `Failed to parse metadata JSON for URL: ${url}) with error: ${e}`);
|
||||||
resolve(null);
|
resolve(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -172,23 +177,28 @@ export default function App({ children }: { children: React.ReactNode }) {
|
|||||||
|
|
||||||
command.on('error', error => {
|
command.on('error', error => {
|
||||||
console.error(`Error fetching metadata: ${error}`);
|
console.error(`Error fetching metadata: ${error}`);
|
||||||
|
LOG.error('NEODLP', `Error occurred while fetching metadata for URL: ${url} : ${error}`);
|
||||||
resolve(null);
|
resolve(null);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
LOG.info('NEODLP', `Fetching metadata for URL: ${url}, with args: ${args.join(' ')}`);
|
||||||
command.spawn().then(child => {
|
command.spawn().then(child => {
|
||||||
setSearchPid(child.pid);
|
setSearchPid(child.pid);
|
||||||
}).catch(e => {
|
}).catch(e => {
|
||||||
console.error(`Failed to spawn command: ${e}`);
|
console.error(`Failed to spawn command: ${e}`);
|
||||||
|
LOG.error('NEODLP', `Failed to spawn yt-dlp process for fetching metadata for URL: ${url} : ${e}`);
|
||||||
resolve(null);
|
resolve(null);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(`Failed to fetch metadata: ${e}`);
|
console.error(`Failed to fetch metadata: ${e}`);
|
||||||
|
LOG.error('NEODLP', `Failed to fetch metadata for URL: ${url} : ${e}`);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const startDownload = async (url: string, selectedFormat: string, selectedSubtitles?: string | null, resumeState?: DownloadState, playlistItems?: string) => {
|
const startDownload = async (url: string, selectedFormat: string, selectedSubtitles?: string | null, resumeState?: DownloadState, playlistItems?: string) => {
|
||||||
|
LOG.info('NEODLP', `Initiating yt-dlp download for URL: ${url}`);
|
||||||
// set error states to default
|
// set error states to default
|
||||||
setIsErrored(false);
|
setIsErrored(false);
|
||||||
setIsErrorExpected(false);
|
setIsErrorExpected(false);
|
||||||
@@ -343,6 +353,7 @@ export default function App({ children }: { children: React.ReactNode }) {
|
|||||||
'--downloader', 'dash,m3u8:native',
|
'--downloader', 'dash,m3u8:native',
|
||||||
'--downloader-args', 'aria2c:-c -j 16 -x 16 -s 16 -k 1M --check-certificate=false'
|
'--downloader-args', 'aria2c:-c -j 16 -x 16 -s 16 -k 1M --check-certificate=false'
|
||||||
);
|
);
|
||||||
|
LOG.warning('NEODLP', `Looks like you are using aria2 for this yt-dlp download: ${downloadId}. Make sure aria2 is installed on your system if you are on macOS for this to work. Also, pause/resume might not work as expected especially on windows (using aria2 is not recommended for most downloads).`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (resumeState || USE_ARIA2) {
|
if (resumeState || USE_ARIA2) {
|
||||||
@@ -357,6 +368,7 @@ export default function App({ children }: { children: React.ReactNode }) {
|
|||||||
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}`);
|
||||||
|
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 (!isErrorExpected) {
|
||||||
setIsErrored(true);
|
setIsErrored(true);
|
||||||
setErroredDownloadId(downloadId);
|
setErroredDownloadId(downloadId);
|
||||||
@@ -364,6 +376,7 @@ export default function App({ children }: { children: React.ReactNode }) {
|
|||||||
} else {
|
} else {
|
||||||
if (await fs.exists(tempDownloadPath)) {
|
if (await fs.exists(tempDownloadPath)) {
|
||||||
downloadFilePath = await generateSafeFilePath(downloadFilePath);
|
downloadFilePath = await generateSafeFilePath(downloadFilePath);
|
||||||
|
LOG.info('NEODLP', `yt-dlp download completed with id: ${downloadId}, moving downloaded file from: "${tempDownloadPath}" to final destination: "${downloadFilePath}"`);
|
||||||
await fs.copyFile(tempDownloadPath, downloadFilePath);
|
await fs.copyFile(tempDownloadPath, downloadFilePath);
|
||||||
await fs.remove(tempDownloadPath);
|
await fs.remove(tempDownloadPath);
|
||||||
}
|
}
|
||||||
@@ -392,6 +405,7 @@ export default function App({ children }: { children: React.ReactNode }) {
|
|||||||
|
|
||||||
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}`);
|
||||||
setIsErrored(true);
|
setIsErrored(true);
|
||||||
setErroredDownloadId(downloadId);
|
setErroredDownloadId(downloadId);
|
||||||
});
|
});
|
||||||
@@ -399,6 +413,7 @@ export default function App({ children }: { children: React.ReactNode }) {
|
|||||||
command.stdout.on('data', line => {
|
command.stdout.on('data', line => {
|
||||||
if (line.startsWith('status:') || line.startsWith('[#')) {
|
if (line.startsWith('status:') || line.startsWith('[#')) {
|
||||||
console.log(line);
|
console.log(line);
|
||||||
|
LOG.info(`YT-DLP Download ${downloadId}`, line);
|
||||||
const currentProgress = parseProgressLine(line);
|
const currentProgress = parseProgressLine(line);
|
||||||
const state: DownloadState = {
|
const state: DownloadState = {
|
||||||
download_id: downloadId,
|
download_id: downloadId,
|
||||||
@@ -457,6 +472,7 @@ export default function App({ children }: { children: React.ReactNode }) {
|
|||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
console.log(line);
|
console.log(line);
|
||||||
|
if (line.trim() !== '') LOG.info(`YT-DLP Download ${downloadId}`, line);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -553,21 +569,25 @@ export default function App({ children }: { children: React.ReactNode }) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
if (!ongoingDownloads || ongoingDownloads && ongoingDownloads?.length < MAX_PARALLEL_DOWNLOADS) {
|
if (!ongoingDownloads || ongoingDownloads && ongoingDownloads?.length < MAX_PARALLEL_DOWNLOADS) {
|
||||||
|
LOG.info('NEODLP', `Starting yt-dlp download with args: ${args.join(' ')}`);
|
||||||
const child = await command.spawn();
|
const child = await command.spawn();
|
||||||
processPid = child.pid;
|
processPid = child.pid;
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
} else {
|
} else {
|
||||||
console.log("Download is queued, not starting immediately.");
|
console.log("Download is queued, not starting immediately.");
|
||||||
|
LOG.info('NEODLP', `Download queued with id: ${downloadId}`);
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(`Failed to start download: ${e}`);
|
console.error(`Failed to start download: ${e}`);
|
||||||
|
LOG.error('NEODLP', `Failed to start download for URL: ${url} with error: ${e}`);
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const pauseDownload = async (downloadState: DownloadState) => {
|
const pauseDownload = async (downloadState: DownloadState) => {
|
||||||
try {
|
try {
|
||||||
|
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
|
setIsErrorExpected(true); // Set error expected to true to handle UI state
|
||||||
console.log("Killing process with PID:", downloadState.process_id);
|
console.log("Killing process with PID:", downloadState.process_id);
|
||||||
@@ -611,6 +631,7 @@ export default function App({ children }: { children: React.ReactNode }) {
|
|||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
} 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}`);
|
||||||
isProcessingQueueRef.current = false;
|
isProcessingQueueRef.current = false;
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
@@ -618,6 +639,7 @@ export default function App({ children }: { children: React.ReactNode }) {
|
|||||||
|
|
||||||
const resumeDownload = async (downloadState: DownloadState) => {
|
const resumeDownload = async (downloadState: DownloadState) => {
|
||||||
try {
|
try {
|
||||||
|
LOG.info('NEODLP', `Resuming yt-dlp download with id: ${downloadState.download_id} (as per user request)`);
|
||||||
await startDownload(
|
await startDownload(
|
||||||
downloadState.playlist_id && downloadState.playlist_index ? downloadState.playlist_url : downloadState.url,
|
downloadState.playlist_id && downloadState.playlist_index ? downloadState.playlist_url : downloadState.url,
|
||||||
downloadState.format_id,
|
downloadState.format_id,
|
||||||
@@ -627,12 +649,14 @@ export default function App({ children }: { children: React.ReactNode }) {
|
|||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(`Failed to resume download: ${e}`);
|
console.error(`Failed to resume download: ${e}`);
|
||||||
|
LOG.error('NEODLP', `Failed to resume download with id: ${downloadState.download_id} with error: ${e}`);
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const cancelDownload = async (downloadState: DownloadState) => {
|
const cancelDownload = async (downloadState: DownloadState) => {
|
||||||
try {
|
try {
|
||||||
|
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
|
setIsErrorExpected(true); // Set error expected to true to handle UI state
|
||||||
console.log("Killing process with PID:", downloadState.process_id);
|
console.log("Killing process with PID:", downloadState.process_id);
|
||||||
@@ -658,6 +682,7 @@ export default function App({ children }: { children: React.ReactNode }) {
|
|||||||
return Promise.resolve();
|
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}`);
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -698,6 +723,7 @@ export default function App({ children }: { children: React.ReactNode }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
console.log("Starting queued download:", downloadToStart.download_id);
|
console.log("Starting queued download:", downloadToStart.download_id);
|
||||||
|
LOG.info('NEODLP', `Starting queued download with id: ${downloadToStart.download_id}`);
|
||||||
lastProcessedDownloadIdRef.current = downloadToStart.download_id;
|
lastProcessedDownloadIdRef.current = downloadToStart.download_id;
|
||||||
|
|
||||||
// Update status to 'starting' first
|
// Update status to 'starting' first
|
||||||
@@ -719,6 +745,7 @@ export default function App({ children }: { children: React.ReactNode }) {
|
|||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error processing download queue:", error);
|
console.error("Error processing download queue:", error);
|
||||||
|
LOG.error('NEODLP', `Error processing download queue: ${error}`);
|
||||||
} finally {
|
} finally {
|
||||||
// Important: reset the processing flag
|
// Important: reset the processing flag
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@@ -761,6 +788,7 @@ export default function App({ children }: { children: React.ReactNode }) {
|
|||||||
appWindow.setFocus();
|
appWindow.setFocus();
|
||||||
navigate('/');
|
navigate('/');
|
||||||
if (event.payload.url) {
|
if (event.payload.url) {
|
||||||
|
LOG.info('NEODLP', `Received download request from neodlp browser extension for URL: ${event.payload.url}`);
|
||||||
const { setRequestedUrl, setAutoSubmitSearch } = useCurrentVideoMetadataStore.getState();
|
const { setRequestedUrl, setAutoSubmitSearch } = useCurrentVideoMetadataStore.getState();
|
||||||
setRequestedUrl(event.payload.url);
|
setRequestedUrl(event.payload.url);
|
||||||
setAutoSubmitSearch(true);
|
setAutoSubmitSearch(true);
|
||||||
@@ -912,6 +940,7 @@ export default function App({ children }: { children: React.ReactNode }) {
|
|||||||
const YTDLP_UPDATE_INTERVAL = 86400000 // 24H;
|
const YTDLP_UPDATE_INTERVAL = 86400000 // 24H;
|
||||||
if (YTDLP_AUTO_UPDATE && (ytDlpUpdateLastCheck === null || currentTimestamp - ytDlpUpdateLastCheck > YTDLP_UPDATE_INTERVAL)) {
|
if (YTDLP_AUTO_UPDATE && (ytDlpUpdateLastCheck === null || currentTimestamp - ytDlpUpdateLastCheck > YTDLP_UPDATE_INTERVAL)) {
|
||||||
console.log("Running auto-update for yt-dlp...");
|
console.log("Running auto-update for yt-dlp...");
|
||||||
|
LOG.info('NEODLP', 'Updating yt-dlp to latest version (triggered because auto-update is enabled)');
|
||||||
updateYtDlp();
|
updateYtDlp();
|
||||||
} else {
|
} else {
|
||||||
console.log("Skipping yt-dlp auto-update, either disabled or recently updated.");
|
console.log("Skipping yt-dlp auto-update, either disabled or recently updated.");
|
||||||
@@ -938,14 +967,18 @@ export default function App({ children }: { children: React.ReactNode }) {
|
|||||||
const currentPlatform = platform();
|
const currentPlatform = platform();
|
||||||
if (currentPlatform === 'macos' && (!macOsRegisteredVersion || macOsRegisteredVersion !== appVersion)) {
|
if (currentPlatform === 'macos' && (!macOsRegisteredVersion || macOsRegisteredVersion !== appVersion)) {
|
||||||
console.log("Running MacOS auto registration...");
|
console.log("Running MacOS auto registration...");
|
||||||
|
LOG.info('NEODLP', 'Running macOS registration');
|
||||||
registerToMac().then((result: { success: boolean, message: string }) => {
|
registerToMac().then((result: { success: boolean, message: string }) => {
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
console.log("MacOS registration successful:", result.message);
|
console.log("MacOS registration successful:", result.message);
|
||||||
|
LOG.info('NEODLP', 'macOS registration successful');
|
||||||
} else {
|
} else {
|
||||||
console.error("MacOS registration failed:", result.message);
|
console.error("MacOS registration failed:", result.message);
|
||||||
|
LOG.error('NEODLP', `macOS registration failed: ${result.message}`);
|
||||||
}
|
}
|
||||||
}).catch((error) => {
|
}).catch((error) => {
|
||||||
console.error("Error during macOS registration:", error);
|
console.error("Error during macOS registration:", error);
|
||||||
|
LOG.error('NEODLP', `Error during macOS registration: ${error}`);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [isSettingsStatePropagated, isKvPairsStatePropagated]);
|
}, [isSettingsStatePropagated, isKvPairsStatePropagated]);
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
import { useLocation } from "react-router-dom";
|
import { useLocation } from "react-router-dom";
|
||||||
import { SidebarTrigger } from "@/components/ui/sidebar";
|
import { SidebarTrigger } from "@/components/ui/sidebar";
|
||||||
import { getRouteName } from "@/utils";
|
import { getRouteName } from "@/utils";
|
||||||
// import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
// import { Terminal } from "lucide-react";
|
import { Terminal } from "lucide-react";
|
||||||
// import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
|
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
|
||||||
|
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog";
|
||||||
|
import { useLogger } from "@/helpers/use-logger";
|
||||||
|
|
||||||
export default function Navbar() {
|
export default function Navbar() {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
const logs = useLogger().getLogs();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<nav className="flex justify-between items-center py-3 px-4 sticky top-0 backdrop-blur supports-[backdrop-filter]:bg-background/60 border-b z-50">
|
<nav className="flex justify-between items-center py-3 px-4 sticky top-0 backdrop-blur supports-[backdrop-filter]:bg-background/60 border-b z-50">
|
||||||
@@ -15,16 +18,38 @@ export default function Navbar() {
|
|||||||
<h1 className="text-lg text-primary font-semibold ml-4">{getRouteName(location.pathname)}</h1>
|
<h1 className="text-lg text-primary font-semibold ml-4">{getRouteName(location.pathname)}</h1>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-center items-center">
|
<div className="flex justify-center items-center">
|
||||||
{/* <Tooltip>
|
<Dialog>
|
||||||
|
<Tooltip>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
|
<DialogTrigger asChild>
|
||||||
<Button variant="outline" size="icon">
|
<Button variant="outline" size="icon">
|
||||||
<Terminal />
|
<Terminal />
|
||||||
</Button>
|
</Button>
|
||||||
|
</DialogTrigger>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>
|
<TooltipContent>
|
||||||
<p>Logs</p>
|
<p>Logs</p>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip> */}
|
</Tooltip>
|
||||||
|
<DialogContent className="sm:max-w-[600px]">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>Log Viewer</DialogTitle>
|
||||||
|
<DialogDescription>Monitor real-time neodlp logs for the current session</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
<div className="flex flex-col gap-2 p-2 max-h-[300px] overflow-y-scroll overflow-x-hidden bg-muted">
|
||||||
|
{logs.length === 0 ? (
|
||||||
|
<p className="text-sm text-muted-foreground">NO LOGS TO SHOW!</p>
|
||||||
|
) : (
|
||||||
|
logs.slice().reverse().map((log, index) => (
|
||||||
|
<div key={index} className={`flex flex-col ${log.level === 'error' ? 'text-red-500' : log.level === 'warning' ? 'text-amber-500' : log.level === 'debug' ? 'text-sky-500' : 'text-foreground'}`}>
|
||||||
|
<p className="text-xs"><strong>{new Date(log.timestamp).toLocaleTimeString()}</strong> [{log.level.toUpperCase()}] <em>{log.context}</em> :</p>
|
||||||
|
<p className="text-xs font-mono break-all">{log.message}</p>
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
)
|
)
|
||||||
|
|||||||
26
src/helpers/use-logger.ts
Normal file
26
src/helpers/use-logger.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import { useLogsStore } from "@/services/store";
|
||||||
|
|
||||||
|
export function useLogger() {
|
||||||
|
const logs = useLogsStore((state) => state.logs);
|
||||||
|
const addLog = useLogsStore((state) => state.addLog);
|
||||||
|
const clearLogs = useLogsStore((state) => state.clearLogs);
|
||||||
|
|
||||||
|
const logger = {
|
||||||
|
info: (context: string, message: string) => {
|
||||||
|
addLog({ timestamp: Date.now(), level: 'info', context, message });
|
||||||
|
},
|
||||||
|
warning: (context: string, message: string) => {
|
||||||
|
addLog({ timestamp: Date.now(), level: 'warning', context, message });
|
||||||
|
},
|
||||||
|
error: (context: string, message: string) => {
|
||||||
|
addLog({ timestamp: Date.now(), level: 'error', context, message });
|
||||||
|
},
|
||||||
|
debug: (context: string, message: string) => {
|
||||||
|
addLog({ timestamp: Date.now(), level: 'debug', context, message });
|
||||||
|
},
|
||||||
|
getLogs: () => logs,
|
||||||
|
clearLogs,
|
||||||
|
};
|
||||||
|
|
||||||
|
return logger;
|
||||||
|
}
|
||||||
@@ -741,7 +741,7 @@ export default function SettingsPage() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="force-internet-protocol">
|
<div className="force-internet-protocol">
|
||||||
<h3 className="font-semibold">Force Internet Protocol</h3>
|
<h3 className="font-semibold">Force Internet Protocol</h3>
|
||||||
<p className="text-xs text-muted-foreground mb-3">Force using a specific internet protocol (ipv4/ipv6) for all downloads, useful if you network supports only one (some sites may not work)</p>
|
<p className="text-xs text-muted-foreground mb-3">Force using a specific internet protocol (ipv4/ipv6) for all downloads, useful if your network supports only one (some sites may not work)</p>
|
||||||
<div className="flex items-center space-x-2 mb-4">
|
<div className="flex items-center space-x-2 mb-4">
|
||||||
<Switch
|
<Switch
|
||||||
id="use-force-internet-protocol"
|
id="use-force-internet-protocol"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { BasePathsStore, CurrentVideoMetadataStore, DownloadActionStatesStore, DownloaderPageStatesStore, DownloadStatesStore, KvPairsStatesStore, LibraryPageStatesStore, SettingsPageStatesStore } from '@/types/store';
|
import { BasePathsStore, CurrentVideoMetadataStore, DownloadActionStatesStore, DownloaderPageStatesStore, DownloadStatesStore, KvPairsStatesStore, LibraryPageStatesStore, LogsStore, SettingsPageStatesStore } from '@/types/store';
|
||||||
import { create } from 'zustand';
|
import { create } from 'zustand';
|
||||||
|
|
||||||
export const useBasePathsStore = create<BasePathsStore>((set) => ({
|
export const useBasePathsStore = create<BasePathsStore>((set) => ({
|
||||||
@@ -239,3 +239,10 @@ export const useKvPairsStatesStore = create<KvPairsStatesStore>((set) => ({
|
|||||||
})),
|
})),
|
||||||
setKvPairs: (kvPairs) => set(() => ({ kvPairs }))
|
setKvPairs: (kvPairs) => set(() => ({ kvPairs }))
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
export const useLogsStore = create<LogsStore>((set) => ({
|
||||||
|
logs: [],
|
||||||
|
setLogs: (logs) => set(() => ({ logs })),
|
||||||
|
addLog: (log) => set((state) => ({ logs: [...state.logs, log] })),
|
||||||
|
clearLogs: () => set(() => ({ logs: [] }))
|
||||||
|
}));
|
||||||
6
src/types/logs.ts
Normal file
6
src/types/logs.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
export interface Log {
|
||||||
|
timestamp: number;
|
||||||
|
level: 'info' | 'warning' | 'error' | 'debug';
|
||||||
|
context: string;
|
||||||
|
message: string;
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ import { RawVideoInfo } from "@/types/video";
|
|||||||
import { Settings } from "@/types/settings";
|
import { Settings } from "@/types/settings";
|
||||||
import { KvStore } from "@/types/kvStore";
|
import { KvStore } from "@/types/kvStore";
|
||||||
import { Update } from "@tauri-apps/plugin-updater";
|
import { Update } from "@tauri-apps/plugin-updater";
|
||||||
|
import { Log } from "@/types/logs";
|
||||||
|
|
||||||
export interface BasePathsStore {
|
export interface BasePathsStore {
|
||||||
ffmpegPath: string | null;
|
ffmpegPath: string | null;
|
||||||
@@ -119,3 +120,10 @@ export interface KvPairsStatesStore {
|
|||||||
setKvPairsKey: (key: string, value: unknown) => void;
|
setKvPairsKey: (key: string, value: unknown) => void;
|
||||||
setKvPairs: (kvPairs: KvStore) => void;
|
setKvPairs: (kvPairs: KvStore) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface LogsStore {
|
||||||
|
logs: Log[];
|
||||||
|
setLogs: (logs: Log[]) => void;
|
||||||
|
addLog: (log: Log) => void;
|
||||||
|
clearLogs: () => void;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user