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-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/crossbeam-utils-bab62be590a5955d/out" isTestSource="false" />
|
||||||
<sourceFolder url="file://$MODULE_DIR$/target/debug/build/memoffset-235ac8b3550fb50a/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" />
|
<excludeFolder url="file://$MODULE_DIR$/target" />
|
||||||
</content>
|
</content>
|
||||||
<orderEntry type="inheritedJdk" />
|
<orderEntry type="inheritedJdk" />
|
||||||
|
|||||||
6
Cargo.lock
generated
6
Cargo.lock
generated
@@ -710,9 +710,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "once_cell"
|
||||||
version = "1.8.0"
|
version = "1.9.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56"
|
checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "opaque-debug"
|
name = "opaque-debug"
|
||||||
@@ -1170,6 +1170,8 @@ dependencies = [
|
|||||||
"futures",
|
"futures",
|
||||||
"json5",
|
"json5",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
|
"log",
|
||||||
|
"once_cell",
|
||||||
"path-clean",
|
"path-clean",
|
||||||
"regex",
|
"regex",
|
||||||
"reqwest",
|
"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]
|
[dependencies]
|
||||||
thiserror = "1.0"
|
thiserror = "1.0"
|
||||||
async-trait = "0.1.51"
|
async-trait = "0.1.51"
|
||||||
|
once_cell = "1.9.0"
|
||||||
|
|
||||||
daedalus = "0.1.6"
|
daedalus = "0.1.6"
|
||||||
|
|
||||||
@@ -33,6 +34,8 @@ futures = "0.3"
|
|||||||
|
|
||||||
sys-info = "0.9.0"
|
sys-info = "0.9.0"
|
||||||
|
|
||||||
|
log = "0.4.14"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
argh = "0.1.6"
|
argh = "0.1.6"
|
||||||
|
|
||||||
|
|||||||
@@ -20,50 +20,39 @@ pub enum LauncherError {
|
|||||||
url: String,
|
url: String,
|
||||||
tries: u32,
|
tries: u32,
|
||||||
},
|
},
|
||||||
|
|
||||||
#[error("Failed to run processor: {0}")]
|
#[error("Failed to run processor: {0}")]
|
||||||
ProcessorError(String),
|
ProcessorError(String),
|
||||||
|
|
||||||
#[error("Invalid input: {0}")]
|
#[error("Invalid input: {0}")]
|
||||||
InvalidInput(String),
|
InvalidInput(String),
|
||||||
|
|
||||||
#[error("Error while managing asynchronous tasks")]
|
#[error("Error while managing asynchronous tasks")]
|
||||||
TaskError(#[from] tokio::task::JoinError),
|
TaskError(#[from] tokio::task::JoinError),
|
||||||
|
|
||||||
#[error("Error while reading/writing to the disk: {0}")]
|
#[error("Error while reading/writing to the disk: {0}")]
|
||||||
IoError(#[from] std::io::Error),
|
IoError(#[from] std::io::Error),
|
||||||
|
|
||||||
#[error("Error while spawning child process {process}")]
|
#[error("Error while spawning child process {process}")]
|
||||||
ProcessError {
|
ProcessError {
|
||||||
inner: std::io::Error,
|
inner: std::io::Error,
|
||||||
process: String,
|
process: String,
|
||||||
},
|
},
|
||||||
|
|
||||||
#[error("Error while deserializing JSON")]
|
#[error("Error while deserializing JSON")]
|
||||||
SerdeError(#[from] serde_json::Error),
|
SerdeError(#[from] serde_json::Error),
|
||||||
|
|
||||||
#[error("Unable to fetch {item}")]
|
#[error("Unable to fetch {item}")]
|
||||||
FetchError { inner: reqwest::Error, item: String },
|
FetchError { inner: reqwest::Error, item: String },
|
||||||
|
|
||||||
#[error("{0}")]
|
#[error("{0}")]
|
||||||
ParseError(String),
|
ParseError(String),
|
||||||
|
|
||||||
#[error("Error while fetching metadata: {0}")]
|
#[error("Error while fetching metadata: {0}")]
|
||||||
DaedalusError(#[from] daedalus::Error),
|
DaedalusError(#[from] daedalus::Error),
|
||||||
}
|
|
||||||
|
|
||||||
const META_URL: &str = "https://staging-cdn.modrinth.com/gamedata";
|
#[error("Error while reading metadata: {0}")]
|
||||||
|
MetaError(#[from] crate::meta::MetaError),
|
||||||
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?))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq, Clone, Copy, Deserialize, Serialize)]
|
#[derive(Debug, Eq, PartialEq, Clone, Copy, Deserialize, Serialize)]
|
||||||
@@ -86,7 +75,7 @@ pub async fn launch_minecraft(
|
|||||||
root_dir: &Path,
|
root_dir: &Path,
|
||||||
credentials: &Credentials,
|
credentials: &Credentials,
|
||||||
) -> Result<(), LauncherError> {
|
) -> 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 versions_path = crate::util::absolute_path(root_dir.join("versions"))?;
|
||||||
let libraries_path = crate::util::absolute_path(root_dir.join("libraries"))?;
|
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(
|
let mut version = download::download_version_info(
|
||||||
&versions_path,
|
&versions_path,
|
||||||
game.versions
|
metadata
|
||||||
|
.minecraft
|
||||||
|
.versions
|
||||||
.iter()
|
.iter()
|
||||||
.find(|x| x.id == version_name)
|
.find(|x| x.id == version_name)
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| {
|
||||||
@@ -105,7 +96,8 @@ pub async fn launch_minecraft(
|
|||||||
ModLoader::Vanilla => None,
|
ModLoader::Vanilla => None,
|
||||||
ModLoader::Forge | ModLoader::Fabric => {
|
ModLoader::Forge | ModLoader::Fabric => {
|
||||||
let loaders = if mod_loader.unwrap_or_default() == ModLoader::Forge {
|
let loaders = if mod_loader.unwrap_or_default() == ModLoader::Forge {
|
||||||
&forge
|
&metadata
|
||||||
|
.forge
|
||||||
.game_versions
|
.game_versions
|
||||||
.iter()
|
.iter()
|
||||||
.find(|x| x.id == version_name)
|
.find(|x| x.id == version_name)
|
||||||
@@ -117,7 +109,8 @@ pub async fn launch_minecraft(
|
|||||||
})?
|
})?
|
||||||
.loaders
|
.loaders
|
||||||
} else {
|
} else {
|
||||||
&fabric
|
&metadata
|
||||||
|
.fabric
|
||||||
.game_versions
|
.game_versions
|
||||||
.iter()
|
.iter()
|
||||||
.find(|x| x.id == version_name)
|
.find(|x| x.id == version_name)
|
||||||
|
|||||||
@@ -5,6 +5,33 @@
|
|||||||
|
|
||||||
#![warn(missing_docs, unused_import_braces, missing_debug_implementations)]
|
#![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;
|
pub mod launcher;
|
||||||
|
mod meta;
|
||||||
pub mod modpack;
|
pub mod modpack;
|
||||||
mod util;
|
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?;
|
.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);
|
let mut zip = zip::ZipWriter::new(&result_zip);
|
||||||
zip.create_from_directory(&result_dir)?;
|
zip.create_from_directory(&result_dir)?;
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,9 @@
|
|||||||
use std::{
|
use std::{collections::HashSet, convert::TryFrom, path::PathBuf};
|
||||||
collections::HashSet,
|
|
||||||
convert::TryFrom,
|
|
||||||
path::{Path, PathBuf},
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::launcher::ModLoader;
|
use crate::launcher::ModLoader;
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
manifest::{ManifestEnv, ManifestEnvs, ManifestHashes},
|
manifest::{ManifestEnvs, ManifestHashes},
|
||||||
pack::{ModpackEnv, ModpackFile, ModpackFileHashes, ModpackGame},
|
pack::{ModpackEnv, ModpackFile, ModpackFileHashes, ModpackGame},
|
||||||
ModpackError, ModpackResult,
|
ModpackError, ModpackResult,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,13 +4,14 @@ use serde::{Deserialize, Serialize};
|
|||||||
use std::{
|
use std::{
|
||||||
collections::HashSet,
|
collections::HashSet,
|
||||||
hash::Hash,
|
hash::Hash,
|
||||||
path::{Path, PathBuf}, iter::FromIterator,
|
iter::FromIterator,
|
||||||
|
path::{Path, PathBuf},
|
||||||
};
|
};
|
||||||
use tokio::fs;
|
use tokio::fs;
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
modrinth_api::{self, ModrinthV1},
|
modrinth_api::{self, ModrinthV1},
|
||||||
ModpackResult, ModpackError,
|
ModpackError, ModpackResult,
|
||||||
};
|
};
|
||||||
use crate::launcher::ModLoader;
|
use crate::launcher::ModLoader;
|
||||||
|
|
||||||
@@ -104,7 +105,13 @@ impl Modpack {
|
|||||||
Ok(())
|
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)
|
let whitelisted_domains = std::env::var(MODRINTH_MODPACK_DOMAIN_WHITELIST_VAR)
|
||||||
.map(|it| serde_json::from_str::<Vec<String>>(&it).ok().unwrap())
|
.map(|it| serde_json::from_str::<Vec<String>>(&it).ok().unwrap())
|
||||||
.unwrap_or(
|
.unwrap_or(
|
||||||
@@ -115,15 +122,21 @@ impl Modpack {
|
|||||||
.collect::<Vec<String>>(),
|
.collect::<Vec<String>>(),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (whitelisted_domains.iter().find(|it| it == &source.host_str().unwrap()).is_none()) {
|
if whitelisted_domains
|
||||||
return Err(ModpackError::SourceWhitelistError(String::from(source.host_str().unwrap())));
|
.iter()
|
||||||
}
|
.find(|it| it == &source.host_str().unwrap())
|
||||||
|
.is_none()
|
||||||
|
{
|
||||||
|
return Err(ModpackError::SourceWhitelistError(String::from(
|
||||||
|
source.host_str().unwrap(),
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
let file = ModpackFile {
|
let file = ModpackFile {
|
||||||
path: PathBuf::from(dest),
|
path: PathBuf::from(dest),
|
||||||
hashes,
|
hashes,
|
||||||
env: env.unwrap_or(ModpackEnv::Both),
|
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);
|
self.files.insert(file);
|
||||||
|
|||||||
Reference in New Issue
Block a user