diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs index cff229470b..6239cda5a5 100644 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -3436,12 +3436,16 @@ impl Hud { .clamped(1.25, max_zoom / 64.0); global_state.settings.interface.map_zoom = new_zoom_lvl; - global_state.settings.save_to_file_warn(); + global_state + .settings + .save_to_file_warn(&global_state.config_dir); } else if global_state.settings.interface.minimap_show { let new_zoom_lvl = global_state.settings.interface.minimap_zoom * factor; global_state.settings.interface.minimap_zoom = new_zoom_lvl; - global_state.settings.save_to_file_warn(); + global_state + .settings + .save_to_file_warn(&global_state.config_dir); } show.map && global_state.settings.interface.minimap_show diff --git a/voxygen/src/lib.rs b/voxygen/src/lib.rs index 394217ad32..8f1abe6a07 100644 --- a/voxygen/src/lib.rs +++ b/voxygen/src/lib.rs @@ -50,9 +50,12 @@ use crate::{ use common::clock::Clock; use common_base::span; use i18n::LocalizationHandle; +use std::path::PathBuf; /// A type used to store state that is shared between all play states. pub struct GlobalState { + pub userdata_dir: PathBuf, + pub config_dir: PathBuf, pub settings: Settings, pub profile: Profile, pub window: Window, diff --git a/voxygen/src/main.rs b/voxygen/src/main.rs index 75f20a2ef7..627ea0a293 100644 --- a/voxygen/src/main.rs +++ b/voxygen/src/main.rs @@ -17,33 +17,54 @@ use veloren_voxygen::{ #[cfg(feature = "hot-reloading")] use common::assets; use common::clock::Clock; -use std::panic; +use std::{panic, path::PathBuf}; use tracing::{error, info, warn}; #[cfg(feature = "egui-ui")] use veloren_voxygen::ui::egui::EguiState; #[allow(clippy::manual_unwrap_or)] fn main() { + let userdata_dir = common_base::userdata_dir_workspace!(); + + // Determine where Voxygen's logs should go + // Choose a path to store the logs by the following order: + // - The VOXYGEN_LOGS environment variable + // - The /voxygen/logs + let logs_dir = std::env::var_os("VOXYGEN_LOGS") + .map(PathBuf::from) + .unwrap_or_else(|| userdata_dir.join("voxygen").join("logs")); + + // Init logging and hold the guards. + const LOG_FILENAME: &str = "voxygen.log"; + let _guards = common_frontend::init_stdout(Some((&logs_dir, LOG_FILENAME))); + + info!("Using userdata dir at: {}", userdata_dir.display()); + + // Determine Voxygen's config directory either by env var or placed in veloren's + // userdata folder + let config_dir = std::env::var_os("VOXYGEN_CONFIG") + .map(PathBuf::from) + .and_then(|path| { + if path.exists() { + Some(path) + } else { + warn!(?path, "VOXYGEN_CONFIG points to invalid path."); + None + } + }) + .unwrap_or_else(|| userdata_dir.join("voxygen")); + info!("Using config dir at: {}", config_dir.display()); + // Load the settings // Note: This won't log anything due to it being called before // `logging::init`. The issue is we need to read a setting to decide // whether we create a log file or not. - let mut settings = Settings::load(); + let mut settings = Settings::load(&config_dir); // Save settings to add new fields or create the file if it is not already there - if let Err(err) = settings.save_to_file() { + if let Err(err) = settings.save_to_file(&config_dir) { panic!("Failed to save settings: {:?}", err); } - // Init logging and hold the guards. - const LOG_FILENAME: &str = "voxygen.log"; - let _guards = common_frontend::init_stdout(Some((&settings.log.logs_path, LOG_FILENAME))); - - if let Some(path) = veloren_voxygen::settings::voxygen_data_dir().parent() { - info!("Using userdata dir at: {}", path.display()); - } else { - error!("Can't log userdata dir, voxygen data dir has no parent!"); - } - // Set up panic handler to relay swish panic messages to the user let default_hook = panic::take_hook(); panic::set_hook(Box::new(move |panic_info| { @@ -92,9 +113,7 @@ fn main() { Panic Payload: {:?}\n\ PanicInfo: {}\n\ Game version: {} [{}]", - Settings::load() - .log - .logs_path + logs_dir .join("voxygen-.log") .display(), reason, @@ -171,7 +190,7 @@ fn main() { audio.set_sfx_volume(settings.audio.sfx_volume); // Load the profile. - let profile = Profile::load(); + let profile = Profile::load(&config_dir); let mut i18n = LocalizationHandle::load(&settings.language.selected_language).unwrap_or_else(|error| { @@ -198,6 +217,8 @@ fn main() { let egui_state = EguiState::new(&window); let global_state = GlobalState { + userdata_dir, + config_dir, audio, profile, window, diff --git a/voxygen/src/menu/char_selection/mod.rs b/voxygen/src/menu/char_selection/mod.rs index 551c7ac90a..e2c67b6c42 100644 --- a/voxygen/src/menu/char_selection/mod.rs +++ b/voxygen/src/menu/char_selection/mod.rs @@ -139,7 +139,9 @@ impl PlayState for CharSelectionState { global_state .profile .set_selected_character(server_name, selected); - global_state.profile.save_to_file_warn(); + global_state + .profile + .save_to_file_warn(&global_state.config_dir); }, } } @@ -185,7 +187,9 @@ impl PlayState for CharSelectionState { match event { client::Event::SetViewDistance(vd) => { global_state.settings.graphics.view_distance = vd; - global_state.settings.save_to_file_warn(); + global_state + .settings + .save_to_file_warn(&global_state.config_dir); }, client::Event::Disconnect => { global_state.info_message = Some( diff --git a/voxygen/src/menu/main/mod.rs b/voxygen/src/menu/main/mod.rs index 6d6ee3b44f..06457e5069 100644 --- a/voxygen/src/menu/main/mod.rs +++ b/voxygen/src/menu/main/mod.rs @@ -175,7 +175,9 @@ impl PlayState for MainMenuState { match event { client::Event::SetViewDistance(vd) => { global_state.settings.graphics.view_distance = vd; - global_state.settings.save_to_file_warn(); + global_state + .settings + .save_to_file_warn(&global_state.config_dir); }, client::Event::Disconnect => { global_state.info_message = Some( @@ -238,7 +240,9 @@ impl PlayState for MainMenuState { if !net_settings.servers.contains(&server_address) { net_settings.servers.push(server_address.clone()); } - global_state.settings.save_to_file_warn(); + global_state + .settings + .save_to_file_warn(&global_state.config_dir); let connection_args = if use_quic { ConnectionArgs::Quic { @@ -302,7 +306,9 @@ impl PlayState for MainMenuState { .networking .trusted_auth_servers .insert(auth_server.clone()); - global_state.settings.save_to_file_warn(); + global_state + .settings + .save_to_file_warn(&global_state.config_dir); } self.init .client() diff --git a/voxygen/src/meta.rs b/voxygen/src/meta.rs deleted file mode 100644 index 4794e6725d..0000000000 --- a/voxygen/src/meta.rs +++ /dev/null @@ -1,100 +0,0 @@ -use common::comp; -use directories::ProjectDirs; -use serde::{Deserialize, Serialize}; -use std::{fs, io::Write, path::PathBuf}; -use tracing::warn; - -const VALID_VERSION: u32 = 0; // Change this if you broke charsaves -#[derive(Clone, Debug, Serialize, Deserialize)] -#[repr(C)] -pub struct CharacterData { - pub name: String, - pub body: comp::Body, - pub tool: Option, -} - -#[derive(Clone, Debug, Default, Serialize, Deserialize)] -//#[serde(default)] -#[repr(C)] -pub struct Meta { - pub characters: Vec, - pub selected_character: usize, - pub version: u32, -} - -impl Meta { - pub fn delete_character(&mut self, index: usize) { - self.characters.remove(index); - if index < self.selected_character { - self.selected_character -= 1; - } - } - - pub fn add_character(&mut self, data: CharacterData) -> usize { - self.characters.push(data); - // return new character's index - self.characters.len() - 1 - } - - pub fn load() -> Self { - let path = Self::get_meta_path(); - - if let Ok(file) = fs::File::open(&path) { - match ron::de::from_reader::<_, Meta>(file) { - Ok(s) => { - if s.version == VALID_VERSION { - return s; - } - }, - Err(e) => { - warn!(?e, ?file, "Failed to parse meta file! Fallback to default"); - // Rename the corrupted settings file - let mut new_path = path.to_owned(); - new_path.pop(); - new_path.push("meta.invalid.ron"); - if let Err(e) = std::fs::rename(path.clone(), new_path.clone()) { - warn!(?e, ?path, ?new_path, "Failed to rename meta file"); - } - }, - } - } - // This is reached if either: - // - The file can't be opened (presumably it doesn't exist) - // - Or there was an error parsing the file - let default = Self::default(); - default.save_to_file_warn(); - default - } - - pub fn save_to_file_warn(&self) { - if let Err(err) = self.save_to_file() { - warn!(?e, "Failed to save settings"); - } - } - - pub fn save_to_file(&self) -> std::io::Result<()> { - let path = Self::get_meta_path(); - if let Some(dir) = path.parent() { - fs::create_dir_all(dir)?; - } - let mut meta_file = fs::File::create(path)?; - - let s: &str = &ron::ser::to_string_pretty(self, ron::ser::PrettyConfig::default()).unwrap(); - meta_file.write_all(s.as_bytes()).unwrap(); - Ok(()) - } - - pub fn get_meta_path() -> PathBuf { - if let Some(path) = std::env::var_os("VOXYGEN_CONFIG") { - let meta = PathBuf::from(path).join("meta.ron"); - if meta.exists() || meta.parent().map(|x| x.exists()).unwrap_or(false) { - return meta; - } - warn!(?path, "VOXYGEN_CONFIG points to invalid path."); - } - - let proj_dirs = ProjectDirs::from("net", "veloren", "voxygen") - .expect("System's $HOME directory path not found!"); - proj_dirs.config_dir().join("meta").with_extension("ron") - } -} diff --git a/voxygen/src/profile.rs b/voxygen/src/profile.rs index 81ea9b96af..6c94695616 100644 --- a/voxygen/src/profile.rs +++ b/voxygen/src/profile.rs @@ -1,8 +1,12 @@ -use crate::{hud, settings}; +use crate::hud; use common::{character::CharacterId, comp::slot::InvSlotId}; use hashbrown::HashMap; use serde::{Deserialize, Serialize}; -use std::{fs, io::Write, path::PathBuf}; +use std::{ + fs, + io::Write, + path::{Path, PathBuf}, +}; use tracing::warn; /// Represents a character in the profile. @@ -76,8 +80,8 @@ impl Default for Profile { impl Profile { /// Load the profile.ron file from the standard path or create it. - pub fn load() -> Self { - let path = Profile::get_path(); + pub fn load(config_dir: &Path) -> Self { + let path = Profile::get_path(config_dir); if let Ok(file) = fs::File::open(&path) { match ron::de::from_reader(file) { @@ -100,13 +104,13 @@ impl Profile { // - The file can't be opened (presumably it doesn't exist) // - Or there was an error parsing the file let default_profile = Self::default(); - default_profile.save_to_file_warn(); + default_profile.save_to_file_warn(config_dir); default_profile } /// Save the current profile to disk, warn on failure. - pub fn save_to_file_warn(&self) { - if let Err(e) = self.save_to_file() { + pub fn save_to_file_warn(&self, config_dir: &Path) { + if let Err(e) = self.save_to_file(config_dir) { warn!(?e, "Failed to save profile"); } } @@ -194,8 +198,8 @@ impl Profile { } /// Save the current profile to disk. - fn save_to_file(&self) -> std::io::Result<()> { - let path = Profile::get_path(); + fn save_to_file(&self, config_dir: &Path) -> std::io::Result<()> { + let path = Profile::get_path(config_dir); if let Some(dir) = path.parent() { fs::create_dir_all(dir)?; } @@ -206,19 +210,7 @@ impl Profile { Ok(()) } - fn get_path() -> PathBuf { - if let Some(path) = std::env::var_os("VOXYGEN_CONFIG") { - let profile = PathBuf::from(path.clone()).join("profile.ron"); - if profile.exists() || profile.parent().map(|x| x.exists()).unwrap_or(false) { - return profile; - } - warn!(?path, "VOXYGEN_CONFIG points to invalid path."); - } - - let mut path = settings::voxygen_data_dir(); - path.push("profile.ron"); - path - } + fn get_path(config_dir: &Path) -> PathBuf { config_dir.join("profile.ron") } } #[cfg(test)] diff --git a/voxygen/src/run.rs b/voxygen/src/run.rs index d1d60bca88..ed21b503da 100644 --- a/voxygen/src/run.rs +++ b/voxygen/src/run.rs @@ -83,8 +83,12 @@ pub fn run(mut global_state: GlobalState, event_loop: EventLoop) { }, winit::event::Event::LoopDestroyed => { // Save any unsaved changes to settings and profile - global_state.settings.save_to_file_warn(); - global_state.profile.save_to_file_warn(); + global_state + .settings + .save_to_file_warn(&global_state.config_dir); + global_state + .profile + .save_to_file_warn(&global_state.config_dir); }, _ => {}, } @@ -100,7 +104,7 @@ fn handle_main_events_cleared( // Screenshot / Fullscreen toggle global_state .window - .resolve_deduplicated_events(&mut global_state.settings); + .resolve_deduplicated_events(&mut global_state.settings, &global_state.config_dir); // Run tick here // What's going on here? diff --git a/voxygen/src/session/mod.rs b/voxygen/src/session/mod.rs index 42d122a510..b84f57fe0b 100644 --- a/voxygen/src/session/mod.rs +++ b/voxygen/src/session/mod.rs @@ -259,7 +259,9 @@ impl SessionState { }, client::Event::SetViewDistance(vd) => { global_state.settings.graphics.view_distance = vd; - global_state.settings.save_to_file_warn(); + global_state + .settings + .save_to_file_warn(&global_state.config_dir); }, client::Event::Outcome(outcome) => outcomes.push(outcome), client::Event::CharacterCreated(_) => {}, @@ -1305,7 +1307,9 @@ impl PlayState for SessionState { state.slots, ); - global_state.profile.save_to_file_warn(); + global_state + .profile + .save_to_file_warn(&global_state.config_dir); info!("Event! -> ChangedHotbarState") }, diff --git a/voxygen/src/session/settings_change.rs b/voxygen/src/session/settings_change.rs index 0b62766580..6069ed5642 100644 --- a/voxygen/src/session/settings_change.rs +++ b/voxygen/src/session/settings_change.rs @@ -202,7 +202,6 @@ impl SettingsChange { global_state.audio.set_sfx_volume(audio.sfx_volume); }, } - settings.save_to_file_warn(); }, SettingsChange::Chat(chat_change) => { let chat_tabs = &mut settings.chat.chat_tabs; @@ -242,7 +241,6 @@ impl SettingsChange { settings.chat = ChatSettings::default(); }, } - settings.save_to_file_warn(); }, SettingsChange::Control(control_change) => match control_change { Control::ChangeBinding(game_input) => { @@ -250,7 +248,6 @@ impl SettingsChange { }, Control::ResetKeyBindings => { settings.controls = ControlSettings::default(); - settings.save_to_file_warn(); }, }, SettingsChange::Gamepad(gamepad_change) => match gamepad_change {}, @@ -323,7 +320,6 @@ impl SettingsChange { window.mouse_y_inversion = settings.gameplay.mouse_y_inversion; }, } - settings.save_to_file_warn(); }, SettingsChange::Graphics(graphics_change) => { match graphics_change { @@ -423,7 +419,6 @@ impl SettingsChange { global_state.window.set_size(graphics.window_size.into()); }, } - settings.save_to_file_warn(); }, SettingsChange::Interface(interface_change) => { match interface_change { @@ -534,7 +529,6 @@ impl SettingsChange { .set_scaling_mode(settings.interface.ui_scale); }, } - settings.save_to_file_warn(); }, SettingsChange::Language(language_change) => match language_change { Language::ChangeLanguage(new_language) => { @@ -556,5 +550,6 @@ impl SettingsChange { }, SettingsChange::Networking(networking_change) => match networking_change {}, } + settings.save_to_file_warn(&global_state.config_dir); } } diff --git a/voxygen/src/settings/mod.rs b/voxygen/src/settings/mod.rs index 6790526ff2..39010e4fdd 100644 --- a/voxygen/src/settings/mod.rs +++ b/voxygen/src/settings/mod.rs @@ -1,6 +1,9 @@ use directories_next::UserDirs; use serde::{Deserialize, Serialize}; -use std::{fs, path::PathBuf}; +use std::{ + fs, + path::{Path, PathBuf}, +}; use tracing::warn; pub mod audio; @@ -23,40 +26,6 @@ pub use interface::InterfaceSettings; pub use language::LanguageSettings; pub use networking::NetworkingSettings; -/// `Log` stores whether we should create a log file -#[derive(Clone, Debug, Serialize, Deserialize)] -#[serde(default)] -pub struct Log { - // Whether to create a log file or not. - // Default is to create one. - pub log_to_file: bool, - // The path on which the logs will be stored - pub logs_path: PathBuf, -} - -impl Default for Log { - fn default() -> Self { - // Chooses a path to store the logs by the following order: - // - The VOXYGEN_LOGS environment variable - // - The ProjectsDirs data local directory - // This function is only called if there isn't already an entry in the settings - // file. However, the VOXYGEN_LOGS environment variable always overrides - // the log file path if set. - let logs_path = std::env::var_os("VOXYGEN_LOGS") - .map(PathBuf::from) - .unwrap_or_else(|| { - let mut path = voxygen_data_dir(); - path.push("logs"); - path - }); - - Self { - log_to_file: true, - logs_path, - } - } -} - /// `Settings` contains everything that can be configured in the settings.ron /// file. #[derive(Clone, Debug, Serialize, Deserialize)] @@ -67,7 +36,6 @@ pub struct Settings { pub interface: InterfaceSettings, pub gameplay: GameplaySettings, pub networking: NetworkingSettings, - pub log: Log, pub graphics: GraphicsSettings, pub audio: AudioSettings, pub show_disclaimer: bool, @@ -104,7 +72,6 @@ impl Default for Settings { interface: InterfaceSettings::default(), gameplay: GameplaySettings::default(), networking: NetworkingSettings::default(), - log: Log::default(), graphics: GraphicsSettings::default(), audio: AudioSettings::default(), show_disclaimer: true, @@ -118,25 +85,12 @@ impl Default for Settings { } impl Settings { - pub fn load() -> Self { - let path = Self::get_settings_path(); + pub fn load(config_dir: &Path) -> Self { + let path = Self::get_path(config_dir); if let Ok(file) = fs::File::open(&path) { match ron::de::from_reader::<_, Self>(file) { - Ok(mut s) => { - // Override the logs path if it is explicitly set using the VOXYGEN_LOGS - // environment variable. This is needed to support package managers that enforce - // strict application confinement (e.g. snap). In fact, the veloren snap package - // relies on this environment variable to be respected in - // order to communicate a path where the snap package is - // allowed to write to. - if let Some(logs_path_override) = - std::env::var_os("VOXYGEN_LOGS").map(PathBuf::from) - { - s.log.logs_path = logs_path_override; - } - return s; - }, + Ok(s) => return s, Err(e) => { warn!(?e, "Failed to parse setting file! Fallback to default."); // Rename the corrupted settings file @@ -153,18 +107,18 @@ impl Settings { // - The file can't be opened (presumably it doesn't exist) // - Or there was an error parsing the file let default_settings = Self::default(); - default_settings.save_to_file_warn(); + default_settings.save_to_file_warn(config_dir); default_settings } - pub fn save_to_file_warn(&self) { - if let Err(e) = self.save_to_file() { + pub fn save_to_file_warn(&self, config_dir: &Path) { + if let Err(e) = self.save_to_file(config_dir) { warn!(?e, "Failed to save settings"); } } - pub fn save_to_file(&self) -> std::io::Result<()> { - let path = Self::get_settings_path(); + pub fn save_to_file(&self, config_dir: &Path) -> std::io::Result<()> { + let path = Self::get_path(config_dir); if let Some(dir) = path.parent() { fs::create_dir_all(dir)?; } @@ -173,25 +127,5 @@ impl Settings { fs::write(path, ron.as_bytes()) } - pub fn get_settings_path() -> PathBuf { - if let Some(path) = std::env::var_os("VOXYGEN_CONFIG") { - let settings = PathBuf::from(&path).join("settings.ron"); - if settings.exists() || settings.parent().map(|x| x.exists()).unwrap_or(false) { - return settings; - } - warn!(?path, "VOXYGEN_CONFIG points to invalid path."); - } - - let mut path = voxygen_data_dir(); - path.push("settings.ron"); - path - } -} - -pub fn voxygen_data_dir() -> PathBuf { - // Note: since voxygen is technically a lib we made need to lift this up to - // run.rs - let mut path = common_base::userdata_dir_workspace!(); - path.push("voxygen"); - path + fn get_path(config_dir: &Path) -> PathBuf { config_dir.join("settings.ron") } } diff --git a/voxygen/src/window.rs b/voxygen/src/window.rs index e641f6a80b..a5609fd415 100644 --- a/voxygen/src/window.rs +++ b/voxygen/src/window.rs @@ -703,7 +703,11 @@ impl Window { pub fn renderer_mut(&mut self) -> &mut Renderer { &mut self.renderer } - pub fn resolve_deduplicated_events(&mut self, settings: &mut Settings) { + pub fn resolve_deduplicated_events( + &mut self, + settings: &mut Settings, + config_dir: &std::path::Path, + ) { // Handle screenshots and toggling fullscreen if self.take_screenshot { self.take_screenshot = false; @@ -711,7 +715,7 @@ impl Window { } if self.toggle_fullscreen { self.toggle_fullscreen = false; - self.toggle_fullscreen(settings); + self.toggle_fullscreen(settings, config_dir); } } @@ -1170,7 +1174,7 @@ impl Window { } } - pub fn toggle_fullscreen(&mut self, settings: &mut Settings) { + pub fn toggle_fullscreen(&mut self, settings: &mut Settings, config_dir: &std::path::Path) { let fullscreen = FullScreenSettings { enabled: !self.is_fullscreen(), ..settings.graphics.fullscreen @@ -1178,7 +1182,7 @@ impl Window { self.set_fullscreen_mode(fullscreen); settings.graphics.fullscreen = fullscreen; - settings.save_to_file_warn(); + settings.save_to_file_warn(config_dir); } pub fn is_fullscreen(&self) -> bool { self.fullscreen.enabled }