mod screenshot;
use crate::error::{CobbleError, CobbleResult};
use futures::TryStreamExt;
pub use screenshot::*;
use std::path::{Path, PathBuf};
use std::time::SystemTime;
use tokio::fs::{metadata, read_dir};
use tokio_stream::wrappers::ReadDirStream;
#[cfg_attr(doc_cfg, doc(cfg(feature = "screenshots")))]
#[instrument(
name = "load_screenshots",
level = "debug",
skip_all,
fields(minecraft_path)
)]
pub async fn load_screenshots(
minecraft_path: impl AsRef<Path> + Send,
) -> CobbleResult<Vec<Screenshot>> {
let mut screenshots_path = PathBuf::from(minecraft_path.as_ref());
screenshots_path.push("screenshots");
if !screenshots_path.is_dir() {
trace!("Screenshots directory is empty");
return Ok(vec![]);
}
trace!("Loading screenshot files...");
let file_stream = ReadDirStream::new(read_dir(screenshots_path).await?);
let screenshots = file_stream
.map_err(CobbleError::from)
.try_filter_map(|e| parse_screenshot(e.path()))
.try_collect()
.await?;
Ok(screenshots)
}
#[instrument(name = "parse_screenshot", level = "trace", skip_all, fields(path,))]
async fn parse_screenshot(path: impl AsRef<Path>) -> CobbleResult<Option<Screenshot>> {
if !path.as_ref().is_file() {
trace!("Entry is not a file.");
return Ok(None);
}
let mime = match mime_guess::from_path(&path).first() {
Some(mime) => mime,
None => {
trace!("Could not get MIME type for file.");
return Ok(None);
}
};
if mime.type_() != mime_guess::mime::IMAGE {
trace!("Entry is not an image");
return Ok(None);
}
let name = match path.as_ref().file_name() {
Some(name) => name.to_string_lossy().to_string(),
None => return Ok(None),
};
let created = metadata(path.as_ref())
.await
.and_then(|m| m.created())
.ok()
.and_then(|st| st.duration_since(SystemTime::UNIX_EPOCH).ok())
.and_then(|d| time::OffsetDateTime::from_unix_timestamp(d.as_secs() as i64).ok());
Ok(Some(Screenshot {
name,
path: PathBuf::from(path.as_ref()),
created,
}))
}