rqbit/crates/librqbit/src/chunk_tracker.rs

163 lines
5.1 KiB
Rust
Raw Normal View History

2021-06-25 13:47:51 +01:00
use log::{debug, info};
use crate::{
2021-06-27 10:10:59 +01:00
lengths::{ChunkInfo, Lengths, ValidPieceIndex},
2021-06-28 11:29:20 +01:00
peer_binary_protocol::Piece,
2021-06-25 13:47:51 +01:00
type_aliases::BF,
};
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.
// Better to rename into piece_queue or smth, and maybe use some other form of a queue.
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-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.
2021-06-25 13:47:51 +01:00
fn compute_chunk_status(lengths: &Lengths, needed_pieces: &BF) -> BF {
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];
let mut chunk_bf = BF::from_vec(vec);
2021-06-26 17:29:59 +01:00
for piece_index in needed_pieces
.get(0..lengths.total_pieces() as usize)
.unwrap()
.iter_zeros()
{
let offset = piece_index * lengths.default_chunks_per_piece() as usize;
let chunks_per_piece = lengths
.chunks_per_piece(lengths.validate_piece_index(piece_index as u32).unwrap())
as usize;
2021-06-26 17:32:17 +01:00
chunk_bf
.get_mut(offset..offset + chunks_per_piece)
.unwrap()
.set_all(true);
2021-06-25 13:47:51 +01:00
}
chunk_bf
}
2021-06-27 10:10:59 +01:00
pub enum ChunkMarkingResult {
PreviouslyCompleted,
NotCompleted,
Completed,
}
2021-06-25 13:47:51 +01:00
impl ChunkTracker {
2021-06-26 18:13:46 +01:00
pub fn new(needed_pieces: BF, have_pieces: BF, lengths: Lengths) -> Self {
2021-06-25 13:47:51 +01:00
Self {
chunk_status: compute_chunk_status(&lengths, &needed_pieces),
needed_pieces,
lengths,
2021-06-26 18:13:46 +01:00
have: have_pieces,
2021-06-25 13:47:51 +01:00
}
}
pub fn get_needed_pieces(&self) -> &BF {
&self.needed_pieces
}
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)
}
// 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,
) -> 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)
}
pub fn mark_piece_broken(&mut self, index: ValidPieceIndex) -> bool {
info!("remarking piece={} as broken", index);
2021-06-25 13:47:51 +01:00
self.needed_pieces.set(index.get() as usize, true);
self.chunk_status
.get_mut(self.lengths.chunk_range(index))
.map(|s| {
s.set_all(false);
true
})
.unwrap_or_default()
}
2021-06-26 18:13:46 +01:00
pub fn mark_piece_downloaded(&mut self, idx: ValidPieceIndex) {
self.have.set(idx.get() as usize, true)
}
2021-06-27 10:10:59 +01:00
pub fn is_chunk_downloaded(&self, chunk: &ChunkInfo) -> bool {
*self
.chunk_status
.get(chunk.absolute_index as usize)
.unwrap()
}
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
pub fn mark_chunk_downloaded<ByteBuf>(
2021-06-27 10:10:59 +01:00
&mut self,
piece: &Piece<ByteBuf>,
) -> Option<ChunkMarkingResult>
where
ByteBuf: AsRef<[u8]>,
{
let chunk_info = self.lengths.chunk_info_from_received_piece(piece)?;
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);
2021-06-25 13:47:51 +01:00
debug!(
"piece={}, chunk_info={:?}, bits={:?}",
piece.index, chunk_info, chunk_range,
);
2021-06-27 10:10:59 +01:00
// TODO: remove me, it's for debugging
// {
// use std::io::Write;
// let mut f = std::fs::OpenOptions::new()
// .write(true)
// .create(true)
// .open("/tmp/chunks")
// .unwrap();
// write!(f, "{:?}", &self.have).unwrap();
// }
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
}
}