//! The [`Findings`] and [`ScanError`] structs, used for conveying whether a given file was able to be scanned and //! whether its MIME type could be inferred. use std::cmp::Ordering; use std::fmt::{Display, Formatter}; use std::path::{Path, PathBuf}; use mime::Mime; #[cfg(feature = "json")] use serde::{ser::SerializeStruct, Serializer}; use crate::files::mime_extension_lookup; use crate::String; /// Information about a scanned file. #[derive(Eq, PartialEq)] pub struct Findings { /// The location of the scanned file. pub file: PathBuf, /// Whether or not the file's extension is valid for its mimetype. pub valid: bool, /// The file's mimetype. pub mime: Mime, } impl PartialOrd for Findings { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } impl Ord for Findings { fn cmp(&self, other: &Self) -> Ordering { // files with no recommended extension should appear first, so that fif outputs the "no known extension for x" // comments before the "mv x y" instructions // since fif doesn't output anything for valid files, the comparison will consider any comparison involving a // valid Findings to be equal, avoiding the (somewhat) expensive call to recommended_extension. after all, since // fif never displays valid files, it really doesn't matter what position they end up in. if self.valid || other.valid { return Ordering::Equal; } match (self.recommended_extension(), other.recommended_extension()) { (None, Some(_)) => Ordering::Greater, (Some(_), None) => Ordering::Less, _ => self.file.cmp(&other.file), } } } #[cfg(feature = "json")] impl serde::Serialize for Findings { fn serialize(&self, serializer: S) -> Result where S: Serializer, { // the second parameter is the number of fields in the struct -- in this case, 3 let mut state = serializer.serialize_struct("Findings", 3)?; state.serialize_field("file", &self.file)?; state.serialize_field("valid", &self.valid)?; state.serialize_field("mime", &self.mime.essence_str())?; state.end() } } impl Findings { pub fn recommended_extension(&self) -> Option { mime_extension_lookup(self.mime.essence_str().into()).map(|extensions| extensions[0].clone()) } } #[derive(Debug, PartialEq, PartialOrd, Ord, Eq)] #[cfg_attr(feature = "json", derive(serde::Serialize))] #[cfg_attr(feature = "json", serde(tag = "type", content = "path"))] pub enum ScanError<'a> { /// Something went wrong while trying to read the given file. File(&'a Path), /// Failed to determine the mimetype of the given file. Mime(&'a Path), } impl<'a> Display for ScanError<'a> { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!( f, "Couldn't {} file: {}", match self { Self::File(_) => "read", Self::Mime(_) => "determine mime type of", }, match self { Self::File(f) | Self::Mime(f) => f.to_string_lossy(), } ) } }