OneLauncher-Cracked/packages/oneclient_app/src/install.rs
oiupoyt 991a85c589
Some checks are pending
Release Multiplatform Binaries / build (deb,appimage, linux-x86_64, ubuntu-22.04, x86_64-unknown-linux-gnu) (push) Waiting to run
Release Multiplatform Binaries / build (dmg,app, macos-arm64, macos-14, aarch64-apple-darwin) (push) Waiting to run
Release Multiplatform Binaries / build (dmg,app, macos-x86_64, macos-14, x86_64-apple-darwin) (push) Waiting to run
Release Multiplatform Binaries / build (nsis, windows-x86_64, windows-latest, x86_64-pc-windows-msvc) (push) Waiting to run
Release Multiplatform Binaries / create-release (push) Blocked by required conditions
feat: update base to official upstream 2.5.1 with all cracked features intact (2.5.1-c1)
2026-09-18 07:16:25 +05:30

394 lines
11 KiB
Rust

use std::sync::Arc;
use oneclient_core::LauncherState;
use oneclient_content::packages::PackageStore;
use oneclient_events::Level;
use crate::components::IconType;
use crate::notifications::{
ClusterUpdateItem, ClusterUpdateSummary, NotificationAction, NotificationActionKind,
NotificationSpec, OptionalModsGroup, PackageUpdateGroup,
};
/// Falls back to a placeholder a missing row must not lose the whole message
pub async fn cluster_display_name(
cluster_id: i64,
services: &oneclient_core::LauncherServices,
) -> String {
PackageStore::get_cluster(cluster_id, &services.content())
.await
.map(|cluster| cluster.name)
.unwrap_or_else(|_| "Cluster".to_string())
}
pub async fn package_update_group(
cluster_id: i64,
updates: &[oneclient_core::BrowserPackageUpdate],
services: &oneclient_core::LauncherServices,
) -> Option<PackageUpdateGroup> {
if updates.is_empty() {
return None;
}
Some(PackageUpdateGroup {
cluster_id,
cluster_name: cluster_display_name(cluster_id, services).await,
packages: updates.to_vec(),
})
}
pub fn item_from_bundle_file(file: &oneclient_core::BundleFile) -> ClusterUpdateItem {
match &file.kind {
oneclient_core::BundleFileKind::Managed {
provider,
project_id,
..
} => ClusterUpdateItem {
provider: *provider,
project_id: Some(project_id.clone()),
fallback: file.display_name(),
offer: None,
status: None,
},
oneclient_core::BundleFileKind::External(_) => ClusterUpdateItem {
provider: oneclient_content::packages::ProviderId::Local,
project_id: None,
fallback: file.display_name(),
offer: None,
status: None,
},
}
}
fn item_from_optional(package: &oneclient_core::BundleOptionalPackage) -> ClusterUpdateItem {
ClusterUpdateItem {
offer: Some((package.bundle_name.clone(), package.package_id.clone())),
..item_from_bundle_file(&package.file)
}
}
async fn cluster_update_summary(
cluster_id: i64,
result: &oneclient_core::ApplyBundleUpdatesResult,
services: &oneclient_core::LauncherServices,
) -> Option<ClusterUpdateSummary> {
let updated: Vec<ClusterUpdateItem> = result
.updates_applied
.iter()
.map(|u| item_from_bundle_file(&u.new_file))
.collect();
let added: Vec<ClusterUpdateItem> = result
.additions_applied
.iter()
.map(|a| item_from_bundle_file(&a.new_file))
.collect();
let removed: Vec<ClusterUpdateItem> = result
.removals_applied
.iter()
.map(|r| ClusterUpdateItem {
provider: r
.provider
.unwrap_or(oneclient_content::packages::ProviderId::Local),
project_id: r.project_id.clone(),
fallback: r
.display_name
.clone()
.unwrap_or_else(|| r.package_id.clone()),
offer: None,
status: None,
})
.collect();
let optional: Vec<ClusterUpdateItem> = result
.optional_available
.iter()
.map(item_from_optional)
.collect();
if updated.is_empty() && added.is_empty() && removed.is_empty() && optional.is_empty() {
return None;
}
let cluster_name =
oneclient_content::packages::PackageStore::get_cluster(cluster_id, &services.content())
.await
.map(|c| c.name)
.unwrap_or_else(|_| "Cluster".to_string());
Some(ClusterUpdateSummary {
cluster_id,
cluster_name,
updated,
added,
removed,
optional,
})
}
pub async fn cluster_update_notification(
cluster_id: i64,
result: &oneclient_core::ApplyBundleUpdatesResult,
services: &oneclient_core::LauncherServices,
) -> Option<NotificationSpec> {
let summary = cluster_update_summary(cluster_id, result, services).await?;
let total = summary.total();
// A bundle can offer an opt-in mod without changing anything else
// "0 packages changed" would be both wrong and alarming
let (title, body) = if total == 0 {
let offers = summary.optional.len();
(
"Optional mods available",
format!(
"{offers} optional mod{} in {}",
if offers == 1 { "" } else { "s" },
summary.cluster_name
),
)
} else {
(
"Cluster updated",
format!(
"{total} package{} changed in {}",
if total == 1 { "" } else { "s" },
summary.cluster_name
),
)
};
Some(NotificationSpec {
title: title.to_string(),
body,
level: Level::Info,
icon: Some(IconType::DownloadCloud02),
progress: None,
actions: vec![NotificationAction {
label: "View changes".to_string(),
kind: NotificationActionKind::OpenClusterUpdate(vec![summary]),
}],
})
}
pub async fn pending_optional_group(
cluster_id: i64,
pending: &[oneclient_core::PendingOptionalMod],
services: &oneclient_core::LauncherServices,
) -> Option<OptionalModsGroup> {
if pending.is_empty() {
return None;
}
let mods: Vec<ClusterUpdateItem> = pending
.iter()
.map(|entry| ClusterUpdateItem {
offer: Some((entry.bundle_name.clone(), entry.package_id.clone())),
status: Some(entry.status),
..item_from_bundle_file(&entry.file)
})
.collect();
Some(OptionalModsGroup {
cluster_id,
cluster_name: cluster_display_name(cluster_id, services).await,
mods,
})
}
pub struct PackageInstall {
pub session_id: Option<uuid::Uuid>,
pub result: anyhow::Result<String>,
pub dependencies: Vec<String>,
pub missing_dependencies: Vec<String>,
/// The game is open but could not take the package, so the toast must not
/// claim it is there now
pub live_deferred: bool,
}
pub fn install_body(
name: &str,
dependencies: &[String],
missing: &[String],
live_deferred: bool,
) -> String {
let mut body = format!("Added {name}");
if !dependencies.is_empty() {
body.push_str(&format!(
" with {} dependenc{}",
dependencies.len(),
if dependencies.len() == 1 { "y" } else { "ies" }
));
}
body.push('.');
if !missing.is_empty() {
body.push_str(&format!(" Could not add: {}.", missing.join(", ")));
}
if live_deferred {
body.push_str(" Minecraft is running, so it will be there at the next launch.");
}
body
}
impl PackageInstall {
fn failed(err: anyhow::Error) -> Self {
Self {
session_id: None,
result: Err(err),
dependencies: Vec::new(),
missing_dependencies: Vec::new(),
live_deferred: false,
}
}
}
pub async fn install_package(
state: &Arc<LauncherState>,
provider: oneclient_content::packages::ProviderId,
project_id: &str,
version_id: &str,
cluster_id: i64,
) -> PackageInstall {
let lookup = async {
let provider_impl = state.services.packages.get(provider)?;
let project = provider_impl
.get_project(project_id, &state.services.content())
.await?;
let version = provider_impl
.get_version(project_id, version_id, &state.services.content())
.await?;
anyhow::Ok((project, version))
}
.await;
let (project, version) = match lookup {
Ok(found) => found,
Err(err) => return PackageInstall::failed(err),
};
// Resolved before the session starts so its children can be announced up front
let mut resolution = oneclient_content::packages::DependencyResolution::default();
if oneclient_content::packages::resolves_dependencies(project.content_type) {
match oneclient_content::packages::resolve_required(
provider,
&version,
cluster_id,
&state.services.content(),
)
.await
{
Ok(resolved) => resolution = resolved,
Err(err) => {
tracing::warn!(%err, "dependency resolution failed, installing package alone");
}
}
}
let session = oneclient_events::GroupedProgressSession::start(
&state.services.events,
format!("Installing {}", project.name),
);
let size = version.primary_file().map(|f| f.size).unwrap_or(0);
let dependency_bytes: u64 = resolution
.install
.iter()
.map(|dep| dep.version.primary_file().map(|f| f.size).unwrap_or(0))
.sum();
session.expect(
oneclient_events::TaskCategory::Packages,
1 + resolution.install.len() as u64,
size + dependency_bytes,
);
let mut installed_dependencies = Vec::new();
let mut missing_dependencies = resolution.unresolved;
// Dependencies first so the package is never in a cluster without them however the run ends
for dependency in &resolution.install {
let child = session.child(
dependency.project.name.clone(),
dependency
.version
.primary_file()
.map(|f| f.size)
.unwrap_or(0),
oneclient_events::TaskCategory::Packages,
);
let result = PackageStore::install_to_cluster(
provider,
&dependency.project,
&dependency.version,
cluster_id,
false,
false,
Some(&child),
&state.services.content(),
)
.await;
child.finish();
match result {
Ok((artifact, _)) => {
mark_new(cluster_id, &artifact.hash, state).await;
installed_dependencies.push(dependency.project.name.clone());
}
Err(err) => {
tracing::warn!(
dependency = %dependency.project.name,
%err,
"failed to install dependency"
);
missing_dependencies.push(dependency.project.name.clone());
}
}
}
let child = session.child(
project.name.clone(),
size,
oneclient_events::TaskCategory::Packages,
);
let result = PackageStore::install_to_cluster(
provider,
&project,
&version,
cluster_id,
false,
false,
Some(&child),
&state.services.content(),
)
.await;
child.finish();
let live_deferred = matches!(
result,
Ok((_, oneclient_content::packages::LiveSync::Deferred))
) && state.games.is_active(cluster_id);
if let Ok((artifact, _)) = &result {
mark_new(cluster_id, &artifact.hash, state).await;
}
PackageInstall {
session_id: Some(session.detach()),
result: result.map(|_| project.name).map_err(anyhow::Error::from),
dependencies: installed_dependencies,
missing_dependencies,
live_deferred,
}
}
async fn mark_new(cluster_id: i64, hash: &str, state: &LauncherState) {
if let Err(err) =
PackageStore::mark_artifact_new(cluster_id, hash, &state.services.content()).await
{
tracing::warn!(cluster_id, hash, %err, "failed to badge package as new");
}
}