Made changes for Desktop app to work
This commit is contained in:
parent
fe04e17d63
commit
28c2db2a37
15 changed files with 74 additions and 38 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
|
@ -1313,6 +1313,7 @@ name = "librqbit-core"
|
||||||
version = "3.1.0"
|
version = "3.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"directories",
|
||||||
"hex 0.4.3",
|
"hex 0.4.3",
|
||||||
"itertools 0.12.0",
|
"itertools 0.12.0",
|
||||||
"librqbit-bencode",
|
"librqbit-bencode",
|
||||||
|
|
@ -1335,7 +1336,6 @@ dependencies = [
|
||||||
"backoff",
|
"backoff",
|
||||||
"chrono",
|
"chrono",
|
||||||
"dashmap",
|
"dashmap",
|
||||||
"directories",
|
|
||||||
"futures",
|
"futures",
|
||||||
"hex 0.4.3",
|
"hex 0.4.3",
|
||||||
"indexmap 2.1.0",
|
"indexmap 2.1.0",
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,6 @@ backoff = "0.4.0"
|
||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
rand = "0.8"
|
rand = "0.8"
|
||||||
indexmap = "2"
|
indexmap = "2"
|
||||||
directories = "5"
|
|
||||||
dashmap = {version = "5.5.3", features = ["serde"]}
|
dashmap = {version = "5.5.3", features = ["serde"]}
|
||||||
|
|
||||||
clone_to_owned = {path="../clone_to_owned", package="librqbit-clone-to-owned", version = "2.2.1"}
|
clone_to_owned = {path="../clone_to_owned", package="librqbit-clone-to-owned", version = "2.2.1"}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
// TODO: this now stores only the routing table, but we also need AT LEAST the same socket address...
|
// TODO: this now stores only the routing table, but we also need AT LEAST the same socket address...
|
||||||
|
|
||||||
|
use librqbit_core::directories::get_configuration_directory;
|
||||||
use librqbit_core::spawn_utils::spawn;
|
use librqbit_core::spawn_utils::spawn;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::fs::OpenOptions;
|
use std::fs::OpenOptions;
|
||||||
|
|
@ -72,8 +73,7 @@ impl PersistentDht {
|
||||||
let config_filename = match config.config_filename.take() {
|
let config_filename = match config.config_filename.take() {
|
||||||
Some(config_filename) => config_filename,
|
Some(config_filename) => config_filename,
|
||||||
None => {
|
None => {
|
||||||
let dirs = directories::ProjectDirs::from("com", "rqbit", "dht")
|
let dirs = get_configuration_directory("dht")?;
|
||||||
.context("cannot determine project directory for com.rqbit.dht")?;
|
|
||||||
let path = dirs.cache_dir().join("dht.json");
|
let path = dirs.cache_dir().join("dht.json");
|
||||||
info!("will store DHT routing table to {:?} periodically", &path);
|
info!("will store DHT routing table to {:?} periodically", &path);
|
||||||
path
|
path
|
||||||
|
|
|
||||||
|
|
@ -143,8 +143,10 @@ impl Api {
|
||||||
info,
|
info,
|
||||||
only_files,
|
only_files,
|
||||||
seen_peers,
|
seen_peers,
|
||||||
|
output_folder,
|
||||||
}) => ApiAddTorrentResponse {
|
}) => ApiAddTorrentResponse {
|
||||||
id: None,
|
id: None,
|
||||||
|
output_folder: output_folder.to_string_lossy().into_owned(),
|
||||||
seen_peers: Some(seen_peers),
|
seen_peers: Some(seen_peers),
|
||||||
details: make_torrent_details(&info_hash, &info, only_files.as_deref())
|
details: make_torrent_details(&info_hash, &info, only_files.as_deref())
|
||||||
.context("error making torrent details")?,
|
.context("error making torrent details")?,
|
||||||
|
|
@ -159,6 +161,7 @@ impl Api {
|
||||||
ApiAddTorrentResponse {
|
ApiAddTorrentResponse {
|
||||||
id: Some(id),
|
id: Some(id),
|
||||||
details,
|
details,
|
||||||
|
output_folder: handle.info().out_dir.to_string_lossy().into_owned(),
|
||||||
seen_peers: None,
|
seen_peers: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -227,6 +230,7 @@ pub struct TorrentDetailsResponse {
|
||||||
pub struct ApiAddTorrentResponse {
|
pub struct ApiAddTorrentResponse {
|
||||||
pub id: Option<usize>,
|
pub id: Option<usize>,
|
||||||
pub details: TorrentDetailsResponse,
|
pub details: TorrentDetailsResponse,
|
||||||
|
pub output_folder: String,
|
||||||
pub seen_peers: Option<Vec<SocketAddr>>,
|
pub seen_peers: Option<Vec<SocketAddr>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ use bencode::{bencode_serialize_to_writer, BencodeDeserializer};
|
||||||
use buffers::ByteString;
|
use buffers::ByteString;
|
||||||
use dht::{Dht, DhtBuilder, Id20, PersistentDht, PersistentDhtConfig, RequestPeersStream};
|
use dht::{Dht, DhtBuilder, Id20, PersistentDht, PersistentDhtConfig, RequestPeersStream};
|
||||||
use librqbit_core::{
|
use librqbit_core::{
|
||||||
|
directories::get_configuration_directory,
|
||||||
magnet::Magnet,
|
magnet::Magnet,
|
||||||
peer_id::generate_peer_id,
|
peer_id::generate_peer_id,
|
||||||
torrent_metainfo::{torrent_from_bytes, TorrentMetaV1Info, TorrentMetaV1Owned},
|
torrent_metainfo::{torrent_from_bytes, TorrentMetaV1Info, TorrentMetaV1Owned},
|
||||||
|
|
@ -66,7 +67,7 @@ impl SessionDatabase {
|
||||||
|
|
||||||
fn serialize(&self) -> SerializedSessionDatabase {
|
fn serialize(&self) -> SerializedSessionDatabase {
|
||||||
SerializedSessionDatabase {
|
SerializedSessionDatabase {
|
||||||
torrents_v2: self
|
torrents: self
|
||||||
.torrents
|
.torrents
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(id, torrent)| {
|
.map(|(id, torrent)| {
|
||||||
|
|
@ -135,7 +136,7 @@ where
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
struct SerializedSessionDatabase {
|
struct SerializedSessionDatabase {
|
||||||
torrents_v2: HashMap<usize, SerializedTorrent>,
|
torrents: HashMap<usize, SerializedTorrent>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Session {
|
pub struct Session {
|
||||||
|
|
@ -208,9 +209,11 @@ pub struct ListOnlyResponse {
|
||||||
pub info_hash: Id20,
|
pub info_hash: Id20,
|
||||||
pub info: TorrentMetaV1Info<ByteString>,
|
pub info: TorrentMetaV1Info<ByteString>,
|
||||||
pub only_files: Option<Vec<usize>>,
|
pub only_files: Option<Vec<usize>>,
|
||||||
|
pub output_folder: PathBuf,
|
||||||
pub seen_peers: Vec<SocketAddr>,
|
pub seen_peers: Vec<SocketAddr>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::large_enum_variant)]
|
||||||
pub enum AddTorrentResponse {
|
pub enum AddTorrentResponse {
|
||||||
AlreadyManaged(TorrentId, ManagedTorrentHandle),
|
AlreadyManaged(TorrentId, ManagedTorrentHandle),
|
||||||
ListOnly(ListOnlyResponse),
|
ListOnly(ListOnlyResponse),
|
||||||
|
|
@ -276,7 +279,6 @@ 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 persistence_filename: Option<PathBuf>,
|
||||||
pub dht_config: Option<PersistentDhtConfig>,
|
pub dht_config: Option<PersistentDhtConfig>,
|
||||||
pub peer_id: Option<Id20>,
|
pub peer_id: Option<Id20>,
|
||||||
|
|
@ -308,9 +310,12 @@ 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 persistence_filename = opts
|
let persistence_filename = match opts.persistence_filename {
|
||||||
.persistence_filename
|
Some(filename) => filename,
|
||||||
.unwrap_or_else(|| output_folder.join(".rqbit-session.json"));
|
None => get_configuration_directory("session")?
|
||||||
|
.data_dir()
|
||||||
|
.join("session.json"),
|
||||||
|
};
|
||||||
let session = Arc::new(Self {
|
let session = Arc::new(Self {
|
||||||
persistence_filename,
|
persistence_filename,
|
||||||
peer_id,
|
peer_id,
|
||||||
|
|
@ -322,6 +327,10 @@ impl Session {
|
||||||
});
|
});
|
||||||
|
|
||||||
if opts.persistence {
|
if opts.persistence {
|
||||||
|
info!(
|
||||||
|
"will use {:?} for session persistence",
|
||||||
|
session.persistence_filename
|
||||||
|
);
|
||||||
if let Some(parent) = session.persistence_filename.parent() {
|
if let Some(parent) = session.persistence_filename.parent() {
|
||||||
std::fs::create_dir_all(parent).with_context(|| {
|
std::fs::create_dir_all(parent).with_context(|| {
|
||||||
format!("couldn't create directory {:?} for session storage", parent)
|
format!("couldn't create directory {:?} for session storage", parent)
|
||||||
|
|
@ -391,7 +400,7 @@ impl Session {
|
||||||
let db: SerializedSessionDatabase =
|
let db: SerializedSessionDatabase =
|
||||||
serde_json::from_reader(&mut rdr).context("error deserializing session database")?;
|
serde_json::from_reader(&mut rdr).context("error deserializing session database")?;
|
||||||
let mut futures = Vec::new();
|
let mut futures = Vec::new();
|
||||||
for (id, storrent) in db.torrents_v2.into_iter() {
|
for (id, storrent) in db.torrents.into_iter() {
|
||||||
let trackers: Vec<ByteString> = storrent
|
let trackers: Vec<ByteString> = storrent
|
||||||
.trackers
|
.trackers
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
|
@ -646,15 +655,6 @@ impl Session {
|
||||||
|
|
||||||
let only_files = get_only_files(opts.only_files, opts.only_files_regex, opts.list_only)?;
|
let only_files = get_only_files(opts.only_files, opts.only_files_regex, opts.list_only)?;
|
||||||
|
|
||||||
if opts.list_only {
|
|
||||||
return Ok(AddTorrentResponse::ListOnly(ListOnlyResponse {
|
|
||||||
info_hash,
|
|
||||||
info,
|
|
||||||
only_files,
|
|
||||||
seen_peers: initial_peers,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
let sub_folder = opts.sub_folder.map(PathBuf::from).unwrap_or_default();
|
let sub_folder = opts.sub_folder.map(PathBuf::from).unwrap_or_default();
|
||||||
let output_folder = opts
|
let output_folder = opts
|
||||||
.output_folder
|
.output_folder
|
||||||
|
|
@ -662,6 +662,16 @@ impl Session {
|
||||||
.unwrap_or_else(|| self.output_folder.clone())
|
.unwrap_or_else(|| self.output_folder.clone())
|
||||||
.join(sub_folder);
|
.join(sub_folder);
|
||||||
|
|
||||||
|
if opts.list_only {
|
||||||
|
return Ok(AddTorrentResponse::ListOnly(ListOnlyResponse {
|
||||||
|
info_hash,
|
||||||
|
info,
|
||||||
|
only_files,
|
||||||
|
output_folder,
|
||||||
|
seen_peers: initial_peers,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
let mut builder = ManagedTorrentBuilder::new(info, info_hash, output_folder.clone());
|
let mut builder = ManagedTorrentBuilder::new(info, info_hash, output_folder.clone());
|
||||||
builder
|
builder
|
||||||
.overwrite(opts.overwrite)
|
.overwrite(opts.overwrite)
|
||||||
|
|
|
||||||
BIN
crates/librqbit/webui/assets/logo.png
Normal file
BIN
crates/librqbit/webui/assets/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 154 KiB |
16
crates/librqbit/webui/dist/assets/index.js
vendored
16
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"
|
"src": "assets/logo.svg"
|
||||||
},
|
},
|
||||||
"index.html": {
|
"index.html": {
|
||||||
"file": "assets/index-386e9e08.js",
|
"file": "assets/index-3e661b92.js",
|
||||||
"isEntry": true,
|
"isEntry": true,
|
||||||
"src": "index.html"
|
"src": "index.html"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ export interface TorrentDetails {
|
||||||
export interface AddTorrentResponse {
|
export interface AddTorrentResponse {
|
||||||
id: number | null;
|
id: number | null;
|
||||||
details: TorrentDetails;
|
details: TorrentDetails;
|
||||||
|
output_folder: string,
|
||||||
seen_peers?: Array<string>;
|
seen_peers?: Array<string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -72,6 +72,9 @@ export const API: RqbitAPI = {
|
||||||
if (opts?.initial_peers) {
|
if (opts?.initial_peers) {
|
||||||
url += `&initial_peers=${opts.initial_peers.join(',')}`;
|
url += `&initial_peers=${opts.initial_peers.join(',')}`;
|
||||||
}
|
}
|
||||||
|
if (opts?.output_folder) {
|
||||||
|
url += `&output_folder=${opts.output_folder}`;
|
||||||
|
}
|
||||||
if (typeof data === 'string') {
|
if (typeof data === 'string') {
|
||||||
url += '&is_url=true';
|
url += '&is_url=true';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import { API } from "./http-api";
|
||||||
ReactDOM.createRoot(document.getElementById('app') as HTMLInputElement).render(
|
ReactDOM.createRoot(document.getElementById('app') as HTMLInputElement).render(
|
||||||
<StrictMode>
|
<StrictMode>
|
||||||
<APIContext.Provider value={API}>
|
<APIContext.Provider value={API}>
|
||||||
<RqbitWebUI />
|
<RqbitWebUI title="rqbit web UI - version 4.0.0-beta.3" />
|
||||||
</APIContext.Provider>
|
</APIContext.Provider>
|
||||||
</StrictMode>
|
</StrictMode>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -349,7 +349,7 @@ const TorrentsList = (props: { torrents: Array<TorrentId> | null, loading: boole
|
||||||
</>;
|
</>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const RqbitWebUI = () => {
|
export const RqbitWebUI = (props: { title: string }) => {
|
||||||
const [closeableError, setCloseableError] = useState<Error | null>(null);
|
const [closeableError, setCloseableError] = useState<Error | null>(null);
|
||||||
const [otherError, setOtherError] = useState<Error | null>(null);
|
const [otherError, setOtherError] = useState<Error | null>(null);
|
||||||
|
|
||||||
|
|
@ -382,7 +382,7 @@ export const RqbitWebUI = () => {
|
||||||
|
|
||||||
return <AppContext.Provider value={context}>
|
return <AppContext.Provider value={context}>
|
||||||
<div className='text-center'>
|
<div className='text-center'>
|
||||||
<h1 className="mt-3 mb-4">rqbit web 4.0.0-beta.0</h1>
|
<h1 className="mt-3 mb-4">{props.title}</h1>
|
||||||
<RootContent
|
<RootContent
|
||||||
closeableError={closeableError}
|
closeableError={closeableError}
|
||||||
otherError={otherError}
|
otherError={otherError}
|
||||||
|
|
@ -591,11 +591,14 @@ const FileSelectionModal = (props: {
|
||||||
const [uploading, setUploading] = useState(false);
|
const [uploading, setUploading] = useState(false);
|
||||||
const [uploadError, setUploadError] = useState<Error | null>(null);
|
const [uploadError, setUploadError] = useState<Error | null>(null);
|
||||||
const [unpopularTorrent, setUnpopularTorrent] = useState(false);
|
const [unpopularTorrent, setUnpopularTorrent] = useState(false);
|
||||||
|
const [outputFolder, setOutputFolder] = useState<string>('');
|
||||||
const ctx = useContext(AppContext);
|
const ctx = useContext(AppContext);
|
||||||
const API = useContext(APIContext);
|
const API = useContext(APIContext);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
console.log(listTorrentResponse);
|
||||||
setSelectedFiles(listTorrentResponse ? listTorrentResponse.details.files.map((_, id) => id) : []);
|
setSelectedFiles(listTorrentResponse ? listTorrentResponse.details.files.map((_, id) => id) : []);
|
||||||
|
setOutputFolder(listTorrentResponse?.output_folder || '');
|
||||||
}, [listTorrentResponse]);
|
}, [listTorrentResponse]);
|
||||||
|
|
||||||
const clear = () => {
|
const clear = () => {
|
||||||
|
|
@ -623,6 +626,7 @@ const FileSelectionModal = (props: {
|
||||||
overwrite: true,
|
overwrite: true,
|
||||||
only_files: selectedFiles,
|
only_files: selectedFiles,
|
||||||
initial_peers: initialPeers,
|
initial_peers: initialPeers,
|
||||||
|
output_folder: outputFolder,
|
||||||
};
|
};
|
||||||
if (unpopularTorrent) {
|
if (unpopularTorrent) {
|
||||||
opts.peer_opts = {
|
opts.peer_opts = {
|
||||||
|
|
@ -647,7 +651,7 @@ const FileSelectionModal = (props: {
|
||||||
return <ErrorComponent error={listTorrentError}></ErrorComponent>;
|
return <ErrorComponent error={listTorrentError}></ErrorComponent>;
|
||||||
} else if (listTorrentResponse) {
|
} else if (listTorrentResponse) {
|
||||||
return <Form>
|
return <Form>
|
||||||
<fieldset className='mb-5'>
|
<fieldset className='mb-4'>
|
||||||
<legend>Pick the files to download</legend>
|
<legend>Pick the files to download</legend>
|
||||||
{listTorrentResponse.details.files.map((file, index) => (
|
{listTorrentResponse.details.files.map((file, index) => (
|
||||||
<Form.Group key={index} controlId={`check-${index}`}>
|
<Form.Group key={index} controlId={`check-${index}`}>
|
||||||
|
|
@ -661,9 +665,16 @@ const FileSelectionModal = (props: {
|
||||||
))}
|
))}
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>Other options</legend>
|
<legend>Options</legend>
|
||||||
|
<Form.Group controlId='output-folder' className="mb-3">
|
||||||
<Form.Group controlId='unpopular-torrent'>
|
<Form.Label>Output folder</Form.Label>
|
||||||
|
<Form.Control
|
||||||
|
type="text"
|
||||||
|
value={outputFolder}
|
||||||
|
onChange={(e) => setOutputFolder(e.target.value)}
|
||||||
|
/>
|
||||||
|
</Form.Group>
|
||||||
|
<Form.Group controlId='unpopular-torrent' className="mb-3">
|
||||||
<Form.Check
|
<Form.Check
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
label="Increase timeouts"
|
label="Increase timeouts"
|
||||||
|
|
@ -673,7 +684,7 @@ const FileSelectionModal = (props: {
|
||||||
<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>
|
<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>
|
</Form.Group>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</Form >
|
</Form>
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ buffers = {path="../buffers", package="librqbit-buffers", version = "2.2.1"}
|
||||||
bencode = {path = "../bencode", default-features=false, package="librqbit-bencode", version="2.2.1"}
|
bencode = {path = "../bencode", default-features=false, package="librqbit-bencode", version="2.2.1"}
|
||||||
clone_to_owned = {path="../clone_to_owned", package="librqbit-clone-to-owned", version = "2.2.1"}
|
clone_to_owned = {path="../clone_to_owned", package="librqbit-clone-to-owned", version = "2.2.1"}
|
||||||
itertools = "0.12"
|
itertools = "0.12"
|
||||||
|
directories = "5"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
6
crates/librqbit_core/src/directories.rs
Normal file
6
crates/librqbit_core/src/directories.rs
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
use anyhow::Context;
|
||||||
|
|
||||||
|
pub fn get_configuration_directory(application: &str) -> anyhow::Result<directories::ProjectDirs> {
|
||||||
|
directories::ProjectDirs::from("com", "rqbit", application)
|
||||||
|
.with_context(|| format!("cannot determine project directory for com.rqbit.{application}"))
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
pub mod constants;
|
pub mod constants;
|
||||||
|
pub mod directories;
|
||||||
pub mod id20;
|
pub mod id20;
|
||||||
pub mod lengths;
|
pub mod lengths;
|
||||||
pub mod magnet;
|
pub mod magnet;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue