Pass back peers from Web UI when adding a magnet in attempt to speed it up
This commit is contained in:
parent
21b1bd9e7d
commit
f337ab1837
8 changed files with 77 additions and 27 deletions
|
|
@ -11,6 +11,7 @@ use librqbit_core::id20::Id20;
|
|||
use librqbit_core::torrent_metainfo::TorrentMetaV1Info;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::net::SocketAddr;
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use tokio::sync::mpsc::UnboundedSender;
|
||||
|
|
@ -281,6 +282,7 @@ pub struct TorrentDetailsResponse {
|
|||
pub struct ApiAddTorrentResponse {
|
||||
pub id: Option<usize>,
|
||||
pub details: TorrentDetailsResponse,
|
||||
pub seen_peers: Option<Vec<SocketAddr>>,
|
||||
}
|
||||
|
||||
pub struct OnlyFiles(Vec<usize>);
|
||||
|
|
@ -324,6 +326,36 @@ impl<'de> Deserialize<'de> for OnlyFiles {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct InitialPeers(pub Vec<SocketAddr>);
|
||||
|
||||
impl<'de> Deserialize<'de> for InitialPeers {
|
||||
fn deserialize<D>(deserializer: D) -> std::prelude::v1::Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
use serde::de::Error;
|
||||
let string = String::deserialize(deserializer)?;
|
||||
let mut addrs = Vec::new();
|
||||
for addr_str in string.split(',') {
|
||||
addrs.push(SocketAddr::from_str(addr_str).map_err(D::Error::custom)?);
|
||||
}
|
||||
Ok(InitialPeers(addrs))
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for InitialPeers {
|
||||
fn serialize<S>(&self, serializer: S) -> std::prelude::v1::Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
self.0
|
||||
.iter()
|
||||
.map(|s| s.to_string())
|
||||
.join(",")
|
||||
.serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Default)]
|
||||
pub struct TorrentAddQueryParams {
|
||||
pub overwrite: Option<bool>,
|
||||
|
|
@ -333,6 +365,7 @@ pub struct TorrentAddQueryParams {
|
|||
pub only_files: Option<OnlyFiles>,
|
||||
pub peer_connect_timeout: Option<u64>,
|
||||
pub peer_read_write_timeout: Option<u64>,
|
||||
pub initial_peers: Option<InitialPeers>,
|
||||
pub list_only: Option<bool>,
|
||||
}
|
||||
|
||||
|
|
@ -345,6 +378,7 @@ impl TorrentAddQueryParams {
|
|||
output_folder: self.output_folder,
|
||||
sub_folder: self.sub_folder,
|
||||
list_only: self.list_only.unwrap_or(false),
|
||||
initial_peers: self.initial_peers.map(|i| i.0),
|
||||
peer_opts: Some(PeerConnectionOptions {
|
||||
connect_timeout: self.peer_connect_timeout.map(Duration::from_secs),
|
||||
read_write_timeout: self.peer_read_write_timeout.map(Duration::from_secs),
|
||||
|
|
@ -471,8 +505,10 @@ impl ApiInternal {
|
|||
info_hash,
|
||||
info,
|
||||
only_files,
|
||||
seen_peers,
|
||||
}) => ApiAddTorrentResponse {
|
||||
id: None,
|
||||
seen_peers: Some(seen_peers),
|
||||
details: make_torrent_details(&info_hash, &info, only_files.as_deref())
|
||||
.context("error making torrent details")?,
|
||||
},
|
||||
|
|
@ -486,6 +522,7 @@ impl ApiInternal {
|
|||
ApiAddTorrentResponse {
|
||||
id: Some(id),
|
||||
details,
|
||||
seen_peers: None,
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -192,7 +192,7 @@ pub struct AddTorrentOptions {
|
|||
pub sub_folder: Option<String>,
|
||||
pub peer_opts: Option<PeerConnectionOptions>,
|
||||
pub force_tracker_interval: Option<Duration>,
|
||||
|
||||
pub initial_peers: Option<Vec<SocketAddr>>,
|
||||
// This is used to restore the session.
|
||||
pub preferred_id: Option<usize>,
|
||||
}
|
||||
|
|
@ -201,6 +201,7 @@ pub struct ListOnlyResponse {
|
|||
pub info_hash: Id20,
|
||||
pub info: TorrentMetaV1Info<ByteString>,
|
||||
pub only_files: Option<Vec<usize>>,
|
||||
pub seen_peers: Vec<SocketAddr>,
|
||||
}
|
||||
|
||||
pub enum AddTorrentResponse {
|
||||
|
|
@ -642,6 +643,7 @@ impl Session {
|
|||
info_hash,
|
||||
info,
|
||||
only_files,
|
||||
seen_peers: initial_peers,
|
||||
}));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ use tracing::debug;
|
|||
use tracing::error;
|
||||
use tracing::error_span;
|
||||
use tracing::warn;
|
||||
use tracing::trace;
|
||||
use url::Url;
|
||||
|
||||
use crate::chunk_tracker::ChunkTracker;
|
||||
|
|
@ -207,6 +208,7 @@ impl ManagedTorrent {
|
|||
{
|
||||
let live: Arc<TorrentStateLive> =
|
||||
live.upgrade().context("no longer live")?;
|
||||
trace!("adding {} initial peers", initial_peers.len());
|
||||
for peer in initial_peers {
|
||||
live.add_peer_if_not_seen(peer).context("torrent closed")?;
|
||||
}
|
||||
|
|
|
|||
2
crates/librqbit/webui/dist/assets/index.js
vendored
2
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-b3c336f4.js",
|
||||
"file": "assets/index-75fed916.js",
|
||||
"isEntry": true,
|
||||
"src": "index.html"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ export interface TorrentDetails {
|
|||
export interface AddTorrentResponse {
|
||||
id: number | null;
|
||||
details: TorrentDetails;
|
||||
seen_peers?: Array<string>;
|
||||
}
|
||||
|
||||
export interface ListTorrentsResponse {
|
||||
|
|
@ -147,7 +148,10 @@ export const API = {
|
|||
},
|
||||
|
||||
uploadTorrent: (data: string | File, opts?: {
|
||||
listOnly?: boolean, selectedFiles?: Array<number>, unpopularTorrent?: boolean,
|
||||
listOnly?: boolean,
|
||||
selectedFiles?: Array<number>,
|
||||
unpopularTorrent?: boolean,
|
||||
initialPeers?: Array<string>,
|
||||
}): Promise<AddTorrentResponse> => {
|
||||
opts = opts || {};
|
||||
let url = '/torrents?&overwrite=true';
|
||||
|
|
@ -160,6 +164,9 @@ export const API = {
|
|||
if (opts.unpopularTorrent) {
|
||||
url += '&peer_connect_timeout=20&peer_read_write_timeout=60';
|
||||
}
|
||||
if (opts.initialPeers) {
|
||||
url += `&initial_peers=${opts.initialPeers.join(',')}`;
|
||||
}
|
||||
return makeRequest('POST', url, data)
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -378,11 +378,11 @@ const ErrorComponent = (props: { error: Error, remove?: () => void }) => {
|
|||
|
||||
const UploadButton = ({ buttonText, onClick, data, resetData, variant }) => {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [fileList, setFileList] = useState([]);
|
||||
const [fileListError, setFileListError] = useState(null);
|
||||
const [listTorrentResponse, setListTorrentResponse] = useState<AddTorrentResponse>(null);
|
||||
const [listTorrentError, setListTorrentError] = useState<Error>(null);
|
||||
const ctx = useContext(AppContext);
|
||||
|
||||
const showModal = data !== null || fileListError !== null;
|
||||
const showModal = data !== null || listTorrentError !== null;
|
||||
|
||||
// Get the torrent file list if there's data.
|
||||
useEffect(() => {
|
||||
|
|
@ -394,9 +394,9 @@ const UploadButton = ({ buttonText, onClick, data, resetData, variant }) => {
|
|||
setLoading(true);
|
||||
try {
|
||||
const response = await API.uploadTorrent(data, { listOnly: true });
|
||||
setFileList(response.details.files);
|
||||
setListTorrentResponse(response);
|
||||
} catch (e) {
|
||||
setFileListError({ text: 'Error uploading torrent', details: e });
|
||||
setListTorrentError({ text: 'Error listing torrent files', details: e });
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
|
|
@ -406,8 +406,8 @@ const UploadButton = ({ buttonText, onClick, data, resetData, variant }) => {
|
|||
|
||||
const clear = () => {
|
||||
resetData();
|
||||
setFileListError(null);
|
||||
setFileList([]);
|
||||
setListTorrentError(null);
|
||||
setListTorrentResponse(null);
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
|
|
@ -420,10 +420,10 @@ const UploadButton = ({ buttonText, onClick, data, resetData, variant }) => {
|
|||
<FileSelectionModal
|
||||
show={showModal}
|
||||
onHide={clear}
|
||||
fileListError={fileListError}
|
||||
fileList={fileList}
|
||||
listTorrentError={listTorrentError}
|
||||
listTorrentResponse={listTorrentResponse}
|
||||
data={data}
|
||||
fileListLoading={loading}
|
||||
listTorrentLoading={loading}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
|
@ -471,12 +471,12 @@ const FileInput = () => {
|
|||
const FileSelectionModal = (props: {
|
||||
show: boolean,
|
||||
onHide: () => void,
|
||||
fileList: Array<TorrentFile>,
|
||||
fileListError: Error,
|
||||
fileListLoading: boolean,
|
||||
listTorrentResponse: AddTorrentResponse,
|
||||
listTorrentError: Error,
|
||||
listTorrentLoading: boolean,
|
||||
data: string | File
|
||||
}) => {
|
||||
let { show, onHide, fileList, fileListError, fileListLoading, data } = props;
|
||||
let { show, onHide, listTorrentResponse, listTorrentError, listTorrentLoading, data } = props;
|
||||
|
||||
const [selectedFiles, setSelectedFiles] = useState([]);
|
||||
const [uploading, setUploading] = useState(false);
|
||||
|
|
@ -485,8 +485,8 @@ const FileSelectionModal = (props: {
|
|||
const ctx = useContext(AppContext);
|
||||
|
||||
useEffect(() => {
|
||||
setSelectedFiles(fileList.map((_, id) => id));
|
||||
}, [fileList]);
|
||||
setSelectedFiles(listTorrentResponse ? listTorrentResponse.details.files.map((_, id) => id) : []);
|
||||
}, [listTorrentResponse]);
|
||||
|
||||
const clear = () => {
|
||||
onHide();
|
||||
|
|
@ -505,7 +505,8 @@ const FileSelectionModal = (props: {
|
|||
|
||||
const handleUpload = async () => {
|
||||
setUploading(true);
|
||||
API.uploadTorrent(data, { selectedFiles, unpopularTorrent }).then(() => {
|
||||
let initialPeers = listTorrentResponse.seen_peers ? listTorrentResponse.seen_peers.slice(0, 32) : null;
|
||||
API.uploadTorrent(data, { selectedFiles, unpopularTorrent, initialPeers }).then(() => {
|
||||
onHide();
|
||||
ctx.refreshTorrents();
|
||||
},
|
||||
|
|
@ -518,16 +519,16 @@ const FileSelectionModal = (props: {
|
|||
return (
|
||||
<Modal show={show} onHide={clear} size='lg'>
|
||||
<Modal.Header closeButton>
|
||||
{!!fileListError || <Modal.Title>Add torrent</Modal.Title>}
|
||||
<Modal.Title>Add torrent</Modal.Title>
|
||||
</Modal.Header>
|
||||
<Modal.Body>
|
||||
<Form>
|
||||
<fieldset className='mb-5'>
|
||||
<legend>Pick the files to download</legend>
|
||||
{fileListLoading ? <Spinner />
|
||||
: fileListError ? <ErrorComponent error={fileListError}></ErrorComponent> :
|
||||
{listTorrentLoading ? <Spinner />
|
||||
: listTorrentError ? <ErrorComponent error={listTorrentError}></ErrorComponent> :
|
||||
<>
|
||||
{fileList.map((file, index) => (
|
||||
{listTorrentResponse?.details.files.map((file, index) => (
|
||||
<Form.Group key={index} controlId={`check-${index}`}>
|
||||
<Form.Check
|
||||
type="checkbox"
|
||||
|
|
@ -559,7 +560,7 @@ const FileSelectionModal = (props: {
|
|||
</Modal.Body>
|
||||
<Modal.Footer>
|
||||
{uploading && <Spinner />}
|
||||
<Button variant="primary" onClick={handleUpload} disabled={fileListLoading || uploading || selectedFiles.length == 0}>
|
||||
<Button variant="primary" onClick={handleUpload} disabled={listTorrentLoading || uploading || selectedFiles.length == 0}>
|
||||
OK
|
||||
</Button>
|
||||
<Button variant="secondary" onClick={clear}>
|
||||
|
|
|
|||
|
|
@ -437,7 +437,7 @@ async fn async_main(opts: Opts, spawner: BlockingSpawner) -> anyhow::Result<()>
|
|||
)
|
||||
.await
|
||||
{
|
||||
Ok(ApiAddTorrentResponse { id, details }) => {
|
||||
Ok(ApiAddTorrentResponse { id, details, .. }) => {
|
||||
if let Some(id) = id {
|
||||
info!("{} added to the server with index {}. Query {}/torrents/{}/(stats/haves) for details", details.info_hash, id, http_api_url, id)
|
||||
}
|
||||
|
|
@ -509,6 +509,7 @@ async fn async_main(opts: Opts, spawner: BlockingSpawner) -> anyhow::Result<()>
|
|||
info_hash: _,
|
||||
info,
|
||||
only_files,
|
||||
..
|
||||
}) => {
|
||||
for (idx, (filename, len)) in
|
||||
info.iter_filenames_and_lengths()?.enumerate()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue