use crate::errors::Error;
use crate::resources::resource::{
UPPER_FOLDER_PATHS, UPPER_PARENT, UPPER_PARENT_LINUX, UPPER_PARENT_STARTS_SLASH,
UPPER_PARENT_STARTS_SLASH_LINUX,
};
use crate::resources::retrieve::UpdatedAssetData;
use crate::utils;
use mime_guess::Mime;
use std::fmt::{Display, Formatter};
use std::hash::Hash;
use std::path::{MAIN_SEPARATOR_STR, Path, PathBuf};
use tracing::{debug, trace};
use url::Url;
#[derive(Clone, PartialEq, Debug)]
pub(crate) enum AssetKind {
Remote(Url),
Local(PathBuf),
#[allow(dead_code)]
Embedded,
}
#[derive(Clone, PartialEq, Debug)]
pub(crate) struct Asset {
pub(crate) original_link: String,
pub(crate) location_on_disk: PathBuf,
pub(crate) filename: PathBuf,
pub(crate) mimetype: Mime,
pub(crate) source: AssetKind,
}
impl Asset {
pub(crate) fn new<H, P, Q, K>(
original_link: H,
filename: P,
absolute_location: Q,
source: K,
) -> Self
where
H: Hash + ToString,
P: Into<PathBuf>,
Q: Into<PathBuf>,
K: Into<AssetKind>,
{
let location_on_disk = absolute_location.into();
let mt = mime_guess::from_path(&location_on_disk).first_or_octet_stream();
let source = source.into();
Self {
original_link: original_link.to_string(),
location_on_disk,
filename: filename.into(),
mimetype: mt,
source,
}
}
pub(crate) fn with_updated_fields(&self, updated_data: UpdatedAssetData) -> Self {
Asset {
original_link: self.original_link.clone(),
location_on_disk: updated_data.location_on_disk,
filename: updated_data.filename,
mimetype: updated_data.mimetype,
source: self.source.clone(),
}
}
pub(crate) fn from_url(link_key: &str, url: Url, dest_dir: &Path) -> Result<Asset, Error> {
debug!("Extract from URL: {:#?} into folder = {:?}", url, dest_dir);
let filename = utils::hash_link(&url);
let dest_dir = utils::normalize_path(dest_dir);
let full_filename = dest_dir.join(filename);
let absolute_location = utils::normalize_path(full_filename.as_path());
let filename = absolute_location.strip_prefix(dest_dir)?;
trace!(
"File from URL: absolute_location = {:?}",
&absolute_location
);
let asset = Asset::new(
link_key,
filename,
&absolute_location,
AssetKind::Remote(url),
);
debug!("Created from URL:\n{}", asset);
Ok(asset)
}
pub(crate) fn from_local(
link: &str,
src_dir: &Path,
chapter_path: &Path,
) -> Result<Asset, Error> {
debug!(
"Composing asset path for {:?} + {:?} in chapter = {:?}",
src_dir, link, chapter_path
);
let chapter_path = src_dir.join(chapter_path);
let stripped_path = Self::compute_asset_path_by_src_and_link(link, &chapter_path);
let normalized_link = utils::normalize_path(PathBuf::from(link).as_path());
debug!(
"Composing full_filename by '{:?}' + '{:?}'",
&stripped_path,
&normalized_link.clone()
);
let full_filename = stripped_path.join(normalized_link);
debug!("Joined full_filename = {:?}", &full_filename.display());
let absolute_location = full_filename.canonicalize().map_err(|this_error| {
Error::AssetFileNotFound(format!(
"Asset was not found: '{link}' by '{}', error = {}",
&full_filename.display(),
this_error
))
})?;
if !absolute_location.is_file() || absolute_location.is_symlink() {
return Err(Error::AssetFile(absolute_location));
}
let filename = full_filename.strip_prefix(src_dir)?;
let asset = Asset::new(
link,
filename,
&absolute_location,
AssetKind::Local(PathBuf::from(link)),
);
debug!("Created from local: {:#?}", asset);
Ok(asset)
}
pub(crate) fn compute_asset_path_by_src_and_link(link: &str, chapter_dir: &PathBuf) -> PathBuf {
let mut reassigned_asset_root: PathBuf = PathBuf::from(chapter_dir);
let link_string = String::from(link);
if chapter_dir.ends_with("index.md") && !chapter_dir.exists() {
debug!("It seems a `README.md` chapter, adjust the chapter root.");
reassigned_asset_root.pop();
}
if chapter_dir.is_file() {
reassigned_asset_root.pop();
}
trace!(
"check if parent present by '{}' = '{}' || '{}' || '{}'",
link_string, MAIN_SEPARATOR_STR, UPPER_PARENT, UPPER_PARENT_STARTS_SLASH
);
if !link_string.is_empty()
&& (link_string.starts_with(MAIN_SEPARATOR_STR)
|| link_string.starts_with(UPPER_PARENT_LINUX)
|| link_string.starts_with(UPPER_PARENT)
|| link_string.starts_with(UPPER_PARENT_STARTS_SLASH)
|| link_string.starts_with(UPPER_PARENT_STARTS_SLASH_LINUX))
{
reassigned_asset_root.pop(); let new_link = Self::remove_prefixes(link_string, UPPER_FOLDER_PATHS);
reassigned_asset_root =
Self::compute_asset_path_by_src_and_link(&new_link, &reassigned_asset_root);
}
reassigned_asset_root }
pub(crate) fn remove_prefixes(link_to_strip: String, prefixes: &[&str]) -> String {
let mut stripped_link = link_to_strip.clone();
for prefix in prefixes {
match link_to_strip.strip_prefix(prefix) {
Some(s) => {
stripped_link = s.to_string();
return stripped_link;
}
None => &link_to_strip,
};
}
stripped_link
}
}
impl Display for AssetKind {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
AssetKind::Remote(url) => write!(f, "Remote: '{}'", url.as_str()),
AssetKind::Local(path) => write!(f, "Local '{}'", path.display()),
AssetKind::Embedded => write!(f, "Embedded"),
}
}
}
impl Display for Asset {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"Asset {{\n\toriginal_link: {},\n\tlocation_on_disk: {:?},\n\tfilename: {:?},\n\tmimetype: {},\n\tkind: {} }}",
self.original_link, self.location_on_disk, self.filename, self.mimetype, self.source
)
}
}