mirror of
https://github.com/modrinth/code.git
synced 2026-08-28 02:24:56 +00:00
* feat: base of instances v2 * feat: use old profiles with compat layer * prototype: instances v2 * fix: install_from using profile * fix: skins migration fix * fix: frontend still using profile path * fix: add update proj multiselect guard * fix: cargo fmt * fix: content missing fields * feat: break up app-lib/api/instance.rs * fix: check_content_updates mismatch * fix: updater modal cleanup w/new structure * feat: better update all handling * fix: remove preview_update_all * fix: feedback on bulk update + lint * fix: rem transitions * fix: change to jsonb * feat: app db backup after update * fix: lint * fix: sqlx prepare + use sqlx macros * fix: lint * fix: bugs * feat: defuck the installing process up * fix: bug of hell * fix: shear * fix: fmt * fix: install progress spacing + change mc/content/overrides to bytes * fix: lint * fix: prepr * fix: navtabs anim not working in app * fix: worlds.vue improvements + browse page fixes * feat: optimise queries + adapter fns * fix: lint * fix: lint * feat: shared modrinth-content-management crate (#6469) * feat: disable warnings setting * feat: add instances shortcuts (#6329) * Add modrinth://launch deep link to start a profile Support external profile launching via modrinth://launch/{profile_path} for integrations such as Stream Deck. * Change route to /launch/profile/{id} for future extensibility * fix: ensure profile path is url decoded * fix: URL-decode profile path from deep link * fix: use urlencoding crate for URL decoding * feat: implement app instance shortcuts * feat: change windows shortcut creation to use windows api instead * feat: implement creating a shortcut launching world/server * format * fmt * fix multiline inline tables * pnpm prepr * feat: move create shortcut to last item * refactor: split up shortcuts.rs for individual platforms * refactor: turn profile launch url into url type * use string literal and add safety comment * pt2 * refactor: rename anything that's profile into instance * update mac shortcut --------- Co-authored-by: DJCheesusReal <134006619+DJCheesusReal@users.noreply.github.com> --------- Co-authored-by: Truman Gao <106889354+tdgao@users.noreply.github.com> Co-authored-by: DJCheesusReal <134006619+DJCheesusReal@users.noreply.github.com>
337 lines
8.5 KiB
Rust
337 lines
8.5 KiB
Rust
//! 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 tokio::sync::OnceCell;
|
|
use uuid::Uuid;
|
|
|
|
pub mod emit;
|
|
|
|
// Global event state
|
|
// Stores the Tauri app handle and other event-related state variables
|
|
static EVENT_STATE: OnceCell<Arc<EventState>> = OnceCell::const_new();
|
|
pub struct EventState {
|
|
/// Tauri app
|
|
#[cfg(feature = "tauri")]
|
|
pub app: tauri::AppHandle,
|
|
pub loading_bars: DashMap<Uuid, LoadingBar>,
|
|
}
|
|
|
|
impl EventState {
|
|
#[cfg(feature = "tauri")]
|
|
pub async fn init(app: tauri::AppHandle) -> crate::Result<Arc<Self>> {
|
|
EVENT_STATE
|
|
.get_or_try_init(|| async {
|
|
Ok(Arc::new(Self {
|
|
app,
|
|
loading_bars: DashMap::new(),
|
|
}))
|
|
})
|
|
.await
|
|
.cloned()
|
|
}
|
|
|
|
#[cfg(not(feature = "tauri"))]
|
|
pub async fn init() -> crate::Result<Arc<Self>> {
|
|
EVENT_STATE
|
|
.get_or_try_init(|| async {
|
|
Ok(Arc::new(Self {
|
|
loading_bars: DashMap::new(),
|
|
}))
|
|
})
|
|
.await
|
|
.cloned()
|
|
}
|
|
|
|
pub fn get() -> crate::Result<Arc<Self>> {
|
|
Ok(EVENT_STATE.get().ok_or(EventError::NotInitialized)?.clone())
|
|
}
|
|
|
|
// 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()?;
|
|
Ok(value.loading_bars.clone())
|
|
}
|
|
|
|
#[cfg(feature = "tauri")]
|
|
pub async fn get_main_window() -> crate::Result<Option<tauri::WebviewWindow>>
|
|
{
|
|
use tauri::Manager;
|
|
let value = Self::get()?;
|
|
Ok(value.app.get_webview_window("main"))
|
|
}
|
|
}
|
|
|
|
#[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
|
|
pub loading_bar_uuid: Uuid,
|
|
pub message: String,
|
|
pub total: f64,
|
|
pub current: f64,
|
|
#[serde(skip)]
|
|
pub last_sent: f64,
|
|
pub bar_type: LoadingBarType,
|
|
#[cfg(feature = "cli")]
|
|
#[serde(skip)]
|
|
pub cli_progress_bar: indicatif::ProgressBar,
|
|
}
|
|
|
|
#[derive(Serialize, Debug, Clone)]
|
|
pub struct LoadingBarId(Uuid);
|
|
|
|
// When Loading bar id is dropped, we should remove it from the hashmap
|
|
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)
|
|
{
|
|
#[cfg(feature = "tauri")]
|
|
{
|
|
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();
|
|
}
|
|
}
|
|
|
|
#[cfg(not(any(feature = "tauri", feature = "cli")))]
|
|
event_state.loading_bars.remove(&loader_uuid);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Clone, Debug, Hash, PartialEq, Eq)]
|
|
#[serde(tag = "type")]
|
|
#[serde(rename_all = "snake_case")]
|
|
pub enum LoadingBarType {
|
|
LegacyDataMigration,
|
|
DirectoryMove {
|
|
old: PathBuf,
|
|
new: PathBuf,
|
|
},
|
|
JavaDownload {
|
|
version: u32,
|
|
},
|
|
PackFileDownload {
|
|
instance_id: String,
|
|
pack_name: String,
|
|
icon: Option<String>,
|
|
pack_version: String,
|
|
},
|
|
PackDownload {
|
|
instance_id: String,
|
|
pack_name: String,
|
|
icon: Option<PathBuf>,
|
|
pack_id: Option<String>,
|
|
pack_version: Option<String>,
|
|
},
|
|
MinecraftDownload {
|
|
instance_id: String,
|
|
instance_name: String,
|
|
},
|
|
InstanceUpdate {
|
|
instance_id: String,
|
|
instance_name: String,
|
|
},
|
|
ZipExtract {
|
|
instance_id: String,
|
|
instance_name: String,
|
|
},
|
|
ConfigChange {
|
|
new_path: PathBuf,
|
|
},
|
|
CopyInstance {
|
|
import_location: PathBuf,
|
|
instance_name: String,
|
|
},
|
|
LauncherUpdate {
|
|
version: String,
|
|
current_version: String,
|
|
},
|
|
}
|
|
|
|
#[derive(Serialize, Clone)]
|
|
#[cfg(feature = "tauri")]
|
|
pub struct LoadingPayload {
|
|
pub event: LoadingBarType,
|
|
pub loader_uuid: Uuid,
|
|
pub fraction: Option<f64>, // by convention, if optional, it means the loading is done
|
|
pub message: String,
|
|
}
|
|
|
|
#[derive(Serialize, Clone)]
|
|
#[cfg(feature = "tauri")]
|
|
pub struct WarningPayload {
|
|
pub message: String,
|
|
}
|
|
|
|
#[derive(Serialize, Clone)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct InstanceBulkUpdateProgressPayload {
|
|
pub instance_id: String,
|
|
pub stage: InstanceBulkUpdateProgressStage,
|
|
pub current: usize,
|
|
pub total: usize,
|
|
}
|
|
|
|
#[derive(Serialize, Clone)]
|
|
#[serde(rename_all = "snake_case")]
|
|
pub enum InstanceBulkUpdateProgressStage {
|
|
ResolvingVersions,
|
|
Downloading,
|
|
Finishing,
|
|
}
|
|
|
|
#[derive(Serialize, Clone)]
|
|
#[serde(tag = "event")]
|
|
pub enum CommandPayload {
|
|
InstallMod {
|
|
id: String,
|
|
},
|
|
InstallVersion {
|
|
id: String,
|
|
},
|
|
InstallModpack {
|
|
id: String,
|
|
},
|
|
InstallServer {
|
|
id: String,
|
|
},
|
|
LaunchInstance {
|
|
id: String,
|
|
server: Option<String>,
|
|
singleplayer_world: Option<String>,
|
|
},
|
|
RunMRPack {
|
|
// run or install .mrpack
|
|
path: PathBuf,
|
|
},
|
|
}
|
|
|
|
#[derive(Serialize, Clone)]
|
|
#[cfg(feature = "tauri")]
|
|
pub struct ProcessPayload {
|
|
pub instance_id: String,
|
|
pub uuid: Uuid,
|
|
pub event: ProcessPayloadType,
|
|
pub message: String,
|
|
}
|
|
|
|
#[derive(Serialize, Clone, Debug)]
|
|
#[serde(rename_all = "snake_case")]
|
|
pub enum ProcessPayloadType {
|
|
Launched,
|
|
Finished,
|
|
}
|
|
|
|
#[derive(Serialize, Clone)]
|
|
#[cfg(feature = "tauri")]
|
|
pub struct InstancePayload {
|
|
pub instance_id: String,
|
|
#[serde(flatten)]
|
|
pub event: InstancePayloadType,
|
|
}
|
|
|
|
#[derive(Serialize, Clone)]
|
|
#[serde(tag = "event", rename_all = "snake_case")]
|
|
pub enum InstancePayloadType {
|
|
Created,
|
|
Synced,
|
|
ServersUpdated,
|
|
WorldUpdated {
|
|
world: String,
|
|
},
|
|
ServerJoined {
|
|
host: String,
|
|
port: u16,
|
|
timestamp: DateTime<Utc>,
|
|
},
|
|
Edited,
|
|
ContentInstallFinished {
|
|
project_ids: Vec<String>,
|
|
},
|
|
ContentInstallFailed {
|
|
project_ids: Vec<String>,
|
|
message: String,
|
|
},
|
|
Removed,
|
|
}
|
|
|
|
#[derive(Serialize, Clone)]
|
|
#[serde(rename_all = "snake_case")]
|
|
#[serde(tag = "event")]
|
|
pub enum FriendPayload {
|
|
FriendRequest { from: UserId },
|
|
UserOffline { id: UserId },
|
|
StatusUpdate { user_status: UserStatus },
|
|
StatusSync,
|
|
}
|
|
|
|
#[cfg(feature = "tauri")]
|
|
pub use self::log_types::*;
|
|
|
|
#[cfg(feature = "tauri")]
|
|
mod log_types {
|
|
use crate::state::Log4jEvent;
|
|
use serde::Serialize;
|
|
|
|
#[derive(Serialize, Clone)]
|
|
#[serde(tag = "type", rename_all = "snake_case")]
|
|
pub enum LogEvent {
|
|
Log4j(Log4jEvent),
|
|
Legacy { message: String },
|
|
}
|
|
|
|
#[derive(Serialize, Clone)]
|
|
pub struct LogPayload {
|
|
pub instance_id: String,
|
|
#[serde(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("Tauri error: {0}")]
|
|
TauriError(#[from] tauri::Error),
|
|
}
|