Use bytes crate for zerocopy and memory re-use (#182)
* Use bytes. Not yet zerocopy everywhere but compiles * Actually zerocopy * Actually zerocopy * Not actually storing the torrent on disk now
This commit is contained in:
parent
3cc9e444b1
commit
c7ed475f54
20 changed files with 182 additions and 95 deletions
8
Cargo.lock
generated
8
Cargo.lock
generated
|
|
@ -1363,6 +1363,7 @@ name = "librqbit-bencode"
|
||||||
version = "2.2.3"
|
version = "2.2.3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"bytes",
|
||||||
"librqbit-buffers",
|
"librqbit-buffers",
|
||||||
"librqbit-clone-to-owned",
|
"librqbit-clone-to-owned",
|
||||||
"librqbit-sha1-wrapper",
|
"librqbit-sha1-wrapper",
|
||||||
|
|
@ -1373,6 +1374,7 @@ dependencies = [
|
||||||
name = "librqbit-buffers"
|
name = "librqbit-buffers"
|
||||||
version = "3.0.1"
|
version = "3.0.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
"librqbit-clone-to-owned",
|
"librqbit-clone-to-owned",
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
@ -1380,12 +1382,16 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "librqbit-clone-to-owned"
|
name = "librqbit-clone-to-owned"
|
||||||
version = "2.2.1"
|
version = "2.2.1"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "librqbit-core"
|
name = "librqbit-core"
|
||||||
version = "3.9.0"
|
version = "3.9.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"bytes",
|
||||||
"data-encoding",
|
"data-encoding",
|
||||||
"directories",
|
"directories",
|
||||||
"hex 0.4.3",
|
"hex 0.4.3",
|
||||||
|
|
@ -1409,6 +1415,7 @@ version = "5.0.4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"backoff",
|
"backoff",
|
||||||
|
"bytes",
|
||||||
"chrono",
|
"chrono",
|
||||||
"dashmap",
|
"dashmap",
|
||||||
"futures",
|
"futures",
|
||||||
|
|
@ -1437,6 +1444,7 @@ dependencies = [
|
||||||
"bincode",
|
"bincode",
|
||||||
"bitvec",
|
"bitvec",
|
||||||
"byteorder",
|
"byteorder",
|
||||||
|
"bytes",
|
||||||
"librqbit-bencode",
|
"librqbit-bencode",
|
||||||
"librqbit-buffers",
|
"librqbit-buffers",
|
||||||
"librqbit-clone-to-owned",
|
"librqbit-clone-to-owned",
|
||||||
|
|
|
||||||
|
|
@ -16,3 +16,4 @@ buffers = { path = "../buffers", package = "librqbit-buffers", version = "3.0.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" }
|
||||||
anyhow = "1"
|
anyhow = "1"
|
||||||
sha1w = { path = "../sha1w", default-features = false, package = "librqbit-sha1-wrapper", version = "3.0.0" }
|
sha1w = { path = "../sha1w", default-features = false, package = "librqbit-sha1-wrapper", version = "3.0.0" }
|
||||||
|
bytes = "1.7.1"
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
use std::{collections::HashMap, marker::PhantomData};
|
use std::{collections::HashMap, marker::PhantomData};
|
||||||
|
|
||||||
use buffers::{ByteBuf, ByteBufOwned};
|
use buffers::{ByteBuf, ByteBufOwned};
|
||||||
|
use bytes::Bytes;
|
||||||
use clone_to_owned::CloneToOwned;
|
use clone_to_owned::CloneToOwned;
|
||||||
use serde::Deserializer;
|
use serde::Deserializer;
|
||||||
|
|
||||||
|
|
@ -122,12 +123,12 @@ where
|
||||||
{
|
{
|
||||||
type Target = BencodeValue<<BufT as CloneToOwned>::Target>;
|
type Target = BencodeValue<<BufT as CloneToOwned>::Target>;
|
||||||
|
|
||||||
fn clone_to_owned(&self) -> Self::Target {
|
fn clone_to_owned(&self, within_buffer: Option<&Bytes>) -> Self::Target {
|
||||||
match self {
|
match self {
|
||||||
BencodeValue::Bytes(b) => BencodeValue::Bytes(b.clone_to_owned()),
|
BencodeValue::Bytes(b) => BencodeValue::Bytes(b.clone_to_owned(within_buffer)),
|
||||||
BencodeValue::Integer(i) => BencodeValue::Integer(*i),
|
BencodeValue::Integer(i) => BencodeValue::Integer(*i),
|
||||||
BencodeValue::List(l) => BencodeValue::List(l.clone_to_owned()),
|
BencodeValue::List(l) => BencodeValue::List(l.clone_to_owned(within_buffer)),
|
||||||
BencodeValue::Dict(d) => BencodeValue::Dict(d.clone_to_owned()),
|
BencodeValue::Dict(d) => BencodeValue::Dict(d.clone_to_owned(within_buffer)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,3 +12,4 @@ readme = "README.md"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
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" }
|
||||||
|
bytes = "1"
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,13 @@
|
||||||
//
|
//
|
||||||
// Not useful outside of librqbit.
|
// Not useful outside of librqbit.
|
||||||
|
|
||||||
|
use bytes::Bytes;
|
||||||
use serde::{Deserialize, Deserializer};
|
use serde::{Deserialize, Deserializer};
|
||||||
|
|
||||||
use clone_to_owned::CloneToOwned;
|
use clone_to_owned::CloneToOwned;
|
||||||
|
|
||||||
#[derive(Default, PartialEq, Eq, Hash, Clone, PartialOrd, Ord)]
|
#[derive(Default, PartialEq, Eq, Hash, Clone, PartialOrd, Ord)]
|
||||||
pub struct ByteBufOwned(pub Box<[u8]>);
|
pub struct ByteBufOwned(pub bytes::Bytes);
|
||||||
|
|
||||||
#[derive(Default, Deserialize, PartialEq, Eq, Hash, Clone, PartialOrd, Ord)]
|
#[derive(Default, Deserialize, PartialEq, Eq, Hash, Clone, PartialOrd, Ord)]
|
||||||
#[serde(transparent)]
|
#[serde(transparent)]
|
||||||
|
|
@ -90,15 +91,30 @@ impl std::fmt::Display for ByteBufOwned {
|
||||||
impl<'a> CloneToOwned for ByteBuf<'a> {
|
impl<'a> CloneToOwned for ByteBuf<'a> {
|
||||||
type Target = ByteBufOwned;
|
type Target = ByteBufOwned;
|
||||||
|
|
||||||
fn clone_to_owned(&self) -> Self::Target {
|
fn clone_to_owned(&self, within_buffer: Option<&Bytes>) -> Self::Target {
|
||||||
ByteBufOwned(self.as_slice().to_owned().into_boxed_slice())
|
// Try zero-copy from the provided buffer.
|
||||||
|
if let Some(within_buffer) = within_buffer {
|
||||||
|
let haystack = within_buffer.as_ptr() as usize;
|
||||||
|
let haystack_end = haystack + within_buffer.len();
|
||||||
|
let needle = self.0.as_ptr() as usize;
|
||||||
|
let needle_end = needle + self.0.len();
|
||||||
|
|
||||||
|
if needle >= haystack && needle_end <= haystack_end {
|
||||||
|
return ByteBufOwned(within_buffer.slice_ref(self.0.as_ref()));
|
||||||
|
} else {
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
panic!("bug: broken buffers! not inside within_buffer");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ByteBufOwned(Bytes::copy_from_slice(self.0))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CloneToOwned for ByteBufOwned {
|
impl CloneToOwned for ByteBufOwned {
|
||||||
type Target = ByteBufOwned;
|
type Target = ByteBufOwned;
|
||||||
|
|
||||||
fn clone_to_owned(&self) -> Self::Target {
|
fn clone_to_owned(&self, _within_buffer: Option<&Bytes>) -> Self::Target {
|
||||||
ByteBufOwned(self.0.clone())
|
ByteBufOwned(self.0.clone())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -139,13 +155,19 @@ impl<'a> From<&'a [u8]> for ByteBuf<'a> {
|
||||||
|
|
||||||
impl<'a> From<&'a [u8]> for ByteBufOwned {
|
impl<'a> From<&'a [u8]> for ByteBufOwned {
|
||||||
fn from(b: &'a [u8]) -> Self {
|
fn from(b: &'a [u8]) -> Self {
|
||||||
Self(b.into())
|
Self(b.to_owned().into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Vec<u8>> for ByteBufOwned {
|
impl From<Vec<u8>> for ByteBufOwned {
|
||||||
fn from(b: Vec<u8>) -> Self {
|
fn from(b: Vec<u8>) -> Self {
|
||||||
Self(b.into_boxed_slice())
|
Self(b.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Bytes> for ByteBufOwned {
|
||||||
|
fn from(b: Bytes) -> Self {
|
||||||
|
Self(b)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,3 +10,4 @@ readme = "README.md"
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
bytes = "1.7.1"
|
||||||
|
|
|
||||||
|
|
@ -6,12 +6,13 @@
|
||||||
// This lets us express types like TorrentMetaInfo<&[u8]> for zero-copy metadata about a bencode buffer in memory,
|
// This lets us express types like TorrentMetaInfo<&[u8]> for zero-copy metadata about a bencode buffer in memory,
|
||||||
// but to have one-line conversion for it into TorrentMetaInfo<Vec<u8>> so that we can store it later somewhere.
|
// but to have one-line conversion for it into TorrentMetaInfo<Vec<u8>> so that we can store it later somewhere.
|
||||||
|
|
||||||
|
use bytes::Bytes;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
pub trait CloneToOwned {
|
pub trait CloneToOwned {
|
||||||
type Target;
|
type Target;
|
||||||
|
|
||||||
fn clone_to_owned(&self) -> Self::Target;
|
fn clone_to_owned(&self, within_buffer: Option<&Bytes>) -> Self::Target;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> CloneToOwned for Option<T>
|
impl<T> CloneToOwned for Option<T>
|
||||||
|
|
@ -20,8 +21,8 @@ where
|
||||||
{
|
{
|
||||||
type Target = Option<<T as CloneToOwned>::Target>;
|
type Target = Option<<T as CloneToOwned>::Target>;
|
||||||
|
|
||||||
fn clone_to_owned(&self) -> Self::Target {
|
fn clone_to_owned(&self, within_buffer: Option<&Bytes>) -> Self::Target {
|
||||||
self.as_ref().map(|i| i.clone_to_owned())
|
self.as_ref().map(|i| i.clone_to_owned(within_buffer))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -31,15 +32,17 @@ where
|
||||||
{
|
{
|
||||||
type Target = Vec<<T as CloneToOwned>::Target>;
|
type Target = Vec<<T as CloneToOwned>::Target>;
|
||||||
|
|
||||||
fn clone_to_owned(&self) -> Self::Target {
|
fn clone_to_owned(&self, within_buffer: Option<&Bytes>) -> Self::Target {
|
||||||
self.iter().map(|i| i.clone_to_owned()).collect()
|
self.iter()
|
||||||
|
.map(|i| i.clone_to_owned(within_buffer))
|
||||||
|
.collect()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CloneToOwned for u8 {
|
impl CloneToOwned for u8 {
|
||||||
type Target = u8;
|
type Target = u8;
|
||||||
|
|
||||||
fn clone_to_owned(&self) -> Self::Target {
|
fn clone_to_owned(&self, _within_buffer: Option<&Bytes>) -> Self::Target {
|
||||||
*self
|
*self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -47,7 +50,7 @@ impl CloneToOwned for u8 {
|
||||||
impl CloneToOwned for u32 {
|
impl CloneToOwned for u32 {
|
||||||
type Target = u32;
|
type Target = u32;
|
||||||
|
|
||||||
fn clone_to_owned(&self) -> Self::Target {
|
fn clone_to_owned(&self, _within_buffer: Option<&Bytes>) -> Self::Target {
|
||||||
*self
|
*self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -60,10 +63,13 @@ where
|
||||||
{
|
{
|
||||||
type Target = HashMap<<K as CloneToOwned>::Target, <V as CloneToOwned>::Target>;
|
type Target = HashMap<<K as CloneToOwned>::Target, <V as CloneToOwned>::Target>;
|
||||||
|
|
||||||
fn clone_to_owned(&self) -> Self::Target {
|
fn clone_to_owned(&self, within_buffer: Option<&Bytes>) -> Self::Target {
|
||||||
let mut result = HashMap::with_capacity(self.capacity());
|
let mut result = HashMap::with_capacity(self.capacity());
|
||||||
for (k, v) in self {
|
for (k, v) in self {
|
||||||
result.insert(k.clone_to_owned(), v.clone_to_owned());
|
result.insert(
|
||||||
|
k.clone_to_owned(within_buffer),
|
||||||
|
v.clone_to_owned(within_buffer),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@ clone_to_owned = { path = "../clone_to_owned", package = "librqbit-clone-to-owne
|
||||||
librqbit-core = { path = "../librqbit_core", version = "3.9.0" }
|
librqbit-core = { path = "../librqbit_core", version = "3.9.0" }
|
||||||
chrono = { version = "0.4.31", features = ["serde"] }
|
chrono = { version = "0.4.31", features = ["serde"] }
|
||||||
tokio-util = "0.7.10"
|
tokio-util = "0.7.10"
|
||||||
|
bytes = "1.7.1"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tracing-subscriber = "0.3"
|
tracing-subscriber = "0.3"
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ use std::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use bencode::{ByteBuf, ByteBufOwned};
|
use bencode::{ByteBuf, ByteBufOwned};
|
||||||
|
use bytes::Bytes;
|
||||||
use clone_to_owned::CloneToOwned;
|
use clone_to_owned::CloneToOwned;
|
||||||
use librqbit_core::hash_id::Id20;
|
use librqbit_core::hash_id::Id20;
|
||||||
use serde::{
|
use serde::{
|
||||||
|
|
@ -73,10 +74,10 @@ where
|
||||||
{
|
{
|
||||||
type Target = ErrorDescription<<BufT as CloneToOwned>::Target>;
|
type Target = ErrorDescription<<BufT as CloneToOwned>::Target>;
|
||||||
|
|
||||||
fn clone_to_owned(&self) -> Self::Target {
|
fn clone_to_owned(&self, within_buffer: Option<&Bytes>) -> Self::Target {
|
||||||
ErrorDescription {
|
ErrorDescription {
|
||||||
code: self.code,
|
code: self.code,
|
||||||
description: self.description.clone_to_owned(),
|
description: self.description.clone_to_owned(within_buffer),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -382,7 +382,7 @@ impl<H: PeerConnectionHandler> PeerConnection<H> {
|
||||||
trace!("received: {:?}", &message);
|
trace!("received: {:?}", &message);
|
||||||
|
|
||||||
if let Message::Extended(ExtendedMessage::Handshake(h)) = &message {
|
if let Message::Extended(ExtendedMessage::Handshake(h)) = &message {
|
||||||
*extended_handshake_ref.write() = Some(h.clone_to_owned());
|
*extended_handshake_ref.write() = Some(h.clone_to_owned(None));
|
||||||
self.handler.on_extended_handshake(h)?;
|
self.handler.on_extended_handshake(h)?;
|
||||||
trace!("remembered extended handshake for future serializing");
|
trace!("remembered extended handshake for future serializing");
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ use std::{net::SocketAddr, sync::Arc};
|
||||||
|
|
||||||
use bencode::from_bytes;
|
use bencode::from_bytes;
|
||||||
use buffers::{ByteBuf, ByteBufOwned};
|
use buffers::{ByteBuf, ByteBufOwned};
|
||||||
|
use bytes::Bytes;
|
||||||
use librqbit_core::{
|
use librqbit_core::{
|
||||||
constants::CHUNK_SIZE,
|
constants::CHUNK_SIZE,
|
||||||
hash_id::Id20,
|
hash_id::Id20,
|
||||||
|
|
@ -178,9 +179,14 @@ impl PeerConnectionHandler for Handler {
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.record_piece(piece, &data, self.info_hash)?;
|
.record_piece(piece, &data, self.info_hash)?;
|
||||||
if piece_ready {
|
if piece_ready {
|
||||||
let buf = self.locked.write().take().unwrap().buffer;
|
let buf = Bytes::from(self.locked.write().take().unwrap().buffer);
|
||||||
let info = from_bytes::<TorrentMetaV1Info<ByteBufOwned>>(&buf);
|
let info = from_bytes::<TorrentMetaV1Info<ByteBuf>>(&buf)
|
||||||
let info = info.map(|i| (i, ByteBufOwned(buf.into_boxed_slice())));
|
.map(|i| {
|
||||||
|
use clone_to_owned::CloneToOwned;
|
||||||
|
i.clone_to_owned(Some(&buf))
|
||||||
|
})
|
||||||
|
.map(|i| (i, ByteBufOwned(buf)));
|
||||||
|
|
||||||
self.result_tx
|
self.result_tx
|
||||||
.lock()
|
.lock()
|
||||||
.take()
|
.take()
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,8 @@ use librqbit_core::{
|
||||||
peer_id::generate_peer_id,
|
peer_id::generate_peer_id,
|
||||||
spawn_utils::spawn_with_cancel,
|
spawn_utils::spawn_with_cancel,
|
||||||
torrent_metainfo::{
|
torrent_metainfo::{
|
||||||
torrent_from_bytes as bencode_torrent_from_bytes, TorrentMetaV1Info, TorrentMetaV1Owned,
|
torrent_from_bytes as bencode_torrent_from_bytes, TorrentMetaV1Borrowed, TorrentMetaV1Info,
|
||||||
|
TorrentMetaV1Owned,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use parking_lot::RwLock;
|
use parking_lot::RwLock;
|
||||||
|
|
@ -61,7 +62,7 @@ pub const SUPPORTED_SCHEMES: [&str; 3] = ["http:", "https:", "magnet:"];
|
||||||
|
|
||||||
pub type TorrentId = usize;
|
pub type TorrentId = usize;
|
||||||
|
|
||||||
fn torrent_from_bytes(bytes: &[u8]) -> anyhow::Result<TorrentMetaV1Owned> {
|
fn torrent_from_bytes(bytes: &[u8]) -> anyhow::Result<TorrentMetaV1Borrowed> {
|
||||||
debug!(
|
debug!(
|
||||||
"all fields in torrent: {:#?}",
|
"all fields in torrent: {:#?}",
|
||||||
bencode::dyn_from_bytes::<ByteBuf>(bytes)
|
bencode::dyn_from_bytes::<ByteBuf>(bytes)
|
||||||
|
|
@ -120,7 +121,11 @@ impl SessionDatabase {
|
||||||
.map(|u| u.to_string())
|
.map(|u| u.to_string())
|
||||||
.collect(),
|
.collect(),
|
||||||
info_hash: torrent.info_hash().as_string(),
|
info_hash: torrent.info_hash().as_string(),
|
||||||
torrent_bytes: torrent.info.torrent_bytes.clone(),
|
// TODO: this could take up too much space / time / resources to write on interval.
|
||||||
|
// Store this outside the JSON file
|
||||||
|
//
|
||||||
|
// torrent_bytes: torrent.info.torrent_bytes.clone(),
|
||||||
|
torrent_bytes: Bytes::new(),
|
||||||
info: torrent.info().info.clone(),
|
info: torrent.info().info.clone(),
|
||||||
only_files: torrent.only_files().clone(),
|
only_files: torrent.only_files().clone(),
|
||||||
is_paused: torrent
|
is_paused: torrent
|
||||||
|
|
@ -251,8 +256,10 @@ async fn torrent_from_url(
|
||||||
.await
|
.await
|
||||||
.with_context(|| format!("error reading response body from {url}"))?;
|
.with_context(|| format!("error reading response body from {url}"))?;
|
||||||
Ok((
|
Ok((
|
||||||
torrent_from_bytes(&b).context("error decoding torrent")?,
|
torrent_from_bytes(&b)
|
||||||
b.to_vec().into(),
|
.context("error decoding torrent")?
|
||||||
|
.clone_to_owned(Some(&b)),
|
||||||
|
b.into(),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -415,7 +422,7 @@ pub fn read_local_file_including_stdin(filename: &str) -> anyhow::Result<Vec<u8>
|
||||||
pub enum AddTorrent<'a> {
|
pub enum AddTorrent<'a> {
|
||||||
Url(Cow<'a, str>),
|
Url(Cow<'a, str>),
|
||||||
TorrentFileBytes(Cow<'a, [u8]>),
|
TorrentFileBytes(Cow<'a, [u8]>),
|
||||||
TorrentInfo(Box<TorrentMetaV1Owned>),
|
TorrentInfo(Box<TorrentMetaV1Owned>, Bytes),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> AddTorrent<'a> {
|
impl<'a> AddTorrent<'a> {
|
||||||
|
|
@ -448,7 +455,7 @@ impl<'a> AddTorrent<'a> {
|
||||||
match self {
|
match self {
|
||||||
Self::Url(s) => s.into_owned().into_bytes(),
|
Self::Url(s) => s.into_owned().into_bytes(),
|
||||||
Self::TorrentFileBytes(b) => b.into_owned(),
|
Self::TorrentFileBytes(b) => b.into_owned(),
|
||||||
Self::TorrentInfo(_) => unimplemented!(),
|
Self::TorrentInfo(..) => unimplemented!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -752,7 +759,7 @@ impl Session {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let handshake = h.clone_to_owned();
|
let handshake = h.clone_to_owned(None);
|
||||||
|
|
||||||
return Ok((
|
return Ok((
|
||||||
live,
|
live,
|
||||||
|
|
@ -877,24 +884,42 @@ impl Session {
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|t| ByteBufOwned::from(t.into_bytes()))
|
.map(|t| ByteBufOwned::from(t.into_bytes()))
|
||||||
.collect();
|
.collect();
|
||||||
let info = TorrentMetaV1Owned {
|
|
||||||
announce: trackers.first().cloned(),
|
let torrent_bytes = storrent.torrent_bytes;
|
||||||
announce_list: vec![trackers],
|
|
||||||
info: storrent.info,
|
let info = if !torrent_bytes.is_empty() {
|
||||||
comment: None,
|
torrent_from_bytes(&torrent_bytes)
|
||||||
created_by: None,
|
.map(|t| t.clone_to_owned(Some(&torrent_bytes)))
|
||||||
encoding: None,
|
.ok()
|
||||||
publisher: None,
|
} else {
|
||||||
publisher_url: None,
|
None
|
||||||
creation_date: None,
|
|
||||||
info_hash: Id20::from_str(&storrent.info_hash)?,
|
|
||||||
};
|
};
|
||||||
|
let info = match info {
|
||||||
|
Some(info) => info,
|
||||||
|
None => {
|
||||||
|
let info_hash = Id20::from_str(&storrent.info_hash)?;
|
||||||
|
debug!(?info_hash, "torrent added before 6.1.0, need to readd");
|
||||||
|
TorrentMetaV1Owned {
|
||||||
|
announce: trackers.first().cloned(),
|
||||||
|
announce_list: vec![trackers],
|
||||||
|
info: storrent.info,
|
||||||
|
comment: None,
|
||||||
|
created_by: None,
|
||||||
|
encoding: None,
|
||||||
|
publisher: None,
|
||||||
|
publisher_url: None,
|
||||||
|
creation_date: None,
|
||||||
|
info_hash,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
futures.push({
|
futures.push({
|
||||||
let session = self.clone();
|
let session = self.clone();
|
||||||
async move {
|
async move {
|
||||||
session
|
session
|
||||||
.add_torrent(
|
.add_torrent(
|
||||||
AddTorrent::TorrentInfo(Box::new(info)),
|
AddTorrent::TorrentInfo(Box::new(info), torrent_bytes),
|
||||||
Some(AddTorrentOptions {
|
Some(AddTorrentOptions {
|
||||||
paused: storrent.is_paused,
|
paused: storrent.is_paused,
|
||||||
output_folder: Some(
|
output_folder: Some(
|
||||||
|
|
@ -1041,14 +1066,19 @@ impl Session {
|
||||||
url
|
url
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
AddTorrent::TorrentFileBytes(bytes) => (
|
AddTorrent::TorrentFileBytes(bytes) => {
|
||||||
torrent_from_bytes(&bytes).context("error decoding torrent")?,
|
let bytes = match bytes {
|
||||||
ByteBufOwned::from(bytes.into_owned()),
|
Cow::Borrowed(b) => ::bytes::Bytes::copy_from_slice(b),
|
||||||
),
|
Cow::Owned(v) => ::bytes::Bytes::from(v),
|
||||||
AddTorrent::TorrentInfo(t) => {
|
};
|
||||||
// TODO: this is lossy, as we don't store the bytes.
|
(
|
||||||
(*t, ByteBufOwned(Vec::new().into_boxed_slice()))
|
torrent_from_bytes(&bytes)
|
||||||
|
.map(|t| t.clone_to_owned(Some(&bytes)))
|
||||||
|
.context("error decoding torrent")?,
|
||||||
|
ByteBufOwned(bytes),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
AddTorrent::TorrentInfo(t, bytes) => (*t, bytes.into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
let trackers = torrent
|
let trackers = torrent
|
||||||
|
|
@ -1081,7 +1111,7 @@ impl Session {
|
||||||
InternalAddResult {
|
InternalAddResult {
|
||||||
info_hash: torrent.info_hash,
|
info_hash: torrent.info_hash,
|
||||||
info: torrent.info,
|
info: torrent.info,
|
||||||
torrent_bytes: Bytes::from(bytes.0),
|
torrent_bytes: bytes.0,
|
||||||
trackers,
|
trackers,
|
||||||
peer_rx,
|
peer_rx,
|
||||||
initial_peers: opts
|
initial_peers: opts
|
||||||
|
|
|
||||||
|
|
@ -779,7 +779,7 @@ impl<'a> PeerConnectionHandler for &'a PeerHandler {
|
||||||
.context("on_download_request")?;
|
.context("on_download_request")?;
|
||||||
}
|
}
|
||||||
Message::Bitfield(b) => self
|
Message::Bitfield(b) => self
|
||||||
.on_bitfield(b.clone_to_owned())
|
.on_bitfield(b.clone_to_owned(None))
|
||||||
.context("on_bitfield")?,
|
.context("on_bitfield")?,
|
||||||
Message::Choke => self.on_i_am_choked(),
|
Message::Choke => self.on_i_am_choked(),
|
||||||
Message::Unchoke => self.on_i_am_unchoked(),
|
Message::Unchoke => self.on_i_am_unchoked(),
|
||||||
|
|
@ -1127,7 +1127,7 @@ impl PeerHandler {
|
||||||
}
|
}
|
||||||
self.state
|
self.state
|
||||||
.peers
|
.peers
|
||||||
.update_bitfield_from_vec(self.addr, bitfield.0);
|
.update_bitfield_from_vec(self.addr, bitfield.0.to_vec().into_boxed_slice());
|
||||||
self.on_bitfield_notify.notify_waiters();
|
self.on_bitfield_notify.notify_waiters();
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
@ -1480,7 +1480,7 @@ impl PeerHandler {
|
||||||
let state = self.state.clone();
|
let state = self.state.clone();
|
||||||
let addr = self.addr;
|
let addr = self.addr;
|
||||||
let counters = self.counters.clone();
|
let counters = self.counters.clone();
|
||||||
let piece = piece.clone_to_owned();
|
let piece = piece.clone_to_owned(None);
|
||||||
let tx = self.tx.clone();
|
let tx = self.tx.clone();
|
||||||
|
|
||||||
let span = tracing::error_span!("deferred_write");
|
let span = tracing::error_span!("deferred_write");
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ itertools = "0.12"
|
||||||
directories = "5"
|
directories = "5"
|
||||||
tokio-util = "0.7.10"
|
tokio-util = "0.7.10"
|
||||||
data-encoding = "2.6.0"
|
data-encoding = "2.6.0"
|
||||||
|
bytes = "1.7.1"
|
||||||
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
use std::{iter::once, path::PathBuf};
|
|
||||||
|
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use bencode::BencodeDeserializer;
|
use bencode::BencodeDeserializer;
|
||||||
use buffers::{ByteBuf, ByteBufOwned};
|
use buffers::{ByteBuf, ByteBufOwned};
|
||||||
|
use bytes::Bytes;
|
||||||
use clone_to_owned::CloneToOwned;
|
use clone_to_owned::CloneToOwned;
|
||||||
use itertools::Either;
|
use itertools::Either;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::{iter::once, path::PathBuf};
|
||||||
|
|
||||||
use crate::{hash_id::Id20, lengths::Lengths};
|
use crate::{hash_id::Id20, lengths::Lengths};
|
||||||
|
|
||||||
|
|
@ -274,10 +274,10 @@ where
|
||||||
{
|
{
|
||||||
type Target = TorrentMetaV1File<<BufType as CloneToOwned>::Target>;
|
type Target = TorrentMetaV1File<<BufType as CloneToOwned>::Target>;
|
||||||
|
|
||||||
fn clone_to_owned(&self) -> Self::Target {
|
fn clone_to_owned(&self, within_buffer: Option<&Bytes>) -> Self::Target {
|
||||||
TorrentMetaV1File {
|
TorrentMetaV1File {
|
||||||
length: self.length,
|
length: self.length,
|
||||||
path: self.path.clone_to_owned(),
|
path: self.path.clone_to_owned(within_buffer),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -288,14 +288,14 @@ where
|
||||||
{
|
{
|
||||||
type Target = TorrentMetaV1Info<<BufType as CloneToOwned>::Target>;
|
type Target = TorrentMetaV1Info<<BufType as CloneToOwned>::Target>;
|
||||||
|
|
||||||
fn clone_to_owned(&self) -> Self::Target {
|
fn clone_to_owned(&self, within_buffer: Option<&Bytes>) -> Self::Target {
|
||||||
TorrentMetaV1Info {
|
TorrentMetaV1Info {
|
||||||
name: self.name.clone_to_owned(),
|
name: self.name.clone_to_owned(within_buffer),
|
||||||
pieces: self.pieces.clone_to_owned(),
|
pieces: self.pieces.clone_to_owned(within_buffer),
|
||||||
piece_length: self.piece_length,
|
piece_length: self.piece_length,
|
||||||
length: self.length,
|
length: self.length,
|
||||||
md5sum: self.md5sum.clone_to_owned(),
|
md5sum: self.md5sum.clone_to_owned(within_buffer),
|
||||||
files: self.files.clone_to_owned(),
|
files: self.files.clone_to_owned(within_buffer),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -306,16 +306,16 @@ where
|
||||||
{
|
{
|
||||||
type Target = TorrentMetaV1<<BufType as CloneToOwned>::Target>;
|
type Target = TorrentMetaV1<<BufType as CloneToOwned>::Target>;
|
||||||
|
|
||||||
fn clone_to_owned(&self) -> Self::Target {
|
fn clone_to_owned(&self, within_buffer: Option<&Bytes>) -> Self::Target {
|
||||||
TorrentMetaV1 {
|
TorrentMetaV1 {
|
||||||
announce: self.announce.clone_to_owned(),
|
announce: self.announce.clone_to_owned(within_buffer),
|
||||||
announce_list: self.announce_list.clone_to_owned(),
|
announce_list: self.announce_list.clone_to_owned(within_buffer),
|
||||||
info: self.info.clone_to_owned(),
|
info: self.info.clone_to_owned(within_buffer),
|
||||||
comment: self.comment.clone_to_owned(),
|
comment: self.comment.clone_to_owned(within_buffer),
|
||||||
created_by: self.created_by.clone_to_owned(),
|
created_by: self.created_by.clone_to_owned(within_buffer),
|
||||||
encoding: self.encoding.clone_to_owned(),
|
encoding: self.encoding.clone_to_owned(within_buffer),
|
||||||
publisher: self.publisher.clone_to_owned(),
|
publisher: self.publisher.clone_to_owned(within_buffer),
|
||||||
publisher_url: self.publisher_url.clone_to_owned(),
|
publisher_url: self.publisher_url.clone_to_owned(within_buffer),
|
||||||
creation_date: self.creation_date,
|
creation_date: self.creation_date,
|
||||||
info_hash: self.info_hash,
|
info_hash: self.info_hash,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,3 +20,4 @@ clone_to_owned = { path = "../clone_to_owned", package = "librqbit-clone-to-owne
|
||||||
librqbit-core = { path = "../librqbit_core", version = "3.9.0" }
|
librqbit-core = { path = "../librqbit_core", version = "3.9.0" }
|
||||||
bitvec = "1"
|
bitvec = "1"
|
||||||
anyhow = "1"
|
anyhow = "1"
|
||||||
|
bytes = "1.7.1"
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ use std::{
|
||||||
use buffers::ByteBuf;
|
use buffers::ByteBuf;
|
||||||
use byteorder::ByteOrder;
|
use byteorder::ByteOrder;
|
||||||
use byteorder::BE;
|
use byteorder::BE;
|
||||||
|
use bytes::Bytes;
|
||||||
use clone_to_owned::CloneToOwned;
|
use clone_to_owned::CloneToOwned;
|
||||||
use serde::{Deserialize, Deserializer, Serialize};
|
use serde::{Deserialize, Deserializer, Serialize};
|
||||||
|
|
||||||
|
|
@ -75,14 +76,14 @@ where
|
||||||
{
|
{
|
||||||
type Target = ExtendedHandshake<<ByteBuf as CloneToOwned>::Target>;
|
type Target = ExtendedHandshake<<ByteBuf as CloneToOwned>::Target>;
|
||||||
|
|
||||||
fn clone_to_owned(&self) -> Self::Target {
|
fn clone_to_owned(&self, within_buffer: Option<&Bytes>) -> Self::Target {
|
||||||
ExtendedHandshake {
|
ExtendedHandshake {
|
||||||
m: self.m.clone_to_owned(),
|
m: self.m.clone_to_owned(within_buffer),
|
||||||
p: self.p,
|
p: self.p,
|
||||||
v: self.v.clone_to_owned(),
|
v: self.v.clone_to_owned(within_buffer),
|
||||||
yourip: self.yourip,
|
yourip: self.yourip,
|
||||||
ipv6: self.ipv6.clone_to_owned(),
|
ipv6: self.ipv6.clone_to_owned(within_buffer),
|
||||||
ipv4: self.ipv4.clone_to_owned(),
|
ipv4: self.ipv4.clone_to_owned(within_buffer),
|
||||||
reqq: self.reqq,
|
reqq: self.reqq,
|
||||||
metadata_size: self.metadata_size,
|
metadata_size: self.metadata_size,
|
||||||
complete_ago: self.complete_ago,
|
complete_ago: self.complete_ago,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
use bencode::bencode_serialize_to_writer;
|
use bencode::bencode_serialize_to_writer;
|
||||||
use bencode::from_bytes;
|
use bencode::from_bytes;
|
||||||
use bencode::BencodeValue;
|
use bencode::BencodeValue;
|
||||||
|
use bytes::Bytes;
|
||||||
use clone_to_owned::CloneToOwned;
|
use clone_to_owned::CloneToOwned;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
|
@ -27,11 +28,15 @@ where
|
||||||
{
|
{
|
||||||
type Target = ExtendedMessage<<ByteBuf as CloneToOwned>::Target>;
|
type Target = ExtendedMessage<<ByteBuf as CloneToOwned>::Target>;
|
||||||
|
|
||||||
fn clone_to_owned(&self) -> Self::Target {
|
fn clone_to_owned(&self, within_buffer: Option<&Bytes>) -> Self::Target {
|
||||||
match self {
|
match self {
|
||||||
ExtendedMessage::Handshake(h) => ExtendedMessage::Handshake(h.clone_to_owned()),
|
ExtendedMessage::Handshake(h) => {
|
||||||
ExtendedMessage::Dyn(u, d) => ExtendedMessage::Dyn(*u, d.clone_to_owned()),
|
ExtendedMessage::Handshake(h.clone_to_owned(within_buffer))
|
||||||
ExtendedMessage::UtMetadata(m) => ExtendedMessage::UtMetadata(m.clone_to_owned()),
|
}
|
||||||
|
ExtendedMessage::Dyn(u, d) => ExtendedMessage::Dyn(*u, d.clone_to_owned(within_buffer)),
|
||||||
|
ExtendedMessage::UtMetadata(m) => {
|
||||||
|
ExtendedMessage::UtMetadata(m.clone_to_owned(within_buffer))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
use std::io::Write;
|
|
||||||
|
|
||||||
use bencode::bencode_serialize_to_writer;
|
use bencode::bencode_serialize_to_writer;
|
||||||
use bencode::BencodeDeserializer;
|
use bencode::BencodeDeserializer;
|
||||||
|
use bytes::Bytes;
|
||||||
use clone_to_owned::CloneToOwned;
|
use clone_to_owned::CloneToOwned;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
use std::io::Write;
|
||||||
|
|
||||||
use crate::MessageDeserializeError;
|
use crate::MessageDeserializeError;
|
||||||
|
|
||||||
|
|
@ -22,7 +22,7 @@ pub enum UtMetadata<ByteBuf> {
|
||||||
impl<ByteBuf: CloneToOwned> CloneToOwned for UtMetadata<ByteBuf> {
|
impl<ByteBuf: CloneToOwned> CloneToOwned for UtMetadata<ByteBuf> {
|
||||||
type Target = UtMetadata<<ByteBuf as CloneToOwned>::Target>;
|
type Target = UtMetadata<<ByteBuf as CloneToOwned>::Target>;
|
||||||
|
|
||||||
fn clone_to_owned(&self) -> Self::Target {
|
fn clone_to_owned(&self, within_buffer: Option<&Bytes>) -> Self::Target {
|
||||||
match self {
|
match self {
|
||||||
UtMetadata::Request(req) => UtMetadata::Request(*req),
|
UtMetadata::Request(req) => UtMetadata::Request(*req),
|
||||||
UtMetadata::Data {
|
UtMetadata::Data {
|
||||||
|
|
@ -32,7 +32,7 @@ impl<ByteBuf: CloneToOwned> CloneToOwned for UtMetadata<ByteBuf> {
|
||||||
} => UtMetadata::Data {
|
} => UtMetadata::Data {
|
||||||
piece: *piece,
|
piece: *piece,
|
||||||
total_size: *total_size,
|
total_size: *total_size,
|
||||||
data: data.clone_to_owned(),
|
data: data.clone_to_owned(within_buffer),
|
||||||
},
|
},
|
||||||
UtMetadata::Reject(piece) => UtMetadata::Reject(*piece),
|
UtMetadata::Reject(piece) => UtMetadata::Reject(*piece),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ pub mod extended;
|
||||||
use bincode::Options;
|
use bincode::Options;
|
||||||
use buffers::{ByteBuf, ByteBufOwned};
|
use buffers::{ByteBuf, ByteBufOwned};
|
||||||
use byteorder::{ByteOrder, BE};
|
use byteorder::{ByteOrder, BE};
|
||||||
|
use bytes::Bytes;
|
||||||
use clone_to_owned::CloneToOwned;
|
use clone_to_owned::CloneToOwned;
|
||||||
use librqbit_core::{constants::CHUNK_SIZE, hash_id::Id20, lengths::ChunkInfo};
|
use librqbit_core::{constants::CHUNK_SIZE, hash_id::Id20, lengths::ChunkInfo};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
@ -84,11 +85,11 @@ pub struct Piece<B> {
|
||||||
impl<B: CloneToOwned> CloneToOwned for Piece<B> {
|
impl<B: CloneToOwned> CloneToOwned for Piece<B> {
|
||||||
type Target = Piece<B::Target>;
|
type Target = Piece<B::Target>;
|
||||||
|
|
||||||
fn clone_to_owned(&self) -> Self::Target {
|
fn clone_to_owned(&self, within_buffer: Option<&Bytes>) -> Self::Target {
|
||||||
Piece {
|
Piece {
|
||||||
index: self.index,
|
index: self.index,
|
||||||
begin: self.begin,
|
begin: self.begin,
|
||||||
block: self.block.clone_to_owned(),
|
block: self.block.clone_to_owned(within_buffer),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -211,23 +212,23 @@ where
|
||||||
{
|
{
|
||||||
type Target = Message<<ByteBuf as CloneToOwned>::Target>;
|
type Target = Message<<ByteBuf as CloneToOwned>::Target>;
|
||||||
|
|
||||||
fn clone_to_owned(&self) -> Self::Target {
|
fn clone_to_owned(&self, within_buffer: Option<&Bytes>) -> Self::Target {
|
||||||
match self {
|
match self {
|
||||||
Message::Request(req) => Message::Request(*req),
|
Message::Request(req) => Message::Request(*req),
|
||||||
Message::Cancel(req) => Message::Cancel(*req),
|
Message::Cancel(req) => Message::Cancel(*req),
|
||||||
Message::Bitfield(b) => Message::Bitfield(b.clone_to_owned()),
|
Message::Bitfield(b) => Message::Bitfield(b.clone_to_owned(within_buffer)),
|
||||||
Message::Choke => Message::Choke,
|
Message::Choke => Message::Choke,
|
||||||
Message::Unchoke => Message::Unchoke,
|
Message::Unchoke => Message::Unchoke,
|
||||||
Message::Interested => Message::Interested,
|
Message::Interested => Message::Interested,
|
||||||
Message::Piece(piece) => Message::Piece(Piece {
|
Message::Piece(piece) => Message::Piece(Piece {
|
||||||
index: piece.index,
|
index: piece.index,
|
||||||
begin: piece.begin,
|
begin: piece.begin,
|
||||||
block: piece.block.clone_to_owned(),
|
block: piece.block.clone_to_owned(within_buffer),
|
||||||
}),
|
}),
|
||||||
Message::KeepAlive => Message::KeepAlive,
|
Message::KeepAlive => Message::KeepAlive,
|
||||||
Message::Have(v) => Message::Have(*v),
|
Message::Have(v) => Message::Have(*v),
|
||||||
Message::NotInterested => Message::NotInterested,
|
Message::NotInterested => Message::NotInterested,
|
||||||
Message::Extended(e) => Message::Extended(e.clone_to_owned()),
|
Message::Extended(e) => Message::Extended(e.clone_to_owned(within_buffer)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -585,9 +586,9 @@ where
|
||||||
{
|
{
|
||||||
type Target = Handshake<<B as CloneToOwned>::Target>;
|
type Target = Handshake<<B as CloneToOwned>::Target>;
|
||||||
|
|
||||||
fn clone_to_owned(&self) -> Self::Target {
|
fn clone_to_owned(&self, within_buffer: Option<&Bytes>) -> Self::Target {
|
||||||
Handshake {
|
Handshake {
|
||||||
pstr: self.pstr.clone_to_owned(),
|
pstr: self.pstr.clone_to_owned(within_buffer),
|
||||||
reserved: self.reserved,
|
reserved: self.reserved,
|
||||||
info_hash: self.info_hash,
|
info_hash: self.info_hash,
|
||||||
peer_id: self.peer_id,
|
peer_id: self.peer_id,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue