use std::{ fs::File, path::PathBuf, sync::atomic::{AtomicU64, Ordering}, }; use anyhow::Context; use librqbit_core::lengths::Lengths; use parking_lot::Mutex; #[derive(Debug)] pub(crate) struct OpenedFile { pub file: Mutex, pub filename: PathBuf, pub offset_in_torrent: u64, pub have: AtomicU64, pub piece_range: std::ops::Range, pub len: u64, } pub(crate) fn dummy_file() -> anyhow::Result { #[cfg(target_os = "windows")] const DEVNULL: &str = "NUL"; #[cfg(not(target_os = "windows"))] const DEVNULL: &str = "/dev/null"; std::fs::OpenOptions::new() .read(true) .open(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) -> impl Iterator { // 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 { pub fn new( f: File, filename: PathBuf, have: u64, len: u64, offset_in_torrent: u64, piece_range: std::ops::Range, ) -> Self { Self { file: Mutex::new(f), filename, have: AtomicU64::new(have), len, offset_in_torrent, piece_range, } } pub fn take(&self) -> anyhow::Result { let mut f = self.file.lock(); let dummy = dummy_file()?; let f = std::mem::replace(&mut *f, dummy); Ok(f) } pub fn take_clone(&self) -> anyhow::Result { let f = self.take()?; Ok(Self { 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 { 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 { 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| -> Vec { iter_piece_priorities(r).collect() }; assert_eq!(it(0..0), Vec::::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]); } }