Fix a couple bugs

This commit is contained in:
Igor Katson 2023-11-25 11:21:45 +00:00
parent 1bea1f9235
commit 1c53aeba2f
No known key found for this signature in database
GPG key ID: B4EC22B66D61A3F5
6 changed files with 57 additions and 30 deletions

View file

@ -36,12 +36,8 @@ async fn main() -> Result<(), anyhow::Error> {
.add_torrent( .add_torrent(
AddTorrent::from_url(MAGNET_LINK), AddTorrent::from_url(MAGNET_LINK),
Some(AddTorrentOptions { Some(AddTorrentOptions {
// Set this to true to allow writing on top of existing files. // Allow writing on top of existing files.
// If the file is partially downloaded, librqbit will only download the overwrite: true,
// missing pieces.
//
// Otherwise it will throw an error that the file exists.
overwrite: false,
..Default::default() ..Default::default()
}), }),
) )

View file

@ -38,7 +38,11 @@ impl HttpApi {
} }
} }
pub async fn make_http_api_and_run(self, addr: SocketAddr) -> anyhow::Result<()> { pub async fn make_http_api_and_run(
self,
addr: SocketAddr,
read_only: bool,
) -> anyhow::Result<()> {
let state = self.inner; let state = self.inner;
async fn api_root() -> impl IntoResponse { async fn api_root() -> impl IntoResponse {
@ -160,22 +164,26 @@ impl HttpApi {
state.api_set_rust_log(new_value).map(axum::Json) state.api_set_rust_log(new_value).map(axum::Json)
} }
#[allow(unused_mut)]
let mut app = Router::new() let mut app = Router::new()
.route("/", get(api_root)) .route("/", get(api_root))
.route("/rust_log", post(set_rust_log))
.route("/dht/stats", get(dht_stats)) .route("/dht/stats", get(dht_stats))
.route("/dht/table", get(dht_table)) .route("/dht/table", get(dht_table))
.route("/torrents", get(torrents_list).post(torrents_post)) .route("/torrents", get(torrents_list))
.route("/torrents/:id", get(torrent_details)) .route("/torrents/:id", get(torrent_details))
.route("/torrents/:id/haves", get(torrent_haves)) .route("/torrents/:id/haves", get(torrent_haves))
.route("/torrents/:id/stats", get(torrent_stats_v0)) .route("/torrents/:id/stats", get(torrent_stats_v0))
.route("/torrents/:id/stats/v1", get(torrent_stats_v1)) .route("/torrents/:id/stats/v1", get(torrent_stats_v1))
.route("/torrents/:id/peer_stats", get(peer_stats)) .route("/torrents/:id/peer_stats", get(peer_stats));
.route("/torrents/:id/pause", post(torrent_action_pause))
.route("/torrents/:id/start", post(torrent_action_start)) if !read_only {
.route("/torrents/:id/forget", post(torrent_action_forget)) app = app
.route("/torrents/:id/delete", post(torrent_action_delete)) .route("/torrents", post(torrents_post))
.route("/rust_log", post(set_rust_log)); .route("/torrents/:id/pause", post(torrent_action_pause))
.route("/torrents/:id/start", post(torrent_action_start))
.route("/torrents/:id/forget", post(torrent_action_forget))
.route("/torrents/:id/delete", post(torrent_action_delete));
}
#[cfg(feature = "webui")] #[cfg(feature = "webui")]
{ {

View file

@ -210,6 +210,8 @@ pub struct SessionOptions {
pub disable_dht: bool, pub disable_dht: bool,
pub disable_dht_persistence: bool, pub disable_dht_persistence: bool,
pub persistence: bool, pub persistence: bool,
// Will default to output_folder/.rqbit-session.json
pub persistence_filename: Option<PathBuf>,
pub dht_config: Option<PersistentDhtConfig>, pub dht_config: Option<PersistentDhtConfig>,
pub peer_id: Option<Id20>, pub peer_id: Option<Id20>,
pub peer_opts: Option<PeerConnectionOptions>, pub peer_opts: Option<PeerConnectionOptions>,
@ -240,7 +242,9 @@ impl Session {
Some(dht) Some(dht)
}; };
let peer_opts = opts.peer_opts.unwrap_or_default(); let peer_opts = opts.peer_opts.unwrap_or_default();
let session_filename = output_folder.join(".rqbit-session.json"); let session_filename = opts
.persistence_filename
.unwrap_or_else(|| output_folder.join(".rqbit-session.json"));
let session = Arc::new(Self { let session = Arc::new(Self {
persistence_filename: session_filename, persistence_filename: session_filename,
peer_id, peer_id,

View file

@ -191,7 +191,7 @@ impl Serialize for Speed {
} }
Tmp { Tmp {
mbps: self.mbps, mbps: self.mbps,
human_readable: format!("{:?}", self.mbps), human_readable: format!("{}", self),
} }
.serialize(serializer) .serialize(serializer)
} }

View file

@ -1,7 +1,7 @@
import { MouseEventHandler, StrictMode, createContext, useContext, useEffect, useRef, useState } from 'react'; import { MouseEventHandler, StrictMode, createContext, useContext, useEffect, useRef, useState } from 'react';
import ReactDOM from 'react-dom/client'; import ReactDOM from 'react-dom/client';
import { ProgressBar, Button, Container, Row, Col, Alert, Modal, Form, Spinner, Table } from 'react-bootstrap'; import { ProgressBar, Button, Container, Row, Col, Alert, Modal, Form, Spinner, Table } from 'react-bootstrap';
import { AddTorrentResponse, TorrentDetails, TorrentFile, TorrentId, TorrentStats, ErrorDetails, API, STATE_INITIALIZING, STATE_LIVE, STATE_PAUSED } from './api'; import { AddTorrentResponse, TorrentDetails, TorrentFile, TorrentId, TorrentStats, ErrorDetails, API, STATE_INITIALIZING, STATE_LIVE, STATE_PAUSED, STATE_ERROR } from './api';
interface Error { interface Error {
text: string, text: string,
@ -171,13 +171,16 @@ const TorrentRow: React.FC<{
return `${peer_stats.live} / ${peer_stats.seen}`; return `${peer_stats.live} / ${peer_stats.seen}`;
} }
const formatDownloadSped = () => { const formatDownloadSpeed = () => {
if (finished) { if (finished) {
return 'Completed'; return 'Completed';
} }
if (state == STATE_INITIALIZING) { switch (state) {
return 'Checking files'; case STATE_PAUSED: return 'Paused';
case STATE_INITIALIZING: return 'Checking files';
case STATE_ERROR: return 'Error';
} }
return statsResponse.live?.download_speed.human_readable ?? "N/A"; return statsResponse.live?.download_speed.human_readable ?? "N/A";
} }
@ -206,14 +209,14 @@ const TorrentRow: React.FC<{
{statsResponse ? {statsResponse ?
<> <>
<Column label="Size">{`${formatBytes(totalBytes)} `}</Column> <Column label="Size">{`${formatBytes(totalBytes)} `}</Column>
<Column size={2} label={state == STATE_PAUSED ? 'Progress (PAUSED)' : 'Progress'}> <Column size={2} label={state == STATE_PAUSED ? 'Progress' : 'Progress'}>
<ProgressBar <ProgressBar
now={progressPercentage} now={progressPercentage}
label={progressLabel} label={progressLabel}
animated={isAnimated} animated={isAnimated}
variant={progressBarVariant} /> variant={progressBarVariant} />
</Column> </Column>
<Column size={2} label="Down Speed">{formatDownloadSped()}</Column> <Column size={2} label="Down Speed">{formatDownloadSpeed()}</Column>
<Column label="ETA">{getCompletionETA(statsResponse)}</Column> <Column label="ETA">{getCompletionETA(statsResponse)}</Column>
<Column size={2} label="Peers">{formatPeersString()}</Column > <Column size={2} label="Peers">{formatPeersString()}</Column >
<Column label="Actions"> <Column label="Actions">
@ -393,7 +396,7 @@ const UploadButton = ({ buttonText, onClick, data, resetData, variant }) => {
const response = await API.uploadTorrent(data, { listOnly: true }); const response = await API.uploadTorrent(data, { listOnly: true });
setFileList(response.details.files); setFileList(response.details.files);
} catch (e) { } catch (e) {
setFileListError({ text: 'Error listing torrent', details: e }); setFileListError({ text: 'Error uploading torrent', details: e });
} finally { } finally {
setLoading(false); setLoading(false);
} }

View file

@ -77,6 +77,13 @@ struct Opts {
struct ServerStartOptions { struct ServerStartOptions {
/// The output folder to write to. If not exists, it will be created. /// The output folder to write to. If not exists, it will be created.
output_folder: String, output_folder: String,
#[arg(
long = "disable-persistence",
help = "Disable server persistence. It will not read or write its state to disk."
)]
disable_persistence: bool,
#[arg(long = "persistence-filename")]
persistence_filename: Option<String>,
} }
#[derive(Parser)] #[derive(Parser)]
@ -275,11 +282,12 @@ fn main() -> anyhow::Result<()> {
async fn async_main(opts: Opts, spawner: BlockingSpawner) -> anyhow::Result<()> { async fn async_main(opts: Opts, spawner: BlockingSpawner) -> anyhow::Result<()> {
let logging_reload_tx = init_logging(&opts); let logging_reload_tx = init_logging(&opts);
let sopts = SessionOptions { let mut sopts = SessionOptions {
disable_dht: opts.disable_dht, disable_dht: opts.disable_dht,
disable_dht_persistence: opts.disable_dht_persistence, disable_dht_persistence: opts.disable_dht_persistence,
dht_config: None, dht_config: None,
persistence: true, persistence: false,
persistence_filename: None,
peer_id: None, peer_id: None,
peer_opts: Some(PeerConnectionOptions { peer_opts: Some(PeerConnectionOptions {
connect_timeout: Some(opts.peer_connect_timeout), connect_timeout: Some(opts.peer_connect_timeout),
@ -343,6 +351,9 @@ async fn async_main(opts: Opts, spawner: BlockingSpawner) -> anyhow::Result<()>
match &opts.subcommand { match &opts.subcommand {
SubCommand::Server(server_opts) => match &server_opts.subcommand { SubCommand::Server(server_opts) => match &server_opts.subcommand {
ServerSubcommand::Start(start_opts) => { ServerSubcommand::Start(start_opts) => {
sopts.persistence = !start_opts.disable_persistence;
sopts.persistence_filename =
start_opts.persistence_filename.clone().map(PathBuf::from);
let session = Session::new_with_opts( let session = Session::new_with_opts(
PathBuf::from(&start_opts.output_folder), PathBuf::from(&start_opts.output_folder),
spawner, spawner,
@ -358,7 +369,7 @@ async fn async_main(opts: Opts, spawner: BlockingSpawner) -> anyhow::Result<()>
let http_api = HttpApi::new(session, Some(logging_reload_tx)); let http_api = HttpApi::new(session, Some(logging_reload_tx));
let http_api_listen_addr = opts.http_api_listen_addr; let http_api_listen_addr = opts.http_api_listen_addr;
http_api http_api
.make_http_api_and_run(http_api_listen_addr) .make_http_api_and_run(http_api_listen_addr, false)
.await .await
.context("error starting HTTP API") .context("error starting HTTP API")
} }
@ -438,7 +449,9 @@ async fn async_main(opts: Opts, spawner: BlockingSpawner) -> anyhow::Result<()>
spawn( spawn(
"http_api", "http_api",
error_span!("http_api"), error_span!("http_api"),
http_api.clone().make_http_api_and_run(http_api_listen_addr), http_api
.clone()
.make_http_api_and_run(http_api_listen_addr, true),
); );
let mut added = false; let mut added = false;
@ -504,8 +517,11 @@ async fn async_main(opts: Opts, spawner: BlockingSpawner) -> anyhow::Result<()>
if download_opts.exit_on_finish { if download_opts.exit_on_finish {
let results = futures::future::join_all( let results = futures::future::join_all(
handles.iter().map(|h| h.wait_until_completed()), handles.iter().map(|h| h.wait_until_completed()),
); )
results.await; .await;
if results.iter().any(|r| r.is_err()) {
anyhow::bail!("some downloads failed")
}
info!("All downloads completed, exiting"); info!("All downloads completed, exiting");
Ok(()) Ok(())
} else { } else {