use super::*; use std::fs::{ File, Metadata, OpenOptions, }; use std::convert::TryFrom; use std::path::Path; use std::io::{ self, Read, }; use std::{fmt,error}; /// A job's completion status /// /// If the job has not completed, it will be the default value `Pending`. /// Once the job completes, the result of the job can be inserted to set to `Complete`. /// Afterwards, you can extract the value and it will have the state `Taken`; which means "job has completed, but the value has been consumed". /// /// # Notes /// This is not coupled to anything, it's the user's responsibility to update this status when a job completes. #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum Status { /// For running operations Pending, /// An operation that has completed with its value Complete(T), /// An operation who's completed value has been extracted and processed Taken, } impl Status { /// Assign a completed value to this instance. /// /// If there was one already set, it is ignored. #[inline] pub fn complete(&mut self, value: T) { *self = Self::Complete(value); } /// Has the value not yet been assigned or taken? pub fn is_pending(&self) -> bool { match self { Self::Pending => true, _ => false, } } /// Take the completed value from this instance pub fn take(&mut self) -> Option { match std::mem::replace(self, Self::Taken) { Self::Complete(t) => Some(t), _ => None } } /// Get a mutable reference to the completed value if there is one. pub fn peek_mut(&mut self) -> Option<&mut T> { match self { Self::Complete(ref mut t) => Some(t), _ => None, } } /// Get a reference to the completed value if there is one. pub fn peek(&self) -> Option<&T> { match self { Self::Complete(ref t) => Some(t), _ => None, } } } impl Default for Status { #[inline] fn default() -> Self { Self::Pending } } #[derive(Debug)] pub struct Prelude { file: File, stat: Metadata, file_num: usize, offset: usize, } impl Prelude{ pub fn len(&self) -> usize { usize::try_from(self.stat.len()).expect("Failed to fit file size into pointer size") } /// Convert this job prelude into a job, assigning it this state pub fn start(self, state: state::State) -> Job { Job { fd: self.file, stat: self.stat, offset: self.offset, file_num: self.file_num, state, } } } #[derive(Debug)] pub struct Job { fd: File, stat: Metadata, file_num: usize, /// We grab the slice of memory we write to from here state: state::State, /// From this offset offset: usize, } impl Job { pub fn state(&self) -> &state::State { &self.state } pub fn info(&self) -> (&File, &Metadata) { (&self.fd, &self.stat) } pub fn len(&self) -> usize { usize::try_from(self.stat.len()).expect("Failed to fit file size into pointer size") } pub fn start(&self) -> usize { self.offset } pub fn end(&self) -> usize { self.len() + self.offset } /// Get the output slice for this job. pub fn output_slice<'a, 'b>(&'a mut self) -> &'b mut [u8] where 'a: 'b { unsafe { self.state.slice(self.start() .. self.end()) } } /// Complete this job pub fn complete(self, size: usize) -> Result<(), CompletionError> { self.state.send_complete(self.file_num, size).map_err(|_| CompletionError) } } impl Read for Job { #[inline] fn read(&mut self, buf: &mut [u8]) -> io::Result { self.fd.read(buf) } } /// Create a job description for this opened input /// /// `sz` is the offset of the *end* of the last job. (or 0 for the first). /// `sz` is then updated with this file's size for this method to be used again on the next file. pub fn create(file_num: usize, open::Input(file, stat): open::Input, sz: &mut usize) -> Prelude { let offset = *sz; let prelude = Prelude { file, stat, offset, file_num }; *sz += prelude.len(); prelude } /// Create a job description for this file. /// /// `sz` is the offset of the *end* of the last job. (or 0 for the first). /// `sz` is then updated with this file's size for this method to be used again on the next file. pub fn create_from_file(file_num: usize, file: impl AsRef, sz: &mut usize) -> io::Result { let file = OpenOptions::new() .read(true) .open(file.as_ref())?; let stat = file.metadata()?; let offset = *sz; let prelude = Prelude { file, stat, offset, file_num }; *sz += prelude.len(); Ok(prelude) } /// Error returned when main thread's completion receiver was dropped. This should be fatal. #[derive(Debug)] pub struct CompletionError; impl error::Error for CompletionError{} impl fmt::Display for CompletionError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "unable to send completion signal because main thread's completion receiver was dropped.") } }