Better error display in UI
This commit is contained in:
parent
414b2c5f65
commit
4078eacf1d
7 changed files with 92 additions and 51 deletions
|
|
@ -23,6 +23,7 @@ use crate::http_api_error::{ApiError, ApiErrorExt};
|
|||
use crate::peer_connection::PeerConnectionOptions;
|
||||
use crate::session::{
|
||||
AddTorrent, AddTorrentOptions, AddTorrentResponse, ListOnlyResponse, Session, TorrentId,
|
||||
SUPPORTED_SCHEMES,
|
||||
};
|
||||
use crate::torrent_state::peer::stats::snapshot::{PeerStatsFilter, PeerStatsSnapshot};
|
||||
use crate::torrent_state::stats::{LiveStats, TorrentStats};
|
||||
|
|
@ -88,10 +89,29 @@ impl HttpApi {
|
|||
Query(params): Query<TorrentAddQueryParams>,
|
||||
data: Bytes,
|
||||
) -> Result<impl IntoResponse> {
|
||||
let is_url = params.is_url;
|
||||
let opts = params.into_add_torrent_options();
|
||||
let add = match String::from_utf8(data.to_vec()) {
|
||||
Ok(s) => AddTorrent::Url(s.into()),
|
||||
Err(e) => AddTorrent::TorrentFileBytes(e.into_bytes().into()),
|
||||
let data = data.to_vec();
|
||||
let add = match is_url {
|
||||
Some(true) => AddTorrent::Url(
|
||||
String::from_utf8(data)
|
||||
.context("invalid utf-8 for passed URL")?
|
||||
.into(),
|
||||
),
|
||||
Some(false) => AddTorrent::TorrentFileBytes(data.into()),
|
||||
|
||||
// Guess the format.
|
||||
None if SUPPORTED_SCHEMES
|
||||
.iter()
|
||||
.any(|s| data.starts_with(s.as_bytes())) =>
|
||||
{
|
||||
AddTorrent::Url(
|
||||
String::from_utf8(data)
|
||||
.context("invalid utf-8 for passed URL")?
|
||||
.into(),
|
||||
)
|
||||
}
|
||||
_ => AddTorrent::TorrentFileBytes(data.into()),
|
||||
};
|
||||
state.api_add_torrent(add, Some(opts)).await.map(axum::Json)
|
||||
}
|
||||
|
|
@ -366,6 +386,8 @@ pub struct TorrentAddQueryParams {
|
|||
pub peer_connect_timeout: Option<u64>,
|
||||
pub peer_read_write_timeout: Option<u64>,
|
||||
pub initial_peers: Option<InitialPeers>,
|
||||
// Will force interpreting the content as a URL.
|
||||
pub is_url: Option<bool>,
|
||||
pub list_only: Option<bool>,
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -118,7 +118,7 @@ impl std::fmt::Display for ApiError {
|
|||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match &self.kind {
|
||||
ApiErrorKind::TorrentNotFound(idx) => write!(f, "torrent {idx} not found"),
|
||||
ApiErrorKind::Other(err) => write!(f, "{err:#}"),
|
||||
ApiErrorKind::Other(err) => write!(f, "{err:?}"),
|
||||
ApiErrorKind::DhtDisabled => write!(f, "DHT is disabled"),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -150,14 +150,14 @@ pub struct Session {
|
|||
async fn torrent_from_url(url: &str) -> anyhow::Result<TorrentMetaV1Owned> {
|
||||
let response = reqwest::get(url)
|
||||
.await
|
||||
.with_context(|| format!("error downloading torrent metadata from {url}"))?;
|
||||
.context("error downloading torrent metadata")?;
|
||||
if !response.status().is_success() {
|
||||
anyhow::bail!("GET {} returned {}", url, response.status())
|
||||
}
|
||||
let b = response
|
||||
.bytes()
|
||||
.await
|
||||
.with_context(|| format!("error reading repsonse body from {url}"))?;
|
||||
.with_context(|| format!("error reading response body from {url}"))?;
|
||||
torrent_from_bytes(&b).context("error decoding torrent")
|
||||
}
|
||||
|
||||
|
|
|
|||
10
crates/librqbit/webui/dist/assets/index.js
vendored
10
crates/librqbit/webui/dist/assets/index.js
vendored
File diff suppressed because one or more lines are too long
2
crates/librqbit/webui/dist/manifest.json
vendored
2
crates/librqbit/webui/dist/manifest.json
vendored
|
|
@ -4,7 +4,7 @@
|
|||
"src": "assets/logo.svg"
|
||||
},
|
||||
"index.html": {
|
||||
"file": "assets/index-75fed916.js",
|
||||
"file": "assets/index-3dee43e7.js",
|
||||
"isEntry": true,
|
||||
"src": "index.html"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -167,6 +167,9 @@ export const API = {
|
|||
if (opts.initialPeers) {
|
||||
url += `&initial_peers=${opts.initialPeers.join(',')}`;
|
||||
}
|
||||
if (typeof data === 'string') {
|
||||
url += '&is_url=true';
|
||||
}
|
||||
return makeRequest('POST', url, data)
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -356,8 +356,8 @@ const ErrorDetails = (props: { details: ErrorDetails }) => {
|
|||
return null;
|
||||
}
|
||||
return <>
|
||||
{details.status && <strong>{details.status} {details.statusText}: </strong>}
|
||||
{details.text}
|
||||
<p>{details.status && <strong>{details.status} {details.statusText}</strong>}</p>
|
||||
<pre>{details.text}</pre>
|
||||
</>
|
||||
|
||||
}
|
||||
|
|
@ -438,7 +438,13 @@ const MagnetInput = () => {
|
|||
};
|
||||
|
||||
return (
|
||||
<UploadButton variant='primary' buttonText="Add Torrent from Magnet / URL" onClick={onClick} data={magnet} resetData={() => setMagnet(null)} />
|
||||
<UploadButton
|
||||
variant='primary'
|
||||
buttonText="Add Torrent from Magnet / URL"
|
||||
onClick={onClick}
|
||||
data={magnet}
|
||||
resetData={() => setMagnet(null)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
@ -463,7 +469,13 @@ const FileInput = () => {
|
|||
return (
|
||||
<>
|
||||
<input type="file" ref={inputRef} accept=".torrent" onChange={onFileChange} className='d-none' />
|
||||
<UploadButton variant='secondary' buttonText="Upload .torrent File" onClick={onClick} data={file} resetData={reset} />
|
||||
<UploadButton
|
||||
variant='secondary'
|
||||
buttonText="Upload .torrent File"
|
||||
onClick={onClick}
|
||||
data={file}
|
||||
resetData={reset}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
@ -474,7 +486,7 @@ const FileSelectionModal = (props: {
|
|||
listTorrentResponse: AddTorrentResponse,
|
||||
listTorrentError: Error,
|
||||
listTorrentLoading: boolean,
|
||||
data: string | File
|
||||
data: string | File,
|
||||
}) => {
|
||||
let { show, onHide, listTorrentResponse, listTorrentError, listTorrentLoading, data } = props;
|
||||
|
||||
|
|
@ -516,46 +528,50 @@ const FileSelectionModal = (props: {
|
|||
).finally(() => setUploading(false));
|
||||
};
|
||||
|
||||
const getBody = () => {
|
||||
if (listTorrentLoading) {
|
||||
return <Spinner />;
|
||||
} else if (listTorrentError) {
|
||||
return <ErrorComponent error={listTorrentError}></ErrorComponent>;
|
||||
} else if (listTorrentResponse) {
|
||||
return <Form>
|
||||
<fieldset className='mb-5'>
|
||||
<legend>Pick the files to download</legend>
|
||||
{listTorrentResponse.details.files.map((file, index) => (
|
||||
<Form.Group key={index} controlId={`check-${index}`}>
|
||||
<Form.Check
|
||||
type="checkbox"
|
||||
label={`${file.name} (${formatBytes(file.length)})`}
|
||||
checked={selectedFiles.includes(index)}
|
||||
onChange={() => handleToggleFile(index)}>
|
||||
</Form.Check>
|
||||
</Form.Group>
|
||||
))}
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>Other options</legend>
|
||||
|
||||
<Form.Group controlId='unpopular-torrent'>
|
||||
<Form.Check
|
||||
type="checkbox"
|
||||
label="Increase timeouts"
|
||||
checked={unpopularTorrent}
|
||||
onChange={() => setUnpopularTorrent(!unpopularTorrent)}>
|
||||
</Form.Check>
|
||||
<small id="emailHelp" className="form-text text-muted">This might be useful for unpopular torrents with few peers. It will slow down fast torrents though.</small>
|
||||
</Form.Group>
|
||||
</fieldset>
|
||||
</Form >
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal show={show} onHide={clear} size='lg'>
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title>Add torrent</Modal.Title>
|
||||
</Modal.Header>
|
||||
<Modal.Body>
|
||||
<Form>
|
||||
<fieldset className='mb-5'>
|
||||
<legend>Pick the files to download</legend>
|
||||
{listTorrentLoading ? <Spinner />
|
||||
: listTorrentError ? <ErrorComponent error={listTorrentError}></ErrorComponent> :
|
||||
<>
|
||||
{listTorrentResponse?.details.files.map((file, index) => (
|
||||
<Form.Group key={index} controlId={`check-${index}`}>
|
||||
<Form.Check
|
||||
type="checkbox"
|
||||
label={`${file.name} (${formatBytes(file.length)})`}
|
||||
checked={selectedFiles.includes(index)}
|
||||
onChange={() => handleToggleFile(index)}>
|
||||
</Form.Check>
|
||||
</Form.Group>
|
||||
))}
|
||||
</>
|
||||
}
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>Other options</legend>
|
||||
|
||||
<Form.Group controlId='unpopular-torrent'>
|
||||
<Form.Check
|
||||
type="checkbox"
|
||||
label="Increase timeouts"
|
||||
checked={unpopularTorrent}
|
||||
onChange={() => setUnpopularTorrent(!unpopularTorrent)}>
|
||||
</Form.Check>
|
||||
<small id="emailHelp" className="form-text text-muted">This might be useful for unpopular torrents with few peers.</small>
|
||||
</Form.Group>
|
||||
</fieldset>
|
||||
|
||||
</Form>
|
||||
{getBody()}
|
||||
<ErrorComponent error={uploadError} />
|
||||
</Modal.Body>
|
||||
<Modal.Footer>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue