Better error display in UI

This commit is contained in:
Igor Katson 2023-12-01 11:28:35 +00:00
parent 414b2c5f65
commit 4078eacf1d
No known key found for this signature in database
GPG key ID: B4EC22B66D61A3F5
7 changed files with 92 additions and 51 deletions

View file

@ -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>,
}

View file

@ -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"),
}
}

View file

@ -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")
}

File diff suppressed because one or more lines are too long

View file

@ -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"
}

View file

@ -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)
},

View file

@ -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>