refactor: app event bus (#6985)

* refactor: app event bus

* fix: use messagepack + cleanup

* fix: use postcard

* fix: lint

* fix: import gate

* fix: prettierignore + lint

* fix: lint

* fix: rev
This commit is contained in:
Calum H.
2026-08-13 16:33:06 +00:00
committed by GitHub
parent 5f7493491f
commit 579109a478
93 changed files with 2669 additions and 834 deletions
+3 -1
View File
@@ -126,7 +126,9 @@ pub async fn parse_command(
if let Some(ext) = path.extension()
&& ext == "mrpack"
{
return Ok(CommandPayload::RunMRPack { path });
return Ok(CommandPayload::RunMRPack {
path: path.to_string_lossy().into_owned(),
});
}
emit_warning(&format!(
"Invalid command, unrecognized filetype: {}",
@@ -23,6 +23,10 @@ pub mod gdlauncher;
pub mod mmc;
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(
feature = "export-ts",
derive(ts_rs::TS, postcard_bindgen::PostcardBindings)
)]
pub enum ImportLauncherType {
MultiMC,
PrismLauncher,
+4
View File
@@ -23,6 +23,10 @@ pub struct LabrinthError {
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[cfg_attr(
feature = "export-ts",
derive(ts_rs::TS, postcard_bindgen::PostcardBindings)
)]
#[serde(rename_all = "snake_case")]
pub enum SharedInstanceUnavailableReason {
Deleted,
+42 -79
View File
@@ -1,16 +1,16 @@
use super::{FriendPayload, LoadingBarId};
#[cfg(feature = "tauri")]
use crate::event::{
AppEvent, InstancePayload, LoadingPayload, ProcessPayload, WarningPayload,
};
use crate::event::{
CommandPayload, EventError, InstanceBulkUpdateProgressPayload,
InstancePayloadType, LoadingBar, LoadingBarType, ProcessPayloadType,
};
#[cfg(feature = "tauri")]
use crate::event::{
InstancePayload, LoadingPayload, ProcessPayload, WarningPayload,
};
use futures::prelude::*;
use serde_json::Value;
#[cfg(feature = "tauri")]
use tauri::{Emitter, Manager};
use tauri::Manager;
use uuid::Uuid;
#[cfg(feature = "cli")]
@@ -61,7 +61,7 @@ pub async fn init_loading_unsafe(
total: f64,
title: &str,
) -> crate::Result<LoadingBarId> {
let event_state = crate::EventState::get()?;
let event_state = crate::EventState::get();
let key = LoadingBarId(Uuid::new_v4());
event_state.loading_bars.insert(
@@ -105,7 +105,7 @@ pub fn emit_loading(
increment_frac: f64,
message: Option<&str>,
) -> crate::Result<()> {
let event_state = crate::EventState::get()?;
let event_state = crate::EventState::get();
let Some(mut loading_bar) = event_state.loading_bars.get_mut(&key.0) else {
return Err(EventError::NoLoadingBar(key.0).into());
@@ -131,25 +131,17 @@ pub fn emit_loading(
//Emit event to tauri
#[cfg(feature = "tauri")]
event_state
.app
.emit(
"loading",
LoadingPayload {
fraction: if display_frac >= 1.0 {
None // by convention, when its done, we submit None
// any further updates will be ignored (also sending None)
} else {
Some(display_frac)
},
message: message
.unwrap_or(&loading_bar.message)
.to_string(),
event: loading_bar.bar_type.clone(),
loader_uuid: loading_bar.loading_bar_uuid,
},
)
.map_err(EventError::from)?;
event_state.send(AppEvent::Loading(LoadingPayload {
fraction: if display_frac >= 1.0 {
None // by convention, when its done, we submit None
// any further updates will be ignored (also sending None)
} else {
Some(display_frac)
},
message: message.unwrap_or(&loading_bar.message).to_string(),
event: loading_bar.bar_type.clone(),
loader_uuid: loading_bar.loading_bar_uuid.to_string(),
}))?;
#[cfg(not(any(feature = "cli", feature = "tauri")))]
let _ = message;
@@ -164,16 +156,10 @@ pub fn emit_loading(
pub async fn emit_warning(message: &str) -> crate::Result<()> {
#[cfg(feature = "tauri")]
{
let event_state = crate::EventState::get()?;
event_state
.app
.emit(
"warning",
WarningPayload {
message: message.to_string(),
},
)
.map_err(EventError::from)?;
let event_state = crate::EventState::get();
event_state.send(AppEvent::Warning(WarningPayload {
message: message.to_string(),
}))?;
}
tracing::warn!("{}", message);
Ok(())
@@ -185,11 +171,8 @@ pub async fn emit_instance_bulk_update_progress(
) -> crate::Result<()> {
#[cfg(feature = "tauri")]
{
let event_state = crate::EventState::get()?;
event_state
.app
.emit("instance_bulk_update_progress", payload)
.map_err(EventError::from)?;
let event_state = crate::EventState::get();
event_state.send(AppEvent::InstanceBulkUpdateProgress(payload))?;
}
Ok(())
}
@@ -201,11 +184,8 @@ pub async fn emit_command(command: CommandPayload) -> crate::Result<()> {
tracing::debug!("Command: {}", serde_json::to_string(&command)?);
#[cfg(feature = "tauri")]
{
let event_state = crate::EventState::get()?;
event_state
.app
.emit("command", command)
.map_err(EventError::from)?;
let event_state = crate::EventState::get();
event_state.send(AppEvent::Command(command))?;
if let Some(window) = event_state.app.get_window("main") {
let _ = window.set_focus();
@@ -224,19 +204,13 @@ pub async fn emit_process(
) -> crate::Result<()> {
#[cfg(feature = "tauri")]
{
let event_state = crate::EventState::get()?;
event_state
.app
.emit(
"process",
ProcessPayload {
instance_id: instance_id.to_string(),
uuid,
event,
message: message.to_string(),
},
)
.map_err(EventError::from)?;
let event_state = crate::EventState::get();
event_state.send(AppEvent::Process(ProcessPayload {
instance_id: instance_id.to_string(),
uuid: uuid.to_string(),
event,
message: message.to_string(),
}))?;
}
Ok(())
}
@@ -249,17 +223,11 @@ pub async fn emit_instance(
) -> crate::Result<()> {
#[cfg(feature = "tauri")]
{
let event_state = crate::EventState::get()?;
event_state
.app
.emit(
"instance",
InstancePayload {
instance_id: instance_id.to_string(),
event,
},
)
.map_err(EventError::from)?;
let event_state = crate::EventState::get();
event_state.send(AppEvent::Instance(InstancePayload {
instance_id: instance_id.to_string(),
event,
}))?;
}
Ok(())
}
@@ -268,11 +236,8 @@ pub async fn emit_instance(
pub async fn emit_friend(payload: FriendPayload) -> crate::Result<()> {
#[cfg(feature = "tauri")]
{
let event_state = crate::EventState::get()?;
event_state
.app
.emit("friend", payload)
.map_err(EventError::from)?;
let event_state = crate::EventState::get();
event_state.send(AppEvent::Friend(payload))?;
}
Ok(())
@@ -282,11 +247,9 @@ pub async fn emit_friend(payload: FriendPayload) -> crate::Result<()> {
pub async fn emit_notification(payload: Value) -> crate::Result<()> {
#[cfg(feature = "tauri")]
{
let event_state = crate::EventState::get()?;
let event_state = crate::EventState::get();
event_state
.app
.emit("notification", payload)
.map_err(EventError::from)?;
.send(AppEvent::Notification(serde_json::to_string(&payload)?))?;
}
Ok(())
+299 -81
View File
@@ -1,15 +1,18 @@
//! Theseus state management system
use ariadne::ids::UserId;
use ariadne::users::UserStatus;
use chrono::{DateTime, Utc};
use dashmap::DashMap;
use serde::{Deserialize, Serialize};
use std::{path::PathBuf, sync::Arc};
#[cfg(feature = "tauri")]
use tauri::Emitter;
use parking_lot::RwLock;
use serde::{Deserialize, Serialize};
#[cfg(feature = "export-ts")]
use std::path::PathBuf;
use std::sync::Arc;
#[cfg(feature = "tauri")]
use tauri::ipc::{Channel, InvokeResponseBody};
use tokio::sync::OnceCell;
use uuid::Uuid;
use crate::install::InstallJobSnapshot;
pub mod emit;
// Global event state
@@ -19,21 +22,30 @@ pub struct EventState {
/// Tauri app
#[cfg(feature = "tauri")]
pub app: tauri::AppHandle,
#[cfg(feature = "tauri")]
event_channel: RwLock<Channel<InvokeResponseBody>>,
pub loading_bars: DashMap<Uuid, LoadingBar>,
}
impl EventState {
#[cfg(feature = "tauri")]
pub async fn init(app: tauri::AppHandle) -> crate::Result<Arc<Self>> {
EVENT_STATE
pub async fn init(
app: tauri::AppHandle,
event_channel: Channel<InvokeResponseBody>,
) -> crate::Result<Arc<Self>> {
let state = EVENT_STATE
.get_or_try_init(|| async {
Ok(Arc::new(Self {
Ok::<_, crate::Error>(Arc::new(Self {
app,
event_channel: RwLock::new(event_channel.clone()),
loading_bars: DashMap::new(),
}))
})
.await
.cloned()
.cloned()?;
*state.event_channel.write() = event_channel;
Ok(state)
}
#[cfg(not(feature = "tauri"))]
@@ -48,14 +60,28 @@ impl EventState {
.cloned()
}
pub fn get() -> crate::Result<Arc<Self>> {
Ok(EVENT_STATE.get().ok_or(EventError::NotInitialized)?.clone())
pub fn get() -> Arc<Self> {
EVENT_STATE
.get()
.expect("should be initialized when used")
.clone()
}
#[cfg(feature = "tauri")]
pub fn send(&self, event: AppEvent) -> crate::Result<()> {
let payload =
postcard::to_allocvec(&event).map_err(EventError::from)?;
self.event_channel
.read()
.send(InvokeResponseBody::Raw(payload))
.map_err(EventError::from)?;
Ok(())
}
// Values provided should not be used directly, as they are clones and are not guaranteed to be up-to-date
pub async fn list_progress_bars() -> crate::Result<DashMap<Uuid, LoadingBar>>
{
let value = Self::get()?;
let value = Self::get();
Ok(value.loading_bars.clone())
}
@@ -63,11 +89,122 @@ impl EventState {
pub async fn get_main_window() -> crate::Result<Option<tauri::WebviewWindow>>
{
use tauri::Manager;
let value = Self::get()?;
let value = Self::get();
Ok(value.app.get_webview_window("main"))
}
}
#[derive(Clone)]
#[cfg_attr(
feature = "export-ts",
derive(ts_rs::TS, postcard_bindgen::PostcardBindings)
)]
#[serde_binhum::serde_binhum]
#[serde(tag = "type", content = "payload", rename_all = "snake_case")]
#[cfg_attr(
feature = "export-ts",
ts(
tag = "type",
content = "payload",
rename_all = "snake_case",
export_to = "AppEvent.ts"
)
)]
pub enum AppEvent {
Loading(LoadingPayload),
Process(ProcessPayload),
Instance(InstancePayload),
InstanceBulkUpdateProgress(InstanceBulkUpdateProgressPayload),
InstallJob(std::sync::Arc<InstallJobSnapshot>),
Command(CommandPayload),
Warning(WarningPayload),
Friend(FriendPayload),
Notification(
#[cfg_attr(feature = "export-ts", ts(type = "unknown"))] String,
),
Log(LogPayload),
AdsConsentRequired(bool),
}
#[cfg(feature = "export-ts")]
pub fn export_app_event_bindings(
output: impl Into<PathBuf>,
) -> Result<(), Box<dyn std::error::Error>> {
use postcard_bindgen::{PackageInfo, generate_bindings, javascript};
use ts_rs::{Config, TS};
let output = output.into();
let config = Config::default()
.with_out_dir(output.clone())
.with_large_int("number");
AppEvent::export_all(&config)?;
javascript::build_package(
&output,
PackageInfo {
name: "postcard".into(),
version: "0.0.0".try_into()?,
},
javascript::GenerationSettings::default()
.type_script_types(true)
.module_structure(false)
.esm_module(true),
generate_bindings!(
AppEvent,
LoadingBarType,
LoadingPayload,
WarningPayload,
InstanceBulkUpdateProgressPayload,
InstanceBulkUpdateProgressStage,
CommandPayload,
ProcessPayload,
ProcessPayloadType,
InstancePayload,
InstancePayloadType,
FriendPayload,
FriendStatusPayload,
LogEvent,
LogPayload,
crate::state::Log4jEvent,
crate::install::InstallJobSnapshot,
crate::install::InstallJobKind,
crate::install::InstallJobStatus,
crate::install::model::InstallTarget,
crate::install::InstallPhaseId,
crate::install::InstallProgress,
crate::install::InstallProgressSecondary,
crate::install::InstallPhaseDetails,
crate::install::InstallJavaStep,
crate::install::model::InstallJobDisplay,
crate::install::InstallErrorView,
crate::install::model::InstallApiErrorDetails,
crate::install::InstallErrorContext,
crate::api::pack::import::ImportLauncherType,
crate::state::ModLoader,
crate::SharedInstanceUnavailableReason
),
)?;
fix_postcard_javascript_utf8(&output.join("postcard/index.js"))?;
Ok(())
}
#[cfg(feature = "export-ts")]
fn fix_postcard_javascript_utf8(
output: &std::path::Path,
) -> Result<(), Box<dyn std::error::Error>> {
const GENERATED: &str = "deserialize_string = () => { const str = this.pop_n(Number(this.try_take(U32_BYTES))); return String.fromCharCode(...str) }";
const UTF8: &str = "deserialize_string = () => new TextDecoder().decode(new Uint8Array(this.pop_n(Number(this.try_take(U32_BYTES)))))";
let javascript = std::fs::read_to_string(output)?;
if !javascript.contains(GENERATED) {
return Err(
"postcard-bindgen's generated string decoder changed".into()
);
}
std::fs::write(output, javascript.replace(GENERATED, UTF8))?;
Ok(())
}
#[derive(Serialize, Debug, Clone)]
pub struct LoadingBar {
// loading_bar_uuid not be used directly by external functions as it may not reflect the current state of the loading bar/hashmap
@@ -91,55 +228,58 @@ impl Drop for LoadingBarId {
fn drop(&mut self) {
let loader_uuid = self.0;
tokio::spawn(async move {
if let Ok(event_state) = EventState::get() {
#[cfg(any(feature = "tauri", feature = "cli"))]
if let Some((_, bar)) =
event_state.loading_bars.remove(&loader_uuid)
let event_state = EventState::get();
#[cfg(any(feature = "tauri", feature = "cli"))]
if let Some((_, bar)) =
event_state.loading_bars.remove(&loader_uuid)
{
#[cfg(feature = "tauri")]
{
#[cfg(feature = "tauri")]
{
let loader_uuid = bar.loading_bar_uuid;
let event = bar.bar_type.clone();
let fraction = bar.current / bar.total;
let loader_uuid = bar.loading_bar_uuid;
let event = bar.bar_type.clone();
let fraction = bar.current / bar.total;
let _ = event_state.app.emit(
"loading",
LoadingPayload {
fraction: None,
message: "Completed".to_string(),
event,
loader_uuid,
},
);
tracing::trace!(
"Exited at {fraction} for loading bar: {:?}",
loader_uuid
);
}
// Emit event to indicatif progress bar arc
#[cfg(feature = "cli")]
{
let cli_progress_bar = bar.cli_progress_bar;
cli_progress_bar.finish();
}
let _ =
event_state.send(AppEvent::Loading(LoadingPayload {
fraction: None,
message: "Completed".to_string(),
event,
loader_uuid: loader_uuid.to_string(),
}));
tracing::trace!(
"Exited at {fraction} for loading bar: {:?}",
loader_uuid
);
}
#[cfg(not(any(feature = "tauri", feature = "cli")))]
event_state.loading_bars.remove(&loader_uuid);
// Emit event to indicatif progress bar arc
#[cfg(feature = "cli")]
{
let cli_progress_bar = bar.cli_progress_bar;
cli_progress_bar.finish();
}
}
#[cfg(not(any(feature = "tauri", feature = "cli")))]
event_state.loading_bars.remove(&loader_uuid);
});
}
}
#[derive(Serialize, Deserialize, Clone, Debug, Hash, PartialEq, Eq)]
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
#[cfg_attr(
feature = "export-ts",
derive(ts_rs::TS, postcard_bindgen::PostcardBindings)
)]
#[serde_binhum::serde_binhum]
#[serde(tag = "type")]
#[serde(rename_all = "snake_case")]
#[cfg_attr(feature = "export-ts", ts(tag = "type", rename_all = "snake_case"))]
pub enum LoadingBarType {
LegacyDataMigration,
DirectoryMove {
old: PathBuf,
new: PathBuf,
old: String,
new: String,
},
JavaDownload {
version: u32,
@@ -153,7 +293,7 @@ pub enum LoadingBarType {
PackDownload {
instance_id: String,
pack_name: String,
icon: Option<PathBuf>,
icon: Option<String>,
pack_id: Option<String>,
pack_version: Option<String>,
},
@@ -173,10 +313,10 @@ pub enum LoadingBarType {
instance_name: String,
},
ConfigChange {
new_path: PathBuf,
new_path: String,
},
CopyInstance {
import_location: PathBuf,
import_location: String,
instance_name: String,
},
LauncherUpdate {
@@ -185,22 +325,32 @@ pub enum LoadingBarType {
},
}
#[derive(Serialize, Clone)]
#[cfg(feature = "tauri")]
#[derive(Serialize, Deserialize, Clone)]
#[cfg_attr(
feature = "export-ts",
derive(ts_rs::TS, postcard_bindgen::PostcardBindings)
)]
pub struct LoadingPayload {
pub event: LoadingBarType,
pub loader_uuid: Uuid,
pub loader_uuid: String,
pub fraction: Option<f64>, // by convention, if optional, it means the loading is done
pub message: String,
}
#[derive(Serialize, Clone)]
#[cfg(feature = "tauri")]
#[derive(Serialize, Deserialize, Clone)]
#[cfg_attr(
feature = "export-ts",
derive(ts_rs::TS, postcard_bindgen::PostcardBindings)
)]
pub struct WarningPayload {
pub message: String,
}
#[derive(Serialize, Clone)]
#[derive(Serialize, Deserialize, Clone)]
#[cfg_attr(
feature = "export-ts",
derive(ts_rs::TS, postcard_bindgen::PostcardBindings)
)]
#[serde(rename_all = "camelCase")]
pub struct InstanceBulkUpdateProgressPayload {
pub instance_id: String,
@@ -209,7 +359,11 @@ pub struct InstanceBulkUpdateProgressPayload {
pub total: usize,
}
#[derive(Serialize, Clone)]
#[derive(Serialize, Deserialize, Clone)]
#[cfg_attr(
feature = "export-ts",
derive(ts_rs::TS, postcard_bindgen::PostcardBindings)
)]
#[serde(rename_all = "snake_case")]
pub enum InstanceBulkUpdateProgressStage {
ResolvingVersions,
@@ -217,8 +371,14 @@ pub enum InstanceBulkUpdateProgressStage {
Finishing,
}
#[derive(Serialize, Clone)]
#[derive(Clone)]
#[cfg_attr(
feature = "export-ts",
derive(ts_rs::TS, postcard_bindgen::PostcardBindings)
)]
#[serde_binhum::serde_binhum]
#[serde(tag = "event")]
#[cfg_attr(feature = "export-ts", ts(tag = "event"))]
pub enum CommandPayload {
InstallMod {
id: String,
@@ -242,36 +402,54 @@ pub enum CommandPayload {
},
RunMRPack {
// run or install .mrpack
path: PathBuf,
path: String,
},
}
#[derive(Serialize, Clone)]
#[cfg(feature = "tauri")]
#[derive(Serialize, Deserialize, Clone)]
#[cfg_attr(
feature = "export-ts",
derive(ts_rs::TS, postcard_bindgen::PostcardBindings)
)]
pub struct ProcessPayload {
pub instance_id: String,
pub uuid: Uuid,
pub uuid: String,
pub event: ProcessPayloadType,
pub message: String,
}
#[derive(Serialize, Clone, Debug)]
#[derive(Serialize, Deserialize, Clone, Debug)]
#[cfg_attr(
feature = "export-ts",
derive(ts_rs::TS, postcard_bindgen::PostcardBindings)
)]
#[serde(rename_all = "snake_case")]
pub enum ProcessPayloadType {
Launched,
Finished,
}
#[derive(Serialize, Clone)]
#[cfg(feature = "tauri")]
#[derive(Clone)]
#[cfg_attr(
feature = "export-ts",
derive(ts_rs::TS, postcard_bindgen::PostcardBindings)
)]
#[serde_binhum::serde_binhum]
pub struct InstancePayload {
pub instance_id: String,
#[serde(flatten)]
#[cfg_attr(feature = "export-ts", ts(flatten))]
pub event: InstancePayloadType,
}
#[derive(Serialize, Clone)]
#[derive(Clone)]
#[cfg_attr(
feature = "export-ts",
derive(ts_rs::TS, postcard_bindgen::PostcardBindings)
)]
#[serde_binhum::serde_binhum]
#[serde(tag = "event", rename_all = "snake_case")]
#[cfg_attr(feature = "export-ts", ts(tag = "event", rename_all = "snake_case"))]
pub enum InstancePayloadType {
Created,
Synced,
@@ -282,7 +460,7 @@ pub enum InstancePayloadType {
ServerJoined {
host: String,
port: u16,
timestamp: DateTime<Utc>,
timestamp: String,
},
Edited,
ContentInstallFinished {
@@ -295,47 +473,87 @@ pub enum InstancePayloadType {
Removed,
}
#[derive(Serialize, Clone)]
#[derive(Clone)]
#[cfg_attr(
feature = "export-ts",
derive(ts_rs::TS, postcard_bindgen::PostcardBindings)
)]
#[serde_binhum::serde_binhum]
#[serde(rename_all = "snake_case")]
#[serde(tag = "event")]
#[cfg_attr(feature = "export-ts", ts(tag = "event", rename_all = "snake_case"))]
pub enum FriendPayload {
FriendRequest { from: UserId },
UserOffline { id: UserId },
StatusUpdate { user_status: UserStatus },
FriendRequest { from: String },
UserOffline { id: String },
StatusUpdate { user_status: FriendStatusPayload },
StatusSync,
}
#[cfg(feature = "tauri")]
#[derive(Serialize, Deserialize, Clone)]
#[cfg_attr(
feature = "export-ts",
derive(ts_rs::TS, postcard_bindgen::PostcardBindings)
)]
pub struct FriendStatusPayload {
pub user_id: String,
pub profile_name: Option<String>,
pub last_update: String,
}
impl From<ariadne::users::UserStatus> for FriendStatusPayload {
fn from(status: ariadne::users::UserStatus) -> Self {
Self {
user_id: status.user_id.to_string(),
profile_name: status.profile_name,
last_update: status.last_update.to_rfc3339(),
}
}
}
pub use self::log_types::*;
#[cfg(feature = "tauri")]
mod log_types {
use crate::state::Log4jEvent;
use serde::Serialize;
#[derive(Serialize, Clone)]
#[derive(Clone)]
#[cfg_attr(
feature = "export-ts",
derive(ts_rs::TS, postcard_bindgen::PostcardBindings)
)]
#[serde_binhum::serde_binhum]
#[serde(tag = "type", rename_all = "snake_case")]
#[cfg_attr(
feature = "export-ts",
ts(tag = "type", rename_all = "snake_case")
)]
pub enum LogEvent {
Log4j(Log4jEvent),
Legacy { message: String },
}
#[derive(Serialize, Clone)]
#[derive(Clone)]
#[cfg_attr(
feature = "export-ts",
derive(ts_rs::TS, postcard_bindgen::PostcardBindings)
)]
#[serde_binhum::serde_binhum]
pub struct LogPayload {
pub instance_id: String,
#[serde(flatten)]
#[cfg_attr(feature = "export-ts", ts(flatten))]
pub event: LogEvent,
}
}
#[derive(Debug, thiserror::Error)]
pub enum EventError {
#[error("Event state was not properly initialized")]
NotInitialized,
#[error("Non-existent loading bar of key: {0}")]
NoLoadingBar(Uuid),
#[cfg(feature = "tauri")]
#[error("Postcard encoding error: {0}")]
PostcardEncode(#[from] postcard::Error),
#[cfg(feature = "tauri")]
#[error("Tauri error: {0}")]
TauriError(#[from] tauri::Error),
+4 -7
View File
@@ -200,14 +200,11 @@ pub async fn emit_install_job(
) -> crate::Result<()> {
#[cfg(feature = "tauri")]
{
use tauri::Emitter;
let result: crate::Result<()> = (|| {
let event_state = crate::EventState::get()?;
event_state
.app
.emit("install_job", snapshot)
.map_err(crate::event::EventError::from)?;
let event_state = crate::EventState::get();
event_state.send(crate::event::AppEvent::InstallJob(
std::sync::Arc::new(snapshot.clone()),
))?;
Ok(())
})();
if let Err(error) = result {
+93 -12
View File
@@ -6,7 +6,6 @@ use crate::state::{
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use uuid::Uuid;
pub type InstallModpackPreview = CreatePackInstance;
@@ -300,6 +299,10 @@ impl InstallRequest {
}
#[derive(Serialize, Deserialize, Clone, Copy, Debug, Eq, PartialEq)]
#[cfg_attr(
feature = "export-ts",
derive(ts_rs::TS, postcard_bindgen::PostcardBindings)
)]
#[serde(rename_all = "snake_case")]
pub enum InstallJobKind {
CreateInstance,
@@ -345,6 +348,10 @@ impl InstallJobKind {
}
#[derive(Serialize, Deserialize, Clone, Copy, Debug, Eq, PartialEq)]
#[cfg_attr(
feature = "export-ts",
derive(ts_rs::TS, postcard_bindgen::PostcardBindings)
)]
#[serde(rename_all = "snake_case")]
pub enum InstallJobStatus {
Queued,
@@ -386,8 +393,14 @@ impl InstallJobStatus {
}
}
#[derive(Serialize, Deserialize, Clone, Debug)]
#[derive(Clone, Debug)]
#[cfg_attr(
feature = "export-ts",
derive(ts_rs::TS, postcard_bindgen::PostcardBindings)
)]
#[serde_binhum::serde_binhum]
#[serde(tag = "type", rename_all = "snake_case")]
#[cfg_attr(feature = "export-ts", ts(tag = "type", rename_all = "snake_case"))]
pub enum InstallTarget {
NewInstance { instance_id: Option<String> },
ExistingInstance { instance_id: String },
@@ -408,6 +421,10 @@ pub struct InstallProgressState {
}
#[derive(Serialize, Deserialize, Clone, Copy, Debug, Eq, PartialEq)]
#[cfg_attr(
feature = "export-ts",
derive(ts_rs::TS, postcard_bindgen::PostcardBindings)
)]
#[serde(rename_all = "snake_case")]
pub enum InstallPhaseId {
PreparingInstance,
@@ -425,21 +442,35 @@ pub enum InstallPhaseId {
RollingBack,
}
#[derive(Serialize, Deserialize, Clone, Debug)]
#[derive(Clone, Debug)]
#[cfg_attr(
feature = "export-ts",
derive(ts_rs::TS, postcard_bindgen::PostcardBindings)
)]
#[serde_binhum::serde_binhum]
pub struct InstallProgress {
pub current: u64,
pub total: u64,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "export-ts", ts(optional))]
pub secondary: Option<InstallProgressSecondary>,
}
#[derive(Serialize, Deserialize, Clone, Debug)]
#[cfg_attr(
feature = "export-ts",
derive(ts_rs::TS, postcard_bindgen::PostcardBindings)
)]
pub struct InstallProgressSecondary {
pub current: u64,
pub total: u64,
}
#[derive(Serialize, Deserialize, Clone, Copy, Debug, Eq, PartialEq)]
#[cfg_attr(
feature = "export-ts",
derive(ts_rs::TS, postcard_bindgen::PostcardBindings)
)]
#[serde(rename_all = "snake_case")]
pub enum InstallJavaStep {
Resolving,
@@ -449,8 +480,14 @@ pub enum InstallJavaStep {
Validating,
}
#[derive(Serialize, Deserialize, Clone, Debug)]
#[derive(Clone, Debug)]
#[cfg_attr(
feature = "export-ts",
derive(ts_rs::TS, postcard_bindgen::PostcardBindings)
)]
#[serde_binhum::serde_binhum]
#[serde(tag = "type", rename_all = "snake_case")]
#[cfg_attr(feature = "export-ts", ts(tag = "type", rename_all = "snake_case"))]
pub enum InstallPhaseDetails {
Empty,
Instance {
@@ -481,52 +518,74 @@ pub struct InstallJobPaths {
pub final_instance_path: Option<PathBuf>,
}
#[derive(Serialize, Deserialize, Clone, Debug, bon::Builder)]
#[derive(Clone, Debug, bon::Builder)]
#[cfg_attr(
feature = "export-ts",
derive(ts_rs::TS, postcard_bindgen::PostcardBindings)
)]
#[serde_binhum::serde_binhum]
#[builder(start_fn = new)]
pub struct InstallErrorContext {
#[builder(start_fn, into)]
pub operation: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "export-ts", ts(optional))]
#[builder(into)]
pub source_path: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "export-ts", ts(optional))]
#[builder(into)]
pub target_path: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "export-ts", ts(optional))]
#[builder(into)]
pub file_path: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "export-ts", ts(optional))]
#[builder(into)]
pub entry_path: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
#[serde(default)]
#[builder(default)]
pub urls: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "export-ts", ts(optional))]
#[builder(into)]
pub expected_hash: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "export-ts", ts(optional))]
pub expected_size: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "export-ts", ts(optional))]
pub project_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "export-ts", ts(optional))]
pub version_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "export-ts", ts(optional))]
#[builder(into)]
pub minecraft_version: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "export-ts", ts(optional))]
#[builder(into)]
pub loader: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "export-ts", ts(optional))]
pub java_version: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "export-ts", ts(optional))]
#[builder(into)]
pub os: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "export-ts", ts(optional))]
#[builder(into)]
pub arch: Option<String>,
}
#[derive(Serialize, Deserialize, Clone, Debug)]
#[cfg_attr(
feature = "export-ts",
derive(ts_rs::TS, postcard_bindgen::PostcardBindings)
)]
pub struct InstallJobDisplay {
pub title: String,
pub icon: Option<String>,
@@ -538,30 +597,48 @@ pub struct InstallRollbackState {
pub install_stage: InstanceInstallStage,
}
#[derive(Serialize, Deserialize, Clone, Debug)]
#[derive(Clone, Debug)]
#[cfg_attr(
feature = "export-ts",
derive(ts_rs::TS, postcard_bindgen::PostcardBindings)
)]
#[serde_binhum::serde_binhum]
pub struct InstallErrorView {
pub code: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "export-ts", ts(optional))]
pub phase: Option<InstallPhaseId>,
pub message: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "export-ts", ts(optional))]
pub reason: Option<crate::SharedInstanceUnavailableReason>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "export-ts", ts(optional))]
pub api: Option<InstallApiErrorDetails>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "export-ts", ts(optional))]
pub context: Option<InstallErrorContext>,
}
#[derive(Serialize, Deserialize, Clone, Debug)]
#[derive(Clone, Debug)]
#[cfg_attr(
feature = "export-ts",
derive(ts_rs::TS, postcard_bindgen::PostcardBindings)
)]
#[serde_binhum::serde_binhum]
pub struct InstallApiErrorDetails {
pub error: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "export-ts", ts(optional))]
pub status: Option<u16>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "export-ts", ts(optional))]
pub method: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "export-ts", ts(optional))]
pub url: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "export-ts", ts(optional))]
pub route: Option<String>,
}
@@ -610,8 +687,12 @@ impl InstallErrorView {
}
#[derive(Serialize, Deserialize, Clone, Debug)]
#[cfg_attr(
feature = "export-ts",
derive(ts_rs::TS, postcard_bindgen::PostcardBindings)
)]
pub struct InstallJobSnapshot {
pub job_id: Uuid,
pub job_id: String,
pub instance_id: Option<String>,
pub kind: InstallJobKind,
pub status: InstallJobStatus,
@@ -622,7 +703,7 @@ pub struct InstallJobSnapshot {
pub display: Option<InstallJobDisplay>,
pub error: Option<InstallErrorView>,
pub rollback_error: Option<InstallErrorView>,
pub created: DateTime<Utc>,
pub modified: DateTime<Utc>,
pub finished: Option<DateTime<Utc>>,
pub created: String,
pub modified: String,
pub finished: Option<String>,
}
+4 -4
View File
@@ -34,7 +34,7 @@ struct InstallJobRow {
impl InstallJobRecord {
pub fn snapshot(&self) -> InstallJobSnapshot {
InstallJobSnapshot {
job_id: self.id,
job_id: self.id.to_string(),
instance_id: self.instance_id.clone(),
kind: self.kind,
status: self.status,
@@ -45,9 +45,9 @@ impl InstallJobRecord {
display: self.state.display.clone(),
error: self.state.error.clone(),
rollback_error: self.state.rollback_error.clone(),
created: self.created,
modified: self.modified,
finished: self.finished,
created: self.created.to_rfc3339(),
modified: self.modified.to_rfc3339(),
finished: self.finished.map(|finished| finished.to_rfc3339()),
}
}
}
+3 -1
View File
@@ -20,8 +20,10 @@ mod state;
pub use api::*;
pub use error::*;
#[cfg(feature = "export-ts")]
pub use event::export_app_event_bindings;
pub use event::{
EventState, LoadingBar, LoadingBarType, emit::emit_loading,
AppEvent, EventState, LoadingBar, LoadingBarType, emit::emit_loading,
emit::init_loading,
};
pub use logger::start_logger;
+2 -2
View File
@@ -253,8 +253,8 @@ impl DirectoryInfo {
if prev_dir != move_dir {
let loader_bar_id = init_loading(
LoadingBarType::DirectoryMove {
old: prev_dir.clone(),
new: move_dir.clone(),
old: prev_dir.to_string_lossy().into_owned(),
new: move_dir.to_string_lossy().into_owned(),
},
100.0,
"Moving launcher directory",
+3 -3
View File
@@ -192,11 +192,11 @@ impl FriendsSocket {
match server_message {
ServerToClientMessage::StatusUpdate { status } => {
statuses.insert(status.user_id, status.clone());
let _ = emit_friend(FriendPayload::StatusUpdate { user_status: status }).await;
let _ = emit_friend(FriendPayload::StatusUpdate { user_status: status.into() }).await;
},
ServerToClientMessage::UserOffline { id } => {
statuses.remove(&id);
let _ = emit_friend(FriendPayload::UserOffline { id }).await;
let _ = emit_friend(FriendPayload::UserOffline { id: id.to_string() }).await;
}
ServerToClientMessage::FriendStatuses { statuses: new_statuses } => {
statuses.clear();
@@ -206,7 +206,7 @@ impl FriendsSocket {
let _ = emit_friend(FriendPayload::StatusSync).await;
}
ServerToClientMessage::FriendRequest { from } => {
let _ = emit_friend(FriendPayload::FriendRequest { from }).await;
let _ = emit_friend(FriendPayload::FriendRequest { from: from.to_string() }).await;
}
ServerToClientMessage::FriendRequestRejected { .. } => {}, // TODO
@@ -73,6 +73,10 @@ impl LauncherFeatureVersion {
}
#[derive(Debug, Eq, PartialEq, Clone, Copy, Deserialize, Serialize)]
#[cfg_attr(
feature = "export-ts",
derive(ts_rs::TS, postcard_bindgen::PostcardBindings)
)]
#[serde(rename_all = "lowercase")]
pub enum ModLoader {
Vanilla,
+18 -24
View File
@@ -18,8 +18,6 @@ use std::path::{Path, PathBuf};
use std::process::ExitStatus;
use std::sync::LazyLock;
use std::time::Instant;
#[cfg(feature = "tauri")]
use tauri::Emitter;
use tempfile::TempDir;
use tokio::io::{AsyncBufReadExt, BufReader};
use tokio::process::{Child, Command};
@@ -298,7 +296,11 @@ struct Process {
rpc_server: RpcServer,
}
#[derive(Debug, Default, Serialize, Clone)]
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
#[cfg_attr(
feature = "export-ts",
derive(ts_rs::TS, postcard_bindgen::PostcardBindings)
)]
pub struct Log4jEvent {
pub timestamp_millis: Option<i64>,
pub logger_name: Option<String>,
@@ -606,15 +608,11 @@ impl Process {
#[cfg(feature = "tauri")]
{
if let Ok(event_state) = crate::EventState::get() {
let _ = event_state.app.emit(
"log",
LogPayload {
instance_id: instance_id.to_string(),
event: LogEvent::Log4j(event.clone()),
},
);
}
let event_state = crate::EventState::get();
let _ = event_state.send(crate::event::AppEvent::Log(LogPayload {
instance_id: instance_id.to_string(),
event: LogEvent::Log4j(event.clone()),
}));
}
#[cfg(not(feature = "tauri"))]
{
@@ -627,17 +625,13 @@ impl Process {
#[cfg(feature = "tauri")]
{
if let Ok(event_state) = crate::EventState::get() {
let _ = event_state.app.emit(
"log",
LogPayload {
instance_id: instance_id.to_string(),
event: LogEvent::Legacy {
message: message.to_string(),
},
},
);
}
let event_state = crate::EventState::get();
let _ = event_state.send(crate::event::AppEvent::Log(LogPayload {
instance_id: instance_id.to_string(),
event: LogEvent::Legacy {
message: message.to_string(),
},
}));
}
#[cfg(not(feature = "tauri"))]
{
@@ -735,7 +729,7 @@ impl Process {
InstancePayloadType::ServerJoined {
host,
port,
timestamp,
timestamp: timestamp.to_rfc3339(),
},
)
.await;