diff --git a/Cargo.lock b/Cargo.lock index 04a30bd..226024a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4641,7 +4641,6 @@ dependencies = [ "base64 0.22.1", "directories 5.0.1", "gethostname 0.5.0", - "gtk", "http", "librqbit", "parking_lot", diff --git a/crates/librqbit/webui/src/api-types.ts b/crates/librqbit/webui/src/api-types.ts index 64e41f0..58436e7 100644 --- a/crates/librqbit/webui/src/api-types.ts +++ b/crates/librqbit/webui/src/api-types.ts @@ -23,30 +23,9 @@ export interface TorrentFileAttributes { export interface TorrentDetails { name: string | null; info_hash: string; - output_folder: string; files: Array; } -export interface LocalTorrentFile { - kind: "local-file"; - path: string; - name: string; -} - -export type TorrentInput = string | File | LocalTorrentFile; - -export function localTorrentFile(path: string): LocalTorrentFile { - return { - kind: "local-file", - path, - name: path.split(/[\\/]/).pop() || path, - }; -} - -export function isLocalTorrentFile(data: TorrentInput): data is LocalTorrentFile { - return typeof data === "object" && !(data instanceof File) && data.kind === "local-file"; -} - export interface AddTorrentResponse { id: number | null; details: TorrentDetails; @@ -213,10 +192,9 @@ export interface RqbitAPI { filename?: string | null, ) => string | null; uploadTorrent: ( - data: TorrentInput, + data: string | File, opts?: AddTorrentOptions, ) => Promise; - openTorrentOutput?: (index: number) => Promise; pause: (index: number) => Promise; updateOnlyFiles: (index: number, files: number[]) => Promise; diff --git a/crates/librqbit/webui/src/components/TorrentRow.tsx b/crates/librqbit/webui/src/components/TorrentRow.tsx index 307070a..c7f3e3e 100644 --- a/crates/librqbit/webui/src/components/TorrentRow.tsx +++ b/crates/librqbit/webui/src/components/TorrentRow.tsx @@ -93,82 +93,9 @@ export const TorrentRow: React.FC<{ }; const [extendedView, setExtendedView] = useState(false); - const [contextMenu, setContextMenu] = useState<{ - x: number; - y: number; - } | null>(null); - - useEffect(() => { - if (!contextMenu) { - return; - } - - const close = () => setContextMenu(null); - const closeOnEscape = (event: KeyboardEvent) => { - if (event.key === "Escape") { - setContextMenu(null); - } - }; - - window.addEventListener("click", close); - window.addEventListener("keydown", closeOnEscape); - return () => { - window.removeEventListener("click", close); - window.removeEventListener("keydown", closeOnEscape); - }; - }, [contextMenu]); - - const openOutput = () => { - if (!API.openTorrentOutput) { - return; - } - - API.openTorrentOutput(id).catch((e) => { - setCloseableError({ - text: `Error opening torrent output id=${id}`, - details: e as ErrorDetails, - }); - }); - setContextMenu(null); - }; - - const handleContextMenu = (event: React.MouseEvent) => { - if (!API.openTorrentOutput) { - return; - } - - event.preventDefault(); - setContextMenu({ x: event.clientX, y: event.clientY }); - }; return ( -
- {contextMenu && ( -
event.stopPropagation()} - > - - -
- )} +
{/* Icon */}
{statusIcon("w-10 h-10")}
diff --git a/crates/librqbit/webui/src/components/buttons/TorrentActions.tsx b/crates/librqbit/webui/src/components/buttons/TorrentActions.tsx index 1cc0c91..cda77d8 100644 --- a/crates/librqbit/webui/src/components/buttons/TorrentActions.tsx +++ b/crates/librqbit/webui/src/components/buttons/TorrentActions.tsx @@ -9,7 +9,6 @@ import { FaPlay, FaTrash, FaClipboardList, - FaFolderOpen, } from "react-icons/fa"; import { useErrorStore } from "../../stores/errorStore"; import { ErrorComponent } from "../ErrorComponent"; @@ -35,22 +34,6 @@ export const TorrentActions: React.FC<{ const API = useContext(APIContext); - const openOutput = () => { - if (!API.openTorrentOutput) { - return; - } - - setDisabled(true); - API.openTorrentOutput(id) - .catch((e) => { - setCloseableError({ - text: `Error opening torrent output id=${id}`, - details: e, - }); - }) - .finally(() => setDisabled(false)); - }; - const unpause = () => { setDisabled(true); API.start(id) @@ -153,11 +136,6 @@ export const TorrentActions: React.FC<{ )} - {API.openTorrentOutput && ( - - - - )} diff --git a/crates/librqbit/webui/src/components/buttons/UploadButton.tsx b/crates/librqbit/webui/src/components/buttons/UploadButton.tsx index d78018a..751830b 100644 --- a/crates/librqbit/webui/src/components/buttons/UploadButton.tsx +++ b/crates/librqbit/webui/src/components/buttons/UploadButton.tsx @@ -2,7 +2,6 @@ import { ReactNode, useContext, useEffect, useState } from "react"; import { AddTorrentResponse, ErrorDetails as ApiErrorDetails, - TorrentInput, } from "../../api-types"; import { APIContext } from "../../context"; import { ErrorWithLabel } from "../../rqbit-web"; @@ -11,7 +10,7 @@ import { Button } from "./Button"; export const UploadButton: React.FC<{ onClick: () => void; - data: TorrentInput | null; + data: string | File | null; resetData: () => void; children: ReactNode; className?: string; diff --git a/crates/librqbit/webui/src/components/modal/FileSelectionModal.tsx b/crates/librqbit/webui/src/components/modal/FileSelectionModal.tsx index cba5c72..e1ccd07 100644 --- a/crates/librqbit/webui/src/components/modal/FileSelectionModal.tsx +++ b/crates/librqbit/webui/src/components/modal/FileSelectionModal.tsx @@ -1,9 +1,5 @@ import { useContext, useEffect, useState } from "react"; -import { - AddTorrentResponse, - AddTorrentOptions, - TorrentInput, -} from "../../api-types"; +import { AddTorrentResponse, AddTorrentOptions } from "../../api-types"; import { APIContext } from "../../context"; import { ErrorComponent } from "../ErrorComponent"; import { ErrorWithLabel } from "../../rqbit-web"; @@ -23,7 +19,7 @@ export const FileSelectionModal = (props: { listTorrentResponse: AddTorrentResponse | null; listTorrentError: ErrorWithLabel | null; listTorrentLoading: boolean; - data: TorrentInput; + data: string | File; }) => { let { onHide, diff --git a/crates/librqbit/webui/src/http-api.ts b/crates/librqbit/webui/src/http-api.ts index b4012d8..2e00df3 100644 --- a/crates/librqbit/webui/src/http-api.ts +++ b/crates/librqbit/webui/src/http-api.ts @@ -6,7 +6,6 @@ import { SessionStats, TorrentDetails, TorrentStats, - isLocalTorrentFile, } from "./api-types"; // Define API URL and base path @@ -100,12 +99,6 @@ export const API: RqbitAPI & { getVersion: () => Promise } = { }, uploadTorrent: (data, opts): Promise => { - if (isLocalTorrentFile(data)) { - return Promise.reject({ - text: "Local torrent file paths are only supported in rqbit desktop.", - }); - } - let url = "/torrents?&overwrite=true"; if (opts?.list_only) { url += "&list_only=true"; diff --git a/desktop/src-tauri/Cargo.toml b/desktop/src-tauri/Cargo.toml index 5cdb53f..b48609f 100644 --- a/desktop/src-tauri/Cargo.toml +++ b/desktop/src-tauri/Cargo.toml @@ -33,7 +33,6 @@ serde_with = "3.4.0" parking_lot = "0.12.1" gethostname = "0.5.0" tauri-plugin-shell = "2" -gtk = "0.18.2" [features] # this feature is used for production builds or when `devPath` points to the filesystem diff --git a/desktop/src-tauri/src/main.rs b/desktop/src-tauri/src/main.rs index 62916e9..d8bd8ea 100644 --- a/desktop/src-tauri/src/main.rs +++ b/desktop/src-tauri/src/main.rs @@ -4,17 +4,12 @@ mod config; use std::{ - ffi::OsString, fs::{File, OpenOptions}, - io::{BufReader, BufWriter, Read, Write}, - path::{Path, PathBuf}, - process::Command, + io::{BufReader, BufWriter}, + path::Path, sync::Arc, }; -#[cfg(unix)] -use std::os::unix::net::{UnixListener, UnixStream}; - use anyhow::Context; use config::RqbitDesktopConfig; use http::StatusCode; @@ -29,9 +24,8 @@ use librqbit::{ AddTorrent, AddTorrentOptions, Api, ApiError, PeerConnectionOptions, Session, SessionOptions, SessionPersistenceConfig, }; -use parking_lot::{Mutex, RwLock}; -use serde::{Deserialize, Serialize}; -use tauri::{Emitter, Manager}; +use parking_lot::RwLock; +use serde::Serialize; use tracing::{error, error_span, info, warn}; const ERR_NOT_CONFIGURED: ApiError = @@ -45,302 +39,9 @@ struct StateShared { struct State { config_filename: String, shared: Arc>>, - pending_torrent_inputs: Mutex>, init_logging: InitLoggingResult, } -const TORRENT_INPUTS_EVENT: &str = "torrent-inputs"; - -#[derive(Clone, Deserialize, Serialize)] -#[serde(tag = "type", rename_all = "snake_case")] -enum PendingTorrentInput { - FilePath { path: String }, - Url { url: String }, -} - -#[derive(Deserialize, Serialize)] -struct InstanceMessage { - inputs: Vec, -} - -fn pending_torrent_input_from_arg(arg: OsString) -> Option { - let raw = arg.to_string_lossy(); - let arg = raw.trim(); - if arg.is_empty() { - return None; - } - - let lower_arg = arg.to_ascii_lowercase(); - if lower_arg.starts_with("magnet:") - || lower_arg.starts_with("http://") - || lower_arg.starts_with("https://") - { - return Some(PendingTorrentInput::Url { - url: arg.to_owned(), - }); - } - - let path = if let Some(path) = arg.strip_prefix("file://") { - file_uri_to_path(path)? - } else { - PathBuf::from(arg) - }; - - if path - .extension() - .and_then(|ext| ext.to_str()) - .is_some_and(|ext| ext.eq_ignore_ascii_case("torrent")) - { - return Some(PendingTorrentInput::FilePath { - path: path.to_string_lossy().into_owned(), - }); - } - - None -} - -fn file_uri_to_path(uri_path: &str) -> Option { - let path = if let Some(path) = uri_path.strip_prefix("localhost/") { - format!("/{path}") - } else { - uri_path.to_owned() - }; - percent_decode(&path).map(PathBuf::from) -} - -fn percent_decode(value: &str) -> Option { - fn hex_value(value: u8) -> Option { - match value { - b'0'..=b'9' => Some(value - b'0'), - b'a'..=b'f' => Some(value - b'a' + 10), - b'A'..=b'F' => Some(value - b'A' + 10), - _ => None, - } - } - - let bytes = value.as_bytes(); - let mut decoded = Vec::with_capacity(bytes.len()); - let mut idx = 0; - - while idx < bytes.len() { - if bytes[idx] == b'%' { - let high = *bytes.get(idx + 1)?; - let low = *bytes.get(idx + 2)?; - decoded.push(hex_value(high)? << 4 | hex_value(low)?); - idx += 3; - } else { - decoded.push(bytes[idx]); - idx += 1; - } - } - - String::from_utf8(decoded).ok() -} - -fn pending_torrent_inputs_from_args() -> Vec { - std::env::args_os() - .skip(1) - .filter_map(pending_torrent_input_from_arg) - .collect() -} - -#[cfg(unix)] -enum SingleInstance { - Primary(UnixListener), - Secondary, - Disabled, -} - -#[cfg(unix)] -fn acquire_single_instance(inputs: &[PendingTorrentInput]) -> SingleInstance { - let socket_path = single_instance_socket_path(); - - if send_instance_message(&socket_path, inputs).is_ok() { - return SingleInstance::Secondary; - } - - match bind_single_instance_socket(&socket_path) { - Ok(listener) => SingleInstance::Primary(listener), - Err(e) => { - warn!(error = ?e, path = ?socket_path, "single instance socket disabled"); - SingleInstance::Disabled - } - } -} - -#[cfg(unix)] -fn single_instance_socket_path() -> PathBuf { - if let Some(runtime_dir) = std::env::var_os("XDG_RUNTIME_DIR") { - return PathBuf::from(runtime_dir).join("rqbit-desktop.sock"); - } - - let user = std::env::var("USER").unwrap_or_else(|_| "unknown".to_owned()); - std::env::temp_dir().join(format!("rqbit-desktop-{user}.sock")) -} - -#[cfg(unix)] -fn send_instance_message(path: &Path, inputs: &[PendingTorrentInput]) -> anyhow::Result<()> { - let mut stream = UnixStream::connect(path)?; - serde_json::to_writer( - &mut stream, - &InstanceMessage { - inputs: inputs.to_vec(), - }, - )?; - stream.write_all(b"\n")?; - Ok(()) -} - -#[cfg(unix)] -fn bind_single_instance_socket(path: &Path) -> anyhow::Result { - if let Some(parent) = path.parent() { - std::fs::create_dir_all(parent) - .with_context(|| format!("error creating socket directory {parent:?}"))?; - } - - match UnixListener::bind(path) { - Ok(listener) => Ok(listener), - Err(e) if path.exists() => { - std::fs::remove_file(path) - .with_context(|| format!("error removing stale socket {path:?}"))?; - UnixListener::bind(path) - .with_context(|| format!("error binding single instance socket {path:?}")) - } - Err(e) => Err(e).with_context(|| format!("error binding single instance socket {path:?}")), - } -} - -#[cfg(unix)] -fn start_single_instance_listener(listener: UnixListener, app: tauri::AppHandle) { - std::thread::spawn(move || { - for stream in listener.incoming() { - match stream { - Ok(stream) => { - let app = app.clone(); - std::thread::spawn(move || handle_single_instance_stream(stream, app)); - } - Err(e) => warn!("error accepting single instance connection: {:#}", e), - } - } - }); -} - -#[cfg(unix)] -fn handle_single_instance_stream(mut stream: UnixStream, app: tauri::AppHandle) { - let mut body = String::new(); - if let Err(e) = stream.read_to_string(&mut body) { - warn!("error reading single instance message: {:#}", e); - return; - } - - let message: InstanceMessage = match serde_json::from_str(&body) { - Ok(message) => message, - Err(e) => { - warn!("error parsing single instance message: {:#}", e); - return; - } - }; - - bring_main_window_to_front(&app); - - if message.inputs.is_empty() { - return; - } - - let state = app.state::(); - state.pending_torrent_inputs.lock().extend(message.inputs); - - if let Err(e) = app.emit(TORRENT_INPUTS_EVENT, ()) { - warn!("error emitting torrent inputs event: {:#}", e); - } -} - -fn bring_main_window_to_front(app: &tauri::AppHandle) { - if let Some(window) = app.get_webview_window("main") { - let _ = window.unminimize(); - let _ = window.show(); - let _ = window.set_focus(); - } -} - -#[cfg(target_os = "linux")] -fn apply_gtk_window_controls_layout(app: &tauri::App) { - use gtk::prelude::*; - - let Some(window) = app.get_webview_window("main") else { - return; - }; - let Ok(gtk_window) = window.gtk_window() else { - return; - }; - let Some(titlebar) = gtk_window.titlebar() else { - return; - }; - let Some(header) = find_gtk_header_bar(&titlebar) else { - warn!("could not find GTK header bar for window controls layout"); - return; - }; - - apply_gtk_header_bar_layout(&header); - - let header_for_resize = header.downgrade(); - gtk_window.connect_resizable_notify(move |_| { - if let Some(header) = header_for_resize.upgrade() { - apply_gtk_header_bar_layout(&header); - } - }); - - if let Some(settings) = gtk::Settings::default() { - let header_for_settings = header.downgrade(); - settings.connect_gtk_decoration_layout_notify(move |_| { - if let Some(header) = header_for_settings.upgrade() { - apply_gtk_header_bar_layout(&header); - } - }); - } -} - -#[cfg(target_os = "linux")] -fn find_gtk_header_bar(widget: >k::Widget) -> Option { - use gtk::prelude::*; - - if let Ok(header) = widget.clone().downcast::() { - return Some(header); - } - - let Ok(container) = widget.clone().downcast::() else { - return None; - }; - - for child in container.children() { - if let Some(header) = find_gtk_header_bar(&child) { - return Some(header); - } - } - - None -} - -#[cfg(target_os = "linux")] -fn apply_gtk_header_bar_layout(header: >k::HeaderBar) { - use gtk::prelude::*; - - let layout = gtk_window_controls_layout(); - header.set_decoration_layout(Some(&layout)); - info!(%layout, "applied GTK window controls layout"); -} - -#[cfg(target_os = "linux")] -fn gtk_window_controls_layout() -> String { - use gtk::prelude::*; - - gtk::Settings::default() - .and_then(|settings| settings.gtk_decoration_layout()) - .map(|layout| layout.to_string()) - .filter(|layout| !layout.trim().is_empty()) - .unwrap_or_else(|| "menu:minimize,maximize,close".to_string()) -} - fn read_config(path: &str) -> anyhow::Result { let rdr = BufReader::new(File::open(path)?); let mut config: RqbitDesktopConfig = serde_json::from_reader(rdr)?; @@ -471,11 +172,7 @@ async fn api_from_config( } impl State { - async fn new( - init_logging: InitLoggingResult, - pending_torrent_inputs: Vec, - ) -> Self { - let pending_torrent_inputs = Mutex::new(pending_torrent_inputs); + async fn new(init_logging: InitLoggingResult) -> Self { let config_filename = directories::ProjectDirs::from("com", "rqbit", "desktop") .expect("directories::ProjectDirs::from") .config_dir() @@ -497,7 +194,6 @@ impl State { return Self { config_filename, shared, - pending_torrent_inputs, init_logging, }; } @@ -506,7 +202,6 @@ impl State { config_filename, init_logging, shared: Arc::new(RwLock::new(None)), - pending_torrent_inputs, } } @@ -614,21 +309,6 @@ async fn torrent_create_from_base64_file( .await } -#[tauri::command] -async fn torrent_create_from_file_path( - state: tauri::State<'_, State>, - path: String, - opts: Option, -) -> Result { - let bytes = std::fs::read(&path) - .with_context(|| format!("error reading torrent file {path:?}")) - .map_err(|e| ApiError::new_from_anyhow(StatusCode::BAD_REQUEST, e))?; - state - .api()? - .api_add_torrent(AddTorrent::TorrentFileBytes(bytes.into()), opts) - .await -} - #[tauri::command] async fn torrent_details( state: tauri::State<'_, State>, @@ -694,70 +374,6 @@ async fn stats(state: tauri::State<'_, State>) -> Result) -> Vec { - let mut inputs = state.pending_torrent_inputs.lock(); - std::mem::take(&mut *inputs) -} - -#[tauri::command] -async fn torrent_open_output( - state: tauri::State<'_, State>, - id: TorrentIdOrHash, -) -> Result { - let details = state.api()?.api_torrent_details(id)?; - let path = nearest_existing_path(PathBuf::from(details.output_folder)); - open_file_manager(&path) - .map_err(|e| ApiError::new_from_anyhow(StatusCode::INTERNAL_SERVER_ERROR, e))?; - Ok(EmptyJsonResponse {}) -} - -fn nearest_existing_path(mut path: PathBuf) -> PathBuf { - let original = path.clone(); - while !path.exists() { - if !path.pop() { - return original; - } - } - path -} - -#[cfg(target_os = "linux")] -fn open_file_manager(path: &Path) -> anyhow::Result<()> { - let mut command = Command::new("xdg-open"); - command.arg(path); - spawn_open_command(command) -} - -#[cfg(target_os = "macos")] -fn open_file_manager(path: &Path) -> anyhow::Result<()> { - let mut command = Command::new("open"); - command.arg(path); - spawn_open_command(command) -} - -#[cfg(target_os = "windows")] -fn open_file_manager(path: &Path) -> anyhow::Result<()> { - let mut command = Command::new("explorer"); - command.arg(path); - spawn_open_command(command) -} - -#[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))] -fn open_file_manager(_path: &Path) -> anyhow::Result<()> { - anyhow::bail!("opening downloaded files is not supported on this platform") -} - -fn spawn_open_command(mut command: Command) -> anyhow::Result<()> { - let mut child = command.spawn().context("error opening path")?; - std::thread::spawn(move || { - if let Err(e) = child.wait() { - warn!("error waiting for opener process: {:#}", e); - } - }); - Ok(()) -} - #[tauri::command] fn get_version() -> &'static str { env!("CARGO_PKG_VERSION") @@ -777,30 +393,11 @@ async fn start() { Err(e) => warn!("failed increasing open file limit: {:#}", e), }; - let pending_torrent_inputs = pending_torrent_inputs_from_args(); - - #[cfg(unix)] - let single_instance_listener = match acquire_single_instance(&pending_torrent_inputs) { - SingleInstance::Primary(listener) => Some(listener), - SingleInstance::Secondary => return, - SingleInstance::Disabled => None, - }; - - let state = State::new(init_logging_result, pending_torrent_inputs).await; + let state = State::new(init_logging_result).await; tauri::Builder::default() .plugin(tauri_plugin_shell::init()) .manage(state) - .setup(move |app| { - #[cfg(target_os = "linux")] - apply_gtk_window_controls_layout(app); - - #[cfg(unix)] - if let Some(listener) = single_instance_listener { - start_single_instance_listener(listener, app.handle().clone()); - } - Ok(()) - }) .invoke_handler(tauri::generate_handler![ torrents_list, torrent_details, @@ -812,9 +409,6 @@ async fn start() { torrent_action_start, torrent_action_configure, torrent_create_from_base64_file, - torrent_create_from_file_path, - take_startup_torrent_inputs, - torrent_open_output, stats, get_version, config_default, diff --git a/desktop/src/StartupTorrentInputs.tsx b/desktop/src/StartupTorrentInputs.tsx deleted file mode 100644 index b67bccb..0000000 --- a/desktop/src/StartupTorrentInputs.tsx +++ /dev/null @@ -1,144 +0,0 @@ -import { useContext, useEffect, useState } from "react"; -import { invoke } from "@tauri-apps/api/core"; -import { listen } from "@tauri-apps/api/event"; -import { - AddTorrentResponse, - ErrorDetails as ApiErrorDetails, - TorrentInput, - localTorrentFile, -} from "rqbit-webui/src/api-types"; -import { APIContext } from "rqbit-webui/src/context"; -import { FileSelectionModal } from "rqbit-webui/src/components/modal/FileSelectionModal"; -import { ErrorWithLabel } from "rqbit-webui/src/rqbit-web"; - -type PendingTorrentInput = - | { type: "file_path"; path: string } - | { type: "url"; url: string }; - -const TORRENT_INPUTS_EVENT = "torrent-inputs"; - -const toTorrentInput = (input: PendingTorrentInput): TorrentInput => { - if (input.type === "file_path") { - return localTorrentFile(input.path); - } - return input.url; -}; - -export const StartupTorrentInputs = ({ enabled }: { enabled: boolean }) => { - const API = useContext(APIContext); - const [queue, setQueue] = useState([]); - const [activeInput, setActiveInput] = useState(null); - const [listTorrentResponse, setListTorrentResponse] = - useState(null); - const [listTorrentError, setListTorrentError] = - useState(null); - const [loading, setLoading] = useState(false); - - useEffect(() => { - if (!enabled) { - return; - } - - let unlisten: (() => void) | null = null; - let cancelled = false; - - const drainPendingInputs = () => { - invoke("take_startup_torrent_inputs").then( - (inputs) => { - if (!cancelled && inputs.length > 0) { - setQueue((queue) => [...queue, ...inputs.map(toTorrentInput)]); - } - }, - (e) => { - console.error("error reading startup torrent inputs", e); - }, - ); - }; - - listen(TORRENT_INPUTS_EVENT, drainPendingInputs).then( - (cleanup) => { - if (cancelled) { - cleanup(); - return; - } - unlisten = cleanup; - drainPendingInputs(); - }, - (e) => { - console.error("error listening for startup torrent inputs", e); - drainPendingInputs(); - }, - ); - - return () => { - cancelled = true; - unlisten?.(); - }; - }, [enabled]); - - useEffect(() => { - if (!activeInput && queue.length > 0) { - const [next, ...remaining] = queue; - setActiveInput(next); - setQueue(remaining); - } - }, [activeInput, queue]); - - useEffect(() => { - if (!activeInput) { - return; - } - - let cancelled = false; - setLoading(true); - setListTorrentError(null); - setListTorrentResponse(null); - - API.uploadTorrent(activeInput, { list_only: true }) - .then( - (response) => { - if (!cancelled) { - setListTorrentResponse(response); - } - }, - (e) => { - if (!cancelled) { - setListTorrentError({ - text: "Error listing torrent files", - details: e as ApiErrorDetails, - }); - } - }, - ) - .finally(() => { - if (!cancelled) { - setLoading(false); - } - }); - - return () => { - cancelled = true; - }; - }, [API, activeInput]); - - const clearActive = () => { - setActiveInput(null); - setListTorrentError(null); - setListTorrentResponse(null); - setLoading(false); - }; - - if (!activeInput) { - return null; - } - - return ( - - ); -}; diff --git a/desktop/src/api.tsx b/desktop/src/api.tsx index d59593a..e92a8aa 100644 --- a/desktop/src/api.tsx +++ b/desktop/src/api.tsx @@ -7,7 +7,6 @@ import { TorrentStats, ErrorDetails, SessionStats, - isLocalTorrentFile, } from "rqbit-webui/src/api-types"; import { InvokeArgs, invoke } from "@tauri-apps/api/core"; @@ -111,23 +110,11 @@ export const makeAPI = (configuration: RqbitDesktopConfig): RqbitAPI => { } ); } - if (isLocalTorrentFile(data)) { - return await invokeAPI( - "torrent_create_from_file_path", - { - path: data.path, - opts: opts ?? {}, - } - ); - } return await invokeAPI("torrent_create_from_url", { url: data, opts: opts ?? {}, }); }, - openTorrentOutput: function (id: number): Promise { - return invokeAPI("torrent_open_output", { id }); - }, updateOnlyFiles: function (id, files): Promise { return invokeAPI("torrent_action_configure", { id: id, diff --git a/desktop/src/rqbit-desktop.tsx b/desktop/src/rqbit-desktop.tsx index f66c7ab..e22ff35 100644 --- a/desktop/src/rqbit-desktop.tsx +++ b/desktop/src/rqbit-desktop.tsx @@ -1,4 +1,4 @@ -import { useMemo, useState } from "react"; +import { useState } from "react"; import { RqbitWebUI } from "rqbit-webui/src/rqbit-web"; import { CurrentDesktopState, RqbitDesktopConfig } from "./configuration"; import { ConfigModal } from "./configure"; @@ -6,7 +6,6 @@ import { IconButton } from "rqbit-webui/src/components/buttons/IconButton"; import { BsSliders2 } from "react-icons/bs"; import { APIContext } from "rqbit-webui/src/context"; import { makeAPI } from "./api"; -import { StartupTorrentInputs } from "./StartupTorrentInputs"; export const RqbitDesktop: React.FC<{ version: string; @@ -18,7 +17,6 @@ export const RqbitDesktop: React.FC<{ currentState.config ?? defaultConfig, ); let [configurationOpened, setConfigurationOpened] = useState(false); - const api = useMemo(() => makeAPI(config), [config]); const configButton = ( + {configured && ( - <> - - - + )}