Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion crates/librqbit/webui/src/api-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ export interface ErrorDetails {
status?: number;
statusText?: string;
text: string | React.ReactNode;
timedOut: boolean;
}

export type Duration = number;
Expand Down Expand Up @@ -193,7 +194,8 @@ export interface RqbitAPI {
) => string | null;
uploadTorrent: (
data: string | File,
opts?: AddTorrentOptions,
opts: AddTorrentOptions,
timeout?: number | undefined
) => Promise<AddTorrentResponse>;

pause: (index: number) => Promise<void>;
Expand Down
19 changes: 13 additions & 6 deletions crates/librqbit/webui/src/components/buttons/UploadButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,20 @@ export const UploadButton: React.FC<{
let t = setTimeout(async () => {
setLoading(true);
try {
const response = await API.uploadTorrent(data, { list_only: true });
const response = await API.uploadTorrent(data, { list_only: true }, 2_000);
setListTorrentResponse(response);
} catch (e) {
setListTorrentError({
text: "Error listing torrent files",
details: e as ApiErrorDetails,
});
} catch (e: unknown) {
let error = e as ApiErrorDetails;
if (error.timedOut) {
setListTorrentResponse(null);
// Timeout is not an error for a listOnly request
setListTorrentError(null);
} else {
setListTorrentError({
text: "Error listing torrent files",
details: error,
});
}
} finally {
setLoading(false);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,16 +52,18 @@ export const FileSelectionModal = (props: {
};

const handleUpload = async () => {
if (!listTorrentResponse) {
return;
}
// TODO this comment refers to the desired state, not the current state
// TODO implement listTorrentResponse==null support
// If listTorrentResponse is null, that indicates that the data is not available at the moment
// This is typically the case for magnet links that may not have dht peers
setUploading(true);
let initialPeers = listTorrentResponse.seen_peers
let initialPeers = listTorrentResponse?.seen_peers
? listTorrentResponse.seen_peers.slice(0, 32)
: null;
let only_files = selectedFiles.size <= 0 ? null : Array.from(selectedFiles);
let opts: AddTorrentOptions = {
overwrite: true,
only_files: Array.from(selectedFiles),
only_files,
initial_peers: initialPeers,
output_folder: outputFolder,
};
Expand Down
47 changes: 42 additions & 5 deletions crates/librqbit/webui/src/http-api.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
AddTorrentOptions,
AddTorrentResponse,
ErrorDetails,
ListTorrentsResponse,
Expand All @@ -20,11 +21,39 @@ const apiUrl = (() => {
return "";
})();

// Wrapper around `fetch` to support a custom timeout.
// If specified, uses `AbortController` to timeout the pending fetch
function fetchWithTimeout(url: string, options: RequestInit = {}, timeout: number | undefined) {
let pending;
let timeoutId = undefined;
if (timeout !== undefined) {
const controller = new AbortController();
const { signal } = controller;

timeoutId = setTimeout(() => controller.abort(), timeout);
console.log("Fetching with timeout: ", timeout);
pending = fetch(url, { ...options, signal })
} else {
pending = fetch(url, options)
}

return pending
.then(response => {
clearTimeout(timeoutId);
return response;
})
.catch(error => {
clearTimeout(timeoutId);
throw error;
});
}

const makeRequest = async (
method: string,
path: string,
data?: any,
isJson?: boolean,
timeout?: number,
): Promise<any> => {
console.log(method, path);
const url = apiUrl + path;
Expand All @@ -48,13 +77,21 @@ const makeRequest = async (
method: method,
path: path,
text: "",
timedOut: false,
};

let response: Response;

try {
response = await fetch(url, options);
} catch (e) {
response = await fetchWithTimeout(url, options, timeout);
} catch (e: any) {
console.log(e);
if (e.name === "AbortError") {
error.text = "fetch timed out after " + timeout + "ms"
error.timedOut = true;
return Promise.reject(error);
}
// else, generic network error
error.text = "network error";
return Promise.reject(error);
}
Expand Down Expand Up @@ -92,8 +129,8 @@ export const API: RqbitAPI & { getVersion: () => Promise<string> } = {
stats: (): Promise<SessionStats> => {
return makeRequest("GET", "/stats");
},

uploadTorrent: (data, opts): Promise<AddTorrentResponse> => {
uploadTorrent: (data: string | File, opts: AddTorrentOptions, timeout: number | undefined): Promise<AddTorrentResponse> => {
console.log("Uploading torrent with ", data, opts, timeout);
let url = "/torrents?&overwrite=true";
if (opts?.list_only) {
url += "&list_only=true";
Expand All @@ -116,7 +153,7 @@ export const API: RqbitAPI & { getVersion: () => Promise<string> } = {
if (typeof data === "string") {
url += "&is_url=true";
}
return makeRequest("POST", url, data);
return makeRequest("POST", url, data, undefined, timeout);
},

updateOnlyFiles: (index: number, files: number[]): Promise<void> => {
Expand Down
5 changes: 4 additions & 1 deletion crates/librqbit/webui/src/rqbit-web.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,10 @@ export const RqbitWebUI = (props: {
);
setTorrents(torrents.torrents);
};
setRefreshTorrents(refreshTorrents);

useEffect(() => {
setRefreshTorrents(refreshTorrents);
}, [])

const setStats = useStatsStore((state) => state.setStats);

Expand Down