Metadata state management

This commit is contained in:
Jai A
2021-12-15 22:24:08 -07:00
parent 104afe1bb1
commit e9851a8e23
11 changed files with 198 additions and 53 deletions

View File

@@ -1,10 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RunConfigurationProducerService">
<option name="ignoredProducers">
<set>
<option value="com.android.tools.idea.compose.preview.runconfiguration.ComposePreviewRunConfigurationProducer" />
</set>
</option>
</component>
</project>

1
.idea/theseus.iml generated
View File

@@ -46,6 +46,7 @@
<sourceFolder url="file://$MODULE_DIR$/target/debug/build/crossbeam-queue-96bec372a2ebc7a5/out" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/target/debug/build/crossbeam-utils-bab62be590a5955d/out" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/target/debug/build/memoffset-235ac8b3550fb50a/out" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/theseus/examples" isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/target" />
</content>
<orderEntry type="inheritedJdk" />

6
Cargo.lock generated
View File

@@ -710,9 +710,9 @@ dependencies = [
[[package]]
name = "once_cell"
version = "1.8.0"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56"
checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5"
[[package]]
name = "opaque-debug"
@@ -1170,6 +1170,8 @@ dependencies = [
"futures",
"json5",
"lazy_static",
"log",
"once_cell",
"path-clean",
"regex",
"reqwest",

1
launcher/meta.json Normal file

File diff suppressed because one or more lines are too long

View File

@@ -9,6 +9,7 @@ edition = "2018"
[dependencies]
thiserror = "1.0"
async-trait = "0.1.51"
once_cell = "1.9.0"
daedalus = "0.1.6"
@@ -33,6 +34,8 @@ futures = "0.3"
sys-info = "0.9.0"
log = "0.4.14"
[dev-dependencies]
argh = "0.1.6"

View File

@@ -20,50 +20,39 @@ pub enum LauncherError {
url: String,
tries: u32,
},
#[error("Failed to run processor: {0}")]
ProcessorError(String),
#[error("Invalid input: {0}")]
InvalidInput(String),
#[error("Error while managing asynchronous tasks")]
TaskError(#[from] tokio::task::JoinError),
#[error("Error while reading/writing to the disk: {0}")]
IoError(#[from] std::io::Error),
#[error("Error while spawning child process {process}")]
ProcessError {
inner: std::io::Error,
process: String,
},
#[error("Error while deserializing JSON")]
SerdeError(#[from] serde_json::Error),
#[error("Unable to fetch {item}")]
FetchError { inner: reqwest::Error, item: String },
#[error("{0}")]
ParseError(String),
#[error("Error while fetching metadata: {0}")]
DaedalusError(#[from] daedalus::Error),
}
const META_URL: &str = "https://staging-cdn.modrinth.com/gamedata";
pub async fn fetch_metadata() -> Result<
(
daedalus::minecraft::VersionManifest,
daedalus::modded::Manifest,
daedalus::modded::Manifest,
),
LauncherError,
> {
let (game, forge, fabric) = futures::future::join3(
daedalus::minecraft::fetch_version_manifest(Some(&*format!(
"{}/minecraft/v0/manifest.json",
META_URL
))),
daedalus::modded::fetch_manifest(&*format!("{}/forge/v0/manifest.json", META_URL)),
daedalus::modded::fetch_manifest(&*format!("{}/fabric/v0/manifest.json", META_URL)),
)
.await;
Ok((game?, forge?, fabric?))
#[error("Error while reading metadata: {0}")]
MetaError(#[from] crate::meta::MetaError),
}
#[derive(Debug, Eq, PartialEq, Clone, Copy, Deserialize, Serialize)]
@@ -86,7 +75,7 @@ pub async fn launch_minecraft(
root_dir: &Path,
credentials: &Credentials,
) -> Result<(), LauncherError> {
let (game, forge, fabric) = fetch_metadata().await?;
let metadata = crate::meta::Metadata::get().await?;
let versions_path = crate::util::absolute_path(root_dir.join("versions"))?;
let libraries_path = crate::util::absolute_path(root_dir.join("libraries"))?;
@@ -95,7 +84,9 @@ pub async fn launch_minecraft(
let mut version = download::download_version_info(
&versions_path,
game.versions
metadata
.minecraft
.versions
.iter()
.find(|x| x.id == version_name)
.ok_or_else(|| {
@@ -105,7 +96,8 @@ pub async fn launch_minecraft(
ModLoader::Vanilla => None,
ModLoader::Forge | ModLoader::Fabric => {
let loaders = if mod_loader.unwrap_or_default() == ModLoader::Forge {
&forge
&metadata
.forge
.game_versions
.iter()
.find(|x| x.id == version_name)
@@ -117,7 +109,8 @@ pub async fn launch_minecraft(
})?
.loaders
} else {
&fabric
&metadata
.fabric
.game_versions
.iter()
.find(|x| x.id == version_name)

View File

@@ -5,6 +5,33 @@
#![warn(missing_docs, unused_import_braces, missing_debug_implementations)]
use std::path::Path;
use std::time::Duration;
lazy_static::lazy_static! {
pub static ref LAUNCHER_WORK_DIR: &'static Path = Path::new("./launcher");
}
pub mod launcher;
mod meta;
pub mod modpack;
mod util;
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("Launcher error: {0}")]
LauncherError(#[from] launcher::LauncherError),
#[error("Modpack error: {0}")]
ModpackError(#[from] modpack::ModpackError),
#[error("Meta error: {0}")]
DaedalusError(#[from] meta::MetaError),
}
pub async fn init() -> Result<(), Error> {
std::fs::create_dir_all(*LAUNCHER_WORK_DIR).expect("Unable to create launcher root directory!");
crate::meta::Metadata::init().await?;
Ok(())
}

116
theseus/src/meta.rs Normal file
View File

@@ -0,0 +1,116 @@
use serde::{Deserialize, Serialize};
use std::io;
use tokio::sync::RwLockReadGuard;
const META_FILE: &str = "meta.json";
const META_URL: &str = "https://staging-cdn.modrinth.com/gamedata";
#[derive(thiserror::Error, Debug)]
pub enum MetaError {
#[error("I/O error while reading metadata: {0}")]
IOError(#[from] io::Error),
#[error("Daedalus error: {0}")]
DaedalusError(#[from] daedalus::Error),
#[error("Attempted to access metadata without initializing it!")]
InitializedError,
#[error("Error while serializing/deserializing JSON")]
SerdeError(#[from] serde_json::Error),
}
use once_cell::sync;
use tokio::sync::RwLock;
#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct Metadata {
pub minecraft: daedalus::minecraft::VersionManifest,
pub forge: daedalus::modded::Manifest,
pub fabric: daedalus::modded::Manifest,
}
static METADATA: sync::OnceCell<RwLock<Metadata>> = sync::OnceCell::new();
impl Metadata {
pub async fn init() -> Result<(), MetaError> {
let meta_path = crate::LAUNCHER_WORK_DIR.join(META_FILE);
if meta_path.exists() {
let meta_data = std::fs::read_to_string(meta_path)
.map(|x| serde_json::from_str::<Metadata>(&*x).ok())
.ok()
.flatten();
if let Some(metadata) = meta_data {
METADATA.get_or_init(|| RwLock::new(metadata));
}
}
let future = async {
for attempt in 0..=3 {
let res = async {
let new = Self::fetch().await?;
std::fs::write(
crate::LAUNCHER_WORK_DIR.join(META_FILE),
&*serde_json::to_string(&new)?,
)?;
if let Some(metadata) = METADATA.get() {
*metadata.write().await = new;
} else {
METADATA.get_or_init(|| RwLock::new(new));
}
Ok::<(), MetaError>(())
}
.await;
match res {
Ok(_) => {
break;
}
Err(_) if attempt <= 3 => continue,
Err(err) => {
log::warn!("Unable to fetch launcher metadata: {}", err)
}
}
}
};
if METADATA.get().is_some() {
tokio::task::spawn(future);
} else {
future.await;
}
Ok(())
}
pub async fn fetch() -> Result<Self, MetaError> {
let (game, forge, fabric) = futures::future::join3(
daedalus::minecraft::fetch_version_manifest(Some(&*format!(
"{}/minecraft/v0/manifest.json",
META_URL
))),
daedalus::modded::fetch_manifest(&*format!("{}/forge/v0/manifest.json", META_URL)),
daedalus::modded::fetch_manifest(&*format!("{}/fabric/v0/manifest.json", META_URL)),
)
.await;
Ok(Self {
minecraft: game?,
forge: forge?,
fabric: fabric?,
})
}
pub async fn get<'a>() -> Result<RwLockReadGuard<'a, Self>, MetaError> {
Ok(METADATA
.get()
.ok_or_else(|| MetaError::InitializedError)?
.read()
.await)
}
}

View File

@@ -184,7 +184,10 @@ pub async fn compile_modpack(dir: &Path) -> ModpackResult<()> {
)
.await?;
let result_zip = fs::File::create(dir.join(COMPILED_ZIP)).await?.into_std().await;
let result_zip = fs::File::create(dir.join(COMPILED_ZIP))
.await?
.into_std()
.await;
let mut zip = zip::ZipWriter::new(&result_zip);
zip.create_from_directory(&result_dir)?;

View File

@@ -1,13 +1,9 @@
use std::{
collections::HashSet,
convert::TryFrom,
path::{Path, PathBuf},
};
use std::{collections::HashSet, convert::TryFrom, path::PathBuf};
use crate::launcher::ModLoader;
use super::{
manifest::{ManifestEnv, ManifestEnvs, ManifestHashes},
manifest::{ManifestEnvs, ManifestHashes},
pack::{ModpackEnv, ModpackFile, ModpackFileHashes, ModpackGame},
ModpackError, ModpackResult,
};

View File

@@ -4,13 +4,14 @@ use serde::{Deserialize, Serialize};
use std::{
collections::HashSet,
hash::Hash,
path::{Path, PathBuf}, iter::FromIterator,
iter::FromIterator,
path::{Path, PathBuf},
};
use tokio::fs;
use super::{
modrinth_api::{self, ModrinthV1},
ModpackResult, ModpackError,
ModpackError, ModpackResult,
};
use crate::launcher::ModLoader;
@@ -104,7 +105,13 @@ impl Modpack {
Ok(())
}
pub async fn add_file(&mut self, source: reqwest::Url, dest: &Path, hashes: Option<ModpackFileHashes>, env: Option<ModpackEnv>) -> ModpackResult<()> {
pub async fn add_file(
&mut self,
source: reqwest::Url,
dest: &Path,
hashes: Option<ModpackFileHashes>,
env: Option<ModpackEnv>,
) -> ModpackResult<()> {
let whitelisted_domains = std::env::var(MODRINTH_MODPACK_DOMAIN_WHITELIST_VAR)
.map(|it| serde_json::from_str::<Vec<String>>(&it).ok().unwrap())
.unwrap_or(
@@ -115,15 +122,21 @@ impl Modpack {
.collect::<Vec<String>>(),
);
if (whitelisted_domains.iter().find(|it| it == &source.host_str().unwrap()).is_none()) {
return Err(ModpackError::SourceWhitelistError(String::from(source.host_str().unwrap())));
}
if whitelisted_domains
.iter()
.find(|it| it == &source.host_str().unwrap())
.is_none()
{
return Err(ModpackError::SourceWhitelistError(String::from(
source.host_str().unwrap(),
)));
}
let file = ModpackFile {
path: PathBuf::from(dest),
hashes,
env: env.unwrap_or(ModpackEnv::Both),
downloads: HashSet::from_iter([String::from(source)].into_iter().cloned())
downloads: HashSet::from_iter([String::from(source)].into_iter().cloned()),
};
self.files.insert(file);