Storages
This commit is contained in:
parent
609f9d92ae
commit
1b49257019
13 changed files with 499 additions and 230 deletions
|
|
@ -5,7 +5,10 @@ use librqbit_core::lengths::{ChunkInfo, Lengths, ValidPieceIndex};
|
||||||
use peer_binary_protocol::Piece;
|
use peer_binary_protocol::Piece;
|
||||||
use tracing::{debug, trace};
|
use tracing::{debug, trace};
|
||||||
|
|
||||||
use crate::type_aliases::{FilePriorities, OpenedFiles, BF};
|
use crate::{
|
||||||
|
file_info::FileInfo,
|
||||||
|
type_aliases::{FileInfos, FilePriorities, BF},
|
||||||
|
};
|
||||||
|
|
||||||
pub struct ChunkTracker {
|
pub struct ChunkTracker {
|
||||||
// This forms the basis of a "queue" to pull from.
|
// This forms the basis of a "queue" to pull from.
|
||||||
|
|
@ -29,6 +32,9 @@ pub struct ChunkTracker {
|
||||||
// was called.
|
// was called.
|
||||||
selected: BF,
|
selected: BF,
|
||||||
|
|
||||||
|
// How many bytes do we have per each file.
|
||||||
|
per_file_bytes: Vec<u64>,
|
||||||
|
|
||||||
lengths: Lengths,
|
lengths: Lengths,
|
||||||
|
|
||||||
// Quick to retrieve stats, that MUST be in sync with the BFs
|
// Quick to retrieve stats, that MUST be in sync with the BFs
|
||||||
|
|
@ -128,6 +134,7 @@ impl ChunkTracker {
|
||||||
// Selected pieces are the ones the user has selected
|
// Selected pieces are the ones the user has selected
|
||||||
selected_pieces: BF,
|
selected_pieces: BF,
|
||||||
lengths: Lengths,
|
lengths: Lengths,
|
||||||
|
file_infos: &FileInfos,
|
||||||
) -> anyhow::Result<Self> {
|
) -> anyhow::Result<Self> {
|
||||||
let needed_pieces = compute_queued_pieces(&have_pieces, &selected_pieces)
|
let needed_pieces = compute_queued_pieces(&have_pieces, &selected_pieces)
|
||||||
.context("error computing needed pieces")?;
|
.context("error computing needed pieces")?;
|
||||||
|
|
@ -143,11 +150,44 @@ impl ChunkTracker {
|
||||||
lengths,
|
lengths,
|
||||||
have: have_pieces,
|
have: have_pieces,
|
||||||
hns: HaveNeededSelected::default(),
|
hns: HaveNeededSelected::default(),
|
||||||
|
per_file_bytes: vec![0; file_infos.len()],
|
||||||
};
|
};
|
||||||
|
ct.recalculate_per_file_bytes(file_infos);
|
||||||
ct.hns = ct.calc_hns();
|
ct.hns = ct.calc_hns();
|
||||||
Ok(ct)
|
Ok(ct)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn recalculate_per_file_bytes(&mut self, file_infos: &FileInfos) {
|
||||||
|
for (slot, fi) in self.per_file_bytes.iter_mut().zip(file_infos.iter()) {
|
||||||
|
*slot = fi
|
||||||
|
.piece_range
|
||||||
|
.clone()
|
||||||
|
.filter(|p| self.have[*p as usize])
|
||||||
|
.map(|id| {
|
||||||
|
self.lengths
|
||||||
|
.size_of_piece_in_file(id, fi.offset_in_torrent, fi.len)
|
||||||
|
})
|
||||||
|
.sum();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_empty(lengths: Lengths, file_infos: &FileInfos) -> anyhow::Result<Self> {
|
||||||
|
let have = BF::from_boxed_slice(vec![0; lengths.piece_bitfield_bytes()].into_boxed_slice());
|
||||||
|
let selected = have.clone();
|
||||||
|
let chunk_status =
|
||||||
|
BF::from_boxed_slice(vec![0; lengths.chunk_bitfield_bytes()].into_boxed_slice());
|
||||||
|
let queued = have.clone();
|
||||||
|
Ok(Self {
|
||||||
|
queue_pieces: queued,
|
||||||
|
chunk_status,
|
||||||
|
have,
|
||||||
|
selected,
|
||||||
|
lengths,
|
||||||
|
per_file_bytes: vec![0; file_infos.len()],
|
||||||
|
hns: Default::default(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_lengths(&self) -> &Lengths {
|
pub fn get_lengths(&self) -> &Lengths {
|
||||||
&self.lengths
|
&self.lengths
|
||||||
}
|
}
|
||||||
|
|
@ -182,12 +222,12 @@ impl ChunkTracker {
|
||||||
pub(crate) fn iter_queued_pieces<'a>(
|
pub(crate) fn iter_queued_pieces<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
file_priorities: &'a FilePriorities,
|
file_priorities: &'a FilePriorities,
|
||||||
opened_files: &'a OpenedFiles,
|
opened_files: &'a FileInfos,
|
||||||
) -> impl Iterator<Item = ValidPieceIndex> + 'a {
|
) -> impl Iterator<Item = ValidPieceIndex> + 'a {
|
||||||
file_priorities
|
file_priorities
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|p| opened_files.get(*p))
|
.filter_map(|p| opened_files.get(*p))
|
||||||
.filter(|f| !f.approx_is_finished())
|
// .filter(|f| !f.approx_is_finished())
|
||||||
.flat_map(|f| f.iter_piece_priorities())
|
.flat_map(|f| f.iter_piece_priorities())
|
||||||
.filter(|id| self.queue_pieces[*id])
|
.filter(|id| self.queue_pieces[*id])
|
||||||
.filter_map(|id| id.try_into().ok())
|
.filter_map(|id| id.try_into().ok())
|
||||||
|
|
@ -226,7 +266,12 @@ impl ChunkTracker {
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
debug!("remarking piece={} as broken", index);
|
self.mark_piece_broken(index)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mark_piece_broken(&mut self, index: ValidPieceIndex) {
|
||||||
|
debug!("marking piece={} as broken", index);
|
||||||
|
self.have.set(index.get() as usize, false);
|
||||||
self.queue_pieces.set(index.get() as usize, true);
|
self.queue_pieces.set(index.get() as usize, true);
|
||||||
if let Some(s) = self.chunk_status.get_mut(self.lengths.chunk_range(index)) {
|
if let Some(s) = self.chunk_status.get_mut(self.lengths.chunk_range(index)) {
|
||||||
s.fill(false);
|
s.fill(false);
|
||||||
|
|
@ -372,6 +417,37 @@ impl ChunkTracker {
|
||||||
pub(crate) fn get_selected_pieces(&self) -> &BF {
|
pub(crate) fn get_selected_pieces(&self) -> &BF {
|
||||||
&self.selected
|
&self.selected
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_file_finished(&self, file_info: &FileInfo) -> bool {
|
||||||
|
self.have
|
||||||
|
.get(file_info.piece_range_usize())
|
||||||
|
.map(|r| r.all())
|
||||||
|
.unwrap_or(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn is_finished(&self) -> bool {
|
||||||
|
self.get_hns().finished()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn per_file_have_bytes(&self) -> &[u64] {
|
||||||
|
&self.per_file_bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns remaining bytes
|
||||||
|
pub fn update_file_have_on_piece_completed(
|
||||||
|
&mut self,
|
||||||
|
piece_id: ValidPieceIndex,
|
||||||
|
file_id: usize,
|
||||||
|
file_info: &FileInfo,
|
||||||
|
) -> u64 {
|
||||||
|
let diff_have = self.lengths.size_of_piece_in_file(
|
||||||
|
piece_id.get(),
|
||||||
|
file_info.offset_in_torrent,
|
||||||
|
file_info.len,
|
||||||
|
);
|
||||||
|
self.per_file_bytes[file_id] += diff_have;
|
||||||
|
file_info.len.saturating_sub(self.per_file_bytes[file_id])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
@ -502,7 +578,13 @@ mod tests {
|
||||||
let initial_selected = BF::from_boxed_slice(vec![u8::MAX; bf_len].into_boxed_slice());
|
let initial_selected = BF::from_boxed_slice(vec![u8::MAX; bf_len].into_boxed_slice());
|
||||||
|
|
||||||
// Initially, we need all files and all pieces.
|
// Initially, we need all files and all pieces.
|
||||||
let mut ct = ChunkTracker::new(initial_have.clone(), initial_selected.clone(), l).unwrap();
|
let mut ct = ChunkTracker::new(
|
||||||
|
initial_have.clone(),
|
||||||
|
initial_selected.clone(),
|
||||||
|
l,
|
||||||
|
&Default::default(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
// Select all file, no changes.
|
// Select all file, no changes.
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|
|
||||||
49
crates/librqbit/src/file_info.rs
Normal file
49
crates/librqbit/src/file_info.rs
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct FileInfo {
|
||||||
|
pub filename: PathBuf,
|
||||||
|
pub offset_in_torrent: u64,
|
||||||
|
pub piece_range: std::ops::Range<u32>,
|
||||||
|
pub len: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iterate file pieces in the following order: first, last, everything else from start to end.
|
||||||
|
fn iter_piece_priorities(range: std::ops::Range<usize>) -> impl Iterator<Item = usize> {
|
||||||
|
// First and last of each file first, then the rest of pieces in that file.
|
||||||
|
let r = range;
|
||||||
|
use std::iter::once;
|
||||||
|
|
||||||
|
let first = once(r.start);
|
||||||
|
let last = once(r.start + r.len().overflowing_sub(1).0); // it's ok if it repeats, doesn't matter
|
||||||
|
let mid = r.clone().skip(1).take(r.len().overflowing_sub(2).0);
|
||||||
|
|
||||||
|
// The take(r.len()) is to not yield start/end pieces in case of 0 and 1 lengths.
|
||||||
|
first.chain(last).chain(mid).take(r.len())
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FileInfo {
|
||||||
|
pub fn piece_range_usize(&self) -> std::ops::Range<usize> {
|
||||||
|
self.piece_range.start as usize..self.piece_range.end as usize
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn iter_piece_priorities(&self) -> impl Iterator<Item = usize> {
|
||||||
|
iter_piece_priorities(self.piece_range_usize())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::iter_piece_priorities;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_iter_piece_priorities() {
|
||||||
|
let it = |r: std::ops::Range<usize>| -> Vec<usize> { iter_piece_priorities(r).collect() };
|
||||||
|
assert_eq!(it(0..0), Vec::<usize>::new());
|
||||||
|
|
||||||
|
assert_eq!(it(0..1), vec![0]);
|
||||||
|
assert_eq!(it(0..2), vec![0, 1]);
|
||||||
|
assert_eq!(it(0..3), vec![0, 2, 1]);
|
||||||
|
assert_eq!(it(0..4), vec![0, 3, 1, 2]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,4 @@
|
||||||
use std::{
|
use std::{
|
||||||
fs::File,
|
|
||||||
io::{Read, Seek, SeekFrom, Write},
|
|
||||||
marker::PhantomData,
|
marker::PhantomData,
|
||||||
sync::atomic::{AtomicU64, Ordering},
|
sync::atomic::{AtomicU64, Ordering},
|
||||||
};
|
};
|
||||||
|
|
@ -16,8 +14,9 @@ use sha1w::{ISha1, Sha1};
|
||||||
use tracing::{debug, trace, warn};
|
use tracing::{debug, trace, warn};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
opened_file::OpenedFile,
|
file_info::FileInfo,
|
||||||
type_aliases::{OpenedFiles, PeerHandle, BF},
|
storage::TorrentStorage,
|
||||||
|
type_aliases::{FileInfos, PeerHandle, BF},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub(crate) struct InitialCheckResults {
|
pub(crate) struct InitialCheckResults {
|
||||||
|
|
@ -44,7 +43,9 @@ pub(crate) struct InitialCheckResults {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_hash_from_file<Sha1: ISha1>(
|
pub fn update_hash_from_file<Sha1: ISha1>(
|
||||||
file: &mut File,
|
file_id: usize,
|
||||||
|
mut pos: u64,
|
||||||
|
files: &dyn TorrentStorage,
|
||||||
hash: &mut Sha1,
|
hash: &mut Sha1,
|
||||||
buf: &mut [u8],
|
buf: &mut [u8],
|
||||||
mut bytes_to_read: usize,
|
mut bytes_to_read: usize,
|
||||||
|
|
@ -52,10 +53,12 @@ pub fn update_hash_from_file<Sha1: ISha1>(
|
||||||
let mut read = 0;
|
let mut read = 0;
|
||||||
while bytes_to_read > 0 {
|
while bytes_to_read > 0 {
|
||||||
let chunk = std::cmp::min(buf.len(), bytes_to_read);
|
let chunk = std::cmp::min(buf.len(), bytes_to_read);
|
||||||
file.read_exact(&mut buf[..chunk])
|
files
|
||||||
|
.pread_exact(file_id, pos, &mut buf[..chunk])
|
||||||
.with_context(|| format!("failed reading chunk of size {chunk}, read so far {read}"))?;
|
.with_context(|| format!("failed reading chunk of size {chunk}, read so far {read}"))?;
|
||||||
bytes_to_read -= chunk;
|
bytes_to_read -= chunk;
|
||||||
read += chunk;
|
read += chunk;
|
||||||
|
pos += chunk as u64;
|
||||||
hash.update(&buf[..chunk]);
|
hash.update(&buf[..chunk]);
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
@ -63,7 +66,8 @@ pub fn update_hash_from_file<Sha1: ISha1>(
|
||||||
|
|
||||||
pub(crate) struct FileOps<'a> {
|
pub(crate) struct FileOps<'a> {
|
||||||
torrent: &'a TorrentMetaV1Info<ByteBufOwned>,
|
torrent: &'a TorrentMetaV1Info<ByteBufOwned>,
|
||||||
files: &'a OpenedFiles,
|
files: &'a dyn TorrentStorage,
|
||||||
|
file_infos: &'a FileInfos,
|
||||||
lengths: &'a Lengths,
|
lengths: &'a Lengths,
|
||||||
phantom_data: PhantomData<Sha1>,
|
phantom_data: PhantomData<Sha1>,
|
||||||
}
|
}
|
||||||
|
|
@ -71,12 +75,14 @@ pub(crate) struct FileOps<'a> {
|
||||||
impl<'a> FileOps<'a> {
|
impl<'a> FileOps<'a> {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
torrent: &'a TorrentMetaV1Info<ByteBufOwned>,
|
torrent: &'a TorrentMetaV1Info<ByteBufOwned>,
|
||||||
files: &'a OpenedFiles,
|
files: &'a dyn TorrentStorage,
|
||||||
|
file_infos: &'a FileInfos,
|
||||||
lengths: &'a Lengths,
|
lengths: &'a Lengths,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
torrent,
|
torrent,
|
||||||
files,
|
files,
|
||||||
|
file_infos,
|
||||||
lengths,
|
lengths,
|
||||||
phantom_data: PhantomData,
|
phantom_data: PhantomData,
|
||||||
}
|
}
|
||||||
|
|
@ -85,8 +91,6 @@ impl<'a> FileOps<'a> {
|
||||||
pub fn initial_check(
|
pub fn initial_check(
|
||||||
&self,
|
&self,
|
||||||
only_files: Option<&[usize]>,
|
only_files: Option<&[usize]>,
|
||||||
opened_files: &OpenedFiles,
|
|
||||||
lengths: &Lengths,
|
|
||||||
progress: &AtomicU64,
|
progress: &AtomicU64,
|
||||||
) -> anyhow::Result<InitialCheckResults> {
|
) -> anyhow::Result<InitialCheckResults> {
|
||||||
let mut needed_pieces =
|
let mut needed_pieces =
|
||||||
|
|
@ -102,20 +106,20 @@ impl<'a> FileOps<'a> {
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct CurrentFile<'a> {
|
struct CurrentFile<'a> {
|
||||||
index: usize,
|
index: usize,
|
||||||
fd: &'a OpenedFile,
|
fi: &'a FileInfo,
|
||||||
full_file_required: bool,
|
full_file_required: bool,
|
||||||
processed_bytes: u64,
|
processed_bytes: u64,
|
||||||
is_broken: bool,
|
is_broken: bool,
|
||||||
}
|
}
|
||||||
impl<'a> CurrentFile<'a> {
|
impl<'a> CurrentFile<'a> {
|
||||||
fn remaining(&self) -> u64 {
|
fn remaining(&self) -> u64 {
|
||||||
self.fd.len - self.processed_bytes
|
self.fi.len - self.processed_bytes
|
||||||
}
|
}
|
||||||
fn mark_processed_bytes(&mut self, bytes: u64) {
|
fn mark_processed_bytes(&mut self, bytes: u64) {
|
||||||
self.processed_bytes += bytes
|
self.processed_bytes += bytes
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let mut file_iterator = self.files.iter().enumerate().map(|(idx, fd)| {
|
let mut file_iterator = self.file_infos.iter().enumerate().map(|(idx, fi)| {
|
||||||
let full_file_required = if let Some(only_files) = only_files {
|
let full_file_required = if let Some(only_files) = only_files {
|
||||||
only_files.contains(&idx)
|
only_files.contains(&idx)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -123,7 +127,7 @@ impl<'a> FileOps<'a> {
|
||||||
};
|
};
|
||||||
CurrentFile {
|
CurrentFile {
|
||||||
index: idx,
|
index: idx,
|
||||||
fd,
|
fi,
|
||||||
full_file_required,
|
full_file_required,
|
||||||
processed_bytes: 0,
|
processed_bytes: 0,
|
||||||
is_broken: false,
|
is_broken: false,
|
||||||
|
|
@ -172,19 +176,17 @@ impl<'a> FileOps<'a> {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut fd = current_file.fd.file.lock();
|
|
||||||
|
|
||||||
fd.seek(SeekFrom::Start(pos))
|
|
||||||
.context("bug? error seeking")?;
|
|
||||||
if let Err(err) = update_hash_from_file(
|
if let Err(err) = update_hash_from_file(
|
||||||
&mut fd,
|
current_file.index,
|
||||||
|
pos,
|
||||||
|
self.files,
|
||||||
&mut computed_hash,
|
&mut computed_hash,
|
||||||
&mut read_buffer,
|
&mut read_buffer,
|
||||||
to_read_in_file,
|
to_read_in_file,
|
||||||
) {
|
) {
|
||||||
debug!(
|
debug!(
|
||||||
"error reading from file {} ({:?}) at {}: {:#}",
|
"error reading from file {} ({:?}) at {}: {:#}",
|
||||||
current_file.index, current_file.fd.filename, pos, &err
|
current_file.index, current_file.fi.filename, pos, &err
|
||||||
);
|
);
|
||||||
current_file.is_broken = true;
|
current_file.is_broken = true;
|
||||||
some_files_broken = true;
|
some_files_broken = true;
|
||||||
|
|
@ -216,10 +218,6 @@ impl<'a> FileOps<'a> {
|
||||||
piece_info.piece_index
|
piece_info.piece_index
|
||||||
);
|
);
|
||||||
have_bytes += piece_info.len as u64;
|
have_bytes += piece_info.len as u64;
|
||||||
for file_id in piece_files.drain(..) {
|
|
||||||
opened_files[file_id]
|
|
||||||
.update_have_on_piece_completed(piece_info.piece_index.get(), lengths);
|
|
||||||
}
|
|
||||||
have_pieces.set(piece_info.piece_index.get() as usize, true);
|
have_pieces.set(piece_info.piece_index.get() as usize, true);
|
||||||
} else if piece_selected {
|
} else if piece_selected {
|
||||||
trace!(
|
trace!(
|
||||||
|
|
@ -265,9 +263,8 @@ impl<'a> FileOps<'a> {
|
||||||
}
|
}
|
||||||
let file_remaining_len = file_len - absolute_offset;
|
let file_remaining_len = file_len - absolute_offset;
|
||||||
|
|
||||||
let to_read_in_file =
|
let to_read_in_file: usize =
|
||||||
std::cmp::min(file_remaining_len, piece_remaining_bytes as u64).try_into()?;
|
std::cmp::min(file_remaining_len, piece_remaining_bytes as u64).try_into()?;
|
||||||
let mut file_g = self.files[file_idx].file.lock();
|
|
||||||
trace!(
|
trace!(
|
||||||
"piece={}, handle={}, file_idx={}, seeking to {}. Last received chunk: {:?}",
|
"piece={}, handle={}, file_idx={}, seeking to {}. Last received chunk: {:?}",
|
||||||
piece_index,
|
piece_index,
|
||||||
|
|
@ -276,18 +273,17 @@ impl<'a> FileOps<'a> {
|
||||||
absolute_offset,
|
absolute_offset,
|
||||||
&last_received_chunk
|
&last_received_chunk
|
||||||
);
|
);
|
||||||
file_g
|
update_hash_from_file(
|
||||||
.seek(SeekFrom::Start(absolute_offset))
|
file_idx,
|
||||||
.with_context(|| {
|
absolute_offset,
|
||||||
format!("error seeking to {absolute_offset}, file id: {file_idx}")
|
self.files,
|
||||||
})?;
|
&mut h,
|
||||||
update_hash_from_file(&mut file_g, &mut h, &mut buf, to_read_in_file).with_context(
|
&mut buf,
|
||||||
|| {
|
to_read_in_file,
|
||||||
format!(
|
)
|
||||||
"error reading {to_read_in_file} bytes, file_id: {file_idx} (\"{name:?}\")"
|
.with_context(|| {
|
||||||
)
|
format!("error reading {to_read_in_file} bytes, file_id: {file_idx} (\"{name:?}\")")
|
||||||
},
|
})?;
|
||||||
)?;
|
|
||||||
|
|
||||||
piece_remaining_bytes -= to_read_in_file;
|
piece_remaining_bytes -= to_read_in_file;
|
||||||
|
|
||||||
|
|
@ -335,7 +331,6 @@ impl<'a> FileOps<'a> {
|
||||||
let file_remaining_len = file_len - absolute_offset;
|
let file_remaining_len = file_len - absolute_offset;
|
||||||
let to_read_in_file = std::cmp::min(file_remaining_len, buf.len() as u64).try_into()?;
|
let to_read_in_file = std::cmp::min(file_remaining_len, buf.len() as u64).try_into()?;
|
||||||
|
|
||||||
let mut file_g = self.files[file_idx].file.lock();
|
|
||||||
trace!(
|
trace!(
|
||||||
"piece={}, handle={}, file_idx={}, seeking to {}. To read chunk: {:?}",
|
"piece={}, handle={}, file_idx={}, seeking to {}. To read chunk: {:?}",
|
||||||
chunk_info.piece_index,
|
chunk_info.piece_index,
|
||||||
|
|
@ -344,13 +339,8 @@ impl<'a> FileOps<'a> {
|
||||||
absolute_offset,
|
absolute_offset,
|
||||||
&chunk_info
|
&chunk_info
|
||||||
);
|
);
|
||||||
file_g
|
self.files
|
||||||
.seek(SeekFrom::Start(absolute_offset))
|
.pread_exact(file_idx, absolute_offset, &mut buf[..to_read_in_file])
|
||||||
.with_context(|| {
|
|
||||||
format!("error seeking to {absolute_offset}, file id: {file_idx}")
|
|
||||||
})?;
|
|
||||||
file_g
|
|
||||||
.read_exact(&mut buf[..to_read_in_file])
|
|
||||||
.with_context(|| {
|
.with_context(|| {
|
||||||
format!("error reading {file_idx} bytes, file_id: {to_read_in_file}")
|
format!("error reading {file_idx} bytes, file_id: {to_read_in_file}")
|
||||||
})?;
|
})?;
|
||||||
|
|
@ -388,7 +378,6 @@ impl<'a> FileOps<'a> {
|
||||||
let remaining_len = file_len - absolute_offset;
|
let remaining_len = file_len - absolute_offset;
|
||||||
let to_write = std::cmp::min(buf.len() as u64, remaining_len).try_into()?;
|
let to_write = std::cmp::min(buf.len() as u64, remaining_len).try_into()?;
|
||||||
|
|
||||||
let mut file_g = self.files[file_idx].file.lock();
|
|
||||||
trace!(
|
trace!(
|
||||||
"piece={}, chunk={:?}, handle={}, begin={}, file={}, writing {} bytes at {}",
|
"piece={}, chunk={:?}, handle={}, begin={}, file={}, writing {} bytes at {}",
|
||||||
chunk_info.piece_index,
|
chunk_info.piece_index,
|
||||||
|
|
@ -399,13 +388,8 @@ impl<'a> FileOps<'a> {
|
||||||
to_write,
|
to_write,
|
||||||
absolute_offset
|
absolute_offset
|
||||||
);
|
);
|
||||||
file_g
|
self.files
|
||||||
.seek(SeekFrom::Start(absolute_offset))
|
.pwrite_all(file_idx, absolute_offset, &buf[..to_write])
|
||||||
.with_context(|| {
|
|
||||||
format!("error seeking to {absolute_offset} in file {file_idx} (\"{name:?}\")")
|
|
||||||
})?;
|
|
||||||
file_g
|
|
||||||
.write_all(&buf[..to_write])
|
|
||||||
.with_context(|| format!("error writing to file {file_idx} (\"{name:?}\")"))?;
|
.with_context(|| format!("error writing to file {file_idx} (\"{name:?}\")"))?;
|
||||||
buf = &buf[to_write..];
|
buf = &buf[to_write..];
|
||||||
if buf.is_empty() {
|
if buf.is_empty() {
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ mod api_error;
|
||||||
mod chunk_tracker;
|
mod chunk_tracker;
|
||||||
mod create_torrent_file;
|
mod create_torrent_file;
|
||||||
mod dht_utils;
|
mod dht_utils;
|
||||||
|
pub mod file_info;
|
||||||
mod file_ops;
|
mod file_ops;
|
||||||
pub mod http_api;
|
pub mod http_api;
|
||||||
pub mod http_api_client;
|
pub mod http_api_client;
|
||||||
|
|
@ -40,6 +41,7 @@ mod peer_info_reader;
|
||||||
mod read_buf;
|
mod read_buf;
|
||||||
mod session;
|
mod session;
|
||||||
mod spawn_utils;
|
mod spawn_utils;
|
||||||
|
mod storage;
|
||||||
mod torrent_state;
|
mod torrent_state;
|
||||||
pub mod tracing_subscriber_config_utils;
|
pub mod tracing_subscriber_config_utils;
|
||||||
mod type_aliases;
|
mod type_aliases;
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,11 @@
|
||||||
use std::{
|
use std::fs::File;
|
||||||
fs::File,
|
|
||||||
path::PathBuf,
|
|
||||||
sync::atomic::{AtomicU64, Ordering},
|
|
||||||
};
|
|
||||||
|
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use librqbit_core::lengths::Lengths;
|
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(crate) struct OpenedFile {
|
pub(crate) struct OpenedFile {
|
||||||
pub file: Mutex<File>,
|
pub file: Mutex<File>,
|
||||||
pub filename: PathBuf,
|
|
||||||
pub offset_in_torrent: u64,
|
|
||||||
pub have: AtomicU64,
|
|
||||||
pub piece_range: std::ops::Range<u32>,
|
|
||||||
pub len: u64,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn dummy_file() -> anyhow::Result<std::fs::File> {
|
pub(crate) fn dummy_file() -> anyhow::Result<std::fs::File> {
|
||||||
|
|
@ -30,36 +20,10 @@ pub(crate) fn dummy_file() -> anyhow::Result<std::fs::File> {
|
||||||
.with_context(|| format!("error opening {}", DEVNULL))
|
.with_context(|| format!("error opening {}", DEVNULL))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Iterate file pieces in the following order: first, last, everything else from start to end.
|
|
||||||
fn iter_piece_priorities(range: std::ops::Range<usize>) -> impl Iterator<Item = usize> {
|
|
||||||
// First and last of each file first, then the rest of pieces in that file.
|
|
||||||
let r = range;
|
|
||||||
use std::iter::once;
|
|
||||||
|
|
||||||
let first = once(r.start);
|
|
||||||
let last = once(r.start + r.len().overflowing_sub(1).0); // it's ok if it repeats, doesn't matter
|
|
||||||
let mid = r.clone().skip(1).take(r.len().overflowing_sub(2).0);
|
|
||||||
|
|
||||||
// The take(r.len()) is to not yield start/end pieces in case of 0 and 1 lengths.
|
|
||||||
first.chain(last).chain(mid).take(r.len())
|
|
||||||
}
|
|
||||||
|
|
||||||
impl OpenedFile {
|
impl OpenedFile {
|
||||||
pub fn new(
|
pub fn new(f: File) -> Self {
|
||||||
f: File,
|
|
||||||
filename: PathBuf,
|
|
||||||
have: u64,
|
|
||||||
len: u64,
|
|
||||||
offset_in_torrent: u64,
|
|
||||||
piece_range: std::ops::Range<u32>,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
Self {
|
||||||
file: Mutex::new(f),
|
file: Mutex::new(f),
|
||||||
filename,
|
|
||||||
have: AtomicU64::new(have),
|
|
||||||
len,
|
|
||||||
offset_in_torrent,
|
|
||||||
piece_range,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -74,45 +38,6 @@ impl OpenedFile {
|
||||||
let f = self.take()?;
|
let f = self.take()?;
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
file: Mutex::new(f),
|
file: Mutex::new(f),
|
||||||
filename: self.filename.clone(),
|
|
||||||
offset_in_torrent: self.offset_in_torrent,
|
|
||||||
have: AtomicU64::new(self.have.load(Ordering::Relaxed)),
|
|
||||||
len: self.len,
|
|
||||||
piece_range: self.piece_range.clone(),
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn piece_range_usize(&self) -> std::ops::Range<usize> {
|
|
||||||
self.piece_range.start as usize..self.piece_range.end as usize
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update_have_on_piece_completed(&self, piece_id: u32, lengths: &Lengths) -> u64 {
|
|
||||||
let size = lengths.size_of_piece_in_file(piece_id, self.offset_in_torrent, self.len);
|
|
||||||
self.have.fetch_add(size, Ordering::Relaxed);
|
|
||||||
size
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn approx_is_finished(&self) -> bool {
|
|
||||||
self.have.load(Ordering::Relaxed) == self.len
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn iter_piece_priorities(&self) -> impl Iterator<Item = usize> {
|
|
||||||
iter_piece_priorities(self.piece_range_usize())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use crate::opened_file::iter_piece_priorities;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_iter_piece_priorities() {
|
|
||||||
let it = |r: std::ops::Range<usize>| -> Vec<usize> { iter_piece_priorities(r).collect() };
|
|
||||||
assert_eq!(it(0..0), Vec::<usize>::new());
|
|
||||||
|
|
||||||
assert_eq!(it(0..1), vec![0]);
|
|
||||||
assert_eq!(it(0..2), vec![0, 1]);
|
|
||||||
assert_eq!(it(0..3), vec![0, 2, 1]);
|
|
||||||
assert_eq!(it(0..4), vec![0, 3, 1, 2]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1092,10 +1092,9 @@ impl Session {
|
||||||
warn!(error=?e, "error deleting torrent cleanly");
|
warn!(error=?e, "error deleting torrent cleanly");
|
||||||
}
|
}
|
||||||
(Ok(Some(paused)), true) => {
|
(Ok(Some(paused)), true) => {
|
||||||
for file in paused.files.iter() {
|
for (id, fi) in removed.info().file_infos.iter().enumerate() {
|
||||||
drop(file.take()?);
|
if let Err(e) = paused.files.remove_file(id, &fi.filename) {
|
||||||
if let Err(e) = std::fs::remove_file(&file.filename) {
|
warn!(?fi.filename, error=?e, "could not delete file");
|
||||||
warn!(?file.filename, error=?e, "could not delete file");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
184
crates/librqbit/src/storage.rs
Normal file
184
crates/librqbit/src/storage.rs
Normal file
|
|
@ -0,0 +1,184 @@
|
||||||
|
use std::{
|
||||||
|
collections::HashMap,
|
||||||
|
io::{Read, Seek, SeekFrom, Write},
|
||||||
|
path::Path,
|
||||||
|
};
|
||||||
|
|
||||||
|
use anyhow::Context;
|
||||||
|
use librqbit_core::lengths::{Lengths, ValidPieceIndex};
|
||||||
|
use parking_lot::RwLock;
|
||||||
|
|
||||||
|
use crate::{opened_file::OpenedFile, type_aliases::FileInfos};
|
||||||
|
|
||||||
|
pub trait TorrentStorage: Send + Sync {
|
||||||
|
fn pread_exact(&self, file_id: usize, offset: u64, buf: &mut [u8]) -> anyhow::Result<()>;
|
||||||
|
|
||||||
|
fn pwrite_all(&self, file_id: usize, offset: u64, buf: &[u8]) -> anyhow::Result<()>;
|
||||||
|
|
||||||
|
fn remove_file(&self, file_id: usize, filename: &Path) -> anyhow::Result<()>;
|
||||||
|
|
||||||
|
fn ensure_file_length(&self, file_id: usize, length: u64) -> anyhow::Result<()>;
|
||||||
|
|
||||||
|
fn take(&self) -> anyhow::Result<Box<dyn TorrentStorage>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct FilesystemStorage {
|
||||||
|
opened_files: Vec<OpenedFile>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FilesystemStorage {
|
||||||
|
pub fn new(opened_files: Vec<OpenedFile>) -> Self {
|
||||||
|
Self { opened_files }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TorrentStorage for FilesystemStorage {
|
||||||
|
fn pread_exact(&self, file_id: usize, offset: u64, buf: &mut [u8]) -> anyhow::Result<()> {
|
||||||
|
let mut g = self
|
||||||
|
.opened_files
|
||||||
|
.get(file_id)
|
||||||
|
.context("no such file")?
|
||||||
|
.file
|
||||||
|
.lock();
|
||||||
|
g.seek(SeekFrom::Start(offset))?;
|
||||||
|
Ok(g.read_exact(buf)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pwrite_all(&self, file_id: usize, offset: u64, buf: &[u8]) -> anyhow::Result<()> {
|
||||||
|
let mut g = self
|
||||||
|
.opened_files
|
||||||
|
.get(file_id)
|
||||||
|
.context("no such file")?
|
||||||
|
.file
|
||||||
|
.lock();
|
||||||
|
g.seek(SeekFrom::Start(offset))?;
|
||||||
|
Ok(g.write_all(buf)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_file(&self, _file_id: usize, filename: &Path) -> anyhow::Result<()> {
|
||||||
|
Ok(std::fs::remove_file(filename)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ensure_file_length(&self, file_id: usize, len: u64) -> anyhow::Result<()> {
|
||||||
|
Ok(self.opened_files[file_id].file.lock().set_len(len)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn take(&self) -> anyhow::Result<Box<dyn TorrentStorage>> {
|
||||||
|
Ok(Box::new(Self::new(
|
||||||
|
self.opened_files
|
||||||
|
.iter()
|
||||||
|
.map(|f| f.take_clone())
|
||||||
|
.collect::<anyhow::Result<Vec<_>>>()?,
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TorrentStorage for Box<dyn TorrentStorage> {
|
||||||
|
fn pread_exact(&self, file_id: usize, offset: u64, buf: &mut [u8]) -> anyhow::Result<()> {
|
||||||
|
(**self).pread_exact(file_id, offset, buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pwrite_all(&self, file_id: usize, offset: u64, buf: &[u8]) -> anyhow::Result<()> {
|
||||||
|
(**self).pwrite_all(file_id, offset, buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_file(&self, file_id: usize, filename: &Path) -> anyhow::Result<()> {
|
||||||
|
(**self).remove_file(file_id, filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ensure_file_length(&self, file_id: usize, length: u64) -> anyhow::Result<()> {
|
||||||
|
(**self).ensure_file_length(file_id, length)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn take(&self) -> anyhow::Result<Box<dyn TorrentStorage>> {
|
||||||
|
(**self).take()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct InMemoryPiece {
|
||||||
|
bytes: Box<[u8]>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InMemoryPiece {
|
||||||
|
fn new(l: &Lengths) -> Self {
|
||||||
|
let v = vec![0; l.default_piece_length() as usize].into_boxed_slice();
|
||||||
|
Self { bytes: v }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct InMemoryGarbageCollectingStorage {
|
||||||
|
lengths: Lengths,
|
||||||
|
file_infos: FileInfos,
|
||||||
|
map: RwLock<HashMap<ValidPieceIndex, InMemoryPiece>>,
|
||||||
|
// TODO: chunk tracker - rename to PieceTracker and extract chunks out of it (only keep pieces)
|
||||||
|
// this sucker here would track chunks, and the storage above too.
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InMemoryGarbageCollectingStorage {
|
||||||
|
pub fn new(lengths: Lengths, file_infos: FileInfos) -> anyhow::Result<Self> {
|
||||||
|
// Max memory 128MiB. Make it tunable
|
||||||
|
let max_pieces = 128 * 1024 * 1024 / lengths.default_piece_length();
|
||||||
|
if max_pieces == 0 {
|
||||||
|
anyhow::bail!("pieces too large");
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
lengths,
|
||||||
|
file_infos,
|
||||||
|
map: RwLock::new(HashMap::new()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TorrentStorage for InMemoryGarbageCollectingStorage {
|
||||||
|
fn pread_exact(&self, file_id: usize, offset: u64, buf: &mut [u8]) -> anyhow::Result<()> {
|
||||||
|
let fi = &self.file_infos[file_id];
|
||||||
|
let abs_offset = fi.offset_in_torrent + offset;
|
||||||
|
let piece_id: u32 = (abs_offset / self.lengths.default_piece_length() as u64).try_into()?;
|
||||||
|
let piece_offset: usize =
|
||||||
|
(abs_offset % self.lengths.default_piece_length() as u64).try_into()?;
|
||||||
|
let piece_id = self.lengths.validate_piece_index(piece_id).context("bug")?;
|
||||||
|
|
||||||
|
let g = self.map.read();
|
||||||
|
let inmp = g.get(&piece_id).context("piece expired")?;
|
||||||
|
buf.copy_from_slice(&inmp.bytes[piece_offset..(piece_offset + buf.len())]);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pwrite_all(&self, file_id: usize, offset: u64, buf: &[u8]) -> anyhow::Result<()> {
|
||||||
|
let fi = &self.file_infos[file_id];
|
||||||
|
let abs_offset = fi.offset_in_torrent + offset;
|
||||||
|
let piece_id: u32 = (abs_offset / self.lengths.default_piece_length() as u64).try_into()?;
|
||||||
|
let piece_offset: usize =
|
||||||
|
(abs_offset % self.lengths.default_piece_length() as u64).try_into()?;
|
||||||
|
let piece_id = self.lengths.validate_piece_index(piece_id).context("bug")?;
|
||||||
|
let mut g = self.map.write();
|
||||||
|
let inmp = g
|
||||||
|
.entry(piece_id)
|
||||||
|
.or_insert_with(|| InMemoryPiece::new(&self.lengths));
|
||||||
|
inmp.bytes[piece_offset..(piece_offset + buf.len())].copy_from_slice(buf);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_file(&self, _file_id: usize, _filename: &Path) -> anyhow::Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ensure_file_length(&self, _file_id: usize, _length: u64) -> anyhow::Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn take(&self) -> anyhow::Result<Box<dyn TorrentStorage>> {
|
||||||
|
let map = {
|
||||||
|
let mut g = self.map.write();
|
||||||
|
let mut repl = HashMap::new();
|
||||||
|
std::mem::swap(&mut *g, &mut repl);
|
||||||
|
repl
|
||||||
|
};
|
||||||
|
Ok(Box::new(Self {
|
||||||
|
lengths: self.lengths,
|
||||||
|
map: RwLock::new(map),
|
||||||
|
file_infos: self.file_infos.clone(),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -10,8 +10,10 @@ use size_format::SizeFormatterBinary as SF;
|
||||||
use tracing::{debug, info, warn};
|
use tracing::{debug, info, warn};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
chunk_tracker::ChunkTracker, file_ops::FileOps, opened_file::OpenedFile,
|
chunk_tracker::ChunkTracker,
|
||||||
type_aliases::OpenedFiles,
|
file_ops::FileOps,
|
||||||
|
opened_file::OpenedFile,
|
||||||
|
storage::{FilesystemStorage, InMemoryGarbageCollectingStorage, TorrentStorage},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{paused::TorrentStatePaused, ManagedTorrentInfo};
|
use super::{paused::TorrentStatePaused, ManagedTorrentInfo};
|
||||||
|
|
@ -37,7 +39,23 @@ impl TorrentStateInitializing {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn check(&self) -> anyhow::Result<TorrentStatePaused> {
|
pub async fn check(&self) -> anyhow::Result<TorrentStatePaused> {
|
||||||
let mut files = OpenedFiles::new();
|
// Return in-memory store
|
||||||
|
let store =
|
||||||
|
InMemoryGarbageCollectingStorage::new(self.meta.lengths, self.meta.file_infos.clone())?;
|
||||||
|
let ct = ChunkTracker::new_empty(self.meta.lengths, &self.meta.file_infos)?;
|
||||||
|
|
||||||
|
Ok(TorrentStatePaused {
|
||||||
|
info: self.meta.clone(),
|
||||||
|
files: Box::new(store),
|
||||||
|
chunk_tracker: ct,
|
||||||
|
streams: Arc::new(Default::default()),
|
||||||
|
})
|
||||||
|
|
||||||
|
// self.check_disk().await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn check_disk(&self) -> anyhow::Result<TorrentStatePaused> {
|
||||||
|
let mut files = Vec::<OpenedFile>::new();
|
||||||
for file_details in self.meta.info.iter_file_details(&self.meta.lengths)? {
|
for file_details in self.meta.info.iter_file_details(&self.meta.lengths)? {
|
||||||
let mut full_path = self.meta.out_dir.clone();
|
let mut full_path = self.meta.out_dir.clone();
|
||||||
let relative_path = file_details
|
let relative_path = file_details
|
||||||
|
|
@ -64,26 +82,21 @@ impl TorrentStateInitializing {
|
||||||
.with_context(|| format!("error creating {:?}", &full_path))?;
|
.with_context(|| format!("error creating {:?}", &full_path))?;
|
||||||
OpenOptions::new().read(true).write(true).open(&full_path)?
|
OpenOptions::new().read(true).write(true).open(&full_path)?
|
||||||
};
|
};
|
||||||
files.push(OpenedFile::new(
|
files.push(OpenedFile::new(file));
|
||||||
file,
|
|
||||||
full_path,
|
|
||||||
0,
|
|
||||||
file_details.len,
|
|
||||||
file_details.offset,
|
|
||||||
file_details.pieces,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
let files: Box<dyn TorrentStorage> = Box::new(FilesystemStorage::new(files));
|
||||||
|
|
||||||
debug!("computed lengths: {:?}", &self.meta.lengths);
|
debug!("computed lengths: {:?}", &self.meta.lengths);
|
||||||
|
|
||||||
info!("Doing initial checksum validation, this might take a while...");
|
info!("Doing initial checksum validation, this might take a while...");
|
||||||
let initial_check_results = self.meta.spawner.spawn_block_in_place(|| {
|
let initial_check_results = self.meta.spawner.spawn_block_in_place(|| {
|
||||||
FileOps::new(&self.meta.info, &files, &self.meta.lengths).initial_check(
|
FileOps::new(
|
||||||
self.only_files.as_deref(),
|
&self.meta.info,
|
||||||
&files,
|
&files,
|
||||||
|
&self.meta.file_infos,
|
||||||
&self.meta.lengths,
|
&self.meta.lengths,
|
||||||
&self.checked_bytes,
|
|
||||||
)
|
)
|
||||||
|
.initial_check(self.only_files.as_deref(), &self.checked_bytes)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
info!(
|
info!(
|
||||||
|
|
@ -95,7 +108,7 @@ impl TorrentStateInitializing {
|
||||||
|
|
||||||
// Ensure file lenghts are correct, and reopen read-only.
|
// Ensure file lenghts are correct, and reopen read-only.
|
||||||
self.meta.spawner.spawn_block_in_place(|| {
|
self.meta.spawner.spawn_block_in_place(|| {
|
||||||
for (idx, file) in files.iter().enumerate() {
|
for (idx, fi) in self.meta.file_infos.iter().enumerate() {
|
||||||
if self
|
if self
|
||||||
.only_files
|
.only_files
|
||||||
.as_ref()
|
.as_ref()
|
||||||
|
|
@ -103,16 +116,16 @@ impl TorrentStateInitializing {
|
||||||
.unwrap_or(true)
|
.unwrap_or(true)
|
||||||
{
|
{
|
||||||
let now = Instant::now();
|
let now = Instant::now();
|
||||||
if let Err(err) = file.file.lock().set_len(file.len) {
|
if let Err(err) = files.ensure_file_length(idx, fi.len) {
|
||||||
warn!(
|
warn!(
|
||||||
"Error setting length for file {:?} to {}: {:#?}",
|
"Error setting length for file {:?} to {}: {:#?}",
|
||||||
file.filename, file.len, err
|
fi.filename, fi.len, err
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
debug!(
|
debug!(
|
||||||
"Set length for file {:?} to {} in {:?}",
|
"Set length for file {:?} to {} in {:?}",
|
||||||
file.filename,
|
fi.filename,
|
||||||
SF::new(file.len),
|
SF::new(fi.len),
|
||||||
now.elapsed()
|
now.elapsed()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -125,6 +138,7 @@ impl TorrentStateInitializing {
|
||||||
initial_check_results.have_pieces,
|
initial_check_results.have_pieces,
|
||||||
initial_check_results.selected_pieces,
|
initial_check_results.selected_pieces,
|
||||||
self.meta.lengths,
|
self.meta.lengths,
|
||||||
|
&self.meta.file_infos,
|
||||||
)
|
)
|
||||||
.context("error creating chunk tracker")?;
|
.context("error creating chunk tracker")?;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -86,8 +86,9 @@ use crate::{
|
||||||
PeerConnection, PeerConnectionHandler, PeerConnectionOptions, WriterRequest,
|
PeerConnection, PeerConnectionHandler, PeerConnectionOptions, WriterRequest,
|
||||||
},
|
},
|
||||||
session::CheckedIncomingConnection,
|
session::CheckedIncomingConnection,
|
||||||
|
storage::TorrentStorage,
|
||||||
torrent_state::{peer::Peer, utils::atomic_inc},
|
torrent_state::{peer::Peer, utils::atomic_inc},
|
||||||
type_aliases::{FilePriorities, OpenedFiles, PeerHandle, BF},
|
type_aliases::{FilePriorities, FileStorage, PeerHandle, BF},
|
||||||
};
|
};
|
||||||
|
|
||||||
use self::{
|
use self::{
|
||||||
|
|
@ -141,7 +142,7 @@ impl TorrentStateLocked {
|
||||||
.context("chunk tracker empty, torrent was paused")
|
.context("chunk tracker empty, torrent was paused")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_chunks_mut(&mut self) -> anyhow::Result<&mut ChunkTracker> {
|
pub(crate) fn get_chunks_mut(&mut self) -> anyhow::Result<&mut ChunkTracker> {
|
||||||
self.chunks
|
self.chunks
|
||||||
.as_mut()
|
.as_mut()
|
||||||
.context("chunk tracker empty, torrent was paused")
|
.context("chunk tracker empty, torrent was paused")
|
||||||
|
|
@ -159,7 +160,7 @@ pub struct TorrentStateLive {
|
||||||
meta: Arc<ManagedTorrentInfo>,
|
meta: Arc<ManagedTorrentInfo>,
|
||||||
locked: RwLock<TorrentStateLocked>,
|
locked: RwLock<TorrentStateLocked>,
|
||||||
|
|
||||||
pub(crate) files: OpenedFiles,
|
pub(crate) files: FileStorage,
|
||||||
|
|
||||||
stats: AtomicStats,
|
stats: AtomicStats,
|
||||||
lengths: Lengths,
|
lengths: Lengths,
|
||||||
|
|
@ -195,9 +196,15 @@ impl TorrentStateLive {
|
||||||
|
|
||||||
// TODO: make it configurable
|
// TODO: make it configurable
|
||||||
let file_priorities = {
|
let file_priorities = {
|
||||||
let mut pri = (0..paused.files.len()).collect::<Vec<usize>>();
|
let mut pri = (0..paused.info.file_infos.len()).collect::<Vec<usize>>();
|
||||||
// sort by filename, cause many torrents have random sort order.
|
// sort by filename, cause many torrents have random sort order.
|
||||||
pri.sort_unstable_by_key(|id| paused.files.get(*id).map(|op| op.filename.as_path()));
|
pri.sort_unstable_by_key(|id| {
|
||||||
|
paused
|
||||||
|
.info
|
||||||
|
.file_infos
|
||||||
|
.get(*id)
|
||||||
|
.map(|fi| fi.filename.as_path())
|
||||||
|
});
|
||||||
pri
|
pri
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -482,7 +489,12 @@ impl TorrentStateLive {
|
||||||
self.meta.peer_id
|
self.meta.peer_id
|
||||||
}
|
}
|
||||||
pub(crate) fn file_ops(&self) -> FileOps<'_> {
|
pub(crate) fn file_ops(&self) -> FileOps<'_> {
|
||||||
FileOps::new(&self.meta.info, &self.files, &self.lengths)
|
FileOps::new(
|
||||||
|
&self.meta.info,
|
||||||
|
&self.files,
|
||||||
|
&self.meta().file_infos,
|
||||||
|
&self.lengths,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn lock_read(
|
pub(crate) fn lock_read(
|
||||||
|
|
@ -632,12 +644,6 @@ impl TorrentStateLive {
|
||||||
// It should be impossible to make a fatal error after pausing.
|
// It should be impossible to make a fatal error after pausing.
|
||||||
g.fatal_errors_tx.take();
|
g.fatal_errors_tx.take();
|
||||||
|
|
||||||
let files = self
|
|
||||||
.files
|
|
||||||
.iter()
|
|
||||||
.map(|f| f.take_clone())
|
|
||||||
.collect::<anyhow::Result<Vec<_>>>()?;
|
|
||||||
|
|
||||||
let mut chunk_tracker = g
|
let mut chunk_tracker = g
|
||||||
.chunks
|
.chunks
|
||||||
.take()
|
.take()
|
||||||
|
|
@ -649,7 +655,7 @@ impl TorrentStateLive {
|
||||||
// g.chunks;
|
// g.chunks;
|
||||||
Ok(TorrentStatePaused {
|
Ok(TorrentStatePaused {
|
||||||
info: self.meta.clone(),
|
info: self.meta.clone(),
|
||||||
files,
|
files: self.files.take()?,
|
||||||
chunk_tracker,
|
chunk_tracker,
|
||||||
streams: self.streams.clone(),
|
streams: self.streams.clone(),
|
||||||
})
|
})
|
||||||
|
|
@ -671,7 +677,7 @@ impl TorrentStateLive {
|
||||||
pub(crate) fn update_only_files(&self, only_files: &HashSet<usize>) -> anyhow::Result<()> {
|
pub(crate) fn update_only_files(&self, only_files: &HashSet<usize>) -> anyhow::Result<()> {
|
||||||
let mut g = self.lock_write("update_only_files");
|
let mut g = self.lock_write("update_only_files");
|
||||||
let ct = g.get_chunks_mut()?;
|
let ct = g.get_chunks_mut()?;
|
||||||
let hns = ct.update_only_files(self.files.iter().map(|f| f.len), only_files)?;
|
let hns = ct.update_only_files(self.meta().file_infos.iter().map(|f| f.len), only_files)?;
|
||||||
if !hns.finished() {
|
if !hns.finished() {
|
||||||
self.reconnect_all_not_needed_peers();
|
self.reconnect_all_not_needed_peers();
|
||||||
}
|
}
|
||||||
|
|
@ -682,41 +688,49 @@ impl TorrentStateLive {
|
||||||
self.get_hns().map(|h| h.finished()).unwrap_or_default()
|
self.get_hns().map(|h| h.finished()).unwrap_or_default()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn has_active_streams_unfinished_files(&self) -> bool {
|
fn has_active_streams_unfinished_files(&self, state: &TorrentStateLocked) -> bool {
|
||||||
|
let chunks = match state.get_chunks() {
|
||||||
|
Ok(c) => c,
|
||||||
|
Err(_) => return false,
|
||||||
|
};
|
||||||
self.streams
|
self.streams
|
||||||
.streamed_file_ids()
|
.streamed_file_ids()
|
||||||
.any(|file_id| !self.files[file_id].approx_is_finished())
|
.any(|file_id| !chunks.is_file_finished(&self.meta.file_infos[file_id]))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn is_finished_and_dont_need_peers(&self) -> bool {
|
fn is_finished_and_dont_need_peers(&self) -> bool {
|
||||||
self.is_finished() && !self.has_active_streams_unfinished_files()
|
self.is_finished()
|
||||||
|
&& !self.has_active_streams_unfinished_files(
|
||||||
|
&self.lock_read("is_finished_and_dont_need_peers"),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_piece_completed(&self, id: ValidPieceIndex) -> anyhow::Result<()> {
|
fn on_piece_completed(&self, id: ValidPieceIndex) -> anyhow::Result<()> {
|
||||||
|
let mut g = self.lock_write("on_piece_completed");
|
||||||
|
let chunks = g.get_chunks_mut()?;
|
||||||
|
|
||||||
// if we have all the pieces of the file, reopen it read only
|
// if we have all the pieces of the file, reopen it read only
|
||||||
for (idx, opened_file) in self
|
for (idx, file_info) in self
|
||||||
.files
|
.meta()
|
||||||
|
.file_infos
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.skip_while(|fd| !fd.1.piece_range.contains(&id.get()))
|
.skip_while(|(_, fi)| !fi.piece_range.contains(&id.get()))
|
||||||
.take_while(|fd| fd.1.piece_range.contains(&id.get()))
|
.take_while(|(_, fi)| fi.piece_range.contains(&id.get()))
|
||||||
{
|
{
|
||||||
let bytes = opened_file.update_have_on_piece_completed(id.get(), &self.lengths);
|
let _remaining = chunks.update_file_have_on_piece_completed(id, idx, file_info);
|
||||||
if bytes == 0 {
|
|
||||||
warn!(file_id=idx, piece_id=id.get(), "bug: update_have_on_piece_completed() returned 0, although this piece is present in the file");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.streams
|
self.streams
|
||||||
.wake_streams_on_piece_completed(id, &self.meta.lengths);
|
.wake_streams_on_piece_completed(id, &self.meta.lengths);
|
||||||
|
|
||||||
if self.is_finished() {
|
if chunks.is_finished() {
|
||||||
if self.lock_read("chunks").get_chunks()?.get_selected_pieces()[id.get_usize()] {
|
if chunks.get_selected_pieces()[id.get_usize()] {
|
||||||
info!("torrent finished downloading");
|
info!("torrent finished downloading");
|
||||||
}
|
}
|
||||||
self.finished_notify.notify_waiters();
|
self.finished_notify.notify_waiters();
|
||||||
|
|
||||||
if !self.has_active_streams_unfinished_files() {
|
if !self.has_active_streams_unfinished_files(&g) {
|
||||||
// There is not poing being connected to peers that have all the torrent, when
|
// There is not poing being connected to peers that have all the torrent, when
|
||||||
// we don't need anything from them, and they don't need anything from us.
|
// we don't need anything from them, and they don't need anything from us.
|
||||||
self.disconnect_all_peers_that_have_full_torrent();
|
self.disconnect_all_peers_that_have_full_torrent();
|
||||||
|
|
@ -749,13 +763,6 @@ impl TorrentStateLive {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn get_file_progress(&self) -> Vec<u64> {
|
|
||||||
self.files
|
|
||||||
.iter()
|
|
||||||
.map(|fd| fd.have.load(Ordering::Relaxed))
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct PeerHandlerLocked {
|
struct PeerHandlerLocked {
|
||||||
|
|
@ -989,8 +996,8 @@ impl PeerHandler {
|
||||||
!chunk_tracker.is_piece_have(*pid)
|
!chunk_tracker.is_piece_have(*pid)
|
||||||
&& !g.inflight_pieces.contains_key(pid)
|
&& !g.inflight_pieces.contains_key(pid)
|
||||||
});
|
});
|
||||||
let natural_order_pieces =
|
let natural_order_pieces = chunk_tracker
|
||||||
chunk_tracker.iter_queued_pieces(&g.file_priorities, &self.state.files);
|
.iter_queued_pieces(&g.file_priorities, &self.state.meta().file_infos);
|
||||||
for n in priority_streamed_pieces.chain(natural_order_pieces) {
|
for n in priority_streamed_pieces.chain(natural_order_pieces) {
|
||||||
if bf.get(n.get() as usize).map(|v| *v) == Some(true) {
|
if bf.get(n.get() as usize).map(|v| *v) == Some(true) {
|
||||||
n_opt = Some(n);
|
n_opt = Some(n);
|
||||||
|
|
|
||||||
|
|
@ -34,8 +34,10 @@ use tracing::error_span;
|
||||||
use tracing::warn;
|
use tracing::warn;
|
||||||
|
|
||||||
use crate::chunk_tracker::ChunkTracker;
|
use crate::chunk_tracker::ChunkTracker;
|
||||||
|
use crate::file_info::FileInfo;
|
||||||
use crate::spawn_utils::BlockingSpawner;
|
use crate::spawn_utils::BlockingSpawner;
|
||||||
use crate::torrent_state::stats::LiveStats;
|
use crate::torrent_state::stats::LiveStats;
|
||||||
|
use crate::type_aliases::FileInfos;
|
||||||
use crate::type_aliases::PeerStream;
|
use crate::type_aliases::PeerStream;
|
||||||
|
|
||||||
use initializing::TorrentStateInitializing;
|
use initializing::TorrentStateInitializing;
|
||||||
|
|
@ -98,6 +100,7 @@ pub struct ManagedTorrentInfo {
|
||||||
pub trackers: HashSet<String>,
|
pub trackers: HashSet<String>,
|
||||||
pub peer_id: Id20,
|
pub peer_id: Id20,
|
||||||
pub lengths: Lengths,
|
pub lengths: Lengths,
|
||||||
|
pub file_infos: FileInfos,
|
||||||
pub span: tracing::Span,
|
pub span: tracing::Span,
|
||||||
pub(crate) options: ManagedTorrentOptions,
|
pub(crate) options: ManagedTorrentOptions,
|
||||||
}
|
}
|
||||||
|
|
@ -370,11 +373,7 @@ impl ManagedTorrent {
|
||||||
resp.total_bytes = hns.total();
|
resp.total_bytes = hns.total();
|
||||||
resp.progress_bytes = hns.progress();
|
resp.progress_bytes = hns.progress();
|
||||||
resp.finished = hns.finished();
|
resp.finished = hns.finished();
|
||||||
resp.file_progress = p
|
resp.file_progress = p.chunk_tracker.per_file_have_bytes().to_owned();
|
||||||
.files
|
|
||||||
.iter()
|
|
||||||
.map(|f| f.have.load(Ordering::Relaxed))
|
|
||||||
.collect();
|
|
||||||
}
|
}
|
||||||
ManagedTorrentState::Live(l) => {
|
ManagedTorrentState::Live(l) => {
|
||||||
resp.state = S::Live;
|
resp.state = S::Live;
|
||||||
|
|
@ -384,7 +383,12 @@ impl ManagedTorrent {
|
||||||
resp.progress_bytes = hns.progress();
|
resp.progress_bytes = hns.progress();
|
||||||
resp.finished = hns.finished();
|
resp.finished = hns.finished();
|
||||||
resp.uploaded_bytes = l.get_uploaded_bytes();
|
resp.uploaded_bytes = l.get_uploaded_bytes();
|
||||||
resp.file_progress = l.get_file_progress();
|
resp.file_progress = l
|
||||||
|
.lock_read("file_progress")
|
||||||
|
.get_chunks()
|
||||||
|
.ok()
|
||||||
|
.map(|c| c.per_file_have_bytes().to_owned())
|
||||||
|
.unwrap_or_default();
|
||||||
resp.live = Some(live_stats);
|
resp.live = Some(live_stats);
|
||||||
}
|
}
|
||||||
ManagedTorrentState::Error(e) => {
|
ManagedTorrentState::Error(e) => {
|
||||||
|
|
@ -534,8 +538,21 @@ impl ManagedTorrentBuilder {
|
||||||
|
|
||||||
pub(crate) fn build(self, span: tracing::Span) -> anyhow::Result<ManagedTorrentHandle> {
|
pub(crate) fn build(self, span: tracing::Span) -> anyhow::Result<ManagedTorrentHandle> {
|
||||||
let lengths = Lengths::from_torrent(&self.info)?;
|
let lengths = Lengths::from_torrent(&self.info)?;
|
||||||
|
let file_infos = self
|
||||||
|
.info
|
||||||
|
.iter_file_details(&lengths)?
|
||||||
|
.map(|fd| {
|
||||||
|
Ok::<_, anyhow::Error>(FileInfo {
|
||||||
|
filename: self.output_folder.join(fd.filename.to_pathbuf()?),
|
||||||
|
offset_in_torrent: fd.offset,
|
||||||
|
piece_range: fd.pieces,
|
||||||
|
len: fd.len,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect::<anyhow::Result<Vec<FileInfo>>>()?;
|
||||||
let info = Arc::new(ManagedTorrentInfo {
|
let info = Arc::new(ManagedTorrentInfo {
|
||||||
span,
|
span,
|
||||||
|
file_infos,
|
||||||
info: self.info,
|
info: self.info,
|
||||||
info_hash: self.info_hash,
|
info_hash: self.info_hash,
|
||||||
out_dir: self.output_folder,
|
out_dir: self.output_folder,
|
||||||
|
|
|
||||||
|
|
@ -2,14 +2,14 @@ use std::{collections::HashSet, sync::Arc};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
chunk_tracker::{ChunkTracker, HaveNeededSelected},
|
chunk_tracker::{ChunkTracker, HaveNeededSelected},
|
||||||
type_aliases::OpenedFiles,
|
type_aliases::FileStorage,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{streaming::TorrentStreams, ManagedTorrentInfo};
|
use super::{streaming::TorrentStreams, ManagedTorrentInfo};
|
||||||
|
|
||||||
pub struct TorrentStatePaused {
|
pub struct TorrentStatePaused {
|
||||||
pub(crate) info: Arc<ManagedTorrentInfo>,
|
pub(crate) info: Arc<ManagedTorrentInfo>,
|
||||||
pub(crate) files: OpenedFiles,
|
pub(crate) files: FileStorage,
|
||||||
pub(crate) chunk_tracker: ChunkTracker,
|
pub(crate) chunk_tracker: ChunkTracker,
|
||||||
pub(crate) streams: Arc<TorrentStreams>,
|
pub(crate) streams: Arc<TorrentStreams>,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use std::{
|
use std::{
|
||||||
collections::VecDeque,
|
collections::VecDeque,
|
||||||
io::{Read, Seek, SeekFrom},
|
io::SeekFrom,
|
||||||
sync::{
|
sync::{
|
||||||
atomic::{AtomicUsize, Ordering},
|
atomic::{AtomicUsize, Ordering},
|
||||||
Arc,
|
Arc,
|
||||||
|
|
@ -10,11 +10,12 @@ use std::{
|
||||||
|
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use dashmap::DashMap;
|
use dashmap::DashMap;
|
||||||
|
|
||||||
use librqbit_core::lengths::{Lengths, ValidPieceIndex};
|
use librqbit_core::lengths::{Lengths, ValidPieceIndex};
|
||||||
use tokio::io::{AsyncRead, AsyncSeek};
|
use tokio::io::{AsyncRead, AsyncSeek};
|
||||||
use tracing::{debug, trace};
|
use tracing::{debug, trace};
|
||||||
|
|
||||||
use crate::{opened_file::OpenedFile, type_aliases::OpenedFiles, ManagedTorrent};
|
use crate::{file_info::FileInfo, storage::TorrentStorage, ManagedTorrent};
|
||||||
|
|
||||||
use super::ManagedTorrentHandle;
|
use super::ManagedTorrentHandle;
|
||||||
|
|
||||||
|
|
@ -236,18 +237,22 @@ impl AsyncRead for FileStream {
|
||||||
"will write bytes"
|
"will write bytes"
|
||||||
);
|
);
|
||||||
|
|
||||||
poll_try_io!(poll_try_io!(self.torrent.with_opened_file(
|
poll_try_io!(poll_try_io!(self.torrent.with_storage_and_file(
|
||||||
self.file_id,
|
self.file_id,
|
||||||
|fd| {
|
|files, _fi| {
|
||||||
let mut g = fd.file.lock();
|
files.pread_exact(self.file_id, self.position, buf)?;
|
||||||
g.seek(SeekFrom::Start(self.position))?;
|
|
||||||
g.read_exact(buf)?;
|
|
||||||
Ok::<_, anyhow::Error>(())
|
Ok::<_, anyhow::Error>(())
|
||||||
}
|
}
|
||||||
)));
|
)));
|
||||||
|
|
||||||
self.as_mut().advance(bytes_to_read as u64);
|
self.as_mut().advance(bytes_to_read as u64);
|
||||||
tbuf.advance(bytes_to_read);
|
tbuf.advance(bytes_to_read);
|
||||||
|
self.streams
|
||||||
|
.streams
|
||||||
|
.get_mut(&self.stream_id)
|
||||||
|
.unwrap()
|
||||||
|
.value_mut()
|
||||||
|
.position = self.position;
|
||||||
|
|
||||||
Poll::Ready(Ok(()))
|
Poll::Ready(Ok(()))
|
||||||
}
|
}
|
||||||
|
|
@ -292,30 +297,25 @@ impl Drop for FileStream {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ManagedTorrent {
|
impl ManagedTorrent {
|
||||||
fn with_opened_files<F, R>(&self, f: F) -> anyhow::Result<R>
|
fn with_storage_and_file<F, R>(&self, file_id: usize, f: F) -> anyhow::Result<R>
|
||||||
where
|
where
|
||||||
F: FnOnce(&OpenedFiles) -> R,
|
F: FnOnce(&dyn TorrentStorage, &FileInfo) -> R,
|
||||||
{
|
{
|
||||||
self.with_state(|s| {
|
self.with_state(|s| {
|
||||||
let files = match s {
|
let files = match s {
|
||||||
crate::ManagedTorrentState::Paused(p) => &p.files,
|
crate::ManagedTorrentState::Paused(p) => &p.files,
|
||||||
crate::ManagedTorrentState::Live(l) => &l.files,
|
crate::ManagedTorrentState::Live(l) => &l.files,
|
||||||
s => anyhow::bail!("with_opened_file: invalid state {}", s.name()),
|
_ => anyhow::bail!("invalid state"),
|
||||||
};
|
};
|
||||||
Ok(f(files))
|
let fi = self
|
||||||
|
.info()
|
||||||
|
.file_infos
|
||||||
|
.get(file_id)
|
||||||
|
.context("invalid file")?;
|
||||||
|
Ok(f(files, fi))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn with_opened_file<F, R>(&self, file_id: usize, f: F) -> anyhow::Result<R>
|
|
||||||
where
|
|
||||||
F: FnOnce(&OpenedFile) -> R,
|
|
||||||
{
|
|
||||||
self.with_opened_files(|opened_files| {
|
|
||||||
let fd = opened_files.get(file_id).context("invalid file id")?;
|
|
||||||
Ok(f(fd))
|
|
||||||
})?
|
|
||||||
}
|
|
||||||
|
|
||||||
fn streams(&self) -> anyhow::Result<Arc<TorrentStreams>> {
|
fn streams(&self) -> anyhow::Result<Arc<TorrentStreams>> {
|
||||||
self.with_state(|s| match s {
|
self.with_state(|s| match s {
|
||||||
crate::ManagedTorrentState::Paused(p) => Ok(p.streams.clone()),
|
crate::ManagedTorrentState::Paused(p) => Ok(p.streams.clone()),
|
||||||
|
|
@ -326,7 +326,7 @@ impl ManagedTorrent {
|
||||||
|
|
||||||
fn maybe_reconnect_needed_peers_for_file(&self, file_id: usize) -> bool {
|
fn maybe_reconnect_needed_peers_for_file(&self, file_id: usize) -> bool {
|
||||||
// If we have the full file, don't bother.
|
// If we have the full file, don't bother.
|
||||||
if let Ok(true) = self.with_opened_file(file_id, |f| f.approx_is_finished()) {
|
if self.is_file_finished(file_id) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
self.with_state(|state| {
|
self.with_state(|state| {
|
||||||
|
|
@ -337,9 +337,14 @@ impl ManagedTorrent {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_file_finished(&self, file_id: usize) -> bool {
|
||||||
|
self.with_chunk_tracker(|ct| ct.is_file_finished(&self.info.file_infos[file_id]))
|
||||||
|
.unwrap_or(false)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn stream(self: Arc<Self>, file_id: usize) -> anyhow::Result<FileStream> {
|
pub fn stream(self: Arc<Self>, file_id: usize) -> anyhow::Result<FileStream> {
|
||||||
let (fd_len, fd_offset) =
|
let (fd_len, fd_offset) =
|
||||||
self.with_opened_file(file_id, |fd| (fd.len, fd.offset_in_torrent))?;
|
self.with_storage_and_file(file_id, |_fd, fi| (fi.len, fi.offset_in_torrent))?;
|
||||||
let streams = self.streams()?;
|
let streams = self.streams()?;
|
||||||
let s = FileStream {
|
let s = FileStream {
|
||||||
stream_id: streams.next_id(),
|
stream_id: streams.next_id(),
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,12 @@ use std::net::SocketAddr;
|
||||||
|
|
||||||
use futures::stream::BoxStream;
|
use futures::stream::BoxStream;
|
||||||
|
|
||||||
use crate::opened_file::OpenedFile;
|
use crate::{file_info::FileInfo, storage::TorrentStorage};
|
||||||
|
|
||||||
pub type BF = bitvec::boxed::BitBox<u8, bitvec::order::Msb0>;
|
pub type BF = bitvec::boxed::BitBox<u8, bitvec::order::Msb0>;
|
||||||
|
|
||||||
pub type PeerHandle = SocketAddr;
|
pub type PeerHandle = SocketAddr;
|
||||||
pub type PeerStream = BoxStream<'static, SocketAddr>;
|
pub type PeerStream = BoxStream<'static, SocketAddr>;
|
||||||
pub(crate) type OpenedFiles = Vec<OpenedFile>;
|
pub(crate) type FileInfos = Vec<FileInfo>;
|
||||||
|
pub(crate) type FileStorage = Box<dyn TorrentStorage>;
|
||||||
pub(crate) type FilePriorities = Vec<usize>;
|
pub(crate) type FilePriorities = Vec<usize>;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue