diff --git a/desktop/assets/texts/en-US/preferences_dialog.ftl b/desktop/assets/texts/en-US/preferences_dialog.ftl index 2bd703053..f6f6f3ff3 100644 --- a/desktop/assets/texts/en-US/preferences_dialog.ftl +++ b/desktop/assets/texts/en-US/preferences_dialog.ftl @@ -13,3 +13,7 @@ language = Language audio-output-device = Audio Output Device audio-output-device-default = System Default + +log-filename-pattern = Log Filename +log-filename-pattern-single-file = Single File (ruffle.log) +log-filename-pattern-with-timestamp = With Timestamp diff --git a/desktop/src/gui/preferences_dialog.rs b/desktop/src/gui/preferences_dialog.rs index ea2859a74..e7cd2983f 100644 --- a/desktop/src/gui/preferences_dialog.rs +++ b/desktop/src/gui/preferences_dialog.rs @@ -1,4 +1,5 @@ use crate::gui::{available_languages, optional_text, text}; +use crate::log::FilenamePattern; use crate::preferences::GlobalPreferences; use cpal::traits::{DeviceTrait, HostTrait}; use egui::{Align2, Button, ComboBox, Grid, Ui, Widget, Window}; @@ -25,6 +26,9 @@ pub struct PreferencesDialog { output_device: Option, available_output_devices: Vec, output_device_changed: bool, + + log_filename_pattern: FilenamePattern, + log_filename_pattern_changed: bool, } impl PreferencesDialog { @@ -63,6 +67,9 @@ impl PreferencesDialog { available_output_devices, output_device_changed: false, + log_filename_pattern: preferences.log_filename_pattern(), + log_filename_pattern_changed: false, + preferences, } } @@ -88,6 +95,8 @@ impl PreferencesDialog { self.show_language_preferences(locale, ui); self.show_audio_preferences(locale, ui); + + self.show_log_preferences(locale, ui); }); if self.restart_required() { @@ -115,6 +124,7 @@ impl PreferencesDialog { self.graphics_backend != self.preferences.graphics_backends() || self.power_preference != self.preferences.graphics_power_preference() || self.output_device != self.preferences.output_device_name() + || self.log_filename_pattern != self.preferences.log_filename_pattern() } fn show_graphics_preferences( @@ -237,6 +247,30 @@ impl PreferencesDialog { ui.end_row(); } + fn show_log_preferences(&mut self, locale: &LanguageIdentifier, ui: &mut Ui) { + ui.label(text(locale, "log-filename-pattern")); + + let previous = self.log_filename_pattern; + ComboBox::from_id_source("log-filename-pattern") + .selected_text(filename_pattern_name(locale, self.log_filename_pattern)) + .show_ui(ui, |ui| { + ui.selectable_value( + &mut self.log_filename_pattern, + FilenamePattern::SingleFile, + filename_pattern_name(locale, FilenamePattern::SingleFile), + ); + ui.selectable_value( + &mut self.log_filename_pattern, + FilenamePattern::WithTimestamp, + filename_pattern_name(locale, FilenamePattern::WithTimestamp), + ); + }); + if self.log_filename_pattern != previous { + self.log_filename_pattern_changed = true; + } + ui.end_row(); + } + fn save(&mut self) { if let Err(e) = self.preferences.write_preferences(|preferences| { if self.graphics_backend_changed { @@ -252,6 +286,9 @@ impl PreferencesDialog { preferences.set_output_device(self.output_device.clone()); // [NA] TODO: Inform the running player that the device changed } + if self.log_filename_pattern_changed { + preferences.set_log_filename_pattern(self.log_filename_pattern); + } }) { // [NA] TODO: Better error handling... everywhere in desktop, really tracing::error!("Could not save preferences: {e}"); @@ -276,6 +313,13 @@ fn graphics_power_name(locale: &LanguageIdentifier, power_preference: PowerPrefe } } +fn filename_pattern_name(locale: &LanguageIdentifier, pattern: FilenamePattern) -> Cow { + match pattern { + FilenamePattern::SingleFile => text(locale, "log-filename-pattern-single-file"), + FilenamePattern::WithTimestamp => text(locale, "log-filename-pattern-with-timestamp"), + } +} + fn backend_availability(descriptors: &Descriptors, backend: wgpu::Backends) -> wgpu::Backends { if descriptors .wgpu_instance diff --git a/desktop/src/log.rs b/desktop/src/log.rs new file mode 100644 index 000000000..a1bed77b2 --- /dev/null +++ b/desktop/src/log.rs @@ -0,0 +1,40 @@ +use chrono::Utc; +use std::path::{Path, PathBuf}; +use std::str::FromStr; + +#[derive(Copy, Clone, Hash, Eq, PartialEq, Debug, Default)] +pub enum FilenamePattern { + #[default] + SingleFile, + WithTimestamp, +} + +impl FromStr for FilenamePattern { + type Err = (); + + fn from_str(s: &str) -> Result { + match s { + "single_file" => Ok(FilenamePattern::SingleFile), + "with_timestamp" => Ok(FilenamePattern::WithTimestamp), + _ => Err(()), + } + } +} + +impl FilenamePattern { + pub fn create_path(&self, directory: &Path) -> PathBuf { + match self { + FilenamePattern::SingleFile => directory.join("ruffle.log"), + FilenamePattern::WithTimestamp => { + directory.join(Utc::now().format("ruffle_%F_%H-%M-%S.log").to_string()) + } + } + } + + pub fn as_str(&self) -> &'static str { + match self { + FilenamePattern::SingleFile => "single_file", + FilenamePattern::WithTimestamp => "with_timestamp", + } + } +} diff --git a/desktop/src/main.rs b/desktop/src/main.rs index 7fcfb4f86..371a75d77 100644 --- a/desktop/src/main.rs +++ b/desktop/src/main.rs @@ -12,6 +12,7 @@ mod cli; mod custom_event; mod executor; mod gui; +mod log; mod player; mod preferences; mod task; @@ -155,7 +156,9 @@ fn main() -> Result<(), Error> { // [NA] `_guard` cannot be `_` or it'll immediately drop // https://docs.rs/tracing-appender/latest/tracing_appender/non_blocking/index.html - let log_path = preferences.cli.config.join("ruffle.log"); + let log_path = preferences + .log_filename_pattern() + .create_path(&preferences.cli.config); let (non_blocking_file, _file_guard) = tracing_appender::non_blocking(File::create(log_path)?); let (non_blocking_stdout, _stdout_guard) = tracing_appender::non_blocking(std::io::stdout()); diff --git a/desktop/src/preferences.rs b/desktop/src/preferences.rs index 82400c786..96a2f2d97 100644 --- a/desktop/src/preferences.rs +++ b/desktop/src/preferences.rs @@ -2,6 +2,7 @@ mod read; mod write; use crate::cli::Opt; +use crate::log::FilenamePattern; use crate::preferences::read::read_preferences; use crate::preferences::write::PreferencesWriter; use anyhow::{Context, Error}; @@ -117,6 +118,15 @@ impl GlobalPreferences { }) } + pub fn log_filename_pattern(&self) -> FilenamePattern { + self.preferences + .lock() + .expect("Preferences is not reentrant") + .values + .log + .filename_pattern + } + pub fn write_preferences(&self, fun: impl FnOnce(&mut PreferencesWriter)) -> Result<(), Error> { let mut preferences = self .preferences @@ -158,6 +168,7 @@ pub struct SavedGlobalPreferences { pub output_device: Option, pub mute: bool, pub volume: f32, + pub log: LogPreferences, } impl Default for SavedGlobalPreferences { @@ -173,6 +184,12 @@ impl Default for SavedGlobalPreferences { output_device: None, mute: false, volume: 1.0, + log: Default::default(), } } } + +#[derive(PartialEq, Debug, Default)] +pub struct LogPreferences { + pub filename_pattern: FilenamePattern, +} diff --git a/desktop/src/preferences/read.rs b/desktop/src/preferences/read.rs index 2a2d75b23..8c6583a75 100644 --- a/desktop/src/preferences/read.rs +++ b/desktop/src/preferences/read.rs @@ -72,6 +72,21 @@ pub fn read_preferences(input: &str) -> (ParseResult, Document) { Err(e) => result.add_warning(format!("Invalid mute: {e}")), }; + if let Some(log_item) = document.get("log") { + if let Some(log) = log_item.as_table_like() { + match parse_item_from_str(log.get("filename_pattern")) { + Ok(Some(value)) => result.result.log.filename_pattern = value, + Ok(None) => {} + Err(e) => result.add_warning(format!("Invalid log.filename_pattern: {e}")), + }; + } else { + result.add_warning(format!( + "Invalid log: expected table but found {}", + log_item.type_name() + )); + } + } + (result, document) } @@ -118,6 +133,8 @@ fn parse_item_from_bool(item: Option<&Item>) -> Result, String> { #[cfg(test)] mod tests { use super::*; + use crate::log::FilenamePattern; + use crate::preferences::LogPreferences; use fluent_templates::loader::langid; use ruffle_render_wgpu::clap::{GraphicsBackend, PowerPreference}; @@ -370,4 +387,63 @@ mod tests { read_preferences("volume = -1.0").0 ); } + + #[test] + fn log_filename() { + assert_eq!( + ParseResult { + result: SavedGlobalPreferences { + log: LogPreferences { + ..Default::default() + }, + ..Default::default() + }, + warnings: vec![ + "Invalid log.filename_pattern: expected string but found integer".to_string() + ] + }, + read_preferences("log = {filename_pattern = 5}").0 + ); + + assert_eq!( + ParseResult { + result: SavedGlobalPreferences { + log: LogPreferences { + ..Default::default() + }, + ..Default::default() + }, + warnings: vec![ + "Invalid log.filename_pattern: unsupported value \"???\"".to_string() + ] + }, + read_preferences("log = {filename_pattern = \"???\"}").0 + ); + + assert_eq!( + ParseResult { + result: SavedGlobalPreferences { + log: LogPreferences { + filename_pattern: FilenamePattern::WithTimestamp, + }, + ..Default::default() + }, + warnings: vec![] + }, + read_preferences("log = {filename_pattern = \"with_timestamp\"}").0 + ); + } + + #[test] + fn log() { + assert_eq!( + ParseResult { + result: SavedGlobalPreferences { + ..Default::default() + }, + warnings: vec!["Invalid log: expected table but found string".to_string()] + }, + read_preferences("log = \"yes\"").0 + ); + } } diff --git a/desktop/src/preferences/write.rs b/desktop/src/preferences/write.rs index 316f3242b..1e304a150 100644 --- a/desktop/src/preferences/write.rs +++ b/desktop/src/preferences/write.rs @@ -1,3 +1,4 @@ +use crate::log::FilenamePattern; use crate::preferences::PreferencesAndDocument; use ruffle_render_wgpu::clap::{GraphicsBackend, PowerPreference}; use toml_edit::value; @@ -43,11 +44,17 @@ impl<'a> PreferencesWriter<'a> { self.0.toml_document["volume"] = value(volume as f64); self.0.values.volume = volume; } + + pub fn set_log_filename_pattern(&mut self, pattern: FilenamePattern) { + self.0.toml_document["log"]["filename_pattern"] = value(pattern.as_str()); + self.0.values.log.filename_pattern = pattern; + } } #[cfg(test)] mod tests { use super::*; + use crate::log::FilenamePattern; use crate::preferences::read::read_preferences; use fluent_templates::loader::langid; @@ -149,4 +156,23 @@ mod tests { "mute = false\n", ); } + + #[test] + fn set_log_filename_pattern() { + test( + "", + |writer| writer.set_log_filename_pattern(FilenamePattern::WithTimestamp), + "log = { filename_pattern = \"with_timestamp\" }\n", + ); + test( + "log = { filename_pattern = \"with_timestamp\" }\n", + |writer| writer.set_log_filename_pattern(FilenamePattern::SingleFile), + "log = { filename_pattern = \"single_file\" }\n", + ); + test( + "[log]\nfilename_pattern = \"with_timestamp\"\n", + |writer| writer.set_log_filename_pattern(FilenamePattern::SingleFile), + "[log]\nfilename_pattern = \"single_file\"\n", + ); + } }