use std::io::{Read, Seek};
use std::ops::Range;
use std::slice::Iter;
use thiserror::Error;
cfg_if::cfg_if! {
if #[cfg(any(target_os = "linux",
target_os = "android",
target_os = "freebsd",
target_os = "macos",
))]{
mod unix;
} else if #[cfg(windows)] {
mod windows;
} else {
mod default;
}
}
#[cfg(test)]
mod test_utils;
#[derive(Error, Debug)]
pub enum ScanError {
#[error("IO Error occurred")]
IO(#[from] std::io::Error),
#[error("An unknown error occurred interacting with the C API")]
Raw(i32),
#[error("The operation you are trying to perform is not supported on this platform")]
UnsupportedPlatform,
#[error("The filesystem does not support operating on sparse files")]
UnsupportedFileSystem,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum SegmentType {
Hole,
Data,
}
impl SegmentType {
pub fn opposite(&self) -> Self {
match self {
SegmentType::Hole => SegmentType::Data,
SegmentType::Data => SegmentType::Hole,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Segment {
pub segment_type: SegmentType,
pub range: Range<u64>,
}
#[derive(Debug, Clone)]
pub struct SegmentIter<'a> {
segment_type: SegmentType,
iter: Iter<'a, Segment>,
}
impl<'a> Iterator for SegmentIter<'a> {
type Item = &'a Range<u64>;
fn next(&mut self) -> Option<<Self as Iterator>::Item> {
for segment in self.iter.by_ref() {
if segment.segment_type == self.segment_type {
return Some(&segment.range);
}
}
None
}
}
pub trait Segments {
fn data(&self) -> SegmentIter;
fn holes(&self) -> SegmentIter;
}
impl Segments for Vec<Segment> {
fn data(&self) -> SegmentIter {
SegmentIter {
segment_type: SegmentType::Data,
iter: self.iter(),
}
}
fn holes(&self) -> SegmentIter {
SegmentIter {
segment_type: SegmentType::Hole,
iter: self.iter(),
}
}
}
#[allow(clippy::len_without_is_empty)] impl Segment {
pub fn contains(&self, offset: &u64) -> bool {
self.range.contains(offset)
}
pub fn is_hole(&self) -> bool {
self.segment_type == SegmentType::Hole
}
pub fn is_data(&self) -> bool {
self.segment_type == SegmentType::Data
}
pub fn start(&self) -> u64 {
self.range.start
}
pub fn len(&self) -> u64 {
self.range.start - self.range.end
}
}
pub trait SparseFile: Read + Seek {
fn scan_chunks(&mut self) -> Result<Vec<Segment>, ScanError>;
fn drill_hole(&self, start: u64, end: u64) -> Result<(), ScanError>;
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_utils::*;
use quickcheck_macros::quickcheck;
use std::fs::File;
fn test_chunks_match(file: &mut File, input_segments: &[Segment]) -> bool {
let output_segments = file.scan_chunks().expect("Unable to scan chunks");
if *input_segments != output_segments {
println!("Expected: \n {:?} \n", input_segments);
println!("Got: \n {:?} \n", output_segments);
}
*input_segments == output_segments
}
fn test_round_trips(desc: SparseDescription) -> bool {
let mut file = desc.to_file();
let input_segments = desc.segments();
test_chunks_match(file.as_file_mut(), &input_segments)
}
#[quickcheck]
fn round_trips(desc: SparseDescription) -> bool {
test_round_trips(desc)
}
#[quickcheck]
fn drill_hole(desc: SparseDescription, drop: u8) -> bool {
let mut file = desc.to_file();
let mut input_segments = desc.segments();
if input_segments.is_empty() {
return true;
}
#[cfg(target_os = "macos")]
for hole in input_segments.holes() {
file.as_file_mut()
.drill_hole(hole.start, hole.end)
.expect("pre drill holes");
}
test_chunks_match(file.as_file_mut(), &input_segments);
let drop_idx = drop as usize % input_segments.len();
let drop = &mut input_segments[drop_idx];
file.as_file_mut()
.drill_hole(drop.range.start, drop.range.end)
.expect("drilled hole");
drop.segment_type = SegmentType::Hole;
combine_segments(&mut input_segments);
test_chunks_match(file.as_file_mut(), &input_segments)
}
#[quickcheck]
fn one_big_segment(segment_type: SegmentType) -> bool {
let desc = SparseDescription::one_segment(segment_type, 3545868);
test_round_trips(desc)
}
fn combine_segments(segments: &mut Vec<Segment>) {
let mut prev = 0;
for i in 1..segments.len() {
if segments[prev].segment_type == segments[i].segment_type {
segments[prev].range.end = segments[i].range.end;
} else {
prev += 1;
segments[prev] = segments[i].clone();
}
}
segments.truncate(prev + 1)
}
}