cobble_core/minecraft/screenshots/
mod.rs

1mod screenshot;
2
3use crate::error::{CobbleError, CobbleResult};
4use futures::TryStreamExt;
5pub use screenshot::*;
6use std::path::{Path, PathBuf};
7use std::time::SystemTime;
8use tokio::fs::{metadata, read_dir};
9use tokio_stream::wrappers::ReadDirStream;
10
11/// Loads all screenshots from the minecraft folder.
12#[cfg_attr(doc_cfg, doc(cfg(feature = "screenshots")))]
13#[instrument(
14    name = "load_screenshots",
15    level = "debug",
16    skip_all,
17    fields(minecraft_path)
18)]
19pub async fn load_screenshots(
20    minecraft_path: impl AsRef<Path> + Send,
21) -> CobbleResult<Vec<Screenshot>> {
22    let mut screenshots_path = PathBuf::from(minecraft_path.as_ref());
23    screenshots_path.push("screenshots");
24
25    if !screenshots_path.is_dir() {
26        trace!("Screenshots directory is empty");
27        return Ok(vec![]);
28    }
29
30    trace!("Loading screenshot files...");
31    let file_stream = ReadDirStream::new(read_dir(screenshots_path).await?);
32    let screenshots = file_stream
33        .map_err(CobbleError::from)
34        .try_filter_map(|e| parse_screenshot(e.path()))
35        .try_collect()
36        .await?;
37
38    Ok(screenshots)
39}
40
41#[instrument(name = "parse_screenshot", level = "trace", skip_all, fields(path,))]
42async fn parse_screenshot(path: impl AsRef<Path>) -> CobbleResult<Option<Screenshot>> {
43    // Check if file
44    if !path.as_ref().is_file() {
45        trace!("Entry is not a file.");
46        return Ok(None);
47    }
48
49    // Check if image
50    let mime = match mime_guess::from_path(&path).first() {
51        Some(mime) => mime,
52        None => {
53            trace!("Could not get MIME type for file.");
54            return Ok(None);
55        }
56    };
57    if mime.type_() != mime_guess::mime::IMAGE {
58        trace!("Entry is not an image");
59        return Ok(None);
60    }
61
62    // Parse
63    let name = match path.as_ref().file_name() {
64        Some(name) => name.to_string_lossy().to_string(),
65        None => return Ok(None),
66    };
67
68    let created = metadata(path.as_ref())
69        .await
70        .and_then(|m| m.created())
71        .ok()
72        .and_then(|st| st.duration_since(SystemTime::UNIX_EPOCH).ok())
73        .and_then(|d| time::OffsetDateTime::from_unix_timestamp(d.as_secs() as i64).ok());
74
75    Ok(Some(Screenshot {
76        name,
77        path: PathBuf::from(path.as_ref()),
78        created,
79    }))
80}