2024-03-30 11:46:29 +00:00
|
|
|
use anyhow::Context;
|
2021-07-03 19:10:59 +01:00
|
|
|
use librqbit_core::lengths::{ChunkInfo, Lengths, ValidPieceIndex};
|
|
|
|
|
use peer_binary_protocol::Piece;
|
2023-11-25 01:24:57 +00:00
|
|
|
use tracing::{debug, trace};
|
2021-06-25 13:47:51 +01:00
|
|
|
|
2021-07-03 19:10:59 +01:00
|
|
|
use crate::type_aliases::BF;
|
2021-06-25 13:47:51 +01:00
|
|
|
|
2023-11-24 15:36:37 +00:00
|
|
|
pub struct ChunkTracker {
|
2021-06-26 18:13:46 +01:00
|
|
|
// This forms the basis of a "queue" to pull from.
|
|
|
|
|
// It's set to 1 if we need a piece, but the moment we start requesting a peer,
|
|
|
|
|
// it's set to 0.
|
2023-11-20 10:14:08 +00:00
|
|
|
//
|
|
|
|
|
// Initially this is the opposite of "have", until we start making requests.
|
|
|
|
|
// An in-flight request is not in "needed", and not in "have".
|
2021-06-25 13:47:51 +01:00
|
|
|
needed_pieces: BF,
|
2021-06-26 18:13:46 +01:00
|
|
|
|
|
|
|
|
// This has a bit set per each chunk (block) that we have written to the output file.
|
|
|
|
|
// It doesn't mean it's valid yet. Used to track how much is left in each piece.
|
2021-06-25 13:47:51 +01:00
|
|
|
chunk_status: BF,
|
2021-06-26 18:13:46 +01:00
|
|
|
|
|
|
|
|
// These are the pieces that we actually have, fully checked and downloaded.
|
|
|
|
|
have: BF,
|
|
|
|
|
|
2021-06-25 13:47:51 +01:00
|
|
|
lengths: Lengths,
|
2021-10-18 16:07:30 +01:00
|
|
|
|
2023-11-20 10:14:08 +00:00
|
|
|
// What pieces to download first.
|
2021-10-18 16:07:30 +01:00
|
|
|
priority_piece_ids: Vec<usize>,
|
2023-12-14 11:58:09 +00:00
|
|
|
|
|
|
|
|
total_selected_bytes: u64,
|
2021-06-25 13:47:51 +01:00
|
|
|
}
|
|
|
|
|
|
2021-06-26 18:13:46 +01:00
|
|
|
// TODO: this should be redone from "have" pieces, not from "needed" pieces.
|
|
|
|
|
// Needed pieces are the ones we need to download, not necessarily the ones we have.
|
|
|
|
|
// E.g. we might have more pieces, but the client asks to download only some files
|
|
|
|
|
// partially.
|
2024-03-30 11:46:29 +00:00
|
|
|
fn compute_chunk_status(lengths: &Lengths, needed_pieces: &BF) -> anyhow::Result<BF> {
|
2024-03-30 14:29:59 +00:00
|
|
|
if needed_pieces.len() != lengths.total_pieces() as usize {
|
|
|
|
|
anyhow::bail!(
|
|
|
|
|
"bug: needed_pieces.len() != lengths.total_pieces(); {} != {}",
|
|
|
|
|
needed_pieces.len(),
|
|
|
|
|
lengths.total_pieces()
|
|
|
|
|
);
|
|
|
|
|
}
|
2021-06-26 17:29:59 +01:00
|
|
|
let required_size = lengths.chunk_bitfield_bytes();
|
2021-06-25 13:47:51 +01:00
|
|
|
let vec = vec![0u8; required_size];
|
2024-03-29 11:00:58 +00:00
|
|
|
let mut chunk_bf = BF::from_boxed_slice(vec.into_boxed_slice());
|
2024-03-30 11:46:29 +00:00
|
|
|
let range = 0..lengths.total_pieces() as usize;
|
2021-06-26 17:29:59 +01:00
|
|
|
for piece_index in needed_pieces
|
2024-03-30 11:46:29 +00:00
|
|
|
.get(range.clone())
|
2024-03-30 14:29:59 +00:00
|
|
|
.with_context(|| format!("bug: error getting range {range:?} from needed_pieces. needed_pieces.len() = {}, range={:?}", needed_pieces.len(), range))?
|
2021-06-26 17:29:59 +01:00
|
|
|
.iter_zeros()
|
|
|
|
|
{
|
2024-03-30 12:33:11 +00:00
|
|
|
let offset = piece_index * lengths.default_chunks_per_piece() as usize;
|
2024-03-30 14:29:59 +00:00
|
|
|
let chunks_per_piece = lengths.chunks_per_piece(
|
|
|
|
|
lengths
|
|
|
|
|
.try_validate_piece_index(piece_index as u32)
|
|
|
|
|
.context("bug")?,
|
|
|
|
|
) as usize;
|
2024-03-30 11:46:29 +00:00
|
|
|
let range = offset..offset + chunks_per_piece;
|
2021-06-26 17:32:17 +01:00
|
|
|
chunk_bf
|
2024-03-30 11:46:29 +00:00
|
|
|
.get_mut(range.clone())
|
2024-03-30 14:29:59 +00:00
|
|
|
.with_context(|| format!("bug: error getting range {range:?} from chunk_bf"))?
|
2022-12-04 13:11:40 +00:00
|
|
|
.fill(true);
|
2021-06-25 13:47:51 +01:00
|
|
|
}
|
2024-03-30 11:46:29 +00:00
|
|
|
Ok(chunk_bf)
|
2021-06-25 13:47:51 +01:00
|
|
|
}
|
|
|
|
|
|
2023-11-24 15:36:37 +00:00
|
|
|
pub enum ChunkMarkingResult {
|
2021-06-27 10:10:59 +01:00
|
|
|
PreviouslyCompleted,
|
|
|
|
|
NotCompleted,
|
|
|
|
|
Completed,
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-25 13:47:51 +01:00
|
|
|
impl ChunkTracker {
|
2023-12-14 11:58:09 +00:00
|
|
|
pub fn new(
|
|
|
|
|
needed_pieces: BF,
|
|
|
|
|
have_pieces: BF,
|
|
|
|
|
lengths: Lengths,
|
|
|
|
|
total_selected_bytes: u64,
|
2024-03-30 11:46:29 +00:00
|
|
|
) -> anyhow::Result<Self> {
|
2021-10-18 16:07:30 +01:00
|
|
|
// TODO: ideally this needs to be a list based on needed files, e.g.
|
|
|
|
|
// last needed piece for each file. But let's keep simple for now.
|
2023-12-02 13:01:50 +00:00
|
|
|
|
|
|
|
|
// TODO: bitvec is bugged, the short version panics.
|
|
|
|
|
// let last_needed_piece_id = needed_pieces.iter_ones().next_back();
|
|
|
|
|
let last_needed_piece_id = needed_pieces
|
|
|
|
|
.iter()
|
|
|
|
|
.enumerate()
|
|
|
|
|
.filter_map(|(id, b)| if *b { Some(id) } else { None })
|
|
|
|
|
.last();
|
2021-10-18 16:07:30 +01:00
|
|
|
|
|
|
|
|
// The last pieces first. Often important information is stored in the last piece.
|
|
|
|
|
// E.g. if it's a video file, than the last piece often contains some index, or just
|
|
|
|
|
// players look into it, and it's better be there.
|
|
|
|
|
let priority_piece_ids = last_needed_piece_id.into_iter().collect();
|
2024-03-30 11:46:29 +00:00
|
|
|
Ok(Self {
|
|
|
|
|
chunk_status: compute_chunk_status(&lengths, &needed_pieces)
|
|
|
|
|
.context("error computing chunk status")?,
|
2021-06-25 13:47:51 +01:00
|
|
|
needed_pieces,
|
|
|
|
|
lengths,
|
2021-06-26 18:13:46 +01:00
|
|
|
have: have_pieces,
|
2021-10-18 16:07:30 +01:00
|
|
|
priority_piece_ids,
|
2023-12-14 11:58:09 +00:00
|
|
|
total_selected_bytes,
|
2024-03-30 11:46:29 +00:00
|
|
|
})
|
2021-06-25 13:47:51 +01:00
|
|
|
}
|
2023-11-23 16:57:27 +00:00
|
|
|
|
2023-12-14 11:58:09 +00:00
|
|
|
pub fn get_total_selected_bytes(&self) -> u64 {
|
|
|
|
|
self.total_selected_bytes
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-24 12:44:36 +00:00
|
|
|
pub fn get_lengths(&self) -> &Lengths {
|
|
|
|
|
&self.lengths
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-26 21:00:17 +01:00
|
|
|
pub fn get_have_pieces(&self) -> &BF {
|
|
|
|
|
&self.have
|
|
|
|
|
}
|
2021-06-25 13:47:51 +01:00
|
|
|
pub fn reserve_needed_piece(&mut self, index: ValidPieceIndex) {
|
|
|
|
|
self.needed_pieces.set(index.get() as usize, false)
|
|
|
|
|
}
|
2021-06-27 11:01:41 +01:00
|
|
|
|
2023-11-25 00:54:21 +00:00
|
|
|
pub fn calc_have_bytes(&self) -> u64 {
|
|
|
|
|
self.have
|
|
|
|
|
.iter_ones()
|
|
|
|
|
.filter_map(|piece_id| {
|
|
|
|
|
let piece_id = self.lengths.validate_piece_index(piece_id as u32)?;
|
|
|
|
|
Some(self.lengths.piece_length(piece_id) as u64)
|
|
|
|
|
})
|
|
|
|
|
.sum()
|
|
|
|
|
}
|
|
|
|
|
|
2023-12-14 11:58:09 +00:00
|
|
|
pub fn calc_needed_bytes(&self) -> u64 {
|
|
|
|
|
self.needed_pieces
|
|
|
|
|
.iter_ones()
|
|
|
|
|
.filter_map(|piece_id| {
|
|
|
|
|
let piece_id = self.lengths.validate_piece_index(piece_id as u32)?;
|
|
|
|
|
Some(self.lengths.piece_length(piece_id) as u64)
|
|
|
|
|
})
|
|
|
|
|
.sum()
|
|
|
|
|
}
|
|
|
|
|
|
2021-10-16 13:18:25 +01:00
|
|
|
pub fn iter_needed_pieces(&self) -> impl Iterator<Item = usize> + '_ {
|
2021-10-18 16:07:30 +01:00
|
|
|
self.priority_piece_ids
|
|
|
|
|
.iter()
|
|
|
|
|
.copied()
|
|
|
|
|
.filter(move |piece_id| self.needed_pieces[*piece_id])
|
|
|
|
|
.chain(
|
|
|
|
|
self.needed_pieces
|
|
|
|
|
.iter_ones()
|
|
|
|
|
.filter(move |id| !self.priority_piece_ids.contains(id)),
|
|
|
|
|
)
|
2021-10-16 13:18:25 +01:00
|
|
|
}
|
|
|
|
|
|
2021-06-27 11:01:41 +01:00
|
|
|
// None if wrong chunk
|
|
|
|
|
// true if did something
|
|
|
|
|
// false if didn't do anything
|
|
|
|
|
pub fn mark_chunk_request_cancelled(
|
|
|
|
|
&mut self,
|
|
|
|
|
index: ValidPieceIndex,
|
2021-06-28 11:36:47 +01:00
|
|
|
_chunk: u32,
|
2021-06-27 11:01:41 +01:00
|
|
|
) -> Option<bool> {
|
|
|
|
|
if *self.have.get(index.get() as usize)? {
|
|
|
|
|
return Some(false);
|
|
|
|
|
}
|
|
|
|
|
// This will trigger the requesters to re-check each chunk in this piece.
|
|
|
|
|
let chunk_range = self.lengths.chunk_range(index);
|
|
|
|
|
if !self.chunk_status.get(chunk_range)?.all() {
|
|
|
|
|
self.needed_pieces.set(index.get() as usize, true);
|
|
|
|
|
}
|
|
|
|
|
Some(true)
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-02 18:52:00 +00:00
|
|
|
pub fn mark_piece_broken_if_not_have(&mut self, index: ValidPieceIndex) {
|
|
|
|
|
if self
|
|
|
|
|
.have
|
|
|
|
|
.get(index.get() as usize)
|
|
|
|
|
.map(|r| *r)
|
|
|
|
|
.unwrap_or_default()
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
2023-11-25 01:24:57 +00:00
|
|
|
debug!("remarking piece={} as broken", index);
|
2021-06-25 13:47:51 +01:00
|
|
|
self.needed_pieces.set(index.get() as usize, true);
|
2024-01-02 18:52:00 +00:00
|
|
|
if let Some(s) = self.chunk_status.get_mut(self.lengths.chunk_range(index)) {
|
|
|
|
|
s.fill(false);
|
|
|
|
|
}
|
2021-06-25 13:47:51 +01:00
|
|
|
}
|
|
|
|
|
|
2021-06-26 18:13:46 +01:00
|
|
|
pub fn mark_piece_downloaded(&mut self, idx: ValidPieceIndex) {
|
2023-07-10 11:24:30 +01:00
|
|
|
self.have.set(idx.get() as usize, true);
|
2021-06-26 18:13:46 +01:00
|
|
|
}
|
|
|
|
|
|
2021-06-28 22:21:21 +01:00
|
|
|
pub fn is_chunk_ready_to_upload(&self, chunk: &ChunkInfo) -> bool {
|
|
|
|
|
self.have
|
|
|
|
|
.get(chunk.piece_index.get() as usize)
|
|
|
|
|
.map(|b| *b)
|
|
|
|
|
.unwrap_or(false)
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-25 13:47:51 +01:00
|
|
|
// return true if the whole piece is marked downloaded
|
2021-06-28 16:37:15 +01:00
|
|
|
pub fn mark_chunk_downloaded<ByteBuf>(
|
2021-06-27 10:10:59 +01:00
|
|
|
&mut self,
|
2021-06-28 16:37:15 +01:00
|
|
|
piece: &Piece<ByteBuf>,
|
|
|
|
|
) -> Option<ChunkMarkingResult>
|
|
|
|
|
where
|
|
|
|
|
ByteBuf: AsRef<[u8]>,
|
|
|
|
|
{
|
2024-03-30 14:12:54 +00:00
|
|
|
let chunk_info = self.lengths.chunk_info_from_received_data(
|
|
|
|
|
self.lengths.validate_piece_index(piece.index)?,
|
2021-07-03 19:10:59 +01:00
|
|
|
piece.begin,
|
|
|
|
|
piece.block.as_ref().len() as u32,
|
|
|
|
|
)?;
|
2021-06-25 13:47:51 +01:00
|
|
|
let chunk_range = self.lengths.chunk_range(chunk_info.piece_index);
|
2021-06-27 10:10:59 +01:00
|
|
|
let chunk_range = self.chunk_status.get_mut(chunk_range).unwrap();
|
|
|
|
|
if chunk_range.all() {
|
|
|
|
|
return Some(ChunkMarkingResult::PreviouslyCompleted);
|
|
|
|
|
}
|
|
|
|
|
chunk_range.set(chunk_info.chunk_index as usize, true);
|
2023-11-25 01:24:57 +00:00
|
|
|
trace!(
|
2021-06-25 13:47:51 +01:00
|
|
|
"piece={}, chunk_info={:?}, bits={:?}",
|
2023-11-25 01:24:57 +00:00
|
|
|
piece.index,
|
|
|
|
|
chunk_info,
|
|
|
|
|
chunk_range,
|
2021-06-25 13:47:51 +01:00
|
|
|
);
|
2021-06-27 10:10:59 +01:00
|
|
|
|
|
|
|
|
if chunk_range.all() {
|
|
|
|
|
return Some(ChunkMarkingResult::Completed);
|
|
|
|
|
}
|
2021-07-02 10:21:19 +01:00
|
|
|
Some(ChunkMarkingResult::NotCompleted)
|
2021-06-25 13:47:51 +01:00
|
|
|
}
|
|
|
|
|
}
|
2024-03-30 14:23:55 +00:00
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod tests {
|
|
|
|
|
use librqbit_core::{constants::CHUNK_SIZE, lengths::Lengths};
|
|
|
|
|
|
|
|
|
|
use crate::type_aliases::BF;
|
|
|
|
|
|
|
|
|
|
use super::compute_chunk_status;
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_compute_chunk_status() {
|
|
|
|
|
// Create the most obnoxious lenghts, and ensure it doesn't break in that case.
|
|
|
|
|
let piece_length = CHUNK_SIZE * 2 + 1;
|
|
|
|
|
let l = Lengths::new(piece_length as u64 * 2 + 1, piece_length).unwrap();
|
|
|
|
|
|
|
|
|
|
assert_eq!(l.total_pieces(), 3);
|
|
|
|
|
assert_eq!(l.default_chunks_per_piece(), 3);
|
|
|
|
|
assert_eq!(l.total_chunks(), 7);
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
let mut needed_pieces =
|
|
|
|
|
BF::from_boxed_slice(vec![0u8; l.piece_bitfield_bytes()].into_boxed_slice());
|
|
|
|
|
needed_pieces.set(0, true);
|
|
|
|
|
|
|
|
|
|
let chunks = compute_chunk_status(&l, &needed_pieces).unwrap();
|
|
|
|
|
dbg!(&chunks);
|
|
|
|
|
assert_eq!(chunks[0], false);
|
|
|
|
|
assert_eq!(chunks[1], false);
|
|
|
|
|
assert_eq!(chunks[2], false);
|
|
|
|
|
assert_eq!(chunks[3], true);
|
|
|
|
|
assert_eq!(chunks[4], true);
|
|
|
|
|
assert_eq!(chunks[5], true);
|
|
|
|
|
assert_eq!(chunks[6], true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
let mut needed_pieces =
|
|
|
|
|
BF::from_boxed_slice(vec![0u8; l.piece_bitfield_bytes()].into_boxed_slice());
|
|
|
|
|
needed_pieces.set(1, true);
|
|
|
|
|
|
|
|
|
|
let chunks = compute_chunk_status(&l, &needed_pieces).unwrap();
|
|
|
|
|
dbg!(&chunks);
|
|
|
|
|
assert_eq!(chunks[0], true);
|
|
|
|
|
assert_eq!(chunks[1], true);
|
|
|
|
|
assert_eq!(chunks[2], true);
|
|
|
|
|
assert_eq!(chunks[3], false);
|
|
|
|
|
assert_eq!(chunks[4], false);
|
|
|
|
|
assert_eq!(chunks[5], false);
|
|
|
|
|
assert_eq!(chunks[6], true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
let mut needed_pieces =
|
|
|
|
|
BF::from_boxed_slice(vec![0u8; l.piece_bitfield_bytes()].into_boxed_slice());
|
|
|
|
|
needed_pieces.set(2, true);
|
|
|
|
|
|
|
|
|
|
let chunks = compute_chunk_status(&l, &needed_pieces).unwrap();
|
|
|
|
|
dbg!(&chunks);
|
|
|
|
|
assert_eq!(chunks[0], true);
|
|
|
|
|
assert_eq!(chunks[1], true);
|
|
|
|
|
assert_eq!(chunks[2], true);
|
|
|
|
|
assert_eq!(chunks[3], true);
|
|
|
|
|
assert_eq!(chunks[4], true);
|
|
|
|
|
assert_eq!(chunks[5], true);
|
|
|
|
|
assert_eq!(chunks[6], false);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|