Made changes for Desktop app to work

This commit is contained in:
Igor Katson 2023-12-02 21:12:15 +00:00
parent fe04e17d63
commit 28c2db2a37
No known key found for this signature in database
GPG key ID: B4EC22B66D61A3F5
15 changed files with 74 additions and 38 deletions

2
Cargo.lock generated
View file

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

View file

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

View file

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

View file

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

View file

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 KiB

File diff suppressed because one or more lines are too long

View file

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

View file

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

View file

@ -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';
} }

View file

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

View file

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

View file

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

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

View file

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