// SPDX-FileCopyrightText: 2021-2022 Lynnesbian // SPDX-License-Identifier: GPL-3.0-or-later //! The [`Findings`] and [`ScanError`] structs, used for conveying whether a given file was able to be scanned, whether //! its MIME type could be inferred, and whether the file should be renamed. 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 successfully scanned file. #[derive(Eq, PartialEq, Debug)] pub struct Findings { /// The location of the scanned file. pub file: PathBuf, /// Whether or not the file's extension is valid for its MIME type. pub valid: bool, /// The file's MIME type. pub mime: Mime, } impl Findings { /// Returns the recommended extension for this file, if known. pub fn recommended_extension(&self) -> Option { mime_extension_lookup(self.mime.essence_str().into()).map(|extensions| extensions[0].clone()) } /// Returns the recommended path for this file - i.e. what it should be renamed to - if known. pub fn recommended_path(&self) -> Option { self .recommended_extension() .map(|ext| self.file.with_extension(ext.as_str())) } } 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 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() } } /// Errors that can occur while scanning a file with [`scan_file`](crate::files::scan_file). #[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 MIME type 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(), } ) } }