//! Settings Management use crate::proxy::send::{init_ingress, send_ingress}; use crate::store::{Clusters, Directories, Settings}; use crate::State; use onelauncher_utils::io; use std::path::{Path, PathBuf}; use tokio::sync::RwLock; /// gets a [`Settings`] object state. #[tracing::instrument] pub async fn get() -> crate::Result { let state = State::get().await?; let settings = state.settings.read().await; Ok(settings.clone()) } /// sets a [`Settings`] object state. #[tracing::instrument] pub async fn set(settings: Settings) -> crate::Result<()> { let state = State::get().await?; if settings.config_dir != state.settings.read().await.config_dir { return Err(anyhow::anyhow!("cannot change config directory as a setting").into()); } let (io, fetch) = async { let read = state.settings.read().await; ( settings.max_async_io_operations != read.max_async_io_operations, settings.max_async_fetches != read.max_async_fetches, ) } .await; let discord_rpc = { let read = state.settings.read().await; settings.disable_discord != read.disable_discord }; { *state.settings.write().await = settings; } if discord_rpc { state.discord_rpc.clear(true).await?; } if io { state.reset_io_semaphore().await; } if fetch { state.reset_fetch_semaphore().await; } State::sync().await?; Ok(()) } /// sets the config and caches directory, this can have side effects. pub async fn set_directory(new: PathBuf) -> crate::Result<()> { tracing::trace!("changing config directory to {}", new.display()); if !new.is_dir() { return Err( anyhow::anyhow!("new config directory {} is not a folder!", new.display()).into(), ); } if !is_writable(new.clone()).await? { return Err( anyhow::anyhow!("new config directory {} is not writeable", new.display()).into(), ); } let ingress = init_ingress( crate::IngressType::SyncConfig { new_path: new.clone(), }, 100.0, "syncing and changing configuration directory", ) .await?; tracing::trace!("changing config directory, overriding state write access"); let mut state_write = State::get_and_write().await?; let old = state_write.directories.config_dir.read().await.clone(); tracing::trace!("resetting file watcher after changing config directory"); let fs_watcher = crate::utils::watcher::initialize_watcher().await?; state_write.watcher = RwLock::new(fs_watcher); tracing::trace!("collecting all config files to be transfered"); let mut cfg_entries = io::read_dir(&old).await?; let across_filesystems = is_different_fs(&old, &new); let mut entries = vec![]; let mut cleanable = vec![]; while let Some(e) = cfg_entries .next_entry() .await .map_err(|err| io::IOError::with_path(err, &old))? { let epath = e.path(); if let Some(file_name) = epath.file_name() { if file_name == crate::constants::CLUSTERS_FOLDER || file_name == crate::constants::METADATA_FOLDER { if across_filesystems { entries.extend(crate::package::import::sub(&epath, false).await?); cleanable.push(epath.clone()); } else { entries.push(epath.clone()); } } } } tracing::trace!("transferring configuration files"); let io_semaphore = &state_write.io_semaphore; let nentries = entries.len() as f64; for epath in entries { let relpath = epath .strip_prefix(&old) .map_err(|err| anyhow::anyhow!("failed to strip prefix {err}"))?; let newpath = new.join(relpath); if across_filesystems { crate::utils::http::copy(&epath, &newpath, io_semaphore).await?; } else { io::rename(epath.clone(), newpath.clone()).await?; } send_ingress(&ingress, 80.0 * (1.0 / nentries), None).await?; } tracing::trace!("re-setting configuration settings"); let settings = { let mut settings = state_write.settings.write().await; settings.config_dir = Some(new.clone()); tracing::trace!("updating java paths and keys"); for j in settings.java_versions.keys() { if let Some(java) = settings.java_versions.get_mut(&j) { if let Ok(relpath) = PathBuf::from(java.path.clone()).strip_prefix(&old) { java.path = new.join(relpath).to_string_lossy().to_string(); } } } tracing::trace!("re-syncing settings file"); settings .sync(&state_write.directories.settings_file()) .await?; settings.clone() }; tracing::trace!("re-initializing the config directory"); state_write.directories = Directories::initalize(&settings)?; let cleanable_nentries = cleanable.len(); if cleanable_nentries > 0 { tracing::trace!("deleting old config files"); } for ce in cleanable { io::remove_dir_all(ce).await?; send_ingress(&ingress, 10.0 * (1.0 / cleanable_nentries as f64), None).await?; } tracing::trace!("re-resetting file watching system"); let mut file_watcher = crate::utils::watcher::initialize_watcher().await?; state_write.clusters = RwLock::new(Clusters::initialize(&state_write.directories, &mut file_watcher).await?); state_write.watcher = RwLock::new(file_watcher); send_ingress(&ingress, 10.0, None).await?; tracing::info!( "successfully switched configuration directory to {}", new.display() ); Ok(()) } /// checks if two paths are on different data storage devices fn is_different_fs(first: &Path, second: &Path) -> bool { let first_root = first.components().next(); let second_root = second.components().next(); first_root != second_root } /// checks if a [`PathBuf`] is writable or locked. pub async fn is_writable(path: PathBuf) -> crate::Result { let tmp = path.join(".tmp"); match io::write(tmp.clone(), "test").await { Ok(()) => { io::remove_file(tmp).await?; Ok(true) } Err(e) => { tracing::error!("failed to write to config directory: {}", e); Ok(false) } } }