summaryrefslogtreecommitdiffstats
path: root/src/launcher/assets.rs
blob: 020885ff849e1d114d385f2c08e41ee3afda57b2 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
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<String>),
    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<Path>) -> Result<AssetRepository, io::Error> {
        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<PathBuf, AssetError> {
        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<AssetIndex, AssetError> {
        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!()
    }
}