You've already forked AstralRinth
forked from didirus/AstralRinth
Updating + Profile Repairs + Performance Improvements (#97)
* repairing * Main framework for updating * add jsconfig * more work * Improve performance * Finish updating * run lint
This commit is contained in:
@@ -47,7 +47,7 @@ pub async fn authenticate(
|
||||
})?;
|
||||
|
||||
let credentials = flow.extract_credentials(&state.io_semaphore).await?;
|
||||
users.insert(&credentials)?;
|
||||
users.insert(&credentials).await?;
|
||||
|
||||
if state.settings.read().await.default_user.is_none() {
|
||||
let mut settings = state.settings.write().await;
|
||||
@@ -65,7 +65,7 @@ pub async fn refresh(user: uuid::Uuid) -> crate::Result<Credentials> {
|
||||
let mut users = state.users.write().await;
|
||||
|
||||
let io_sempahore = &state.io_semaphore;
|
||||
futures::future::ready(users.get(user)?.ok_or_else(|| {
|
||||
futures::future::ready(users.get(user).ok_or_else(|| {
|
||||
crate::ErrorKind::OtherError(format!(
|
||||
"Tried to refresh nonexistent user with ID {user}"
|
||||
))
|
||||
@@ -75,7 +75,7 @@ pub async fn refresh(user: uuid::Uuid) -> crate::Result<Credentials> {
|
||||
if chrono::offset::Utc::now() > credentials.expires {
|
||||
inner::refresh_credentials(&mut credentials, io_sempahore).await?;
|
||||
}
|
||||
users.insert(&credentials)?;
|
||||
users.insert(&credentials).await?;
|
||||
Ok(credentials)
|
||||
})
|
||||
.await
|
||||
@@ -89,14 +89,10 @@ pub async fn remove_user(user: uuid::Uuid) -> crate::Result<()> {
|
||||
|
||||
if state.settings.read().await.default_user == Some(user) {
|
||||
let mut settings = state.settings.write().await;
|
||||
settings.default_user = users
|
||||
.0
|
||||
.first()?
|
||||
.map(|it| uuid::Uuid::from_slice(&it.0))
|
||||
.transpose()?;
|
||||
settings.default_user = users.0.values().next().map(|it| it.id);
|
||||
}
|
||||
|
||||
users.remove(user)?;
|
||||
users.remove(user).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -106,15 +102,15 @@ pub async fn has_user(user: uuid::Uuid) -> crate::Result<bool> {
|
||||
let state = State::get().await?;
|
||||
let users = state.users.read().await;
|
||||
|
||||
users.contains(user)
|
||||
Ok(users.contains(user))
|
||||
}
|
||||
|
||||
/// Get a copy of the list of all user credentials
|
||||
#[tracing::instrument]
|
||||
pub async fn users() -> crate::Result<Box<[Credentials]>> {
|
||||
pub async fn users() -> crate::Result<Vec<Credentials>> {
|
||||
let state = State::get().await?;
|
||||
let users = state.users.read().await;
|
||||
users.iter().collect()
|
||||
Ok(users.0.values().cloned().collect())
|
||||
}
|
||||
|
||||
/// Get a specific user by user ID
|
||||
@@ -123,7 +119,7 @@ pub async fn users() -> crate::Result<Box<[Credentials]>> {
|
||||
pub async fn get_user(user: uuid::Uuid) -> crate::Result<Credentials> {
|
||||
let state = State::get().await?;
|
||||
let users = state.users.read().await;
|
||||
let user = users.get(user)?.ok_or_else(|| {
|
||||
let user = users.get(user).ok_or_else(|| {
|
||||
crate::ErrorKind::OtherError(format!(
|
||||
"Tried to get nonexistent user with ID {user}"
|
||||
))
|
||||
|
||||
@@ -40,10 +40,10 @@ pub async fn autodetect_java_globals() -> crate::Result<JavaGlobals> {
|
||||
// this can be overwritten by the user a profile-by-profile basis
|
||||
pub async fn get_optimal_jre_key(profile: &Profile) -> crate::Result<String> {
|
||||
let state = State::get().await?;
|
||||
let metadata = state.metadata.read().await;
|
||||
|
||||
// Fetch version info from stored profile game_version
|
||||
let version = state
|
||||
.metadata
|
||||
let version = metadata
|
||||
.minecraft
|
||||
.versions
|
||||
.iter()
|
||||
@@ -60,6 +60,7 @@ pub async fn get_optimal_jre_key(profile: &Profile) -> crate::Result<String> {
|
||||
&state,
|
||||
version,
|
||||
profile.metadata.loader_version.as_ref(),
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
let optimal_key = match version_info
|
||||
|
||||
@@ -5,7 +5,7 @@ pub use daedalus::modded::Manifest;
|
||||
#[tracing::instrument]
|
||||
pub async fn get_minecraft_versions() -> crate::Result<VersionManifest> {
|
||||
let state = State::get().await?;
|
||||
let tags = state.metadata.minecraft.clone();
|
||||
let tags = state.metadata.read().await.minecraft.clone();
|
||||
|
||||
Ok(tags)
|
||||
}
|
||||
@@ -13,7 +13,7 @@ pub async fn get_minecraft_versions() -> crate::Result<VersionManifest> {
|
||||
#[tracing::instrument]
|
||||
pub async fn get_fabric_versions() -> crate::Result<Manifest> {
|
||||
let state = State::get().await?;
|
||||
let tags = state.metadata.fabric.clone();
|
||||
let tags = state.metadata.read().await.fabric.clone();
|
||||
|
||||
Ok(tags)
|
||||
}
|
||||
@@ -21,7 +21,7 @@ pub async fn get_fabric_versions() -> crate::Result<Manifest> {
|
||||
#[tracing::instrument]
|
||||
pub async fn get_forge_versions() -> crate::Result<Manifest> {
|
||||
let state = State::get().await?;
|
||||
let tags = state.metadata.forge.clone();
|
||||
let tags = state.metadata.read().await.forge.clone();
|
||||
|
||||
Ok(tags)
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
use crate::config::MODRINTH_API_URL;
|
||||
use crate::data::ModLoader;
|
||||
use crate::event::emit::{init_loading, loading_try_for_each_concurrent};
|
||||
use crate::event::emit::{
|
||||
emit_loading, init_loading, loading_try_for_each_concurrent,
|
||||
};
|
||||
use crate::event::LoadingBarType;
|
||||
use crate::state::{ModrinthProject, ModrinthVersion, SideType};
|
||||
use crate::state::{LinkedData, ModrinthProject, ModrinthVersion, SideType};
|
||||
use crate::util::fetch::{
|
||||
fetch, fetch_json, fetch_mirrors, write, write_cached_icon,
|
||||
};
|
||||
@@ -218,13 +220,18 @@ async fn install_pack(
|
||||
};
|
||||
|
||||
let pack_name = pack.name.clone();
|
||||
|
||||
let profile = crate::api::profile_create::profile_create(
|
||||
pack.name,
|
||||
game_version.clone(),
|
||||
mod_loader.unwrap_or(ModLoader::Vanilla),
|
||||
loader_version,
|
||||
icon,
|
||||
project_id.clone(),
|
||||
Some(LinkedData {
|
||||
project_id: project_id.clone(),
|
||||
version_id: version_id.clone(),
|
||||
}),
|
||||
Some(true),
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -238,6 +245,7 @@ async fn install_pack(
|
||||
"Downloading modpack...",
|
||||
)
|
||||
.await?;
|
||||
|
||||
let num_files = pack.files.len();
|
||||
use futures::StreamExt;
|
||||
loading_try_for_each_concurrent(
|
||||
@@ -245,7 +253,7 @@ async fn install_pack(
|
||||
.map(Ok::<PackFile, crate::Error>),
|
||||
None,
|
||||
Some(&loading_bar),
|
||||
100.0,
|
||||
80.0,
|
||||
num_files,
|
||||
None,
|
||||
|project| {
|
||||
@@ -344,11 +352,22 @@ async fn install_pack(
|
||||
.await
|
||||
};
|
||||
|
||||
emit_loading(&loading_bar, 0.05, Some("Extracting overrides")).await?;
|
||||
extract_overrides("overrides".to_string()).await?;
|
||||
extract_overrides("client_overrides".to_string()).await?;
|
||||
emit_loading(&loading_bar, 0.1, Some("Done extacting overrides"))
|
||||
.await?;
|
||||
|
||||
super::profile::sync(&profile).await?;
|
||||
|
||||
if let Some(profile) = crate::api::profile::get(&profile).await? {
|
||||
crate::launcher::install_minecraft(&profile, Some(loading_bar))
|
||||
.await?;
|
||||
} else {
|
||||
emit_loading(&loading_bar, 0.1, Some("Done extacting overrides"))
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(profile)
|
||||
} else {
|
||||
Err(crate::Error::from(crate::ErrorKind::InputError(
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
//! Theseus profile management interface
|
||||
use crate::event::emit::{init_loading, loading_try_for_each_concurrent};
|
||||
use crate::event::LoadingBarType;
|
||||
use crate::state::ProjectMetadata;
|
||||
use crate::{
|
||||
auth::{self, refresh},
|
||||
event::{emit::emit_profile, ProfilePayloadType},
|
||||
@@ -22,7 +25,7 @@ pub async fn remove(path: &Path) -> crate::Result<()> {
|
||||
let state = State::get().await?;
|
||||
let mut profiles = state.profiles.write().await;
|
||||
|
||||
if let Some(profile) = profiles.0.get(path) {
|
||||
if let Some(profile) = profiles.remove(path).await? {
|
||||
emit_profile(
|
||||
profile.uuid,
|
||||
profile.path.clone(),
|
||||
@@ -32,8 +35,6 @@ pub async fn remove(path: &Path) -> crate::Result<()> {
|
||||
.await?;
|
||||
}
|
||||
|
||||
profiles.remove(path).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -108,20 +109,151 @@ pub async fn sync(path: &Path) -> crate::Result<()> {
|
||||
result
|
||||
}
|
||||
|
||||
/// Add a project from a version
|
||||
/// Installs/Repairs a profile
|
||||
#[tracing::instrument]
|
||||
pub async fn add_project_from_version(
|
||||
profile: &Path,
|
||||
pub async fn install(path: &Path) -> crate::Result<()> {
|
||||
let state = State::get().await?;
|
||||
let result = {
|
||||
let mut profiles: tokio::sync::RwLockWriteGuard<
|
||||
crate::state::Profiles,
|
||||
> = state.profiles.write().await;
|
||||
|
||||
if let Some(profile) = profiles.0.get_mut(path) {
|
||||
crate::launcher::install_minecraft(profile, None).await?;
|
||||
Ok(())
|
||||
} else {
|
||||
Err(crate::ErrorKind::UnmanagedProfileError(
|
||||
path.display().to_string(),
|
||||
)
|
||||
.as_error())
|
||||
}
|
||||
};
|
||||
State::sync().await?;
|
||||
result
|
||||
}
|
||||
|
||||
pub async fn update_all(profile_path: &Path) -> crate::Result<()> {
|
||||
let state = State::get().await?;
|
||||
let mut profiles = state.profiles.write().await;
|
||||
|
||||
if let Some(profile) = profiles.0.get_mut(profile_path) {
|
||||
let loading_bar = init_loading(
|
||||
LoadingBarType::ProfileUpdate {
|
||||
profile_uuid: profile.uuid,
|
||||
profile_name: profile.metadata.name.clone(),
|
||||
},
|
||||
100.0,
|
||||
"Updating profile...",
|
||||
)
|
||||
.await?;
|
||||
|
||||
use futures::StreamExt;
|
||||
loading_try_for_each_concurrent(
|
||||
futures::stream::iter(profile.projects.keys())
|
||||
.map(Ok::<&PathBuf, crate::Error>),
|
||||
None,
|
||||
Some(&loading_bar),
|
||||
100.0,
|
||||
profile.projects.len(),
|
||||
None,
|
||||
|project| update_project(profile_path, project, Some(true)),
|
||||
)
|
||||
.await?;
|
||||
|
||||
profile.sync().await?;
|
||||
|
||||
Ok(())
|
||||
} else {
|
||||
Err(crate::ErrorKind::UnmanagedProfileError(
|
||||
profile_path.display().to_string(),
|
||||
)
|
||||
.as_error())
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn update_project(
|
||||
profile_path: &Path,
|
||||
project_path: &Path,
|
||||
should_not_sync: Option<bool>,
|
||||
) -> crate::Result<()> {
|
||||
let state = State::get().await?;
|
||||
let mut profiles = state.profiles.write().await;
|
||||
|
||||
if let Some(profile) = profiles.0.get_mut(profile_path) {
|
||||
if let Some(project) = profile.projects.get(project_path) {
|
||||
if let ProjectMetadata::Modrinth {
|
||||
update_version: Some(update_version),
|
||||
..
|
||||
} = &project.metadata
|
||||
{
|
||||
let path = profile
|
||||
.add_project_version(update_version.id.clone())
|
||||
.await?;
|
||||
|
||||
if path != project_path {
|
||||
profile.remove_project(project_path).await?;
|
||||
}
|
||||
|
||||
if !should_not_sync.unwrap_or(false) {
|
||||
profile.sync().await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
} else {
|
||||
Err(crate::ErrorKind::UnmanagedProfileError(
|
||||
profile_path.display().to_string(),
|
||||
)
|
||||
.as_error())
|
||||
}
|
||||
}
|
||||
|
||||
/// Replaces a project given a new version ID
|
||||
pub async fn replace_project(
|
||||
profile_path: &Path,
|
||||
project: &Path,
|
||||
version_id: String,
|
||||
) -> crate::Result<PathBuf> {
|
||||
let state = State::get().await?;
|
||||
let mut profiles = state.profiles.write().await;
|
||||
|
||||
if let Some(profile) = profiles.0.get_mut(profile) {
|
||||
profile.add_project_version(version_id).await
|
||||
if let Some(profile) = profiles.0.get_mut(profile_path) {
|
||||
let path = profile.add_project_version(version_id).await?;
|
||||
|
||||
if path != project {
|
||||
profile.remove_project(project).await?;
|
||||
}
|
||||
|
||||
profile.sync().await?;
|
||||
|
||||
Ok(path)
|
||||
} else {
|
||||
Err(crate::ErrorKind::UnmanagedProfileError(
|
||||
profile.display().to_string(),
|
||||
profile_path.display().to_string(),
|
||||
)
|
||||
.as_error())
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a project from a version
|
||||
#[tracing::instrument]
|
||||
pub async fn add_project_from_version(
|
||||
profile_path: &Path,
|
||||
version_id: String,
|
||||
) -> crate::Result<PathBuf> {
|
||||
let state = State::get().await?;
|
||||
let mut profiles = state.profiles.write().await;
|
||||
|
||||
if let Some(profile) = profiles.0.get_mut(profile_path) {
|
||||
let path = profile.add_project_version(version_id).await?;
|
||||
|
||||
profile.sync().await?;
|
||||
|
||||
Ok(path)
|
||||
} else {
|
||||
Err(crate::ErrorKind::UnmanagedProfileError(
|
||||
profile_path.display().to_string(),
|
||||
)
|
||||
.as_error())
|
||||
}
|
||||
@@ -130,14 +262,14 @@ pub async fn add_project_from_version(
|
||||
/// Add a project from an FS path
|
||||
#[tracing::instrument]
|
||||
pub async fn add_project_from_path(
|
||||
profile: &Path,
|
||||
profile_path: &Path,
|
||||
path: &Path,
|
||||
project_type: Option<String>,
|
||||
) -> crate::Result<PathBuf> {
|
||||
let state = State::get().await?;
|
||||
let mut profiles = state.profiles.write().await;
|
||||
|
||||
if let Some(profile) = profiles.0.get_mut(profile) {
|
||||
if let Some(profile) = profiles.0.get_mut(profile_path) {
|
||||
let file = fs::read(path).await?;
|
||||
let file_name = path
|
||||
.file_name()
|
||||
@@ -145,16 +277,20 @@ pub async fn add_project_from_path(
|
||||
.to_string_lossy()
|
||||
.to_string();
|
||||
|
||||
profile
|
||||
let path = profile
|
||||
.add_project_bytes(
|
||||
&file_name,
|
||||
bytes::Bytes::from(file),
|
||||
project_type.and_then(|x| serde_json::from_str(&x).ok()),
|
||||
)
|
||||
.await
|
||||
.await?;
|
||||
|
||||
profile.sync().await?;
|
||||
|
||||
Ok(path)
|
||||
} else {
|
||||
Err(crate::ErrorKind::UnmanagedProfileError(
|
||||
profile.display().to_string(),
|
||||
profile_path.display().to_string(),
|
||||
)
|
||||
.as_error())
|
||||
}
|
||||
@@ -214,7 +350,7 @@ pub async fn run(path: &Path) -> crate::Result<Arc<RwLock<MinecraftChild>>> {
|
||||
} else {
|
||||
// If no default account, try to use a logged in account
|
||||
let users = auth::users().await?;
|
||||
let last_account = users.iter().next();
|
||||
let last_account = users.first();
|
||||
if let Some(last_account) = last_account {
|
||||
refresh(last_account.id).await?
|
||||
} else {
|
||||
@@ -233,6 +369,7 @@ pub async fn run_credentials(
|
||||
) -> crate::Result<Arc<RwLock<MinecraftChild>>> {
|
||||
let state = State::get().await?;
|
||||
let settings = state.settings.read().await;
|
||||
let metadata = state.metadata.read().await;
|
||||
let profile = get(path).await?.ok_or_else(|| {
|
||||
crate::ErrorKind::OtherError(format!(
|
||||
"Tried to run a nonexistent or unloaded profile at path {}!",
|
||||
@@ -240,8 +377,7 @@ pub async fn run_credentials(
|
||||
))
|
||||
})?;
|
||||
|
||||
let version = state
|
||||
.metadata
|
||||
let version = metadata
|
||||
.minecraft
|
||||
.versions
|
||||
.iter()
|
||||
@@ -256,6 +392,7 @@ pub async fn run_credentials(
|
||||
&state,
|
||||
version,
|
||||
profile.metadata.loader_version.as_ref(),
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
let pre_launch_hooks =
|
||||
@@ -358,9 +495,6 @@ pub async fn run_credentials(
|
||||
};
|
||||
|
||||
let mc_process = crate::launcher::launch_minecraft(
|
||||
&profile.metadata.game_version,
|
||||
&profile.metadata.loader_version,
|
||||
&profile.path,
|
||||
java_install,
|
||||
java_args,
|
||||
env_args,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
//! Theseus profile management interface
|
||||
use crate::state::LinkedData;
|
||||
use crate::{
|
||||
event::{emit::emit_profile, ProfilePayloadType},
|
||||
jre,
|
||||
@@ -29,6 +30,7 @@ pub async fn profile_create_empty() -> crate::Result<PathBuf> {
|
||||
None, // the modloader version to use, set to "latest", "stable", or the ID of your chosen loader
|
||||
None, // the icon for the profile
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
}
|
||||
@@ -42,9 +44,11 @@ pub async fn profile_create(
|
||||
modloader: ModLoader, // the modloader to use
|
||||
loader_version: Option<String>, // the modloader version to use, set to "latest", "stable", or the ID of your chosen loader. defaults to latest
|
||||
icon: Option<PathBuf>, // the icon for the profile
|
||||
linked_project_id: Option<String>, // the linked project ID (mainly for modpacks)- used for updating
|
||||
linked_data: Option<LinkedData>, // the linked project ID (mainly for modpacks)- used for updating
|
||||
skip_install_profile: Option<bool>,
|
||||
) -> crate::Result<PathBuf> {
|
||||
let state = State::get().await?;
|
||||
let metadata = state.metadata.read().await;
|
||||
|
||||
let uuid = Uuid::new_v4();
|
||||
let path = state.directories.profiles_dir().join(uuid.to_string());
|
||||
@@ -86,8 +90,8 @@ pub async fn profile_create(
|
||||
};
|
||||
|
||||
let loader_data = match loader {
|
||||
ModLoader::Forge => &state.metadata.forge,
|
||||
ModLoader::Fabric => &state.metadata.fabric,
|
||||
ModLoader::Forge => &metadata.forge,
|
||||
ModLoader::Fabric => &metadata.fabric,
|
||||
_ => {
|
||||
return Err(ProfileCreationError::NoManifest(
|
||||
loader.to_string(),
|
||||
@@ -157,7 +161,7 @@ pub async fn profile_create(
|
||||
profile.metadata.loader_version = Some(loader_version);
|
||||
}
|
||||
|
||||
profile.metadata.linked_project_id = linked_project_id;
|
||||
profile.metadata.linked_data = linked_data;
|
||||
|
||||
// Attempts to find optimal JRE for the profile from the JavaGlobals
|
||||
// Finds optimal key, and see if key has been set in JavaGlobals
|
||||
@@ -179,11 +183,15 @@ pub async fn profile_create(
|
||||
ProfilePayloadType::Created,
|
||||
)
|
||||
.await?;
|
||||
|
||||
{
|
||||
let mut profiles = state.profiles.write().await;
|
||||
profiles.insert(profile).await?;
|
||||
profiles.insert(profile.clone()).await?;
|
||||
}
|
||||
|
||||
if !skip_install_profile.unwrap_or(false) {
|
||||
crate::launcher::install_minecraft(&profile, None).await?;
|
||||
}
|
||||
State::sync().await?;
|
||||
|
||||
Ok(path)
|
||||
|
||||
@@ -1,18 +1,16 @@
|
||||
//! Theseus tag management interface
|
||||
pub use crate::{
|
||||
state::{
|
||||
Category, DonationPlatform, GameVersion, License, Loader, TagBundle,
|
||||
},
|
||||
state::{Category, DonationPlatform, GameVersion, Loader, Tags},
|
||||
State,
|
||||
};
|
||||
|
||||
// Get bundled set of tags
|
||||
#[tracing::instrument]
|
||||
pub async fn get_tag_bundle() -> crate::Result<TagBundle> {
|
||||
pub async fn get_tag_bundle() -> crate::Result<Tags> {
|
||||
let state = State::get().await?;
|
||||
let tags = state.tags.read().await;
|
||||
|
||||
tags.get_tag_bundle()
|
||||
Ok(tags.get_tag_bundle())
|
||||
}
|
||||
|
||||
/// Get category tags
|
||||
@@ -21,7 +19,7 @@ pub async fn get_category_tags() -> crate::Result<Vec<Category>> {
|
||||
let state = State::get().await?;
|
||||
let tags = state.tags.read().await;
|
||||
|
||||
tags.get_categories()
|
||||
Ok(tags.get_categories())
|
||||
}
|
||||
|
||||
/// Get report type tags
|
||||
@@ -30,7 +28,7 @@ pub async fn get_report_type_tags() -> crate::Result<Vec<String>> {
|
||||
let state = State::get().await?;
|
||||
let tags = state.tags.read().await;
|
||||
|
||||
tags.get_report_types()
|
||||
Ok(tags.get_report_types())
|
||||
}
|
||||
|
||||
/// Get loader tags
|
||||
@@ -39,7 +37,7 @@ pub async fn get_loader_tags() -> crate::Result<Vec<Loader>> {
|
||||
let state = State::get().await?;
|
||||
let tags = state.tags.read().await;
|
||||
|
||||
tags.get_loaders()
|
||||
Ok(tags.get_loaders())
|
||||
}
|
||||
|
||||
/// Get game version tags
|
||||
@@ -48,16 +46,7 @@ pub async fn get_game_version_tags() -> crate::Result<Vec<GameVersion>> {
|
||||
let state = State::get().await?;
|
||||
let tags = state.tags.read().await;
|
||||
|
||||
tags.get_game_versions()
|
||||
}
|
||||
|
||||
/// Get license tags
|
||||
#[tracing::instrument]
|
||||
pub async fn get_license_tags() -> crate::Result<Vec<License>> {
|
||||
let state = State::get().await?;
|
||||
let tags = state.tags.read().await;
|
||||
|
||||
tags.get_licenses()
|
||||
Ok(tags.get_game_versions())
|
||||
}
|
||||
|
||||
/// Get donation platform tags
|
||||
@@ -67,5 +56,5 @@ pub async fn get_donation_platform_tags() -> crate::Result<Vec<DonationPlatform>
|
||||
let state = State::get().await?;
|
||||
let tags = state.tags.read().await;
|
||||
|
||||
tags.get_donation_platforms()
|
||||
Ok(tags.get_donation_platforms())
|
||||
}
|
||||
|
||||
@@ -1,16 +1,3 @@
|
||||
//! Configuration structs
|
||||
|
||||
use lazy_static::lazy_static;
|
||||
|
||||
lazy_static! {
|
||||
pub static ref BINCODE_CONFIG: bincode::config::Configuration =
|
||||
bincode::config::standard()
|
||||
.with_little_endian()
|
||||
.with_no_limit();
|
||||
}
|
||||
|
||||
pub const MODRINTH_API_URL: &str = "https://api.modrinth.com/v2/";
|
||||
|
||||
pub fn sled_config() -> sled::Config {
|
||||
sled::Config::default().use_compression(true)
|
||||
}
|
||||
|
||||
@@ -13,18 +13,9 @@ pub enum ErrorKind {
|
||||
#[error("Error parsing UUID: {0}")]
|
||||
UUIDError(#[from] uuid::Error),
|
||||
|
||||
#[error("Serialization error (Bincode): {0}")]
|
||||
EncodeError(#[from] bincode::error::EncodeError),
|
||||
|
||||
#[error("Deserialization error (Bincode): {0}")]
|
||||
DecodeError(#[from] bincode::error::DecodeError),
|
||||
|
||||
#[error("Error parsing URL: {0}")]
|
||||
URLError(#[from] url::ParseError),
|
||||
|
||||
#[error("Database error: {0}")]
|
||||
DBError(#[from] sled::Error),
|
||||
|
||||
#[error("Unable to read {0} from any source")]
|
||||
NoValueFor(String),
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::event::{
|
||||
EventError, LoadingBar, LoadingBarId, LoadingBarType, ProcessPayloadType,
|
||||
EventError, LoadingBar, LoadingBarType, ProcessPayloadType,
|
||||
ProfilePayloadType,
|
||||
};
|
||||
use futures::prelude::*;
|
||||
@@ -11,6 +11,7 @@ use crate::event::{
|
||||
};
|
||||
#[cfg(feature = "tauri")]
|
||||
use tauri::Manager;
|
||||
use uuid::Uuid;
|
||||
|
||||
/*
|
||||
Events are a way we can communciate with the Tauri frontend from the Rust backend.
|
||||
@@ -39,22 +40,23 @@ use tauri::Manager;
|
||||
// Initialize a loading bar for use in emit_loading
|
||||
// This will generate a LoadingBarId, which is used to refer to the loading bar uniquely.
|
||||
// total is the total amount of work to be done- all emissions will be considered a fraction of this value (should be 1 or 100 for simplicity)
|
||||
// default_message is the message to display on the loading bar if no message is passed to emit_loading
|
||||
// title is the title of the loading bar
|
||||
pub async fn init_loading(
|
||||
bar_type: LoadingBarType,
|
||||
total: f64,
|
||||
default_message: &str,
|
||||
) -> crate::Result<LoadingBarId> {
|
||||
title: &str,
|
||||
) -> crate::Result<Uuid> {
|
||||
let event_state = crate::EventState::get().await?;
|
||||
let key = LoadingBarId::new(bar_type);
|
||||
let key = Uuid::new_v4();
|
||||
|
||||
event_state.loading_bars.write().await.insert(
|
||||
key.clone(),
|
||||
key,
|
||||
LoadingBar {
|
||||
loading_bar_id: key.clone(),
|
||||
message: default_message.to_string(),
|
||||
loading_bar_id: key,
|
||||
message: title.to_string(),
|
||||
total,
|
||||
current: 0.0,
|
||||
bar_type,
|
||||
},
|
||||
);
|
||||
// attempt an initial loading_emit event to the frontend
|
||||
@@ -62,6 +64,40 @@ pub async fn init_loading(
|
||||
Ok(key)
|
||||
}
|
||||
|
||||
pub async fn init_or_edit_loading(
|
||||
id: Option<Uuid>,
|
||||
bar_type: LoadingBarType,
|
||||
total: f64,
|
||||
title: &str,
|
||||
) -> crate::Result<Uuid> {
|
||||
if let Some(id) = id {
|
||||
edit_loading(id, bar_type, total, title).await?;
|
||||
|
||||
Ok(id)
|
||||
} else {
|
||||
init_loading(bar_type, total, title).await
|
||||
}
|
||||
}
|
||||
|
||||
// Edits a loading bar's type
|
||||
pub async fn edit_loading(
|
||||
id: Uuid,
|
||||
bar_type: LoadingBarType,
|
||||
total: f64,
|
||||
title: &str,
|
||||
) -> crate::Result<()> {
|
||||
let event_state = crate::EventState::get().await?;
|
||||
|
||||
if let Some(bar) = event_state.loading_bars.write().await.get_mut(&id) {
|
||||
bar.bar_type = bar_type;
|
||||
bar.total = total;
|
||||
bar.message = title.to_string();
|
||||
};
|
||||
|
||||
emit_loading(&id, 0.0, None).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// emit_loading emits a loading event to the frontend
|
||||
// key refers to the loading bar to update
|
||||
// increment refers to by what relative increment to the loading struct's total to update
|
||||
@@ -69,7 +105,7 @@ pub async fn init_loading(
|
||||
// By convention, fraction is the fraction of the progress bar that is filled
|
||||
#[allow(unused_variables)]
|
||||
pub async fn emit_loading(
|
||||
key: &LoadingBarId,
|
||||
key: &Uuid,
|
||||
increment_frac: f64,
|
||||
message: Option<&str>,
|
||||
) -> crate::Result<()> {
|
||||
@@ -79,14 +115,14 @@ pub async fn emit_loading(
|
||||
let loading_bar = match loading_bar.get_mut(key) {
|
||||
Some(f) => f,
|
||||
None => {
|
||||
return Err(EventError::NoLoadingBar(key.clone()).into());
|
||||
return Err(EventError::NoLoadingBar(*key).into());
|
||||
}
|
||||
};
|
||||
|
||||
// Tick up loading bar
|
||||
loading_bar.current += increment_frac;
|
||||
let display_frac = loading_bar.current / loading_bar.total;
|
||||
let display_frac = if display_frac > 1.0 {
|
||||
let display_frac = if display_frac >= 1.0 {
|
||||
None // by convention, when its done, we submit None
|
||||
// any further updates will be ignored (also sending None)
|
||||
} else {
|
||||
@@ -101,8 +137,8 @@ pub async fn emit_loading(
|
||||
LoadingPayload {
|
||||
fraction: display_frac,
|
||||
message: message.unwrap_or(&loading_bar.message).to_string(),
|
||||
event: key.key.clone(),
|
||||
loader_uuid: key.uuid,
|
||||
event: loading_bar.bar_type.clone(),
|
||||
loader_uuid: loading_bar.loading_bar_id,
|
||||
},
|
||||
)
|
||||
.map_err(EventError::from)?;
|
||||
@@ -132,7 +168,7 @@ pub async fn emit_warning(message: &str) -> crate::Result<()> {
|
||||
// emit_process(uuid, pid, event, message)
|
||||
#[allow(unused_variables)]
|
||||
pub async fn emit_process(
|
||||
uuid: uuid::Uuid,
|
||||
uuid: Uuid,
|
||||
pid: u32,
|
||||
event: ProcessPayloadType,
|
||||
message: &str,
|
||||
@@ -159,7 +195,7 @@ pub async fn emit_process(
|
||||
// emit_profile(path, event)
|
||||
#[allow(unused_variables)]
|
||||
pub async fn emit_profile(
|
||||
uuid: uuid::Uuid,
|
||||
uuid: Uuid,
|
||||
path: PathBuf,
|
||||
name: &str,
|
||||
event: ProfilePayloadType,
|
||||
@@ -253,7 +289,7 @@ macro_rules! loading_join {
|
||||
pub async fn loading_try_for_each_concurrent<I, F, Fut, T>(
|
||||
stream: I,
|
||||
limit: Option<usize>,
|
||||
key: Option<&LoadingBarId>,
|
||||
key: Option<&Uuid>,
|
||||
total: f64,
|
||||
num_futs: usize, // num is in here as we allow Iterator to be passed in, which doesn't have a size
|
||||
message: Option<&str>,
|
||||
@@ -285,7 +321,7 @@ where
|
||||
pub async fn loading_try_for_each_concurrent<I, F, Fut, T>(
|
||||
stream: I,
|
||||
limit: Option<usize>,
|
||||
_key: Option<&LoadingBarId>,
|
||||
_key: Option<&Uuid>,
|
||||
_total: f64,
|
||||
_num_futs: usize, // num is in here as we allow Iterator to be passed in, which doesn't have a size
|
||||
_message: Option<&str>,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//! Theseus state management system
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{collections::HashMap, fmt, path::PathBuf, sync::Arc};
|
||||
use std::{collections::HashMap, path::PathBuf, sync::Arc};
|
||||
use tokio::sync::OnceCell;
|
||||
use tokio::sync::RwLock;
|
||||
use uuid::Uuid;
|
||||
@@ -14,7 +14,7 @@ pub struct EventState {
|
||||
/// Tauri app
|
||||
#[cfg(feature = "tauri")]
|
||||
pub app: tauri::AppHandle,
|
||||
pub loading_bars: RwLock<HashMap<LoadingBarId, LoadingBar>>,
|
||||
pub loading_bars: RwLock<HashMap<Uuid, LoadingBar>>,
|
||||
}
|
||||
|
||||
impl EventState {
|
||||
@@ -48,6 +48,13 @@ impl EventState {
|
||||
Ok(EVENT_STATE.get().ok_or(EventError::NotInitialized)?.clone())
|
||||
}
|
||||
|
||||
pub async fn list_progress_bars() -> crate::Result<HashMap<Uuid, LoadingBar>>
|
||||
{
|
||||
let value = Self::get().await?;
|
||||
let read = value.loading_bars.read().await;
|
||||
Ok(read.clone())
|
||||
}
|
||||
|
||||
// Initialization requires no app handle in non-tauri mode, so we can just use the same function
|
||||
#[cfg(not(feature = "tauri"))]
|
||||
pub async fn get() -> crate::Result<Arc<Self>> {
|
||||
@@ -55,35 +62,13 @@ impl EventState {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Serialize, Debug, Clone)]
|
||||
pub struct LoadingBar {
|
||||
pub loading_bar_id: LoadingBarId,
|
||||
pub loading_bar_id: Uuid,
|
||||
pub message: String,
|
||||
pub total: f64,
|
||||
pub current: f64,
|
||||
}
|
||||
|
||||
// Loading Bar Id lets us uniquely identify loading bars stored in the state
|
||||
// the uuid lets us identify loading bars across threads
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, Hash, PartialEq, Eq)]
|
||||
pub struct LoadingBarId {
|
||||
pub key: LoadingBarType,
|
||||
pub uuid: Uuid,
|
||||
}
|
||||
|
||||
impl LoadingBarId {
|
||||
pub fn new(key: LoadingBarType) -> Self {
|
||||
Self {
|
||||
key,
|
||||
uuid: Uuid::new_v4(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for LoadingBarId {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{:?}-{}", self.key, self.uuid)
|
||||
}
|
||||
pub bar_type: LoadingBarType,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, Hash, PartialEq, Eq)]
|
||||
@@ -98,7 +83,10 @@ pub enum LoadingBarType {
|
||||
profile_uuid: Uuid,
|
||||
profile_name: String,
|
||||
},
|
||||
ProfileSync,
|
||||
ProfileUpdate {
|
||||
profile_uuid: Uuid,
|
||||
profile_name: String,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Serialize, Clone)]
|
||||
@@ -139,6 +127,7 @@ pub struct ProfilePayload {
|
||||
pub enum ProfilePayloadType {
|
||||
Created,
|
||||
Added, // also triggered when Created
|
||||
Synced,
|
||||
Edited,
|
||||
Removed,
|
||||
}
|
||||
@@ -149,7 +138,7 @@ pub enum EventError {
|
||||
NotInitialized,
|
||||
|
||||
#[error("Non-existent loading bar of key: {0}")]
|
||||
NoLoadingBar(LoadingBarId),
|
||||
NoLoadingBar(Uuid),
|
||||
|
||||
#[cfg(feature = "tauri")]
|
||||
#[error("Tauri error: {0}")]
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
//! Authentication flow based on Hydra
|
||||
use crate::util::fetch::{fetch_advanced, fetch_json};
|
||||
use async_tungstenite as ws;
|
||||
use bincode::{Decode, Encode};
|
||||
use chrono::{prelude::*, Duration};
|
||||
use futures::prelude::*;
|
||||
use lazy_static::lazy_static;
|
||||
@@ -50,14 +49,12 @@ struct ProfileInfoJSON {
|
||||
}
|
||||
|
||||
// Login information
|
||||
#[derive(Encode, Decode, Serialize, Deserialize, Clone, Debug)]
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct Credentials {
|
||||
#[bincode(with_serde)]
|
||||
pub id: uuid::Uuid,
|
||||
pub username: String,
|
||||
pub access_token: String,
|
||||
pub refresh_token: String,
|
||||
#[bincode(with_serde)]
|
||||
pub expires: DateTime<Utc>,
|
||||
_ctor_scope: std::marker::PhantomData<()>,
|
||||
}
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
//! Downloader for Minecraft data
|
||||
|
||||
use crate::{
|
||||
event::{
|
||||
emit::{emit_loading, init_loading, loading_try_for_each_concurrent},
|
||||
LoadingBarId, LoadingBarType,
|
||||
},
|
||||
process::Profile,
|
||||
event::emit::{emit_loading, loading_try_for_each_concurrent},
|
||||
state::State,
|
||||
util::{fetch::*, platform::OsExt},
|
||||
};
|
||||
@@ -19,26 +15,17 @@ use daedalus::{
|
||||
};
|
||||
use futures::prelude::*;
|
||||
use tokio::{fs, sync::OnceCell};
|
||||
use uuid::Uuid;
|
||||
|
||||
#[tracing::instrument(skip_all)]
|
||||
pub async fn download_minecraft(
|
||||
st: &State,
|
||||
version: &GameVersionInfo,
|
||||
profile: &Profile,
|
||||
loading_bar: Uuid,
|
||||
) -> crate::Result<()> {
|
||||
log::info!("Downloading Minecraft version {}", version.id);
|
||||
let assets_index = download_assets_index(st, version).await?;
|
||||
|
||||
let loading_bar = init_loading(
|
||||
LoadingBarType::MinecraftDownload {
|
||||
// If we are downloading minecraft for a profile, provide its name and uuid
|
||||
profile_name: profile.metadata.name.clone(),
|
||||
profile_uuid: profile.uuid,
|
||||
},
|
||||
100.0,
|
||||
"Downloading Minecraft...",
|
||||
)
|
||||
.await?;
|
||||
tokio::try_join! {
|
||||
download_client(st, version, Some(&loading_bar)),
|
||||
download_assets(st, version.assets == "legacy", &assets_index, Some(&loading_bar)),
|
||||
@@ -54,6 +41,7 @@ pub async fn download_version_info(
|
||||
st: &State,
|
||||
version: &GameVersion,
|
||||
loader: Option<&LoaderVersion>,
|
||||
force: Option<bool>,
|
||||
) -> crate::Result<GameVersionInfo> {
|
||||
let version_id = loader
|
||||
.map_or(version.id.clone(), |it| format!("{}-{}", version.id, it.id));
|
||||
@@ -63,7 +51,7 @@ pub async fn download_version_info(
|
||||
.version_dir(&version_id)
|
||||
.join(format!("{version_id}.json"));
|
||||
|
||||
let res = if path.exists() {
|
||||
let res = if path.exists() && !force.unwrap_or(false) {
|
||||
fs::read(path)
|
||||
.err_into::<crate::Error>()
|
||||
.await
|
||||
@@ -90,7 +78,7 @@ pub async fn download_version_info(
|
||||
pub async fn download_client(
|
||||
st: &State,
|
||||
version_info: &GameVersionInfo,
|
||||
loading_bar: Option<&LoadingBarId>,
|
||||
loading_bar: Option<&Uuid>,
|
||||
) -> crate::Result<()> {
|
||||
let version = &version_info.id;
|
||||
log::debug!("Locating client for version {version}");
|
||||
@@ -158,7 +146,7 @@ pub async fn download_assets(
|
||||
st: &State,
|
||||
with_legacy: bool,
|
||||
index: &AssetsIndex,
|
||||
loading_bar: Option<&LoadingBarId>,
|
||||
loading_bar: Option<&Uuid>,
|
||||
) -> crate::Result<()> {
|
||||
log::debug!("Loading assets");
|
||||
let num_futs = index.objects.len();
|
||||
@@ -219,7 +207,7 @@ pub async fn download_libraries(
|
||||
st: &State,
|
||||
libraries: &[Library],
|
||||
version: &str,
|
||||
loading_bar: Option<&LoadingBarId>,
|
||||
loading_bar: Option<&Uuid>,
|
||||
) -> crate::Result<()> {
|
||||
log::debug!("Loading libraries");
|
||||
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
//! Logic for launching Minecraft
|
||||
use crate::event::emit::{emit_loading, init_or_edit_loading};
|
||||
use crate::event::LoadingBarType;
|
||||
use crate::{
|
||||
process,
|
||||
state::{self as st, MinecraftChild},
|
||||
@@ -8,6 +10,7 @@ use dunce::canonicalize;
|
||||
use st::Profile;
|
||||
use std::{path::Path, process::Stdio, sync::Arc};
|
||||
use tokio::process::Command;
|
||||
use uuid::Uuid;
|
||||
|
||||
mod args;
|
||||
|
||||
@@ -48,55 +51,60 @@ macro_rules! processor_rules {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
#[tracing::instrument(skip_all, fields(path = ?instance_path))]
|
||||
pub async fn launch_minecraft(
|
||||
game_version: &str,
|
||||
loader_version: &Option<d::modded::LoaderVersion>,
|
||||
instance_path: &Path,
|
||||
java_install: &Path,
|
||||
java_args: &[String],
|
||||
env_args: &[(String, String)],
|
||||
wrapper: &Option<String>,
|
||||
memory: &st::MemorySettings,
|
||||
resolution: &st::WindowSize,
|
||||
credentials: &auth::Credentials,
|
||||
post_exit_hook: Option<Command>,
|
||||
profile: &Profile, // optional ref to Profile for event tracking
|
||||
) -> crate::Result<Arc<tokio::sync::RwLock<MinecraftChild>>> {
|
||||
pub async fn install_minecraft(
|
||||
profile: &Profile,
|
||||
existing_loading_bar: Option<Uuid>,
|
||||
) -> crate::Result<()> {
|
||||
let state = st::State::get().await?;
|
||||
let instance_path = &canonicalize(instance_path)?;
|
||||
let instance_path = &canonicalize(&profile.path)?;
|
||||
let metadata = state.metadata.read().await;
|
||||
|
||||
let version = state
|
||||
.metadata
|
||||
let version = metadata
|
||||
.minecraft
|
||||
.versions
|
||||
.iter()
|
||||
.find(|it| it.id == game_version)
|
||||
.find(|it| it.id == profile.metadata.game_version)
|
||||
.ok_or(crate::ErrorKind::LauncherError(format!(
|
||||
"Invalid game version: {game_version}"
|
||||
"Invalid game version: {}",
|
||||
profile.metadata.game_version
|
||||
)))?;
|
||||
|
||||
let version_jar =
|
||||
loader_version.as_ref().map_or(version.id.clone(), |it| {
|
||||
let version_jar = profile
|
||||
.metadata
|
||||
.loader_version
|
||||
.as_ref()
|
||||
.map_or(version.id.clone(), |it| {
|
||||
format!("{}-{}", version.id.clone(), it.id.clone())
|
||||
});
|
||||
|
||||
let mut version_info = download::download_version_info(
|
||||
&state,
|
||||
version,
|
||||
loader_version.as_ref(),
|
||||
profile.metadata.loader_version.as_ref(),
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let loading_bar = init_or_edit_loading(
|
||||
existing_loading_bar,
|
||||
LoadingBarType::MinecraftDownload {
|
||||
// If we are downloading minecraft for a profile, provide its name and uuid
|
||||
profile_name: profile.metadata.name.clone(),
|
||||
profile_uuid: profile.uuid,
|
||||
},
|
||||
100.0,
|
||||
"Downloading Minecraft...",
|
||||
)
|
||||
.await?;
|
||||
|
||||
download::download_minecraft(&state, &version_info, loading_bar).await?;
|
||||
st::State::sync().await?;
|
||||
|
||||
let client_path = state
|
||||
.directories
|
||||
.version_dir(&version_jar)
|
||||
.join(format!("{version_jar}.jar"));
|
||||
|
||||
download::download_minecraft(&state, &version_info, profile).await?;
|
||||
st::State::sync().await?;
|
||||
|
||||
if let Some(processors) = &version_info.processors {
|
||||
if let Some(ref mut data) = version_info.data {
|
||||
processor_rules! {
|
||||
@@ -108,7 +116,7 @@ pub async fn launch_minecraft(
|
||||
client => client_path.to_string_lossy(),
|
||||
server => "";
|
||||
"MINECRAFT_VERSION":
|
||||
client => game_version,
|
||||
client => profile.metadata.game_version.clone(),
|
||||
server => "";
|
||||
"ROOT":
|
||||
client => instance_path.to_string_lossy(),
|
||||
@@ -118,7 +126,21 @@ pub async fn launch_minecraft(
|
||||
server => "";
|
||||
}
|
||||
|
||||
for processor in processors {
|
||||
emit_loading(&loading_bar, 0.0, Some("Running forge processors"))
|
||||
.await?;
|
||||
let total_length = processors.len();
|
||||
|
||||
for (index, processor) in processors.iter().enumerate() {
|
||||
emit_loading(
|
||||
&loading_bar,
|
||||
index as f64 / total_length as f64,
|
||||
Some(&format!(
|
||||
"Running forge processor {}/{}",
|
||||
index, total_length
|
||||
)),
|
||||
)
|
||||
.await?;
|
||||
|
||||
if let Some(sides) = &processor.sides {
|
||||
if !sides.contains(&String::from("client")) {
|
||||
continue;
|
||||
@@ -173,6 +195,68 @@ pub async fn launch_minecraft(
|
||||
}
|
||||
}
|
||||
|
||||
crate::api::profile::edit(&profile.path, |prof| {
|
||||
prof.installed = true;
|
||||
|
||||
async { Ok(()) }
|
||||
})
|
||||
.await?;
|
||||
crate::api::profile::sync(&profile.path).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn launch_minecraft(
|
||||
java_install: &Path,
|
||||
java_args: &[String],
|
||||
env_args: &[(String, String)],
|
||||
wrapper: &Option<String>,
|
||||
memory: &st::MemorySettings,
|
||||
resolution: &st::WindowSize,
|
||||
credentials: &auth::Credentials,
|
||||
post_exit_hook: Option<Command>,
|
||||
profile: &Profile,
|
||||
) -> crate::Result<Arc<tokio::sync::RwLock<MinecraftChild>>> {
|
||||
if !profile.installed {
|
||||
install_minecraft(profile, None).await?;
|
||||
}
|
||||
|
||||
let state = st::State::get().await?;
|
||||
let metadata = state.metadata.read().await;
|
||||
let instance_path = &canonicalize(&profile.path)?;
|
||||
|
||||
let version = metadata
|
||||
.minecraft
|
||||
.versions
|
||||
.iter()
|
||||
.find(|it| it.id == profile.metadata.game_version)
|
||||
.ok_or(crate::ErrorKind::LauncherError(format!(
|
||||
"Invalid game version: {}",
|
||||
profile.metadata.game_version
|
||||
)))?;
|
||||
|
||||
let version_jar = profile
|
||||
.metadata
|
||||
.loader_version
|
||||
.as_ref()
|
||||
.map_or(version.id.clone(), |it| {
|
||||
format!("{}-{}", version.id.clone(), it.id.clone())
|
||||
});
|
||||
|
||||
let version_info = download::download_version_info(
|
||||
&state,
|
||||
version,
|
||||
profile.metadata.loader_version.as_ref(),
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let client_path = state
|
||||
.directories
|
||||
.version_dir(&version_jar)
|
||||
.join(format!("{version_jar}.jar"));
|
||||
|
||||
let args = version_info.arguments.clone().unwrap_or_default();
|
||||
let mut command = match wrapper {
|
||||
Some(hook) => {
|
||||
|
||||
@@ -19,5 +19,5 @@ mod state;
|
||||
|
||||
pub use api::*;
|
||||
pub use error::*;
|
||||
pub use event::EventState;
|
||||
pub use event::{EventState, LoadingBar, LoadingBarType};
|
||||
pub use state::State;
|
||||
|
||||
@@ -134,6 +134,11 @@ impl DirectoryInfo {
|
||||
self.config_dir.join("caches")
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn caches_meta_dir(&self) -> PathBuf {
|
||||
self.config_dir.join("caches").join("metadata")
|
||||
}
|
||||
|
||||
/// Get path from environment variable
|
||||
#[inline]
|
||||
fn env_path(name: &str) -> Option<PathBuf> {
|
||||
|
||||
@@ -1,21 +1,18 @@
|
||||
//! Theseus metadata
|
||||
use crate::config::BINCODE_CONFIG;
|
||||
use bincode::{Decode, Encode};
|
||||
use crate::data::DirectoryInfo;
|
||||
use crate::util::fetch::{read_json, write};
|
||||
use daedalus::{
|
||||
minecraft::{fetch_version_manifest, VersionManifest as MinecraftManifest},
|
||||
modded::{
|
||||
fetch_manifest as fetch_loader_manifest, Manifest as LoaderManifest,
|
||||
},
|
||||
};
|
||||
use futures::prelude::*;
|
||||
use std::collections::LinkedList;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::sync::{RwLock, Semaphore};
|
||||
|
||||
const METADATA_URL: &str = "https://meta.modrinth.com";
|
||||
const METADATA_DB_FIELD: &[u8] = b"metadata";
|
||||
const RETRY_ATTEMPTS: i32 = 3;
|
||||
|
||||
// TODO: store as subtree in database
|
||||
#[derive(Encode, Decode, Debug)]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct Metadata {
|
||||
pub minecraft: MinecraftManifest,
|
||||
pub forge: LoaderManifest,
|
||||
@@ -27,7 +24,7 @@ impl Metadata {
|
||||
format!("{METADATA_URL}/{name}/v0/manifest.json")
|
||||
}
|
||||
|
||||
async fn fetch() -> crate::Result<Self> {
|
||||
pub async fn fetch() -> crate::Result<Self> {
|
||||
let (minecraft, forge, fabric) = tokio::try_join! {
|
||||
async {
|
||||
let url = Self::get_manifest("minecraft");
|
||||
@@ -51,41 +48,42 @@ impl Metadata {
|
||||
}
|
||||
|
||||
// Attempt to fetch metadata and store in sled DB
|
||||
#[tracing::instrument(skip_all)]
|
||||
pub async fn init(db: &sled::Db) -> crate::Result<Self> {
|
||||
pub async fn init(
|
||||
dirs: &DirectoryInfo,
|
||||
io_semaphore: &RwLock<Semaphore>,
|
||||
) -> crate::Result<Self> {
|
||||
let mut metadata = None;
|
||||
let metadata_path = dirs.caches_meta_dir().join("metadata.json");
|
||||
|
||||
if let Some(ref meta_bin) = db.get(METADATA_DB_FIELD)? {
|
||||
match bincode::decode_from_slice::<Self, _>(
|
||||
meta_bin,
|
||||
*BINCODE_CONFIG,
|
||||
) {
|
||||
Ok((meta, _)) => metadata = Some(meta),
|
||||
if let Ok(metadata_json) =
|
||||
read_json::<Metadata>(&metadata_path, io_semaphore).await
|
||||
{
|
||||
metadata = Some(metadata_json);
|
||||
} else {
|
||||
let res = async {
|
||||
let metadata_fetch = Self::fetch().await?;
|
||||
|
||||
write(
|
||||
&metadata_path,
|
||||
&serde_json::to_vec(&metadata_fetch).unwrap_or_default(),
|
||||
io_semaphore,
|
||||
)
|
||||
.await?;
|
||||
|
||||
metadata = Some(metadata_fetch);
|
||||
Ok::<(), crate::Error>(())
|
||||
}
|
||||
.await;
|
||||
|
||||
match res {
|
||||
Ok(()) => {}
|
||||
Err(err) => {
|
||||
log::warn!("Could not read launcher metadata: {err}")
|
||||
log::warn!("Unable to fetch launcher metadata: {err}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut fetch_futures = LinkedList::new();
|
||||
for _ in 0..RETRY_ATTEMPTS {
|
||||
fetch_futures.push_back(Self::fetch().boxed());
|
||||
}
|
||||
|
||||
match future::select_ok(fetch_futures).await {
|
||||
Ok(meta) => metadata = Some(meta.0),
|
||||
Err(err) => log::warn!("Unable to fetch launcher metadata: {err}"),
|
||||
}
|
||||
|
||||
if let Some(meta) = metadata {
|
||||
db.insert(
|
||||
METADATA_DB_FIELD,
|
||||
sled::IVec::from(bincode::encode_to_vec(
|
||||
&meta,
|
||||
*BINCODE_CONFIG,
|
||||
)?),
|
||||
)?;
|
||||
db.flush_async().await?;
|
||||
Ok(meta)
|
||||
} else {
|
||||
Err(
|
||||
@@ -94,4 +92,35 @@ impl Metadata {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn update() {
|
||||
let res = async {
|
||||
let metadata_fetch = Metadata::fetch().await?;
|
||||
let state = crate::State::get().await?;
|
||||
|
||||
let metadata_path =
|
||||
state.directories.caches_meta_dir().join("metadata.json");
|
||||
|
||||
write(
|
||||
&metadata_path,
|
||||
&serde_json::to_vec(&metadata_fetch)?,
|
||||
&state.io_semaphore,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let mut old_metadata = state.metadata.write().await;
|
||||
*old_metadata = metadata_fetch;
|
||||
|
||||
Ok::<(), crate::Error>(())
|
||||
}
|
||||
.await;
|
||||
|
||||
match res {
|
||||
Ok(()) => {}
|
||||
Err(err) => {
|
||||
log::warn!("Unable to update launcher metadata: {err}")
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
//! Theseus state management system
|
||||
use crate::config::sled_config;
|
||||
use crate::event::emit::emit_loading;
|
||||
|
||||
use crate::event::emit::init_loading;
|
||||
use crate::event::LoadingBarType;
|
||||
use crate::jre;
|
||||
use crate::loading_join;
|
||||
|
||||
use crate::state::users::Users;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::{OnceCell, RwLock, Semaphore};
|
||||
|
||||
@@ -27,7 +26,6 @@ mod projects;
|
||||
pub use self::projects::*;
|
||||
|
||||
mod users;
|
||||
pub use self::users::*;
|
||||
|
||||
mod children;
|
||||
pub use self::children::*;
|
||||
@@ -51,8 +49,7 @@ pub struct State {
|
||||
/// Stored maximum number of sempahores of current io_semaphore
|
||||
pub io_semaphore_max: RwLock<u32>,
|
||||
/// Launcher metadata
|
||||
pub metadata: Metadata,
|
||||
// TODO: settings API
|
||||
pub metadata: RwLock<Metadata>,
|
||||
/// Launcher configuration
|
||||
pub settings: RwLock<Settings>,
|
||||
/// Reference to minecraft process children
|
||||
@@ -68,76 +65,55 @@ pub struct State {
|
||||
}
|
||||
|
||||
impl State {
|
||||
#[tracing::instrument]
|
||||
/// Get the current launcher state, initializing it if needed
|
||||
pub async fn get() -> crate::Result<Arc<Self>> {
|
||||
LAUNCHER_STATE
|
||||
.get_or_try_init(|| {
|
||||
async {
|
||||
let loading_bar = init_loading(
|
||||
LoadingBarType::StateInit,
|
||||
100.0,
|
||||
"Initializing launcher...",
|
||||
)
|
||||
.await?;
|
||||
|
||||
let loading_bar = init_loading(LoadingBarType::StateInit, 100.0, "Initializing launcher...").await?;
|
||||
// Directories
|
||||
let directories = DirectoryInfo::init().await?;
|
||||
|
||||
// Database
|
||||
// TODO: make database versioned
|
||||
let database = sled_config()
|
||||
.path(directories.database_file())
|
||||
.open()?;
|
||||
|
||||
emit_loading(&loading_bar, 10.0, None).await?;
|
||||
|
||||
// Settings
|
||||
let mut settings =
|
||||
let settings =
|
||||
Settings::init(&directories.settings_file()).await?;
|
||||
let io_semaphore = RwLock::new(Semaphore::new(
|
||||
settings.max_concurrent_downloads,
|
||||
));
|
||||
emit_loading(&loading_bar, 10.0, None).await?;
|
||||
|
||||
// Loose initializations
|
||||
let io_semaphore_max = settings.max_concurrent_downloads;
|
||||
|
||||
let io_semaphore =
|
||||
RwLock::new(Semaphore::new(io_semaphore_max));
|
||||
|
||||
let metadata_fut = Metadata::init(&database);
|
||||
let metadata_fut =
|
||||
Metadata::init(&directories, &io_semaphore);
|
||||
let profiles_fut =
|
||||
Profiles::init(&directories, &io_semaphore);
|
||||
|
||||
let tags_fut = Tags::init(&directories, &io_semaphore);
|
||||
let users_fut = Users::init(&directories, &io_semaphore);
|
||||
// Launcher data
|
||||
let (metadata, profiles) = loading_join! {
|
||||
Some(&loading_bar), 20.0, Some("Initializing metadata and profiles...");
|
||||
metadata_fut, profiles_fut
|
||||
let (metadata, profiles, tags, users) = loading_join! {
|
||||
Some(&loading_bar), 70.0, Some("Initializing...");
|
||||
metadata_fut,
|
||||
profiles_fut,
|
||||
tags_fut,
|
||||
users_fut,
|
||||
}?;
|
||||
|
||||
emit_loading(&loading_bar, 10.0, None).await?;
|
||||
let users = Users::init(&database)?;
|
||||
|
||||
let children = Children::new();
|
||||
|
||||
let auth_flow = AuthTask::new();
|
||||
|
||||
// On launcher initialization, attempt a tag fetch after tags init
|
||||
let mut tags = Tags::init(&database)?;
|
||||
if let Err(tag_fetch_err) =
|
||||
tags.fetch_update(&io_semaphore,Some(&loading_bar)).await
|
||||
{
|
||||
tracing::error!(
|
||||
"Failed to fetch tags on launcher init: {}",
|
||||
tag_fetch_err
|
||||
);
|
||||
};
|
||||
|
||||
emit_loading(&loading_bar, 10.0, None).await?;
|
||||
|
||||
// On launcher initialization, if global java variables are unset, try to find and set them
|
||||
// (they are required for the game to launch)
|
||||
if settings.java_globals.count() == 0 {
|
||||
settings.java_globals = jre::autodetect_java_globals().await?;
|
||||
}
|
||||
|
||||
Ok(Arc::new(Self {
|
||||
directories,
|
||||
io_semaphore,
|
||||
io_semaphore_max: RwLock::new(io_semaphore_max as u32),
|
||||
metadata,
|
||||
io_semaphore_max: RwLock::new(
|
||||
settings.max_concurrent_downloads as u32,
|
||||
),
|
||||
metadata: RwLock::new(metadata),
|
||||
settings: RwLock::new(settings),
|
||||
profiles: RwLock::new(profiles),
|
||||
users: RwLock::new(users),
|
||||
@@ -151,6 +127,14 @@ impl State {
|
||||
.map(Arc::clone)
|
||||
}
|
||||
|
||||
/// Updates state with data from the web
|
||||
pub fn update() {
|
||||
tokio::task::spawn(Metadata::update());
|
||||
tokio::task::spawn(Tags::update());
|
||||
tokio::task::spawn(Profiles::update_projects());
|
||||
tokio::task::spawn(Settings::update_java());
|
||||
}
|
||||
|
||||
#[tracing::instrument]
|
||||
/// Synchronize in-memory state with persistent state
|
||||
pub async fn sync() -> crate::Result<()> {
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
use super::settings::{Hooks, MemorySettings, WindowSize};
|
||||
use crate::config::MODRINTH_API_URL;
|
||||
use crate::data::DirectoryInfo;
|
||||
use crate::event::emit::{
|
||||
emit_profile, init_loading, loading_try_for_each_concurrent,
|
||||
};
|
||||
use crate::event::{LoadingBarType, ProfilePayloadType};
|
||||
use crate::event::emit::emit_profile;
|
||||
use crate::event::ProfilePayloadType;
|
||||
use crate::state::projects::Project;
|
||||
use crate::state::{ModrinthVersion, ProjectType};
|
||||
use crate::util::fetch::{fetch, fetch_json, write, write_cached_icon};
|
||||
@@ -34,6 +32,8 @@ pub const CURRENT_FORMAT_VERSION: u32 = 1;
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct Profile {
|
||||
pub uuid: Uuid, // todo: will be used in restructure to refer to profiles
|
||||
#[serde(default)]
|
||||
pub installed: bool,
|
||||
pub path: PathBuf,
|
||||
pub metadata: ProfileMetadata,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
@@ -58,10 +58,15 @@ pub struct ProfileMetadata {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub loader_version: Option<LoaderVersion>,
|
||||
pub format_version: u32,
|
||||
pub linked_project_id: Option<String>,
|
||||
pub linked_data: Option<LinkedData>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct LinkedData {
|
||||
pub project_id: Option<String>,
|
||||
pub version_id: Option<String>,
|
||||
}
|
||||
|
||||
// TODO: Quilt?
|
||||
#[derive(
|
||||
Debug, Eq, PartialEq, Clone, Copy, Deserialize, Serialize, Default,
|
||||
)]
|
||||
@@ -85,6 +90,17 @@ impl std::fmt::Display for ModLoader {
|
||||
}
|
||||
}
|
||||
|
||||
impl ModLoader {
|
||||
pub(crate) fn as_api_str(&self) -> &'static str {
|
||||
match *self {
|
||||
Self::Vanilla => "vanilla",
|
||||
Self::Forge => "forge",
|
||||
Self::Fabric => "fabric",
|
||||
Self::Quilt => "quilt",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct JavaSettings {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
@@ -110,6 +126,7 @@ impl Profile {
|
||||
|
||||
Ok(Self {
|
||||
uuid,
|
||||
installed: false,
|
||||
path: canonicalize(path)?,
|
||||
metadata: ProfileMetadata {
|
||||
name,
|
||||
@@ -118,7 +135,7 @@ impl Profile {
|
||||
loader: ModLoader::Vanilla,
|
||||
loader_version: None,
|
||||
format_version: CURRENT_FORMAT_VERSION,
|
||||
linked_project_id: None,
|
||||
linked_data: None,
|
||||
},
|
||||
projects: HashMap::new(),
|
||||
java: None,
|
||||
@@ -147,7 +164,7 @@ impl Profile {
|
||||
|
||||
let paths = self.get_profile_project_paths()?;
|
||||
let projects = crate::state::infer_data_from_files(
|
||||
paths,
|
||||
&[(self.clone(), paths)],
|
||||
state.directories.caches_dir(),
|
||||
&state.io_semaphore,
|
||||
)
|
||||
@@ -155,6 +172,14 @@ impl Profile {
|
||||
|
||||
self.projects = projects;
|
||||
|
||||
emit_profile(
|
||||
self.uuid,
|
||||
self.path.clone(),
|
||||
&self.metadata.name,
|
||||
ProfilePayloadType::Synced,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -266,8 +291,6 @@ impl Profile {
|
||||
let path = self.path.join(project_type.get_folder()).join(file_name);
|
||||
write(&path, &bytes, &state.io_semaphore).await?;
|
||||
|
||||
self.sync().await?;
|
||||
|
||||
Ok(path)
|
||||
}
|
||||
|
||||
@@ -345,36 +368,62 @@ impl Profiles {
|
||||
}
|
||||
}
|
||||
|
||||
// project path, parent profile path
|
||||
let mut files: HashMap<PathBuf, PathBuf> = HashMap::new();
|
||||
{
|
||||
for (profile_path, profile) in profiles.iter() {
|
||||
let paths = profile.get_profile_project_paths()?;
|
||||
|
||||
for path in paths {
|
||||
files.insert(path, profile_path.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let inferred = super::projects::infer_data_from_files(
|
||||
files.keys().cloned().collect(),
|
||||
dirs.caches_dir(),
|
||||
io_sempahore,
|
||||
)
|
||||
.await?;
|
||||
|
||||
for (key, value) in inferred {
|
||||
if let Some(profile_path) = files.get(&key) {
|
||||
if let Some(profile) = profiles.get_mut(profile_path) {
|
||||
profile.projects.insert(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Self(profiles))
|
||||
}
|
||||
|
||||
pub async fn update_projects() {
|
||||
let res = async {
|
||||
let state = State::get().await?;
|
||||
|
||||
// profile, child paths
|
||||
let mut files: Vec<(Profile, Vec<PathBuf>)> = Vec::new();
|
||||
{
|
||||
let profiles = state.profiles.read().await;
|
||||
for (_profile_path, profile) in profiles.0.iter() {
|
||||
let paths = profile.get_profile_project_paths()?;
|
||||
|
||||
files.push((profile.clone(), paths));
|
||||
}
|
||||
}
|
||||
|
||||
if !files.is_empty() {
|
||||
let inferred = super::projects::infer_data_from_files(
|
||||
&files,
|
||||
state.directories.caches_dir(),
|
||||
&state.io_semaphore,
|
||||
)
|
||||
.await?;
|
||||
let mut wipe_profiles = Vec::new();
|
||||
for (key, value) in inferred {
|
||||
if let Some((profile, _)) =
|
||||
files.iter().find(|(_, files)| files.contains(&key))
|
||||
{
|
||||
let mut new_profiles = state.profiles.write().await;
|
||||
if let Some(profile) =
|
||||
new_profiles.0.get_mut(&profile.path)
|
||||
{
|
||||
if !wipe_profiles.contains(&profile.path) {
|
||||
profile.projects = HashMap::new();
|
||||
wipe_profiles.push(profile.path.clone());
|
||||
}
|
||||
profile.projects.insert(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok::<(), crate::Error>(())
|
||||
}
|
||||
.await;
|
||||
|
||||
match res {
|
||||
Ok(()) => {}
|
||||
Err(err) => {
|
||||
log::warn!("Unable to fetch profile projects: {err}")
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub async fn insert(&mut self, profile: Profile) -> crate::Result<&Self> {
|
||||
emit_profile(
|
||||
@@ -406,35 +455,26 @@ impl Profiles {
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub async fn remove(&mut self, path: &Path) -> crate::Result<&Self> {
|
||||
pub async fn remove(
|
||||
&mut self,
|
||||
path: &Path,
|
||||
) -> crate::Result<Option<Profile>> {
|
||||
let path =
|
||||
PathBuf::from(&canonicalize(path)?.to_string_lossy().to_string());
|
||||
self.0.remove(&path);
|
||||
let profile = self.0.remove(&path);
|
||||
|
||||
if path.exists() {
|
||||
fs::remove_dir_all(path).await?;
|
||||
}
|
||||
|
||||
Ok(self)
|
||||
Ok(profile)
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all)]
|
||||
pub async fn sync(&self) -> crate::Result<&Self> {
|
||||
let loading_bar = init_loading(
|
||||
LoadingBarType::ProfileSync,
|
||||
100.0,
|
||||
"Syncing profiles...",
|
||||
)
|
||||
.await?;
|
||||
let num_futs = self.0.len();
|
||||
loading_try_for_each_concurrent(
|
||||
stream::iter(self.0.iter()).map(Ok::<_, crate::Error>),
|
||||
None,
|
||||
Some(&loading_bar),
|
||||
100.0,
|
||||
num_futs,
|
||||
None,
|
||||
|(path, profile)| async move {
|
||||
stream::iter(self.0.iter())
|
||||
.map(Ok::<_, crate::Error>)
|
||||
.try_for_each_concurrent(None, |(path, profile)| async move {
|
||||
let json = serde_json::to_vec(&profile)?;
|
||||
|
||||
let json_path = Path::new(&path.to_string_lossy().to_string())
|
||||
@@ -442,9 +482,8 @@ impl Profiles {
|
||||
|
||||
fs::write(json_path, json).await?;
|
||||
Ok::<_, crate::Error>(())
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
})
|
||||
.await?;
|
||||
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
//! Project management + inference
|
||||
|
||||
use crate::config::MODRINTH_API_URL;
|
||||
use crate::data::ModLoader;
|
||||
use crate::state::Profile;
|
||||
use crate::util::fetch::{fetch_json, write_cached_icon};
|
||||
use async_zip::tokio::read::fs::ZipFileReader;
|
||||
use chrono::{DateTime, Utc};
|
||||
@@ -185,6 +187,8 @@ pub enum ProjectMetadata {
|
||||
project: Box<ModrinthProject>,
|
||||
version: Box<ModrinthVersion>,
|
||||
members: Vec<ModrinthTeamMember>,
|
||||
update_version: Option<Box<ModrinthVersion>>,
|
||||
incompatible: bool,
|
||||
},
|
||||
Inferred {
|
||||
title: Option<String>,
|
||||
@@ -246,21 +250,23 @@ async fn read_icon_from_file(
|
||||
}
|
||||
|
||||
pub async fn infer_data_from_files(
|
||||
paths: Vec<PathBuf>,
|
||||
paths: &[(Profile, Vec<PathBuf>)],
|
||||
cache_dir: PathBuf,
|
||||
io_semaphore: &RwLock<Semaphore>,
|
||||
) -> crate::Result<HashMap<PathBuf, Project>> {
|
||||
let mut file_path_hashes = HashMap::new();
|
||||
|
||||
// TODO: Make this concurrent and use progressive hashing to avoid loading each JAR in memory
|
||||
for path in paths.clone() {
|
||||
let mut file = tokio::fs::File::open(path.clone()).await?;
|
||||
for set in paths {
|
||||
for path in &set.1 {
|
||||
let mut file = tokio::fs::File::open(path.clone()).await?;
|
||||
|
||||
let mut buffer = Vec::new();
|
||||
file.read_to_end(&mut buffer).await?;
|
||||
let mut buffer = Vec::new();
|
||||
file.read_to_end(&mut buffer).await?;
|
||||
|
||||
let hash = format!("{:x}", sha2::Sha512::digest(&buffer));
|
||||
file_path_hashes.insert(hash, path.clone());
|
||||
let hash = format!("{:x}", sha2::Sha512::digest(&buffer));
|
||||
file_path_hashes.insert(hash, path.clone());
|
||||
}
|
||||
}
|
||||
|
||||
let files: HashMap<String, ModrinthVersion> = fetch_json(
|
||||
@@ -291,7 +297,6 @@ pub async fn infer_data_from_files(
|
||||
io_semaphore,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let teams: Vec<ModrinthTeamMember> = fetch_json::<
|
||||
Vec<Vec<ModrinthTeamMember>>,
|
||||
>(
|
||||
@@ -312,6 +317,26 @@ pub async fn infer_data_from_files(
|
||||
.flatten()
|
||||
.collect();
|
||||
|
||||
let mut update_versions: Vec<ModrinthVersion> = fetch_json(
|
||||
Method::GET,
|
||||
&format!(
|
||||
"{}versions?ids={}",
|
||||
MODRINTH_API_URL,
|
||||
serde_json::to_string(
|
||||
&projects
|
||||
.iter()
|
||||
.flat_map(|x| x.versions.clone())
|
||||
.collect::<Vec<String>>()
|
||||
)?
|
||||
),
|
||||
None,
|
||||
None,
|
||||
io_semaphore,
|
||||
)
|
||||
.await?;
|
||||
|
||||
update_versions.sort_by(|a, b| b.date_published.cmp(&a.date_published));
|
||||
|
||||
let mut return_projects = HashMap::new();
|
||||
let mut further_analyze_projects: Vec<(String, PathBuf)> = Vec::new();
|
||||
|
||||
@@ -320,18 +345,14 @@ pub async fn infer_data_from_files(
|
||||
if let Some(project) =
|
||||
projects.iter().find(|x| version.project_id == x.id)
|
||||
{
|
||||
let profile = paths.iter().find(|x| x.1.contains(&path));
|
||||
|
||||
let file_name = path
|
||||
.file_name()
|
||||
.unwrap_or_default()
|
||||
.to_string_lossy()
|
||||
.to_string();
|
||||
|
||||
let team_members = teams
|
||||
.iter()
|
||||
.filter(|x| x.team_id == project.team)
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
return_projects.insert(
|
||||
path,
|
||||
Project {
|
||||
@@ -340,7 +361,52 @@ pub async fn infer_data_from_files(
|
||||
metadata: ProjectMetadata::Modrinth {
|
||||
project: Box::new(project.clone()),
|
||||
version: Box::new(version.clone()),
|
||||
members: team_members,
|
||||
members: teams
|
||||
.iter()
|
||||
.filter(|x| x.team_id == project.team)
|
||||
.cloned()
|
||||
.collect::<Vec<_>>(),
|
||||
update_version: if let Some((profile, _)) = &profile
|
||||
{
|
||||
update_versions
|
||||
.iter()
|
||||
.find(|x| {
|
||||
x.project_id == project.id
|
||||
&& x.game_versions.contains(
|
||||
&profile.metadata.game_version,
|
||||
)
|
||||
&& if profile.metadata.loader
|
||||
== ModLoader::Vanilla
|
||||
{
|
||||
true
|
||||
} else {
|
||||
x.loaders.contains(
|
||||
&profile
|
||||
.metadata
|
||||
.loader
|
||||
.as_api_str()
|
||||
.to_string(),
|
||||
)
|
||||
}
|
||||
})
|
||||
.cloned()
|
||||
.map(Box::new)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
incompatible: if let Some((profile, _)) = &profile {
|
||||
!version.loaders.contains(
|
||||
&profile
|
||||
.metadata
|
||||
.loader
|
||||
.as_api_str()
|
||||
.to_string(),
|
||||
) || version
|
||||
.game_versions
|
||||
.contains(&profile.metadata.game_version)
|
||||
} else {
|
||||
false
|
||||
},
|
||||
},
|
||||
file_name,
|
||||
},
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
//! Theseus settings file
|
||||
use crate::{jre, State};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::path::Path;
|
||||
use tokio::fs;
|
||||
@@ -63,6 +64,29 @@ impl Settings {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn update_java() {
|
||||
let res = async {
|
||||
let state = State::get().await?;
|
||||
let settings_read = state.settings.write().await;
|
||||
|
||||
if settings_read.java_globals.count() == 0 {
|
||||
drop(settings_read);
|
||||
let java_globals = jre::autodetect_java_globals().await?;
|
||||
state.settings.write().await.java_globals = java_globals;
|
||||
}
|
||||
|
||||
Ok::<(), crate::Error>(())
|
||||
}
|
||||
.await;
|
||||
|
||||
match res {
|
||||
Ok(()) => {}
|
||||
Err(err) => {
|
||||
log::warn!("Unable to update launcher java: {err}")
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub async fn sync(&self, to: &Path) -> crate::Result<()> {
|
||||
fs::write(to, serde_json::to_vec(self)?)
|
||||
|
||||
@@ -1,152 +1,125 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use bincode::{Decode, Encode};
|
||||
use reqwest::Method;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::sync::{RwLock, Semaphore};
|
||||
|
||||
use crate::config::{BINCODE_CONFIG, MODRINTH_API_URL};
|
||||
use crate::event::LoadingBarId;
|
||||
use crate::loading_join;
|
||||
use crate::util::fetch::fetch_json;
|
||||
use crate::config::MODRINTH_API_URL;
|
||||
use crate::data::DirectoryInfo;
|
||||
use crate::util::fetch::{fetch_json, read_json, write};
|
||||
|
||||
const CATEGORIES_DB_TREE: &[u8] = b"categories";
|
||||
const LOADERS_DB_TREE: &[u8] = b"loaders";
|
||||
const GAME_VERSIONS_DB_TREE: &[u8] = b"game_versions";
|
||||
const LICENSES_DB_TREE: &[u8] = b"licenses";
|
||||
const DONATION_PLATFORMS_DB_TREE: &[u8] = b"donation_platforms";
|
||||
const REPORT_TYPES_DB_TREE: &[u8] = b"report_types";
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct Tags(pub(crate) TagsInner);
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TagsInner {
|
||||
pub categories: sled::Tree,
|
||||
pub loaders: sled::Tree,
|
||||
pub game_versions: sled::Tree,
|
||||
pub licenses: sled::Tree,
|
||||
pub donation_platforms: sled::Tree,
|
||||
pub report_types: sled::Tree,
|
||||
// Serializeable struct for all tags to be fetched together by the frontend
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Tags {
|
||||
pub categories: Vec<Category>,
|
||||
pub loaders: Vec<Loader>,
|
||||
pub game_versions: Vec<GameVersion>,
|
||||
pub donation_platforms: Vec<DonationPlatform>,
|
||||
pub report_types: Vec<String>,
|
||||
}
|
||||
|
||||
impl Tags {
|
||||
#[tracing::instrument(skip(db))]
|
||||
pub fn init(db: &sled::Db) -> crate::Result<Self> {
|
||||
Ok(Tags(TagsInner {
|
||||
categories: db.open_tree(CATEGORIES_DB_TREE)?,
|
||||
loaders: db.open_tree(LOADERS_DB_TREE)?,
|
||||
game_versions: db.open_tree(GAME_VERSIONS_DB_TREE)?,
|
||||
licenses: db.open_tree(LICENSES_DB_TREE)?,
|
||||
donation_platforms: db.open_tree(DONATION_PLATFORMS_DB_TREE)?,
|
||||
report_types: db.open_tree(REPORT_TYPES_DB_TREE)?,
|
||||
}))
|
||||
pub async fn init(
|
||||
dirs: &DirectoryInfo,
|
||||
io_semaphore: &RwLock<Semaphore>,
|
||||
) -> crate::Result<Self> {
|
||||
let mut tags = None;
|
||||
let tags_path = dirs.caches_meta_dir().join("tags.json");
|
||||
|
||||
if let Ok(tags_json) = read_json::<Self>(&tags_path, io_semaphore).await
|
||||
{
|
||||
tags = Some(tags_json);
|
||||
} else {
|
||||
match Self::fetch(io_semaphore).await {
|
||||
Ok(tags_fetch) => tags = Some(tags_fetch),
|
||||
Err(err) => {
|
||||
log::warn!("Unable to fetch launcher tags: {err}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(tags_data) = tags {
|
||||
write(&tags_path, &serde_json::to_vec(&tags_data)?, io_semaphore)
|
||||
.await?;
|
||||
Ok(tags_data)
|
||||
} else {
|
||||
Err(crate::ErrorKind::NoValueFor(String::from("launcher tags"))
|
||||
.as_error())
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn update() {
|
||||
let res = async {
|
||||
let state = crate::State::get().await?;
|
||||
let tags_fetch = Tags::fetch(&state.io_semaphore).await?;
|
||||
|
||||
let tags_path =
|
||||
state.directories.caches_meta_dir().join("tags.json");
|
||||
|
||||
write(
|
||||
&tags_path,
|
||||
&serde_json::to_vec(&tags_fetch)?,
|
||||
&state.io_semaphore,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let mut old_tags = state.tags.write().await;
|
||||
*old_tags = tags_fetch;
|
||||
|
||||
Ok::<(), crate::Error>(())
|
||||
}
|
||||
.await;
|
||||
|
||||
match res {
|
||||
Ok(()) => {}
|
||||
Err(err) => {
|
||||
log::warn!("Unable to update launcher tags: {err}")
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Checks the database for categories tag, returns a Vec::new() if it doesnt exist, otherwise returns the categories
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn get_categories(&self) -> crate::Result<Vec<Category>> {
|
||||
self.0.categories.get("categories")?.map_or(
|
||||
Ok(Vec::new()),
|
||||
|categories| {
|
||||
bincode::decode_from_slice(&categories, *BINCODE_CONFIG)
|
||||
.map_err(crate::Error::from)
|
||||
.map(|it| it.0)
|
||||
},
|
||||
)
|
||||
pub fn get_categories(&self) -> Vec<Category> {
|
||||
self.categories.clone()
|
||||
}
|
||||
|
||||
// Checks the database for loaders tag, returns a Vec::new() if it doesnt exist, otherwise returns the loaders
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn get_loaders(&self) -> crate::Result<Vec<Loader>> {
|
||||
self.0
|
||||
.loaders
|
||||
.get("loaders")?
|
||||
.map_or(Ok(Vec::new()), |loaders| {
|
||||
bincode::decode_from_slice(&loaders, *BINCODE_CONFIG)
|
||||
.map_err(crate::Error::from)
|
||||
.map(|it| it.0)
|
||||
})
|
||||
pub fn get_loaders(&self) -> Vec<Loader> {
|
||||
self.loaders.clone()
|
||||
}
|
||||
|
||||
// Checks the database for game_versions tag, returns a Vec::new() if it doesnt exist, otherwise returns the game_versions
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn get_game_versions(&self) -> crate::Result<Vec<GameVersion>> {
|
||||
self.0.game_versions.get("game_versions")?.map_or(
|
||||
Ok(Vec::new()),
|
||||
|game_versions| {
|
||||
bincode::decode_from_slice(&game_versions, *BINCODE_CONFIG)
|
||||
.map_err(crate::Error::from)
|
||||
.map(|it| it.0)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// Checks the database for licenses tag, returns a Vec::new() if it doesnt exist, otherwise returns the licenses
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn get_licenses(&self) -> crate::Result<Vec<License>> {
|
||||
self.0
|
||||
.licenses
|
||||
.get("licenses")?
|
||||
.map_or(Ok(Vec::new()), |licenses| {
|
||||
bincode::decode_from_slice(&licenses, *BINCODE_CONFIG)
|
||||
.map_err(crate::Error::from)
|
||||
.map(|it| it.0)
|
||||
})
|
||||
pub fn get_game_versions(&self) -> Vec<GameVersion> {
|
||||
self.game_versions.clone()
|
||||
}
|
||||
|
||||
// Checks the database for donation_platforms tag, returns a Vec::new() if it doesnt exist, otherwise returns the donation_platforms
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn get_donation_platforms(
|
||||
&self,
|
||||
) -> crate::Result<Vec<DonationPlatform>> {
|
||||
self.0.donation_platforms.get("donation_platforms")?.map_or(
|
||||
Ok(Vec::new()),
|
||||
|donation_platforms| {
|
||||
bincode::decode_from_slice(&donation_platforms, *BINCODE_CONFIG)
|
||||
.map_err(crate::Error::from)
|
||||
.map(|it| it.0)
|
||||
},
|
||||
)
|
||||
pub fn get_donation_platforms(&self) -> Vec<DonationPlatform> {
|
||||
self.donation_platforms.clone()
|
||||
}
|
||||
|
||||
// Checks the database for report_types tag, returns a Vec::new() if it doesnt exist, otherwise returns the report_types
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn get_report_types(&self) -> crate::Result<Vec<String>> {
|
||||
self.0.report_types.get("report_types")?.map_or(
|
||||
Ok(Vec::new()),
|
||||
|report_types| {
|
||||
bincode::decode_from_slice(&report_types, *BINCODE_CONFIG)
|
||||
.map_err(crate::Error::from)
|
||||
.map(|it| it.0)
|
||||
},
|
||||
)
|
||||
pub fn get_report_types(&self) -> Vec<String> {
|
||||
self.report_types.clone()
|
||||
}
|
||||
|
||||
// Gets all tags together as a serializable bundle
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn get_tag_bundle(&self) -> crate::Result<TagBundle> {
|
||||
Ok(TagBundle {
|
||||
categories: self.get_categories()?,
|
||||
loaders: self.get_loaders()?,
|
||||
game_versions: self.get_game_versions()?,
|
||||
licenses: self.get_licenses()?,
|
||||
donation_platforms: self.get_donation_platforms()?,
|
||||
report_types: self.get_report_types()?,
|
||||
})
|
||||
pub fn get_tag_bundle(&self) -> Tags {
|
||||
self.clone()
|
||||
}
|
||||
|
||||
// Fetches the tags from the Modrinth API and stores them in the database
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub async fn fetch_update(
|
||||
&mut self,
|
||||
semaphore: &RwLock<Semaphore>,
|
||||
loading_bar: Option<&LoadingBarId>,
|
||||
) -> crate::Result<()> {
|
||||
pub async fn fetch(semaphore: &RwLock<Semaphore>) -> crate::Result<Self> {
|
||||
let categories = format!("{MODRINTH_API_URL}tag/category");
|
||||
let loaders = format!("{MODRINTH_API_URL}tag/loader");
|
||||
let game_versions = format!("{MODRINTH_API_URL}tag/game_version");
|
||||
let licenses = format!("{MODRINTH_API_URL}tag/license");
|
||||
let donation_platforms =
|
||||
format!("{MODRINTH_API_URL}tag/donation_platform");
|
||||
let report_types = format!("{MODRINTH_API_URL}tag/report_type");
|
||||
@@ -172,13 +145,6 @@ impl Tags {
|
||||
None,
|
||||
semaphore,
|
||||
);
|
||||
let licenses_fut = fetch_json::<Vec<License>>(
|
||||
Method::GET,
|
||||
&licenses,
|
||||
None,
|
||||
None,
|
||||
semaphore,
|
||||
);
|
||||
let donation_platforms_fut = fetch_json::<Vec<DonationPlatform>>(
|
||||
Method::GET,
|
||||
&donation_platforms,
|
||||
@@ -198,60 +164,27 @@ impl Tags {
|
||||
categories,
|
||||
loaders,
|
||||
game_versions,
|
||||
licenses,
|
||||
donation_platforms,
|
||||
report_types,
|
||||
) = loading_join!(loading_bar, 0.5, None;
|
||||
) = tokio::try_join!(
|
||||
categories_fut,
|
||||
loaders_fut,
|
||||
game_versions_fut,
|
||||
licenses_fut,
|
||||
donation_platforms_fut,
|
||||
report_types_fut
|
||||
)?;
|
||||
|
||||
// Store the tags in the database
|
||||
self.0.categories.insert(
|
||||
"categories",
|
||||
bincode::encode_to_vec(categories, *BINCODE_CONFIG)?,
|
||||
)?;
|
||||
self.0.loaders.insert(
|
||||
"loaders",
|
||||
bincode::encode_to_vec(loaders, *BINCODE_CONFIG)?,
|
||||
)?;
|
||||
self.0.game_versions.insert(
|
||||
"game_versions",
|
||||
bincode::encode_to_vec(game_versions, *BINCODE_CONFIG)?,
|
||||
)?;
|
||||
self.0.licenses.insert(
|
||||
"licenses",
|
||||
bincode::encode_to_vec(licenses, *BINCODE_CONFIG)?,
|
||||
)?;
|
||||
self.0.donation_platforms.insert(
|
||||
"donation_platforms",
|
||||
bincode::encode_to_vec(donation_platforms, *BINCODE_CONFIG)?,
|
||||
)?;
|
||||
self.0.report_types.insert(
|
||||
"report_types",
|
||||
bincode::encode_to_vec(report_types, *BINCODE_CONFIG)?,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
Ok(Self {
|
||||
categories,
|
||||
loaders,
|
||||
game_versions,
|
||||
donation_platforms,
|
||||
report_types,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Serializeable struct for all tags to be fetched together by the frontend
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct TagBundle {
|
||||
pub categories: Vec<Category>,
|
||||
pub loaders: Vec<Loader>,
|
||||
pub game_versions: Vec<GameVersion>,
|
||||
pub licenses: Vec<License>,
|
||||
pub donation_platforms: Vec<DonationPlatform>,
|
||||
pub report_types: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Decode, Encode, Serialize, Deserialize)]
|
||||
pub struct Category {
|
||||
pub name: String,
|
||||
pub project_type: String,
|
||||
@@ -259,26 +192,20 @@ pub struct Category {
|
||||
pub icon: PathBuf,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Decode, Encode, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Loader {
|
||||
pub name: String,
|
||||
pub icon: PathBuf,
|
||||
pub supported_project_types: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Decode, Encode, Serialize, Deserialize)]
|
||||
pub struct License {
|
||||
pub short: String,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Decode, Encode, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct DonationPlatform {
|
||||
pub short: String,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Decode, Encode, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct GameVersion {
|
||||
pub version: String,
|
||||
pub version_type: String,
|
||||
|
||||
@@ -1,79 +1,70 @@
|
||||
//! User login info
|
||||
use crate::{auth::Credentials, config::BINCODE_CONFIG};
|
||||
use crate::auth::Credentials;
|
||||
use crate::data::DirectoryInfo;
|
||||
use crate::util::fetch::{read_json, write};
|
||||
use crate::State;
|
||||
use std::collections::HashMap;
|
||||
use tokio::sync::{RwLock, Semaphore};
|
||||
use uuid::Uuid;
|
||||
|
||||
const USER_DB_TREE: &[u8] = b"users";
|
||||
const USERS_JSON: &str = "users.json";
|
||||
|
||||
/// The set of users stored in the launcher
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct Users(pub(crate) sled::Tree);
|
||||
pub(crate) struct Users(pub(crate) HashMap<Uuid, Credentials>);
|
||||
|
||||
impl Users {
|
||||
#[tracing::instrument(skip(db))]
|
||||
pub fn init(db: &sled::Db) -> crate::Result<Self> {
|
||||
Ok(Self(db.open_tree(USER_DB_TREE)?))
|
||||
pub async fn init(
|
||||
dirs: &DirectoryInfo,
|
||||
io_semaphore: &RwLock<Semaphore>,
|
||||
) -> crate::Result<Self> {
|
||||
let users_path = dirs.caches_meta_dir().join(USERS_JSON);
|
||||
let users = read_json(&users_path, io_semaphore).await.ok();
|
||||
|
||||
if let Some(users) = users {
|
||||
Ok(Self(users))
|
||||
} else {
|
||||
Ok(Self(HashMap::new()))
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn save(&self) -> crate::Result<()> {
|
||||
let state = State::get().await?;
|
||||
let users_path = state.directories.caches_meta_dir().join(USERS_JSON);
|
||||
write(
|
||||
&users_path,
|
||||
&serde_json::to_vec(&self.0)?,
|
||||
&state.io_semaphore,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all)]
|
||||
pub fn insert(
|
||||
pub async fn insert(
|
||||
&mut self,
|
||||
credentials: &Credentials,
|
||||
) -> crate::Result<&Self> {
|
||||
let id = credentials.id.as_bytes();
|
||||
self.0.insert(
|
||||
id,
|
||||
bincode::encode_to_vec(credentials, *BINCODE_CONFIG)?,
|
||||
)?;
|
||||
self.0.insert(credentials.id, credentials.clone());
|
||||
self.save().await?;
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn contains(&self, id: uuid::Uuid) -> crate::Result<bool> {
|
||||
Ok(self.0.contains_key(id.as_bytes())?)
|
||||
pub fn contains(&self, id: Uuid) -> bool {
|
||||
self.0.contains_key(&id)
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn get(&self, id: uuid::Uuid) -> crate::Result<Option<Credentials>> {
|
||||
self.0.get(id.as_bytes())?.map_or(Ok(None), |prof| {
|
||||
bincode::decode_from_slice(&prof, *BINCODE_CONFIG)
|
||||
.map_err(crate::Error::from)
|
||||
.map(|it| Some(it.0))
|
||||
})
|
||||
pub fn get(&self, id: Uuid) -> Option<Credentials> {
|
||||
self.0.get(&id).cloned()
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn remove(&mut self, id: uuid::Uuid) -> crate::Result<&Self> {
|
||||
self.0.remove(id.as_bytes())?;
|
||||
pub async fn remove(&mut self, id: Uuid) -> crate::Result<&Self> {
|
||||
self.0.remove(&id);
|
||||
self.save().await?;
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> UserIter<impl UserInnerIter> {
|
||||
UserIter(self.0.iter().values(), false)
|
||||
}
|
||||
}
|
||||
|
||||
alias_trait! {
|
||||
pub UserInnerIter: Iterator<Item = sled::Result<sled::IVec>>, Send, Sync
|
||||
}
|
||||
|
||||
/// An iterator over the set of users
|
||||
#[derive(Debug)]
|
||||
pub struct UserIter<I: UserInnerIter>(I, bool);
|
||||
|
||||
impl<I: UserInnerIter> Iterator for UserIter<I> {
|
||||
type Item = crate::Result<Credentials>;
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.1 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let it = self.0.next()?;
|
||||
let res = it.map_err(crate::Error::from).and_then(|it| {
|
||||
Ok(bincode::decode_from_slice(&it, *BINCODE_CONFIG)?.0)
|
||||
});
|
||||
|
||||
self.1 = res.is_err();
|
||||
Some(res)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,6 +144,22 @@ pub async fn fetch_mirrors(
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
pub async fn read_json<T>(
|
||||
path: &Path,
|
||||
semaphore: &RwLock<Semaphore>,
|
||||
) -> crate::Result<T>
|
||||
where
|
||||
T: DeserializeOwned,
|
||||
{
|
||||
let io_semaphore = semaphore.read().await;
|
||||
let _permit = io_semaphore.acquire().await?;
|
||||
|
||||
let json = fs::read(path).await?;
|
||||
let json = serde_json::from_slice::<T>(&json)?;
|
||||
|
||||
Ok(json)
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(bytes, semaphore))]
|
||||
pub async fn write<'a>(
|
||||
path: &Path,
|
||||
|
||||
@@ -14,11 +14,3 @@ macro_rules! wrap_ref_builder {
|
||||
it
|
||||
}};
|
||||
}
|
||||
|
||||
/// Alias a trait, used to avoid needing nightly features
|
||||
macro_rules! alias_trait {
|
||||
($scope:vis $name:ident : $bound:path $(, $bounds:path)*) => {
|
||||
$scope trait $name: $bound $(+ $bounds)* {}
|
||||
impl<T: $bound $(+ $bounds)*> $name for T {}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user