diff --git a/pyipv8 b/pyipv8 index 89798d3206e..6233e9cb193 160000 --- a/pyipv8 +++ b/pyipv8 @@ -1 +1 @@ -Subproject commit 89798d3206ed7626fb7858134dae7b824a10c256 +Subproject commit 6233e9cb193908271e9a09b371b054acb7e50c22 diff --git a/src/tribler/core/libtorrent/download_manager/download_state.py b/src/tribler/core/libtorrent/download_manager/download_state.py index bf2fd983a11..440a0d47e39 100644 --- a/src/tribler/core/libtorrent/download_manager/download_state.py +++ b/src/tribler/core/libtorrent/download_manager/download_state.py @@ -99,12 +99,12 @@ def get_status(self) -> DownloadStatus: """ Returns the status of the torrent. """ + if self.get_error(): + return DownloadStatus.STOPPED_ON_ERROR if self.lt_status: if self.lt_status.paused: return DownloadStatus.STOPPED return DOWNLOAD_STATUS_MAP[self.lt_status.state] - if self.get_error(): - return DownloadStatus.STOPPED_ON_ERROR return DownloadStatus.STOPPED def get_error(self) -> str | None: diff --git a/src/tribler/core/restapi/file_endpoint.py b/src/tribler/core/restapi/file_endpoint.py index 89c88aae5aa..79bfe51692e 100644 --- a/src/tribler/core/restapi/file_endpoint.py +++ b/src/tribler/core/restapi/file_endpoint.py @@ -55,9 +55,16 @@ async def browse(self, request: web.Request) -> RESTResponse: # Move up until we find a directory parent_path = Path(path).resolve() - while not parent_path.is_dir(): + while not parent_path.is_dir() and parent_path != parent_path.parent: parent_path = parent_path.parent + # Did we find an existing directory? + if not parent_path.is_dir() or not parent_path.exists(): + return RESTResponse({"error": { + "handled": True, + "message": f"No directory named {parent_path} exists" + }}, status=HTTP_NOT_FOUND) + # Get all files/subdirs results = [] for file in parent_path.iterdir(): diff --git a/src/tribler/test_unit/core/libtorrent/download_manager/test_download_manager.py b/src/tribler/test_unit/core/libtorrent/download_manager/test_download_manager.py index b976fe3f548..a7516f4e4f3 100644 --- a/src/tribler/test_unit/core/libtorrent/download_manager/test_download_manager.py +++ b/src/tribler/test_unit/core/libtorrent/download_manager/test_download_manager.py @@ -398,7 +398,7 @@ async def test_readd_download_safe_seeding(self) -> None: download = Download(TorrentDefNoMetainfo(b"\x01" * 20, b"name", None), None, checkpoint_disabled=True, config=config) download.futures["save_resume_data"] = succeed(True) - download_state = DownloadState(download, Mock(state=4, paused=False), None) + download_state = DownloadState(download, Mock(state=4, paused=False, error=None), None) self.manager.downloads = {b"\x01" * 20: download} self.manager.config.set("libtorrent/download_defaults/number_hops", 42) diff --git a/src/tribler/test_unit/core/libtorrent/download_manager/test_stream.py b/src/tribler/test_unit/core/libtorrent/download_manager/test_stream.py index b1f6395e028..7da8ce2949d 100644 --- a/src/tribler/test_unit/core/libtorrent/download_manager/test_stream.py +++ b/src/tribler/test_unit/core/libtorrent/download_manager/test_stream.py @@ -152,7 +152,7 @@ def create_mock_download(self, piece_size: int | None = None, pieces: list[bool] checkpoint_disabled=True) download.handle = Mock(is_valid=Mock(return_value=True), file_priorities=Mock(return_value=[0] * 6), torrent_file=Mock(return_value=download.tdef.torrent_info)) - download.lt_status = Mock(state=3, paused=False, pieces=[]) + download.lt_status = Mock(state=3, paused=False, error=None, pieces=[]) if piece_size is not None: self.convert_to_piece_size(download, piece_size) if pieces is not None: diff --git a/src/tribler/ui/src/dialogs/SelectRemotePath.tsx b/src/tribler/ui/src/dialogs/SelectRemotePath.tsx index d976372112b..0289474feb1 100644 --- a/src/tribler/ui/src/dialogs/SelectRemotePath.tsx +++ b/src/tribler/ui/src/dialogs/SelectRemotePath.tsx @@ -49,8 +49,17 @@ export default function SelectRemotePath(props: SelectRemotePathProps & JSX.Intr const response = await triblerService.browseFiles(dir, showFiles || false); if (response === undefined) { toast.error(`${t("ToastErrorBrowseFiles")} ${t("ToastErrorGenNetworkErr")}`); - } else if (isErrorDict(response)) { + } else if (isErrorDict(response) && response.errorCode != 404) { toast.error(`${t("ToastErrorBrowseFiles")} ${response.error.message}`); + } else if (isErrorDict(response)) { + // If we couldn't get the requested path, browse the default path instead. + let settings = await triblerService.getSettings() + if (settings !== undefined && !isErrorDict(settings)) { + let nextDir = settings.libtorrent.download_defaults.saveas; + if (dir != nextDir) { + reloadPaths(nextDir); + } + } } else { setPaths(response.paths); setCurrentPath(response.current); diff --git a/src/tribler/ui/src/models/download.model.tsx b/src/tribler/ui/src/models/download.model.tsx index d7635307cac..49722a6c971 100644 --- a/src/tribler/ui/src/models/download.model.tsx +++ b/src/tribler/ui/src/models/download.model.tsx @@ -5,8 +5,20 @@ import { File } from "./file.model"; import { Tracker } from "./tracker.model"; -type state = 'ALLOCATING_DISKSPACE' | 'WAITING_FOR_HASHCHECK' | 'HASHCHECKING' | 'DOWNLOADING' | - 'SEEDING' | 'STOPPED' | 'STOPPED_ON_ERROR' | 'METADATA' | 'LOADING' | 'EXIT_NODES'; +export enum StatusCode { + ALLOCATING_DISKSPACE = 0, + WAITING_FOR_HASHCHECK = 1, + HASHCHECKING = 2, + DOWNLOADING = 3, + SEEDING = 4, + STOPPED = 5, + STOPPED_ON_ERROR = 6, + METADATA = 7, + LOADING = 8, + EXIT_NODES = 9 +} + +export type Status = keyof typeof StatusCode; export interface Download { name: string; @@ -14,8 +26,8 @@ export interface Download { infohash: string; speed_down: number; speed_up: number; - status: state; - status_code: number; + status: Status; + status_code: StatusCode; size: number; eta: number; num_peers: number; diff --git a/src/tribler/ui/src/pages/Downloads/Details.tsx b/src/tribler/ui/src/pages/Downloads/Details.tsx index c80b2fad5a8..835bc81cead 100644 --- a/src/tribler/ui/src/pages/Downloads/Details.tsx +++ b/src/tribler/ui/src/pages/Downloads/Details.tsx @@ -10,7 +10,7 @@ import Peers from "./Peers"; import Trackers from "./Trackers"; import { ScrollArea } from "@/components/ui/scroll-area"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"; -import { Download } from "@/models/download.model"; +import { Download, StatusCode } from "@/models/download.model"; import Pieces from "./Pieces"; import { useLayoutEffect, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; @@ -57,7 +57,10 @@ export default function DownloadDetails({ download }: { download: Download | und
{t('Status')}
-
{capitalize(download.status)}
+ {download.status_code == StatusCode.STOPPED_ON_ERROR && +
Error: {download.error}
} + {download.status_code != StatusCode.STOPPED_ON_ERROR && +
{capitalize(download.status)}
}
{t('Filesize')}
@@ -79,7 +82,7 @@ export default function DownloadDetails({ download }: { download: Download | und - + diff --git a/src/tribler/ui/src/pages/Downloads/Files.tsx b/src/tribler/ui/src/pages/Downloads/Files.tsx index aa73083b573..e5255e3e797 100644 --- a/src/tribler/ui/src/pages/Downloads/Files.tsx +++ b/src/tribler/ui/src/pages/Downloads/Files.tsx @@ -1,7 +1,7 @@ import toast from 'react-hot-toast'; import { ColumnDef, Row } from "@tanstack/react-table"; import { FileTreeItem } from "@/models/file.model"; -import { Download } from "@/models/download.model"; +import { Download, StatusCode } from "@/models/download.model"; import { Dispatch, MutableRefObject, SetStateAction, useEffect, useMemo, useRef, useState } from "react"; import { isErrorDict } from "@/services/reporting"; import { triblerService } from "@/services/tribler.service"; @@ -103,7 +103,7 @@ export default function Files({ download }: { download: Download }) { }, []); useEffect(() => { - if (download.status_code === 3) + if (download.status_code === StatusCode.DOWNLOADING) updateFiles(setFiles, download, initialized); }, [download]); diff --git a/src/tribler/ui/src/pages/Downloads/index.tsx b/src/tribler/ui/src/pages/Downloads/index.tsx index ef65da79839..327af707ee4 100644 --- a/src/tribler/ui/src/pages/Downloads/index.tsx +++ b/src/tribler/ui/src/pages/Downloads/index.tsx @@ -1,7 +1,7 @@ import { ActionButtons, ActionMenu } from "./Actions"; import DownloadDetails from "./Details"; import SimpleTable, { getHeader } from "@/components/ui/simple-table" -import { Download } from "@/models/download.model"; +import { Download, StatusCode } from "@/models/download.model"; import { Progress } from "@/components/ui/progress" import { capitalize, formatBytes, formatDateTime, formatTimeRelative } from "@/lib/utils"; import { isErrorDict } from "@/services/reporting"; @@ -49,13 +49,23 @@ const downloadColumns: ColumnDef[] = [ accessorKey: "progress", header: getHeader('Status'), cell: ({ row }) => { + let status = `${capitalize(row.original.status.replaceAll("_", " "))} ${(row.original.progress * 100).toFixed(0)}%` + let progress = row.original.progress * 100; + let color = "bg-tribler"; + + if (row.original.status_code == StatusCode.STOPPED_ON_ERROR) { + status = "Error"; + progress = 100; + color = "bg-red-600"; + } + return (
- +
- {capitalize(row.original.status.replaceAll("_", " "))} {(row.original.progress * 100).toFixed(0)}% + {status}
) @@ -100,7 +110,7 @@ const downloadColumns: ColumnDef[] = [ hide_by_default: true, }, cell: ({ row }) => { - if (row.original.progress === 1 || row.original.status_code !== 3) + if (row.original.progress === 1 || row.original.status_code !== StatusCode.DOWNLOADING) return - return {formatTimeRelative(row.original.eta, false)} }, @@ -198,7 +208,8 @@ export default function Downloads({ statusFilter }: { statusFilter: number[] })
- d.status !== "LOADING")} /> + d.status_code !== StatusCode.LOADING)} />
@@ -226,7 +237,8 @@ export default function Downloads({ statusFilter }: { statusFilter: number[] }) selectOnRightClick={true} /> - d.status !== "LOADING")} /> + d.status_code !== StatusCode.LOADING)} /> diff --git a/src/tribler/ui/src/services/reporting.ts b/src/tribler/ui/src/services/reporting.ts index 3bfe278ef28..76498bf40b1 100644 --- a/src/tribler/ui/src/services/reporting.ts +++ b/src/tribler/ui/src/services/reporting.ts @@ -1,6 +1,9 @@ import axios, { AxiosError } from "axios"; -export interface ErrorDict { [error: string]: {handled: boolean, message: string}; }; +export interface ErrorDict { + error: {handled: boolean, message: string}, + errorCode?: number +}; export function isErrorDict(object: any): object is ErrorDict { return (typeof object === 'object') && ('error' in object); @@ -38,7 +41,9 @@ export function formatAxiosError(error: Error | AxiosError): ErrorDict | undefin handleHTTPError(error); } // This is some (probably expected) REST API error - return error.response.data; + let errorDict = error.response.data; + errorDict.errorCode = error.status; + return errorDict; } // No idea what this is: make it someone else's problem throw error;