fix: move (almost) all IO off the UI thread (#886)
This commit is contained in:
parent
1574d1afb1
commit
bdb9780794
14 changed files with 521 additions and 339 deletions
|
|
@ -340,7 +340,9 @@ pub async fn start_launcher(
|
||||||
station: RadioStation<AppState, AppChannel>,
|
station: RadioStation<AppState, AppChannel>,
|
||||||
events: oneclient_events::EventBus,
|
events: oneclient_events::EventBus,
|
||||||
) -> Result<(), anyhow::Error> {
|
) -> Result<(), anyhow::Error> {
|
||||||
let state = crate::launcher::install(oneclient_core::LauncherState::new(events).await?);
|
let state = crate::launcher::install(
|
||||||
|
crate::launcher::off_ui(oneclient_core::LauncherState::new(events)).await?,
|
||||||
|
);
|
||||||
|
|
||||||
oneclient_net::status::start(state.services.requester.clone());
|
oneclient_net::status::start(state.services.requester.clone());
|
||||||
oneclient_polyplus::start(std::sync::Arc::clone(&state.auth));
|
oneclient_polyplus::start(std::sync::Arc::clone(&state.auth));
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ use oneclient_events::{Answer, Level};
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
|
|
||||||
use crate::components::IconType;
|
use crate::components::IconType;
|
||||||
|
use crate::launcher::off_ui;
|
||||||
use crate::notifications::{
|
use crate::notifications::{
|
||||||
ClusterUpdateSummary, NotificationAction, NotificationSpec, OptionalModRef, OptionalModsGroup,
|
ClusterUpdateSummary, NotificationAction, NotificationSpec, OptionalModRef, OptionalModsGroup,
|
||||||
OptionalModsOutcome, PackageUpdateGroup, PendingPrompt,
|
OptionalModsOutcome, PackageUpdateGroup, PendingPrompt,
|
||||||
|
|
@ -548,8 +549,14 @@ impl Actions {
|
||||||
spawn_forever(async move {
|
spawn_forever(async move {
|
||||||
let Ok(state) = launcher::state() else { return };
|
let Ok(state) = launcher::state() else { return };
|
||||||
let events = state.services.events.clone();
|
let events = state.services.events.clone();
|
||||||
match oneclient_core::import_migration_game_dir(&state, source, &folder_name, target)
|
match off_ui({
|
||||||
.await
|
let state = state.clone();
|
||||||
|
async move {
|
||||||
|
oneclient_core::import_migration_game_dir(&state, source, &folder_name, target)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.await
|
||||||
{
|
{
|
||||||
Ok(()) => {
|
Ok(()) => {
|
||||||
events
|
events
|
||||||
|
|
@ -571,7 +578,12 @@ impl Actions {
|
||||||
spawn_forever(async move {
|
spawn_forever(async move {
|
||||||
let Ok(state) = launcher::state() else { return };
|
let Ok(state) = launcher::state() else { return };
|
||||||
let events = state.services.events.clone();
|
let events = state.services.events.clone();
|
||||||
match state.java.install_runtime_from(&vendor, major).await {
|
match off_ui({
|
||||||
|
let state = state.clone();
|
||||||
|
async move { state.java.install_runtime_from(&vendor, major).await }
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
{
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
events.signal(oneclient_events::Signal::JavaChanged);
|
events.signal(oneclient_events::Signal::JavaChanged);
|
||||||
invalidate_java_queries().await
|
invalidate_java_queries().await
|
||||||
|
|
@ -788,87 +800,95 @@ impl Actions {
|
||||||
actions.close_optional_mods(OptionalModsOutcome::Launch);
|
actions.close_optional_mods(OptionalModsOutcome::Launch);
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let content = state.services.content();
|
|
||||||
let events = state.services.events.clone();
|
let events = state.services.events.clone();
|
||||||
let session =
|
let (installed, failed) = off_ui(async move {
|
||||||
oneclient_events::GroupedProgressSession::start(&events, "Adding optional mods");
|
let content = state.services.content();
|
||||||
let opt_in = session.child(
|
let session = oneclient_events::GroupedProgressSession::start(
|
||||||
"Enabling mods",
|
&state.services.events,
|
||||||
mods.len() as u64,
|
"Adding optional mods",
|
||||||
oneclient_events::TaskCategory::Packages,
|
);
|
||||||
);
|
let opt_in = session.child(
|
||||||
opt_in.set_phase(oneclient_events::TaskPhase::Installing);
|
"Enabling mods",
|
||||||
|
mods.len() as u64,
|
||||||
|
oneclient_events::TaskCategory::Packages,
|
||||||
|
);
|
||||||
|
opt_in.set_phase(oneclient_events::TaskPhase::Installing);
|
||||||
|
|
||||||
let mut clusters: Vec<ClusterId> = Vec::new();
|
let mut clusters: Vec<ClusterId> = Vec::new();
|
||||||
let mut enabled: Vec<(ClusterId, OptionalModRef)> = Vec::new();
|
let mut enabled: Vec<(ClusterId, OptionalModRef)> = Vec::new();
|
||||||
let mut failed = 0usize;
|
let mut failed = 0usize;
|
||||||
for (cluster_id, (bundle_name, package_id)) in &mods {
|
for (cluster_id, (bundle_name, package_id)) in &mods {
|
||||||
match oneclient_core::set_bundle_package_enabled(
|
match oneclient_core::set_bundle_package_enabled(
|
||||||
*cluster_id,
|
*cluster_id,
|
||||||
bundle_name,
|
bundle_name,
|
||||||
package_id,
|
package_id,
|
||||||
true,
|
true,
|
||||||
false,
|
false,
|
||||||
&content,
|
&content,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(()) => {
|
Ok(()) => {
|
||||||
if !clusters.contains(cluster_id) {
|
if !clusters.contains(cluster_id) {
|
||||||
clusters.push(*cluster_id);
|
clusters.push(*cluster_id);
|
||||||
|
}
|
||||||
|
enabled.push((*cluster_id, (bundle_name.clone(), package_id.clone())));
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
failed += 1;
|
||||||
|
tracing::warn!(
|
||||||
|
cluster_id,
|
||||||
|
%bundle_name,
|
||||||
|
%package_id,
|
||||||
|
error = %err,
|
||||||
|
"failed to opt in to an optional mod"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
enabled.push((*cluster_id, (bundle_name.clone(), package_id.clone())));
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
failed += 1;
|
|
||||||
tracing::warn!(
|
|
||||||
cluster_id,
|
|
||||||
%bundle_name,
|
|
||||||
%package_id,
|
|
||||||
error = %err,
|
|
||||||
"failed to opt in to an optional mod"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
opt_in.finish();
|
opt_in.finish();
|
||||||
|
|
||||||
let mut installed = 0usize;
|
let mut installed = 0usize;
|
||||||
let mut applied: Vec<ClusterId> = Vec::new();
|
let mut applied: Vec<ClusterId> = Vec::new();
|
||||||
for cluster_id in &clusters {
|
for cluster_id in &clusters {
|
||||||
match oneclient_core::apply_bundle_updates_with(
|
match oneclient_core::apply_bundle_updates_with(
|
||||||
*cluster_id,
|
*cluster_id,
|
||||||
state.bundles.as_ref(),
|
state.bundles.as_ref(),
|
||||||
&content,
|
&content,
|
||||||
Some(&session),
|
Some(&session),
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(result) => {
|
Ok(result) => {
|
||||||
installed += result.additions_applied.len();
|
installed += result.additions_applied.len();
|
||||||
failed += result.additions_failed.len();
|
failed += result.additions_failed.len();
|
||||||
applied.push(*cluster_id);
|
applied.push(*cluster_id);
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
tracing::warn!(cluster_id, error = %err, "failed to install optional mods");
|
tracing::warn!(cluster_id, error = %err, "failed to install optional mods");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// An offer only counts as answered once its mod really was enabled
|
// An offer only counts as answered once its mod really was enabled
|
||||||
// and installed otherwise the click is lost and never comes back
|
// and installed otherwise the click is lost and never comes back
|
||||||
enabled.retain(|(cluster_id, _)| applied.contains(cluster_id));
|
enabled.retain(|(cluster_id, _)| applied.contains(cluster_id));
|
||||||
for (cluster_id, package_ids) in group_by_cluster(&enabled) {
|
for (cluster_id, package_ids) in group_by_cluster(&enabled) {
|
||||||
if let Err(err) =
|
if let Err(err) =
|
||||||
oneclient_core::resolve_optional_mods(cluster_id, &package_ids, &content).await
|
oneclient_core::resolve_optional_mods(cluster_id, &package_ids, &content)
|
||||||
{
|
.await
|
||||||
tracing::warn!(cluster_id, error = %err, "failed to clear answered offers");
|
{
|
||||||
|
tracing::warn!(cluster_id, error = %err, "failed to clear answered offers");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
session.finish();
|
session.finish();
|
||||||
|
(installed, failed)
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
|
||||||
super::invalidate_cluster_queries().await;
|
super::invalidate_cluster_queries().await;
|
||||||
|
|
||||||
if installed > 0 {
|
if installed > 0 {
|
||||||
|
|
@ -1030,14 +1050,16 @@ impl Actions {
|
||||||
spawn_forever(async move {
|
spawn_forever(async move {
|
||||||
let Ok(state) = launcher::state() else { return };
|
let Ok(state) = launcher::state() else { return };
|
||||||
let events = state.services.events.clone();
|
let events = state.services.events.clone();
|
||||||
match oneclient_content::packages::PackageStore::import_local_files(
|
let imported = off_ui({
|
||||||
&files,
|
let state = state.clone();
|
||||||
cluster_id,
|
async move {
|
||||||
&state.services.content(),
|
let report = oneclient_content::packages::PackageStore::import_local_files(
|
||||||
)
|
&files,
|
||||||
.await
|
cluster_id,
|
||||||
{
|
&state.services.content(),
|
||||||
Ok(report) => {
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
let mut deferred = false;
|
let mut deferred = false;
|
||||||
for row in &report.imported {
|
for row in &report.imported {
|
||||||
let live = oneclient_content::packages::PackageStore::sync_live_content(
|
let live = oneclient_content::packages::PackageStore::sync_live_content(
|
||||||
|
|
@ -1050,7 +1072,13 @@ impl Actions {
|
||||||
|
|
||||||
deferred |= live == LiveSync::Deferred;
|
deferred |= live == LiveSync::Deferred;
|
||||||
}
|
}
|
||||||
|
Ok::<_, oneclient_content::ContentError>((report, deferred))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
|
||||||
|
match imported {
|
||||||
|
Ok((report, deferred)) => {
|
||||||
notify_import(
|
notify_import(
|
||||||
&events,
|
&events,
|
||||||
&report,
|
&report,
|
||||||
|
|
@ -1137,15 +1165,24 @@ impl Actions {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let install = match crate::install::install_package(
|
let install = match off_ui({
|
||||||
&state,
|
let state = state.clone();
|
||||||
provider,
|
let project_id = project_id.clone();
|
||||||
&project_id,
|
let version_id = version_id.clone();
|
||||||
&version_id,
|
let world = world.clone();
|
||||||
cluster_id,
|
async move {
|
||||||
world.clone(),
|
crate::install::install_package(
|
||||||
allow_flagged,
|
&state,
|
||||||
)
|
provider,
|
||||||
|
&project_id,
|
||||||
|
&version_id,
|
||||||
|
cluster_id,
|
||||||
|
world,
|
||||||
|
allow_flagged,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
})
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(install) => install,
|
Ok(install) => install,
|
||||||
|
|
@ -1299,13 +1336,20 @@ impl Actions {
|
||||||
spawn_forever(async move {
|
spawn_forever(async move {
|
||||||
let Ok(state) = launcher::state() else { return };
|
let Ok(state) = launcher::state() else { return };
|
||||||
let events = state.services.events.clone();
|
let events = state.services.events.clone();
|
||||||
match oneclient_core::install_bundle(
|
match off_ui({
|
||||||
cluster_id,
|
let state = state.clone();
|
||||||
&bundle_name,
|
let bundle_name = bundle_name.clone();
|
||||||
skip_compatibility,
|
async move {
|
||||||
state.bundles.as_ref(),
|
oneclient_core::install_bundle(
|
||||||
&state.services.content(),
|
cluster_id,
|
||||||
)
|
&bundle_name,
|
||||||
|
skip_compatibility,
|
||||||
|
state.bundles.as_ref(),
|
||||||
|
&state.services.content(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
})
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
|
|
@ -1328,12 +1372,18 @@ impl Actions {
|
||||||
let actions = self.clone();
|
let actions = self.clone();
|
||||||
spawn_forever(async move {
|
spawn_forever(async move {
|
||||||
let Ok(state) = launcher::state() else { return };
|
let Ok(state) = launcher::state() else { return };
|
||||||
let result = match oneclient_core::apply_bundle_updates(
|
let result = match off_ui({
|
||||||
cluster_id,
|
let state = state.clone();
|
||||||
state.bundles.as_ref(),
|
async move {
|
||||||
&state.services.content(),
|
oneclient_core::apply_bundle_updates(
|
||||||
None,
|
cluster_id,
|
||||||
)
|
state.bundles.as_ref(),
|
||||||
|
&state.services.content(),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
})
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(result) => result,
|
Ok(result) => result,
|
||||||
|
|
@ -1388,14 +1438,26 @@ impl Actions {
|
||||||
|
|
||||||
let mut station = self.station;
|
let mut station = self.station;
|
||||||
spawn_forever(async move {
|
spawn_forever(async move {
|
||||||
// The copy is on this thread's executor, so it can write the count
|
let (progress, mut seen) = tokio::sync::watch::channel((0, 0));
|
||||||
// straight into the channel the move screen reads
|
let mut copy = std::pin::pin!(off_ui(async move {
|
||||||
let result = oneclient_core::relocate::relocate(&state, &plan, |copied, total| {
|
oneclient_core::relocate::relocate(&state, &plan, |copied, total| {
|
||||||
|
let _ = progress.send((copied, total));
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
}));
|
||||||
|
|
||||||
|
let mut show_progress = |(copied, total)| {
|
||||||
let mut guard = station.write_channel(AppChannel::Relocation);
|
let mut guard = station.write_channel(AppChannel::Relocation);
|
||||||
guard.relocation.copied = copied;
|
guard.relocation.copied = copied;
|
||||||
guard.relocation.total = total;
|
guard.relocation.total = total;
|
||||||
})
|
};
|
||||||
.await;
|
let result = loop {
|
||||||
|
tokio::select! {
|
||||||
|
result = &mut copy => break result,
|
||||||
|
Ok(()) = seen.changed() => show_progress(*seen.borrow_and_update()),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
show_progress(*seen.borrow());
|
||||||
|
|
||||||
if let Err(message) = &result {
|
if let Err(message) = &result {
|
||||||
tracing::error!("moving the data folder failed: {message}");
|
tracing::error!("moving the data folder failed: {message}");
|
||||||
|
|
@ -1445,15 +1507,18 @@ impl Actions {
|
||||||
.syncing_bundles = true;
|
.syncing_bundles = true;
|
||||||
|
|
||||||
spawn_forever(async move {
|
spawn_forever(async move {
|
||||||
if let Err(err) = state.bundles.sync(&state.services.content()).await {
|
off_ui(async move {
|
||||||
tracing::error!("bundle catalog sync failed: {err:#}");
|
if let Err(err) = state.bundles.sync(&state.services.content()).await {
|
||||||
}
|
tracing::error!("bundle catalog sync failed: {err:#}");
|
||||||
if let Err(err) = oneclient_core::clusters::apply_remote_migrations(&state).await {
|
}
|
||||||
tracing::error!("cluster migrations failed: {err:#}");
|
if let Err(err) = oneclient_core::clusters::apply_remote_migrations(&state).await {
|
||||||
}
|
tracing::error!("cluster migrations failed: {err:#}");
|
||||||
if let Err(err) = oneclient_core::clusters::ensure_from_bundles(&state).await {
|
}
|
||||||
tracing::error!("bundle cluster provisioning failed: {err:#}");
|
if let Err(err) = oneclient_core::clusters::ensure_from_bundles(&state).await {
|
||||||
}
|
tracing::error!("bundle cluster provisioning failed: {err:#}");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
|
||||||
actions
|
actions
|
||||||
.station
|
.station
|
||||||
|
|
@ -1488,22 +1553,28 @@ impl Actions {
|
||||||
.launcher
|
.launcher
|
||||||
.syncing_bundles = true;
|
.syncing_bundles = true;
|
||||||
|
|
||||||
let synced =
|
let sync = {
|
||||||
match tokio::time::timeout(BUNDLE_SYNC_BUDGET, state.bundles.sync(&content)).await {
|
let bundles = state.bundles.clone();
|
||||||
Ok(Ok(_)) => true,
|
let content = content.clone();
|
||||||
Ok(Err(err)) => {
|
off_ui(
|
||||||
tracing::warn!(
|
async move { tokio::time::timeout(BUNDLE_SYNC_BUDGET, bundles.sync(&content)).await },
|
||||||
cluster_id,
|
)
|
||||||
error = %err,
|
};
|
||||||
"bundle catalog sync failed, launching against the cached catalog"
|
let synced = match sync.await {
|
||||||
);
|
Ok(Ok(_)) => true,
|
||||||
false
|
Ok(Err(err)) => {
|
||||||
}
|
tracing::warn!(
|
||||||
Err(_elapsed) => {
|
cluster_id,
|
||||||
tracing::debug!(cluster_id, "bundle catalog sync exceeded its launch budget");
|
error = %err,
|
||||||
false
|
"bundle catalog sync failed, launching against the cached catalog"
|
||||||
}
|
);
|
||||||
};
|
false
|
||||||
|
}
|
||||||
|
Err(_elapsed) => {
|
||||||
|
tracing::debug!(cluster_id, "bundle catalog sync exceeded its launch budget");
|
||||||
|
false
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
self.station
|
self.station
|
||||||
.clone()
|
.clone()
|
||||||
|
|
@ -1513,12 +1584,19 @@ impl Actions {
|
||||||
|
|
||||||
// Bundle content is forced, so the update mode never gates it and the
|
// Bundle content is forced, so the update mode never gates it and the
|
||||||
// modal never lists it
|
// modal never lists it
|
||||||
let result = match oneclient_core::apply_bundle_updates(
|
let deadline = Instant::now() + BUNDLE_APPLY_BUDGET;
|
||||||
cluster_id,
|
let result = match off_ui({
|
||||||
state.bundles.as_ref(),
|
let state = state.clone();
|
||||||
&content,
|
async move {
|
||||||
Some(Instant::now() + BUNDLE_APPLY_BUDGET),
|
oneclient_core::apply_bundle_updates(
|
||||||
)
|
cluster_id,
|
||||||
|
state.bundles.as_ref(),
|
||||||
|
&state.services.content(),
|
||||||
|
Some(deadline),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
})
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(result) => result,
|
Ok(result) => result,
|
||||||
|
|
@ -1567,10 +1645,13 @@ impl Actions {
|
||||||
|
|
||||||
// Bounded a hanging network would otherwise hold the launch for
|
// Bounded a hanging network would otherwise hold the launch for
|
||||||
// reqwest's much longer timeouts
|
// reqwest's much longer timeouts
|
||||||
let check = match tokio::time::timeout(
|
let check = match off_ui(async move {
|
||||||
UPDATE_CHECK_BUDGET,
|
tokio::time::timeout(
|
||||||
oneclient_core::refresh_browser_package_updates(cluster_id, &content),
|
UPDATE_CHECK_BUDGET,
|
||||||
)
|
oneclient_core::refresh_browser_package_updates(cluster_id, &content),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
})
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(Ok(check)) => check,
|
Ok(Ok(check)) => check,
|
||||||
|
|
@ -1642,39 +1723,45 @@ impl Actions {
|
||||||
state: &Arc<oneclient_core::LauncherState>,
|
state: &Arc<oneclient_core::LauncherState>,
|
||||||
updates: &[oneclient_core::BrowserPackageUpdate],
|
updates: &[oneclient_core::BrowserPackageUpdate],
|
||||||
) {
|
) {
|
||||||
let content = state.services.content();
|
let state = state.clone();
|
||||||
let session = oneclient_events::GroupedProgressSession::start(
|
let updates = updates.to_vec();
|
||||||
&state.services.events,
|
let (applied, session_id) = off_ui(async move {
|
||||||
"Updating packages",
|
let content = state.services.content();
|
||||||
);
|
let session = oneclient_events::GroupedProgressSession::start(
|
||||||
session.expect(
|
&state.services.events,
|
||||||
oneclient_events::TaskCategory::Packages,
|
"Updating packages",
|
||||||
updates.len() as u64,
|
);
|
||||||
updates.len() as u64,
|
session.expect(
|
||||||
);
|
oneclient_events::TaskCategory::Packages,
|
||||||
|
updates.len() as u64,
|
||||||
let mut applied = 0usize;
|
updates.len() as u64,
|
||||||
for update in updates {
|
|
||||||
let child = session.child(
|
|
||||||
update.display_name.clone(),
|
|
||||||
1,
|
|
||||||
oneclient_events::TaskCategory::Packages,
|
|
||||||
);
|
);
|
||||||
match oneclient_core::apply_browser_package_update(update, Some(&child), &content).await
|
|
||||||
{
|
|
||||||
Ok(_) => applied += 1,
|
|
||||||
// Not a reason to hold the game back the cache row survives
|
|
||||||
// so the next launch offers it again
|
|
||||||
Err(err) => tracing::warn!(
|
|
||||||
package = %update.display_name,
|
|
||||||
error = %err,
|
|
||||||
"automatic package update failed"
|
|
||||||
),
|
|
||||||
}
|
|
||||||
child.finish();
|
|
||||||
}
|
|
||||||
|
|
||||||
let session_id = session.detach();
|
let mut applied = 0usize;
|
||||||
|
for update in &updates {
|
||||||
|
let child = session.child(
|
||||||
|
update.display_name.clone(),
|
||||||
|
1,
|
||||||
|
oneclient_events::TaskCategory::Packages,
|
||||||
|
);
|
||||||
|
match oneclient_core::apply_browser_package_update(update, Some(&child), &content)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(_) => applied += 1,
|
||||||
|
// Not a reason to hold the game back the cache row survives
|
||||||
|
// so the next launch offers it again
|
||||||
|
Err(err) => tracing::warn!(
|
||||||
|
package = %update.display_name,
|
||||||
|
error = %err,
|
||||||
|
"automatic package update failed"
|
||||||
|
),
|
||||||
|
}
|
||||||
|
child.finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
(applied, session.detach())
|
||||||
|
})
|
||||||
|
.await;
|
||||||
let spec = (applied > 0).then(|| NotificationSpec {
|
let spec = (applied > 0).then(|| NotificationSpec {
|
||||||
title: "Packages updated".to_string(),
|
title: "Packages updated".to_string(),
|
||||||
body: format!(
|
body: format!(
|
||||||
|
|
@ -1705,11 +1792,17 @@ impl Actions {
|
||||||
state: &Arc<oneclient_core::LauncherState>,
|
state: &Arc<oneclient_core::LauncherState>,
|
||||||
cluster_id: ClusterId,
|
cluster_id: ClusterId,
|
||||||
) -> OptionalModsOutcome {
|
) -> OptionalModsOutcome {
|
||||||
let pending = match oneclient_core::pending_optional_mods(
|
let pending = match off_ui({
|
||||||
cluster_id,
|
let state = state.clone();
|
||||||
state.bundles.as_ref(),
|
async move {
|
||||||
&state.services.content(),
|
oneclient_core::pending_optional_mods(
|
||||||
)
|
cluster_id,
|
||||||
|
state.bundles.as_ref(),
|
||||||
|
&state.services.content(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
})
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(pending) => pending,
|
Ok(pending) => pending,
|
||||||
|
|
@ -1762,28 +1855,33 @@ impl Actions {
|
||||||
let actions = self.clone();
|
let actions = self.clone();
|
||||||
spawn_forever(async move {
|
spawn_forever(async move {
|
||||||
let Ok(state) = launcher::state() else { return };
|
let Ok(state) = launcher::state() else { return };
|
||||||
let events = state.services.events.clone();
|
|
||||||
|
|
||||||
let session = oneclient_events::GroupedProgressSession::start(
|
let (result, session_id) = off_ui({
|
||||||
&events,
|
let update = update.clone();
|
||||||
format!("Updating {}", update.display_name),
|
async move {
|
||||||
);
|
let session = oneclient_events::GroupedProgressSession::start(
|
||||||
let child = session.child(
|
&state.services.events,
|
||||||
update.display_name.clone(),
|
format!("Updating {}", update.display_name),
|
||||||
1,
|
);
|
||||||
oneclient_events::TaskCategory::Packages,
|
let child = session.child(
|
||||||
);
|
update.display_name.clone(),
|
||||||
|
1,
|
||||||
|
oneclient_events::TaskCategory::Packages,
|
||||||
|
);
|
||||||
|
|
||||||
let result = oneclient_core::apply_browser_package_update(
|
let result = oneclient_core::apply_browser_package_update(
|
||||||
&update,
|
&update,
|
||||||
Some(&child),
|
Some(&child),
|
||||||
&state.services.content(),
|
&state.services.content(),
|
||||||
)
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
child.finish();
|
||||||
|
(result, session.detach())
|
||||||
|
}
|
||||||
|
})
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
child.finish();
|
|
||||||
let session_id = session.detach();
|
|
||||||
|
|
||||||
let spec = match &result {
|
let spec = match &result {
|
||||||
Ok(_) => NotificationSpec {
|
Ok(_) => NotificationSpec {
|
||||||
title: "Updated".to_string(),
|
title: "Updated".to_string(),
|
||||||
|
|
@ -1912,16 +2010,19 @@ async fn launch(actions: &Actions, cluster_id: ClusterId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Err(err) = oneclient_core::launch_cluster(&state, cluster_id, &account, true).await {
|
off_ui(async move {
|
||||||
// A missing file is the one failure the launcher can fix itself and a
|
if let Err(err) = oneclient_core::launch_cluster(&state, cluster_id, &account, true).await {
|
||||||
// path inside our metadata folder gives the user nothing to act on
|
// A missing file is the one failure the launcher can fix itself and a
|
||||||
if err.indicates_missing_files() {
|
// path inside our metadata folder gives the user nothing to act on
|
||||||
repair_and_relaunch(&state, cluster_id, &account, err).await;
|
if err.indicates_missing_files() {
|
||||||
return;
|
repair_and_relaunch(&state, cluster_id, &account, err).await;
|
||||||
}
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
events.game_failed(cluster_id, format!("{err:#}"));
|
events.game_failed(cluster_id, format!("{err:#}"));
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Only ever one retry if the game still will not start after a full rehash and
|
/// Only ever one retry if the game still will not start after a full rehash and
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,8 @@ use oneclient_core::{
|
||||||
};
|
};
|
||||||
use oneclient_db::models::ClusterId;
|
use oneclient_db::models::ClusterId;
|
||||||
|
|
||||||
|
use crate::launcher::off_ui;
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct ClusterBundles {
|
pub struct ClusterBundles {
|
||||||
pub cluster: Cluster,
|
pub cluster: Cluster,
|
||||||
|
|
@ -27,22 +29,25 @@ impl QueryCapability for OnboardingBundlesQuery {
|
||||||
|
|
||||||
async fn run(&self, _keys: &Self::Keys) -> Result<Self::Ok, Self::Err> {
|
async fn run(&self, _keys: &Self::Keys) -> Result<Self::Ok, Self::Err> {
|
||||||
let state = crate::launcher::state()?;
|
let state = crate::launcher::state()?;
|
||||||
let clusters = state.clusters.list().await?;
|
off_ui(async move {
|
||||||
|
let clusters = state.clusters.list().await?;
|
||||||
|
|
||||||
let mut out = Vec::with_capacity(clusters.len());
|
let mut out = Vec::with_capacity(clusters.len());
|
||||||
for cluster in clusters {
|
for cluster in clusters {
|
||||||
let archives = state
|
let archives = state
|
||||||
.bundles
|
.bundles
|
||||||
.archives_for(
|
.archives_for(
|
||||||
&state.services.content(),
|
&state.services.content(),
|
||||||
&cluster.mc_version,
|
&cluster.mc_version,
|
||||||
cluster.mc_loader,
|
cluster.mc_loader,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
out.push(ClusterBundles { cluster, archives });
|
out.push(ClusterBundles { cluster, archives });
|
||||||
}
|
}
|
||||||
Ok(out)
|
Ok(out)
|
||||||
|
})
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -74,11 +79,15 @@ impl QueryCapability for BundlesWithStatusQuery {
|
||||||
async fn run(&self, keys: &Self::Keys) -> Result<Self::Ok, Self::Err> {
|
async fn run(&self, keys: &Self::Keys) -> Result<Self::Ok, Self::Err> {
|
||||||
let _ = keys;
|
let _ = keys;
|
||||||
let state = crate::launcher::state()?;
|
let state = crate::launcher::state()?;
|
||||||
Ok(get_bundles_with_update_status(
|
let cluster_id = self.cluster_id;
|
||||||
self.cluster_id,
|
Ok(off_ui(async move {
|
||||||
state.bundles.as_ref(),
|
get_bundles_with_update_status(
|
||||||
&state.services.content(),
|
cluster_id,
|
||||||
)
|
state.bundles.as_ref(),
|
||||||
|
&state.services.content(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
})
|
||||||
.await?)
|
.await?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -154,11 +163,15 @@ impl QueryCapability for BundleUpdatesQuery {
|
||||||
async fn run(&self, keys: &Self::Keys) -> Result<Self::Ok, Self::Err> {
|
async fn run(&self, keys: &Self::Keys) -> Result<Self::Ok, Self::Err> {
|
||||||
let _ = keys;
|
let _ = keys;
|
||||||
let state = crate::launcher::state()?;
|
let state = crate::launcher::state()?;
|
||||||
Ok(oneclient_core::check_bundle_updates(
|
let cluster_id = self.cluster_id;
|
||||||
self.cluster_id,
|
Ok(off_ui(async move {
|
||||||
state.bundles.as_ref(),
|
oneclient_core::check_bundle_updates(
|
||||||
&state.services.content(),
|
cluster_id,
|
||||||
)
|
state.bundles.as_ref(),
|
||||||
|
&state.services.content(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
})
|
||||||
.await?)
|
.await?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,8 @@ use oneclient_core::{
|
||||||
LauncherError, LogFileInfo, LogLevel, LogLine, MclogsUploadResponse, ReadOptions,
|
LauncherError, LogFileInfo, LogLevel, LogLine, MclogsUploadResponse, ReadOptions,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::launcher::{off_ui, off_ui_blocking};
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||||
pub struct ClusterLogsKeys {
|
pub struct ClusterLogsKeys {
|
||||||
pub cluster_id: i64,
|
pub cluster_id: i64,
|
||||||
|
|
@ -24,7 +26,7 @@ impl QueryCapability for ClusterLogsQuery {
|
||||||
async fn run(&self, keys: &Self::Keys) -> Result<Self::Ok, Self::Err> {
|
async fn run(&self, keys: &Self::Keys) -> Result<Self::Ok, Self::Err> {
|
||||||
let state = crate::launcher::state()?;
|
let state = crate::launcher::state()?;
|
||||||
let cluster = state.clusters.get(keys.cluster_id).await?;
|
let cluster = state.clusters.get(keys.cluster_id).await?;
|
||||||
Ok(oneclient_core::list_cluster_logs(&cluster)?)
|
Ok(off_ui_blocking(move || oneclient_core::list_cluster_logs(&cluster)).await?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -48,15 +50,13 @@ impl QueryCapability for LogContentQuery {
|
||||||
if keys.path.as_os_str().is_empty() {
|
if keys.path.as_os_str().is_empty() {
|
||||||
return Ok(Vec::new());
|
return Ok(Vec::new());
|
||||||
}
|
}
|
||||||
Ok(oneclient_core::read_log_at(
|
let path = keys.path.clone();
|
||||||
&keys.path,
|
let options = ReadOptions {
|
||||||
&ReadOptions {
|
level_filter: keys.level,
|
||||||
level_filter: keys.level,
|
search: keys.search.clone(),
|
||||||
search: keys.search.clone(),
|
max_lines: keys.max_lines,
|
||||||
max_lines: keys.max_lines,
|
};
|
||||||
},
|
Ok(off_ui(async move { oneclient_core::read_log_at(&path, &options).await }).await?)
|
||||||
)
|
|
||||||
.await?)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -109,7 +109,11 @@ impl MutationCapability for UploadLogMutation {
|
||||||
|
|
||||||
async fn run(&self, keys: &Self::Keys) -> Result<Self::Ok, Self::Err> {
|
async fn run(&self, keys: &Self::Keys) -> Result<Self::Ok, Self::Err> {
|
||||||
let state = crate::launcher::state()?;
|
let state = crate::launcher::state()?;
|
||||||
Ok(oneclient_core::upload_log_at(&state.services.requester, &keys.path).await?)
|
let path = keys.path.clone();
|
||||||
|
Ok(off_ui(
|
||||||
|
async move { oneclient_core::upload_log_at(&state.services.requester, &path).await },
|
||||||
|
)
|
||||||
|
.await?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -205,7 +205,12 @@ impl MutationCapability for ClusterMutation {
|
||||||
ClusterAction::VerifyFiles { cluster_id } => {
|
ClusterAction::VerifyFiles { cluster_id } => {
|
||||||
// Reports its own outcome not the generic failure toast a
|
// Reports its own outcome not the generic failure toast a
|
||||||
// verify that finds nothing wrong is still a useful result
|
// verify that finds nothing wrong is still a useful result
|
||||||
match oneclient_core::verify_cluster_files(&state, *cluster_id).await {
|
let (state, cluster_id) = (state.clone(), *cluster_id);
|
||||||
|
match crate::launcher::off_ui(async move {
|
||||||
|
oneclient_core::verify_cluster_files(&state, cluster_id).await
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
{
|
||||||
Ok(report) => {
|
Ok(report) => {
|
||||||
let notify = services.events.notify("Verification complete");
|
let notify = services.events.notify("Verification complete");
|
||||||
let notify = notify.body(report.summary());
|
let notify = notify.body(report.summary());
|
||||||
|
|
|
||||||
|
|
@ -217,17 +217,23 @@ impl QueryCapability for PackageVersionsQuery {
|
||||||
|
|
||||||
async fn run(&self, keys: &Self::Keys) -> Result<Self::Ok, Self::Err> {
|
async fn run(&self, keys: &Self::Keys) -> Result<Self::Ok, Self::Err> {
|
||||||
let state = crate::launcher::state()?;
|
let state = crate::launcher::state()?;
|
||||||
let provider = state.services.packages.get(keys.provider)?;
|
let keys = keys.clone();
|
||||||
Ok(provider
|
crate::launcher::off_ui(async move {
|
||||||
.list_versions(
|
Ok(state
|
||||||
&keys.project_id,
|
.services
|
||||||
keys.game_version.as_deref(),
|
.packages
|
||||||
keys.loader,
|
.get(keys.provider)?
|
||||||
keys.page * VERSIONS_PAGE_SIZE,
|
.list_versions(
|
||||||
VERSIONS_PAGE_SIZE,
|
&keys.project_id,
|
||||||
&state.services.content(),
|
keys.game_version.as_deref(),
|
||||||
)
|
keys.loader,
|
||||||
.await?)
|
keys.page * VERSIONS_PAGE_SIZE,
|
||||||
|
VERSIONS_PAGE_SIZE,
|
||||||
|
&state.services.content(),
|
||||||
|
)
|
||||||
|
.await?)
|
||||||
|
})
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,8 @@ use notify::{Event, EventKind};
|
||||||
use oneclient_core::{LauncherError, ScreenshotInfo};
|
use oneclient_core::{LauncherError, ScreenshotInfo};
|
||||||
use tokio::sync::Semaphore;
|
use tokio::sync::Semaphore;
|
||||||
|
|
||||||
|
use crate::launcher::off_ui_blocking;
|
||||||
|
|
||||||
static LOCAL_IMAGE_SEMAPHORE: OnceLock<Arc<Semaphore>> = OnceLock::new();
|
static LOCAL_IMAGE_SEMAPHORE: OnceLock<Arc<Semaphore>> = OnceLock::new();
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||||
|
|
@ -29,7 +31,7 @@ impl QueryCapability for ClusterScreenshotsQuery {
|
||||||
async fn run(&self, keys: &Self::Keys) -> Result<Self::Ok, Self::Err> {
|
async fn run(&self, keys: &Self::Keys) -> Result<Self::Ok, Self::Err> {
|
||||||
let state = crate::launcher::state()?;
|
let state = crate::launcher::state()?;
|
||||||
let cluster = state.clusters.get(keys.cluster_id).await?;
|
let cluster = state.clusters.get(keys.cluster_id).await?;
|
||||||
Ok(oneclient_core::list_cluster_screenshots(&cluster)?)
|
Ok(off_ui_blocking(move || oneclient_core::list_cluster_screenshots(&cluster)).await?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -133,7 +135,10 @@ impl MutationCapability for ScreenshotActionMutation {
|
||||||
|
|
||||||
async fn run(&self, keys: &Self::Keys) -> Result<Self::Ok, Self::Err> {
|
async fn run(&self, keys: &Self::Keys) -> Result<Self::Ok, Self::Err> {
|
||||||
match keys {
|
match keys {
|
||||||
ScreenshotAction::Delete { path } => Ok(oneclient_core::delete_screenshot(path)?),
|
ScreenshotAction::Delete { path } => {
|
||||||
|
let path = path.clone();
|
||||||
|
Ok(off_ui_blocking(move || oneclient_core::delete_screenshot(&path)).await?)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,8 @@ use freya::query::{
|
||||||
use_mutation, use_query,
|
use_mutation, use_query,
|
||||||
};
|
};
|
||||||
use oneclient_core::LauncherError;
|
use oneclient_core::LauncherError;
|
||||||
|
|
||||||
|
use crate::launcher::off_ui;
|
||||||
use oneclient_core::relocate::{Leftovers, discard_leftovers, leftovers};
|
use oneclient_core::relocate::{Leftovers, discard_leftovers, leftovers};
|
||||||
use oneclient_core::storage::{StorageReport, storage_report};
|
use oneclient_core::storage::{StorageReport, storage_report};
|
||||||
|
|
||||||
|
|
@ -21,7 +23,7 @@ impl QueryCapability for StorageReportQuery {
|
||||||
|
|
||||||
async fn run(&self, _keys: &Self::Keys) -> Result<Self::Ok, Self::Err> {
|
async fn run(&self, _keys: &Self::Keys) -> Result<Self::Ok, Self::Err> {
|
||||||
let state = crate::launcher::state()?;
|
let state = crate::launcher::state()?;
|
||||||
storage_report(&state).await
|
off_ui(async move { storage_report(&state).await }).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -55,19 +57,23 @@ impl MutationCapability for StorageActionMutation {
|
||||||
|
|
||||||
async fn run(&self, keys: &Self::Keys) -> Result<Self::Ok, Self::Err> {
|
async fn run(&self, keys: &Self::Keys) -> Result<Self::Ok, Self::Err> {
|
||||||
let state = crate::launcher::state().map_err(|e| e.to_string())?;
|
let state = crate::launcher::state().map_err(|e| e.to_string())?;
|
||||||
|
let keys = *keys;
|
||||||
|
|
||||||
match keys {
|
off_ui(async move {
|
||||||
StorageAction::CleanUnreferencedCache => {
|
match keys {
|
||||||
oneclient_core::storage::clean_unreferenced_cache(&state)
|
StorageAction::CleanUnreferencedCache => {
|
||||||
.await
|
oneclient_core::storage::clean_unreferenced_cache(&state)
|
||||||
.map(|_| ())
|
.await
|
||||||
|
.map(|_| ())
|
||||||
|
}
|
||||||
|
StorageAction::CleanLegacyClusterContent => {
|
||||||
|
oneclient_core::storage::clean_cluster_leftovers(&state)
|
||||||
|
.await
|
||||||
|
.map(|_| ())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
StorageAction::CleanLegacyClusterContent => {
|
})
|
||||||
oneclient_core::storage::clean_cluster_leftovers(&state)
|
.await
|
||||||
.await
|
|
||||||
.map(|_| ())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.map_err(|e| e.to_string())
|
.map_err(|e| e.to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -97,7 +103,7 @@ impl QueryCapability for LeftoversQuery {
|
||||||
|
|
||||||
async fn run(&self, _keys: &Self::Keys) -> Result<Self::Ok, Self::Err> {
|
async fn run(&self, _keys: &Self::Keys) -> Result<Self::Ok, Self::Err> {
|
||||||
let state = crate::launcher::state()?;
|
let state = crate::launcher::state()?;
|
||||||
Ok(leftovers(&state).await)
|
Ok(off_ui(async move { leftovers(&state).await }).await)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -126,7 +132,7 @@ impl MutationCapability for DiscardLeftoversMutation {
|
||||||
|
|
||||||
async fn run(&self, _keys: &Self::Keys) -> Result<Self::Ok, Self::Err> {
|
async fn run(&self, _keys: &Self::Keys) -> Result<Self::Ok, Self::Err> {
|
||||||
let state = crate::launcher::state().map_err(|err| err.to_string())?;
|
let state = crate::launcher::state().map_err(|err| err.to_string())?;
|
||||||
discard_leftovers(&state).await
|
off_ui(async move { discard_leftovers(&state).await }).await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn on_settled(&self, _keys: &Self::Keys, result: &Result<Self::Ok, Self::Err>) {
|
async fn on_settled(&self, _keys: &Self::Keys, result: &Result<Self::Ok, Self::Err>) {
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@ use freya::query::{Query, QueryCapability, QueryStateData, UseQuery, use_query};
|
||||||
use oneclient_common::domain::GameLoader;
|
use oneclient_common::domain::GameLoader;
|
||||||
use oneclient_core::{LauncherError, VersionMetadata};
|
use oneclient_core::{LauncherError, VersionMetadata};
|
||||||
|
|
||||||
|
use crate::launcher::off_ui;
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||||
pub struct VersionsMetadataQuery;
|
pub struct VersionsMetadataQuery;
|
||||||
|
|
||||||
|
|
@ -15,18 +17,21 @@ impl QueryCapability for VersionsMetadataQuery {
|
||||||
|
|
||||||
async fn run(&self, _keys: &Self::Keys) -> Result<Self::Ok, Self::Err> {
|
async fn run(&self, _keys: &Self::Keys) -> Result<Self::Ok, Self::Err> {
|
||||||
let state = crate::launcher::state()?;
|
let state = crate::launcher::state()?;
|
||||||
let metadata = state
|
off_ui(async move {
|
||||||
.versions
|
let metadata = state
|
||||||
.metadata(&state.services.requester.config().meta_url_base)
|
.versions
|
||||||
.await;
|
.metadata(&state.services.requester.config().meta_url_base)
|
||||||
if !metadata.is_empty() {
|
.await;
|
||||||
return Ok(metadata);
|
if !metadata.is_empty() {
|
||||||
}
|
return Ok(metadata);
|
||||||
state.versions.sync(&state.services).await?;
|
}
|
||||||
Ok(state
|
state.versions.sync(&state.services).await?;
|
||||||
.versions
|
Ok(state
|
||||||
.metadata(&state.services.requester.config().meta_url_base)
|
.versions
|
||||||
.await)
|
.metadata(&state.services.requester.config().meta_url_base)
|
||||||
|
.await)
|
||||||
|
})
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -61,13 +66,17 @@ impl QueryCapability for LoaderVersionsQuery {
|
||||||
|
|
||||||
async fn run(&self, keys: &Self::Keys) -> Result<Self::Ok, Self::Err> {
|
async fn run(&self, keys: &Self::Keys) -> Result<Self::Ok, Self::Err> {
|
||||||
let state = crate::launcher::state()?;
|
let state = crate::launcher::state()?;
|
||||||
let mut metadata = state.metadata.lock().await;
|
let keys = keys.clone();
|
||||||
Ok(oneclient_core::get_loader_versions(
|
Ok(off_ui(async move {
|
||||||
&mut metadata,
|
let mut metadata = state.metadata.lock().await;
|
||||||
&state.services.mc(),
|
oneclient_core::get_loader_versions(
|
||||||
&keys.mc_version,
|
&mut metadata,
|
||||||
keys.loader,
|
&state.services.mc(),
|
||||||
)
|
&keys.mc_version,
|
||||||
|
keys.loader,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
})
|
||||||
.await?)
|
.await?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,3 +20,17 @@ pub fn install(state: Arc<LauncherState>) -> Arc<LauncherState> {
|
||||||
pub fn state() -> LauncherResult<Arc<LauncherState>> {
|
pub fn state() -> LauncherResult<Arc<LauncherState>> {
|
||||||
LAUNCHER.get().cloned().ok_or(LauncherError::NotInitialized)
|
LAUNCHER.get().cloned().ok_or(LauncherError::NotInitialized)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn off_ui<T: Send + 'static>(work: impl Future<Output = T> + Send + 'static) -> T {
|
||||||
|
match tokio::spawn(work).await {
|
||||||
|
Ok(value) => value,
|
||||||
|
Err(err) => std::panic::resume_unwind(err.into_panic()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn off_ui_blocking<T: Send + 'static>(work: impl FnOnce() -> T + Send + 'static) -> T {
|
||||||
|
match tokio::task::spawn_blocking(work).await {
|
||||||
|
Ok(value) => value,
|
||||||
|
Err(err) => std::panic::resume_unwind(err.into_panic()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ use tokio::sync::mpsc::UnboundedSender;
|
||||||
|
|
||||||
use crate::hooks::Actions;
|
use crate::hooks::Actions;
|
||||||
use crate::launcher;
|
use crate::launcher;
|
||||||
|
use crate::launcher::off_ui;
|
||||||
|
|
||||||
/// Front-ends match on these
|
/// Front-ends match on these
|
||||||
pub const MICROSOFT_JAVA_CHOICE_INSTALL: &str = "java.microsoft.install";
|
pub const MICROSOFT_JAVA_CHOICE_INSTALL: &str = "java.microsoft.install";
|
||||||
|
|
@ -97,49 +98,58 @@ pub async fn offer_for_pinned_cluster(actions: &Actions, cluster_id: ClusterId)
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
if opted_out(&state) {
|
let offer = off_ui({
|
||||||
return;
|
let state = state.clone();
|
||||||
}
|
async move {
|
||||||
|
if opted_out(&state) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
let Some(pinned) = pinned_runtime(&state, cluster_id).await else {
|
let pinned = pinned_runtime(&state, cluster_id).await?;
|
||||||
|
|
||||||
|
if pinned.vendor == JavaVendor::Microsoft {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let major = match oneclient_core::required_java_major(&state, cluster_id).await {
|
||||||
|
Ok(Some(major)) => major,
|
||||||
|
Ok(None) => {
|
||||||
|
tracing::info!(cluster_id, "the cluster's manifest names no Java version");
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
tracing::warn!(
|
||||||
|
cluster_id,
|
||||||
|
"could not read the cluster's Java version: {err:#}"
|
||||||
|
);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let installed = match state
|
||||||
|
.java
|
||||||
|
.has_vendor_runtime(&JavaVendor::Microsoft, Some(major))
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(installed) => installed,
|
||||||
|
Err(err) => {
|
||||||
|
tracing::warn!("could not check for a Microsoft Java runtime: {err:#}");
|
||||||
|
false
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if !installed && !publishes(&state, major).await {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
Some((major, installed))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
let Some((major, installed)) = offer else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
if pinned.vendor == JavaVendor::Microsoft {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let major = match oneclient_core::required_java_major(&state, cluster_id).await {
|
|
||||||
Ok(Some(major)) => major,
|
|
||||||
Ok(None) => {
|
|
||||||
tracing::info!(cluster_id, "the cluster's manifest names no Java version");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
tracing::warn!(
|
|
||||||
cluster_id,
|
|
||||||
"could not read the cluster's Java version: {err:#}"
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let installed = match state
|
|
||||||
.java
|
|
||||||
.has_vendor_runtime(&JavaVendor::Microsoft, Some(major))
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Ok(installed) => installed,
|
|
||||||
Err(err) => {
|
|
||||||
tracing::warn!("could not check for a Microsoft Java runtime: {err:#}");
|
|
||||||
false
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if !installed && !publishes(&state, major).await {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if !claim_ask(cluster_id) {
|
if !claim_ask(cluster_id) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ use std::path::PathBuf;
|
||||||
use freya::prelude::spawn_forever;
|
use freya::prelude::spawn_forever;
|
||||||
|
|
||||||
use crate::hooks::Actions;
|
use crate::hooks::Actions;
|
||||||
|
use crate::launcher::off_ui_blocking;
|
||||||
|
|
||||||
pub fn database_path() -> Option<PathBuf> {
|
pub fn database_path() -> Option<PathBuf> {
|
||||||
oneclient_common::paths::database_file().ok()
|
oneclient_common::paths::database_file().ok()
|
||||||
|
|
@ -29,19 +30,25 @@ pub fn restore_latest(actions: &Actions) {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
match oneclient_db::backup::restore(&path, &snapshot) {
|
let actions = actions.clone();
|
||||||
Ok(_) => retry(actions),
|
spawn_forever(async move {
|
||||||
Err(err) => set_error(actions, format!("Couldn't restore the snapshot: {err}")),
|
match off_ui_blocking(move || oneclient_db::backup::restore(&path, &snapshot)).await {
|
||||||
}
|
Ok(_) => retry(&actions),
|
||||||
|
Err(err) => set_error(&actions, format!("Couldn't restore the snapshot: {err}")),
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn reset(actions: &Actions) {
|
pub fn reset(actions: &Actions) {
|
||||||
let Some(path) = database_path() else { return };
|
let Some(path) = database_path() else { return };
|
||||||
|
|
||||||
match oneclient_db::backup::reset(&path) {
|
let actions = actions.clone();
|
||||||
Ok(_) => retry(actions),
|
spawn_forever(async move {
|
||||||
Err(err) => set_error(actions, format!("Couldn't reset the database: {err}")),
|
match off_ui_blocking(move || oneclient_db::backup::reset(&path)).await {
|
||||||
}
|
Ok(_) => retry(&actions),
|
||||||
|
Err(err) => set_error(&actions, format!("Couldn't reset the database: {err}")),
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn retry(actions: &Actions) {
|
fn retry(actions: &Actions) {
|
||||||
|
|
|
||||||
|
|
@ -319,7 +319,7 @@ fn plan(
|
||||||
checking.set(true);
|
checking.set(true);
|
||||||
error.set(None);
|
error.set(None);
|
||||||
|
|
||||||
match plan_move(picked.as_deref()).await {
|
match crate::launcher::off_ui(async move { plan_move(picked.as_deref()).await }).await {
|
||||||
Ok(planned) => pending.set(Some(planned)),
|
Ok(planned) => pending.set(Some(planned)),
|
||||||
Err(message) => error.set(Some(message)),
|
Err(message) => error.set(Some(message)),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -898,8 +898,8 @@ async fn addition_eligible_bundles(
|
||||||
candidate_keys_by_bundle: &HashMap<String, HashSet<String>>,
|
candidate_keys_by_bundle: &HashMap<String, HashSet<String>>,
|
||||||
overrides: &[ClusterBundleOverrideRow],
|
overrides: &[ClusterBundleOverrideRow],
|
||||||
) -> ContentResult<HashSet<String>> {
|
) -> ContentResult<HashSet<String>> {
|
||||||
let (live_managed_keys, _) =
|
let live: Vec<_> = all_linked.iter().filter(|item| item.enabled).collect();
|
||||||
installed_bundle_keys(ctx, all_linked.iter().filter(|item| item.enabled)).await?;
|
let (live_managed_keys, _) = installed_bundle_keys(ctx, live).await?;
|
||||||
|
|
||||||
let mut eligible: HashSet<String> = bundle_packages
|
let mut eligible: HashSet<String> = bundle_packages
|
||||||
.iter()
|
.iter()
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue