You've already forked AstralRinth
forked from didirus/AstralRinth
Metadata state management
This commit is contained in:
10
.idea/runConfigurations.xml
generated
10
.idea/runConfigurations.xml
generated
@@ -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
1
.idea/theseus.iml
generated
@@ -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
6
Cargo.lock
generated
@@ -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
1
launcher/meta.json
Normal file
File diff suppressed because one or more lines are too long
@@ -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"
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
116
theseus/src/meta.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
@@ -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)?;
|
||||
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user