diff --git a/.github/workflows/theseus-build.yml b/.github/workflows/theseus-build.yml index 64ae2b3349..6af86e09b7 100644 --- a/.github/workflows/theseus-build.yml +++ b/.github/workflows/theseus-build.yml @@ -60,6 +60,21 @@ jobs: node-version-file: .nvmrc cache: pnpm + - name: 📄 Generate tauri-dev.conf.json + shell: bash + run: | + GIT_HASH=$(git rev-parse --short HEAD) + cat > apps/app/tauri-dev.conf.json < theseus::Result { async fn main() -> theseus::Result<()> { println!("Starting."); - let _log_guard = theseus::start_logger(); + let _log_guard = theseus::start_logger("ModrinthApp"); // Initialize state - State::init().await?; + State::init("ModrinthApp".to_owned()).await?; let worlds = get_recent_worlds(4, EnumSet::all()).await?; for world in worlds { diff --git a/apps/app/src/api/settings.rs b/apps/app/src/api/settings.rs index 7a184536c7..5b302ef404 100644 --- a/apps/app/src/api/settings.rs +++ b/apps/app/src/api/settings.rs @@ -1,4 +1,5 @@ use crate::api::Result; +use tauri::Runtime; use theseus::prelude::*; pub fn init() -> tauri::plugin::TauriPlugin { @@ -28,7 +29,9 @@ pub async fn settings_set(settings: Settings) -> Result<()> { } #[tauri::command] -pub async fn cancel_directory_change() -> Result<()> { - settings::cancel_directory_change().await?; +pub async fn cancel_directory_change( + handle: tauri::AppHandle, +) -> Result<()> { + settings::cancel_directory_change(&handle.config().identifier).await?; Ok(()) } diff --git a/apps/app/src/api/utils.rs b/apps/app/src/api/utils.rs index e96186f5bc..05ccb5b479 100644 --- a/apps/app/src/api/utils.rs +++ b/apps/app/src/api/utils.rs @@ -102,10 +102,12 @@ pub fn open_path(app: tauri::AppHandle, path: PathBuf) { #[tauri::command] pub fn show_launcher_logs_folder(app: tauri::AppHandle) { - let path = DirectoryInfo::launcher_logs_dir().unwrap_or_default(); - // failure to get folder just opens filesystem - // (ie: if in debug mode only and launcher_logs never created) - open_path(app, path); + if let Some(d) = DirectoryInfo::global_handle_if_ready() { + let path = d.launcher_logs_dir().unwrap_or_default(); + // failure to get folder just opens filesystem + // (ie: if in debug mode only and launcher_logs never created) + open_path(app, path) + } } // Get opening command diff --git a/apps/app/src/main.rs b/apps/app/src/main.rs index 504dccc43c..d2017e39cd 100644 --- a/apps/app/src/main.rs +++ b/apps/app/src/main.rs @@ -26,9 +26,77 @@ async fn initialize_state(app: tauri::AppHandle) -> api::Result<()> { tracing::info!("Initializing app event state..."); theseus::EventState::init(app.clone()).await?; - tracing::info!("Initializing app state..."); - State::init().await?; + let app_identifier = app.config().identifier.clone(); + #[cfg(feature = "updater")] + 'updater: { + if env::var("MODRINTH_EXTERNAL_UPDATE_PROVIDER").is_ok() { + State::init(app_identifier).await?; + break 'updater; + } + + use tauri_plugin_updater::UpdaterExt; + + let updater = app.updater_builder().build()?; + + let update_fut = updater.check(); + + tracing::info!("Initializing app state..."); + State::init(app_identifier).await?; + + let check_bar = theseus::init_loading( + theseus::LoadingBarType::CheckingForUpdates, + 1.0, + "Checking for updates...", + ) + .await?; + + tracing::info!("Checking for updates..."); + let update = update_fut.await; + + drop(check_bar); + + if let Some(update) = update.ok().flatten() { + tracing::info!("Update found: {:?}", update.download_url); + let loader_bar_id = theseus::init_loading( + theseus::LoadingBarType::LauncherUpdate { + version: update.version.clone(), + current_version: update.current_version.clone(), + }, + 1.0, + "Updating Modrinth App...", + ) + .await?; + + // 100 MiB + const DEFAULT_CONTENT_LENGTH: u64 = 1024 * 1024 * 100; + + update + .download_and_install( + |chunk_length, content_length| { + let _ = theseus::emit_loading( + &loader_bar_id, + (chunk_length as f64) + / (content_length + .unwrap_or(DEFAULT_CONTENT_LENGTH) + as f64), + None, + ); + }, + || {}, + ) + .await?; + + app.restart(); + } + } + + #[cfg(not(feature = "updater"))] + { + State::init(app_identifier).await?; + } + + tracing::info!("Finished checking for updates!"); let state = State::get().await?; app.asset_protocol_scope() .allow_directory(state.directories.caches_dir(), true)?; @@ -109,7 +177,10 @@ fn main() { RUST_LOG="theseus=trace" {run command} */ - let _log_guard = theseus::start_logger(); + + let tauri_context = tauri::generate_context!(); + + let _log_guard = theseus::start_logger(&tauri_context.config().identifier); tracing::info!("Initialized tracing subscriber. Loading Modrinth App!"); @@ -241,7 +312,7 @@ fn main() { ]); tracing::info!("Initializing app..."); - let app = builder.build(tauri::generate_context!()); + let app = builder.build(tauri_context); match app { Ok(app) => { diff --git a/packages/app-lib/src/api/settings.rs b/packages/app-lib/src/api/settings.rs index 7619596832..b79e2e524c 100644 --- a/packages/app-lib/src/api/settings.rs +++ b/packages/app-lib/src/api/settings.rs @@ -23,10 +23,12 @@ pub async fn set(settings: Settings) -> crate::Result<()> { } #[tracing::instrument] -pub async fn cancel_directory_change() -> crate::Result<()> { +pub async fn cancel_directory_change( + app_identifier: &str, +) -> crate::Result<()> { // This is called to handle state initialization errors due to folder migrations // failing, so fetching a DB connection pool from `State::get` is not reliable here - let pool = crate::state::db::connect().await?; + let pool = crate::state::db::connect(app_identifier).await?; let mut settings = Settings::get(&pool).await?; if let Some(prev_custom_dir) = settings.prev_custom_dir { diff --git a/packages/app-lib/src/logger.rs b/packages/app-lib/src/logger.rs index 5efcdc0983..fa3d6c6d7e 100644 --- a/packages/app-lib/src/logger.rs +++ b/packages/app-lib/src/logger.rs @@ -18,7 +18,7 @@ // Handling for the live development logging // This will log to the console, and will not log to a file #[cfg(debug_assertions)] -pub fn start_logger() -> Option<()> { +pub fn start_logger(_app_identifier: &str) -> Option<()> { use tracing_subscriber::prelude::*; let filter = tracing_subscriber::EnvFilter::try_from_default_env() @@ -36,7 +36,7 @@ pub fn start_logger() -> Option<()> { // Handling for the live production logging // This will log to a file in the logs directory, and will not show any logs in the console #[cfg(not(debug_assertions))] -pub fn start_logger() -> Option<()> { +pub fn start_logger(app_identifier: &str) -> Option<()> { use crate::prelude::DirectoryInfo; use chrono::Local; use std::fs::OpenOptions; @@ -44,7 +44,9 @@ pub fn start_logger() -> Option<()> { use tracing_subscriber::prelude::*; // Initialize and get logs directory path - let logs_dir = if let Some(d) = DirectoryInfo::launcher_logs_dir() { + let logs_dir = if let Some(d) = + DirectoryInfo::launcher_logs_dir_path(app_identifier) + { d } else { eprintln!("Could not start logger"); diff --git a/packages/app-lib/src/state/db.rs b/packages/app-lib/src/state/db.rs index de5464c4cd..b2a1805602 100644 --- a/packages/app-lib/src/state/db.rs +++ b/packages/app-lib/src/state/db.rs @@ -6,12 +6,13 @@ use sqlx::{Pool, Sqlite}; use std::str::FromStr; use std::time::Duration; -pub(crate) async fn connect() -> crate::Result> { - let settings_dir = DirectoryInfo::get_initial_settings_dir().ok_or( - crate::ErrorKind::FSError( +pub(crate) async fn connect( + app_identifier: &str, +) -> crate::Result> { + let settings_dir = DirectoryInfo::initial_settings_dir_path(app_identifier) + .ok_or(crate::ErrorKind::FSError( "Could not find valid config dir".to_string(), - ), - )?; + ))?; if !settings_dir.exists() { crate::util::io::create_dir_all(&settings_dir).await?; diff --git a/packages/app-lib/src/state/dirs.rs b/packages/app-lib/src/state/dirs.rs index 1f577f7c45..b098dbcac0 100644 --- a/packages/app-lib/src/state/dirs.rs +++ b/packages/app-lib/src/state/dirs.rs @@ -1,6 +1,7 @@ //! Theseus directory information use crate::LoadingBarType; use crate::event::emit::{emit_loading, init_loading}; +use crate::state::LAUNCHER_STATE; use crate::state::{JavaVersion, Profile, Settings}; use crate::util::fetch::IoSemaphore; use dashmap::DashSet; @@ -17,24 +18,35 @@ pub const METADATA_FOLDER_NAME: &str = "meta"; pub struct DirectoryInfo { pub settings_dir: PathBuf, // Base settings directory- app database pub config_dir: PathBuf, // Base config directory- instances, minecraft downloads, etc. Changeable as a setting. + pub app_identifier: String, } impl DirectoryInfo { + pub fn global_handle_if_ready() -> Option<&'static Self> { + LAUNCHER_STATE.get().map(|x| &x.directories) + } + + pub fn get_initial_settings_dir(&self) -> Option { + Self::initial_settings_dir_path(&self.app_identifier) + } + // Get the settings directory // init() is not needed for this function - pub fn get_initial_settings_dir() -> Option { + pub fn initial_settings_dir_path(app_identifier: &str) -> Option { Self::env_path("THESEUS_CONFIG_DIR") - .or_else(|| Some(dirs::data_dir()?.join("ModrinthApp"))) + .or_else(|| Some(dirs::data_dir()?.join(app_identifier))) } /// Get all paths needed for Theseus to operate properly #[tracing::instrument] - pub async fn init(config_dir: Option) -> crate::Result { - let settings_dir = Self::get_initial_settings_dir().ok_or( - crate::ErrorKind::FSError( + pub async fn init( + config_dir: Option, + app_identifier: &str, + ) -> crate::Result { + let settings_dir = Self::initial_settings_dir_path(app_identifier) + .ok_or(crate::ErrorKind::FSError( "Could not find valid settings dir".to_string(), - ), - )?; + ))?; fs::create_dir_all(&settings_dir).await.map_err(|err| { crate::ErrorKind::FSError(format!( @@ -48,6 +60,7 @@ impl DirectoryInfo { Ok(Self { settings_dir, config_dir, + app_identifier: app_identifier.to_owned(), }) } @@ -154,8 +167,14 @@ impl DirectoryInfo { } #[inline] - pub fn launcher_logs_dir() -> Option { - Self::get_initial_settings_dir() + pub fn launcher_logs_dir(&self) -> Option { + self.get_initial_settings_dir() + .map(|d| d.join(LAUNCHER_LOGS_FOLDER_NAME)) + } + + #[inline] + pub fn launcher_logs_dir_path(app_identifier: &str) -> Option { + Self::initial_settings_dir_path(app_identifier) .map(|d| d.join(LAUNCHER_LOGS_FOLDER_NAME)) } @@ -176,15 +195,15 @@ impl DirectoryInfo { settings: &mut Settings, exec: E, io_semaphore: &IoSemaphore, + app_identifier: &str, ) -> crate::Result<()> where E: sqlx::Executor<'a, Database = sqlx::Sqlite> + Copy, { - let app_dir = DirectoryInfo::get_initial_settings_dir().ok_or( - crate::ErrorKind::FSError( + let app_dir = DirectoryInfo::initial_settings_dir_path(app_identifier) + .ok_or(crate::ErrorKind::FSError( "Could not find valid config dir".to_string(), - ), - )?; + ))?; if let Some(ref prev_custom_dir) = settings.prev_custom_dir { let prev_dir = PathBuf::from(prev_custom_dir); diff --git a/packages/app-lib/src/state/mod.rs b/packages/app-lib/src/state/mod.rs index ab7a5e3e94..542d33d59a 100644 --- a/packages/app-lib/src/state/mod.rs +++ b/packages/app-lib/src/state/mod.rs @@ -80,9 +80,9 @@ pub struct State { } impl State { - pub async fn init() -> crate::Result<()> { + pub async fn init(app_identifier: String) -> crate::Result<()> { let state = LAUNCHER_STATE - .get_or_try_init(Self::initialize_state) + .get_or_try_init(move || Self::initialize_state(app_identifier)) .await?; tokio::task::spawn(async move { @@ -131,9 +131,11 @@ impl State { } #[tracing::instrument] - async fn initialize_state() -> crate::Result> { + async fn initialize_state( + app_identifier: String, + ) -> crate::Result> { tracing::info!("Connecting to app database"); - let pool = db::connect().await?; + let pool = db::connect(&app_identifier).await?; legacy_converter::migrate_legacy_data(&pool).await?; @@ -152,9 +154,12 @@ impl State { &mut settings, &pool, &io_semaphore, + &app_identifier, ) .await?; - let directories = DirectoryInfo::init(settings.custom_dir).await?; + + let directories = + DirectoryInfo::init(settings.custom_dir, &app_identifier).await?; let discord_rpc = DiscordGuard::init()?;