use std::error::Error; use std::fmt::{Display, Formatter, Write}; use std::io::ErrorKind; use std::path::{Path, PathBuf}; use std::path::Component::Normal; use log::{debug, info, warn}; use tokio::{fs, io}; use crate::assets::AssetIndex; use crate::util; use crate::util::FileVerifyError; use crate::version::DownloadInfo; const INDEX_PATH: &'static str = "indexes"; const OBJECT_PATH: &'static str = "objects"; pub struct AssetRepository { online: bool, home: PathBuf } #[derive(Debug)] pub enum AssetError { InvalidId(Option), IO { what: &'static str, error: io::Error } } impl Display for AssetError { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { AssetError::InvalidId(None) => f.write_str("missing asset index id"), AssetError::InvalidId(Some(id)) => write!(f, "invalid asset index id: {}", id), AssetError::IO { what, error } => write!(f, "i/o error ({}): {}", what, error) } } } impl Error for AssetError { fn source(&self) -> Option<&(dyn Error + 'static)> { match self { AssetError::IO { error, .. } => Some(error), _ => None } } } impl From<(&'static str, io::Error)> for AssetError { fn from((what, error): (&'static str, io::Error)) -> Self { AssetError::IO { what, error } } } impl AssetRepository { pub async fn new(online: bool, home: impl AsRef) -> Result { let home = home.as_ref().to_owned(); match fs::create_dir_all(&home).await { Ok(_) => (), Err(e) => match e.kind() { ErrorKind::AlreadyExists => (), _ => return Err(e) } }; Ok(AssetRepository { online, home }) } fn get_index_path(&self, id: &str) -> Result { let indexes_path: &Path = (&self.home, INDEX_PATH).as_ref(); let Some(Normal(path)) = Path::new(id).components().last() else { return Err(AssetError::InvalidId(id.into())); }; let path = path.to_str().ok_or(AssetError::InvalidId(Some(path.to_string_lossy())))?; // FIXME: change this once "add_extension" is stabilized Ok((indexes_path, format!("{}.json", path)).into()) } pub async fn load_index(&self, index: &DownloadInfo, id: Option<&str>) -> Result { let Some(id) = index.id.as_ref().map(|s| s.as_str()).or(id) else { return Err(AssetError::InvalidId(None)); }; info!("Loading asset index {}", id); let path = self.get_index_path(id)?; debug!("Asset index {} is located at {}", id, path); match util::verify_file(&path, index.size, index.sha1).await { Ok(_) => todo!(), // load local index Err(FileVerifyError::Open(_, e)) => match e.kind() { ErrorKind::NotFound => (), // download _ => return Err(("opening asset index", e).into()) }, Err(FileVerifyError::Integrity(_, e)) => { info!("Asset index {} has mismatched integrity: {}, must download it.", id, e); let _ = fs::remove_file(&path).await.map_err(|e| warn!("Error deleting modified index {}: {} (ignored)", id, e)); // download }, Err(FileVerifyError::Read(_, e)) => return Err(("reading asset index", e).into()) } if !self.online { warn!("Must redownload asset index {}, but the launcher is in offline mode. Please try again in online mode.", id); return todo!(); } todo!() } }