From b289f1eeaa622db78092f92a41ec7c8854165603 Mon Sep 17 00:00:00 2001 From: Igor Katson Date: Wed, 3 Jan 2024 14:36:16 +0000 Subject: [PATCH] Fix file selection window on Windows that didnt split file with proper separator --- crates/librqbit/src/api.rs | 5 +++ crates/librqbit/webui/src/api-types.ts | 2 + .../webui/src/components/FileListInput.tsx | 43 ++++++++----------- .../webui/src/components/TorrentRow.tsx | 4 +- ...stFileName.ts => getTorrentDisplayName.ts} | 6 ++- crates/librqbit_core/src/torrent_metainfo.rs | 27 ++++++------ 6 files changed, 47 insertions(+), 40 deletions(-) rename crates/librqbit/webui/src/helper/{getLargestFileName.ts => getTorrentDisplayName.ts} (56%) diff --git a/crates/librqbit/src/api.rs b/crates/librqbit/src/api.rs index f8427a6..058a490 100644 --- a/crates/librqbit/src/api.rs +++ b/crates/librqbit/src/api.rs @@ -243,6 +243,7 @@ pub struct TorrentListResponse { #[derive(Serialize, Deserialize)] pub struct TorrentDetailsResponseFile { pub name: String, + pub components: Vec, pub length: u64, pub included: bool, } @@ -253,6 +254,7 @@ pub struct EmptyJsonResponse {} #[derive(Serialize, Deserialize)] pub struct TorrentDetailsResponse { pub info_hash: String, + pub name: Option, pub files: Vec, } @@ -281,9 +283,11 @@ fn make_torrent_details( "".to_string() } }; + let components = filename_it.to_vec().unwrap_or_default(); let included = only_files.map(|o| o.contains(&idx)).unwrap_or(true); TorrentDetailsResponseFile { name, + components, length, included, } @@ -291,6 +295,7 @@ fn make_torrent_details( .collect(); Ok(TorrentDetailsResponse { info_hash: info_hash.as_string(), + name: info.name.as_ref().map(|b| b.to_string()), files, }) } diff --git a/crates/librqbit/webui/src/api-types.ts b/crates/librqbit/webui/src/api-types.ts index b014a7b..8ae05e5 100644 --- a/crates/librqbit/webui/src/api-types.ts +++ b/crates/librqbit/webui/src/api-types.ts @@ -6,12 +6,14 @@ export interface TorrentId { export interface TorrentFile { name: string; + components: string[]; length: number; included: boolean; } // Interface for the Torrent Details API response export interface TorrentDetails { + name: string | null; info_hash: string; files: Array; } diff --git a/crates/librqbit/webui/src/components/FileListInput.tsx b/crates/librqbit/webui/src/components/FileListInput.tsx index eec00b8..f66357e 100644 --- a/crates/librqbit/webui/src/components/FileListInput.tsx +++ b/crates/librqbit/webui/src/components/FileListInput.tsx @@ -1,5 +1,5 @@ import { useMemo, useState } from "react"; -import { AddTorrentResponse } from "../api-types"; +import { AddTorrentResponse, TorrentFile } from "../api-types"; import { FormCheckbox } from "./forms/FormCheckbox"; import { CiSquarePlus, CiSquareMinus } from "react-icons/ci"; import { IconButton } from "./buttons/IconButton"; @@ -7,7 +7,8 @@ import { formatBytes } from "../helper/formatBytes"; type TorrentFileForCheckbox = { id: number; - name: string; + filename: string; + pathComponents: string[]; length: number; }; @@ -18,19 +19,12 @@ type FileTree = { files: TorrentFileForCheckbox[]; }; -const splitOnce = (s: string, sep: string): [string, string | undefined] => { - if (s.indexOf(sep) === -1) { - return [s, undefined]; - } - return [s.slice(0, s.indexOf(sep)), s.slice(s.indexOf(sep) + 1)]; -}; - const newFileTree = (listTorrentResponse: AddTorrentResponse): FileTree => { - const separator = "/"; const newFileTreeInner = ( name: string, id: string, - files: TorrentFileForCheckbox[] + files: TorrentFileForCheckbox[], + depth: number ): FileTree => { let directFiles: TorrentFileForCheckbox[] = []; let groups: FileTree[] = []; @@ -41,22 +35,17 @@ const newFileTree = (listTorrentResponse: AddTorrentResponse): FileTree => { return groupsByName[prefix]; }; - files.forEach((file) => { - let [prefix, name] = splitOnce(file.name, separator); - if (name === undefined) { + files.forEach((file: TorrentFileForCheckbox) => { + if (depth == file.pathComponents.length - 1) { directFiles.push(file); return; } - getGroup(prefix).push({ - id: file.id, - name: name, - length: file.length, - }); + getGroup(file.pathComponents[0]).push(file); }); let childId = 0; for (const [key, value] of Object.entries(groupsByName)) { - groups.push(newFileTreeInner(key, id + "." + childId, value)); + groups.push(newFileTreeInner(key, id + "." + childId, value, depth + 1)); childId += 1; } return { @@ -70,9 +59,15 @@ const newFileTree = (listTorrentResponse: AddTorrentResponse): FileTree => { return newFileTreeInner( "", "filetree-root", - listTorrentResponse.details.files.map((data, id) => { - return { id, name: data.name, length: data.length }; - }) + listTorrentResponse.details.files.map((file, id) => { + return { + id, + filename: file.components[file.components.length - 1], + pathComponents: file.components, + length: file.length, + }; + }), + 0 ); }; @@ -168,7 +163,7 @@ const FileTreeComponent: React.FC<{ handleToggleFile(file.id)} > diff --git a/crates/librqbit/webui/src/components/TorrentRow.tsx b/crates/librqbit/webui/src/components/TorrentRow.tsx index 2c50e15..ee5c4f6 100644 --- a/crates/librqbit/webui/src/components/TorrentRow.tsx +++ b/crates/librqbit/webui/src/components/TorrentRow.tsx @@ -9,7 +9,7 @@ import { TorrentActions } from "./buttons/TorrentActions"; import { ProgressBar } from "./ProgressBar"; import { Speed } from "./Speed"; import { formatBytes } from "../helper/formatBytes"; -import { getLargestFileName } from "../helper/getLargestFileName"; +import { torrentDisplayName } from "../helper/getTorrentDisplayName"; import { getCompletionETA } from "../helper/getCompletionETA"; import { StatusIcon } from "./StatusIcon"; @@ -54,7 +54,7 @@ export const TorrentRow: React.FC<{
{statusIcon("w-5 h-5")}
- {getLargestFileName(detailsResponse)} + {torrentDisplayName(detailsResponse)}
)} diff --git a/crates/librqbit/webui/src/helper/getLargestFileName.ts b/crates/librqbit/webui/src/helper/getTorrentDisplayName.ts similarity index 56% rename from crates/librqbit/webui/src/helper/getLargestFileName.ts rename to crates/librqbit/webui/src/helper/getTorrentDisplayName.ts index cb9e69e..78dee13 100644 --- a/crates/librqbit/webui/src/helper/getLargestFileName.ts +++ b/crates/librqbit/webui/src/helper/getTorrentDisplayName.ts @@ -1,6 +1,6 @@ import { TorrentDetails } from "../api-types"; -export function getLargestFileName(torrentDetails: TorrentDetails): string { +function getLargestFileName(torrentDetails: TorrentDetails): string { const largestFile = torrentDetails.files .filter((f) => f.included) .reduce((prev: any, current: any) => @@ -8,3 +8,7 @@ export function getLargestFileName(torrentDetails: TorrentDetails): string { ); return largestFile.name; } + +export function torrentDisplayName(torrentDetails: TorrentDetails): string { + return torrentDetails.name ?? getLargestFileName(torrentDetails); +} diff --git a/crates/librqbit_core/src/torrent_metainfo.rs b/crates/librqbit_core/src/torrent_metainfo.rs index 883df1f..d386497 100644 --- a/crates/librqbit_core/src/torrent_metainfo.rs +++ b/crates/librqbit_core/src/torrent_metainfo.rs @@ -74,6 +74,7 @@ pub struct TorrentMetaV1Info { pub files: Option>>, } +#[derive(Clone, Copy)] pub enum FileIteratorName<'a, ByteBuf> { Single(Option<&'a ByteBuf>), Tree(&'a [ByteBuf]), @@ -91,11 +92,17 @@ where } } -impl<'a, ByteBuf> FileIteratorName<'a, ByteBuf> { - pub fn to_string(&self) -> anyhow::Result - where - ByteBuf: AsRef<[u8]>, - { +impl<'a, ByteBuf> FileIteratorName<'a, ByteBuf> +where + ByteBuf: AsRef<[u8]>, +{ + pub fn to_vec(&self) -> anyhow::Result> { + self.iter_components() + .map(|c| c.map(|s| s.to_owned())) + .collect() + } + + pub fn to_string(&self) -> anyhow::Result { let mut buf = String::new(); for (idx, bit) in self.iter_components().enumerate() { let bit = bit?; @@ -106,10 +113,7 @@ impl<'a, ByteBuf> FileIteratorName<'a, ByteBuf> { } Ok(buf) } - pub fn to_pathbuf(&self) -> anyhow::Result - where - ByteBuf: AsRef<[u8]>, - { + pub fn to_pathbuf(&self) -> anyhow::Result { let mut buf = PathBuf::new(); for bit in self.iter_components() { let bit = bit?; @@ -117,10 +121,7 @@ impl<'a, ByteBuf> FileIteratorName<'a, ByteBuf> { } Ok(buf) } - pub fn iter_components(&self) -> impl Iterator> - where - ByteBuf: AsRef<[u8]>, - { + pub fn iter_components(&self) -> impl Iterator> { let it = match self { FileIteratorName::Single(None) => return Either::Left(once(Ok("torrent-content"))), FileIteratorName::Single(Some(name)) => Either::Left(once((*name).as_ref())),