desktop: Add storage backend preference

This commit is contained in:
sleepycatcoding 2024-03-26 20:27:50 +02:00 committed by Nathan Adams
parent 9cab16eb68
commit 92f437ec15
10 changed files with 226 additions and 7 deletions

View File

@ -2268,8 +2268,8 @@ impl PlayerBuilder {
/// Sets the storage backend of the player. /// Sets the storage backend of the player.
#[inline] #[inline]
pub fn with_storage(mut self, storage: impl 'static + StorageBackend) -> Self { pub fn with_storage(mut self, storage: Box<dyn StorageBackend>) -> Self {
self.storage = Some(Box::new(storage)); self.storage = Some(storage);
self self
} }

View File

@ -17,3 +17,7 @@ audio-output-device-default = System Default
log-filename-pattern = Log Filename log-filename-pattern = Log Filename
log-filename-pattern-single-file = Single File (ruffle.log) log-filename-pattern-single-file = Single File (ruffle.log)
log-filename-pattern-with-timestamp = With Timestamp log-filename-pattern-with-timestamp = With Timestamp
storage-backend = Save-File Location
storage-backend-disk = Disk
storage-backend-memory = Memory

View File

@ -1,3 +1,4 @@
use crate::preferences::storage::StorageBackend;
use crate::RUFFLE_VERSION; use crate::RUFFLE_VERSION;
use anyhow::{anyhow, Error}; use anyhow::{anyhow, Error};
use clap::{Parser, ValueEnum}; use clap::{Parser, ValueEnum};
@ -54,6 +55,12 @@ pub struct Opt {
#[clap(long, short)] #[clap(long, short)]
pub power: Option<PowerPreference>, pub power: Option<PowerPreference>,
/// Type of storage backend to use. This determines where local storage data is saved (e.g. shared objects).
///
/// This option temporarily overrides any stored preference.
#[clap(long)]
pub storage: Option<StorageBackend>,
/// Width of window in pixels. /// Width of window in pixels.
#[clap(long, display_order = 1)] #[clap(long, display_order = 1)]
pub width: Option<f64>, pub width: Option<f64>,
@ -101,6 +108,8 @@ pub struct Opt {
trace_path: Option<std::path::PathBuf>, trace_path: Option<std::path::PathBuf>,
/// Location to store save data for games. /// Location to store save data for games.
///
/// This option has no effect if `storage` is not `disk`.
#[clap(long, default_value_os_t=get_default_save_directory())] #[clap(long, default_value_os_t=get_default_save_directory())]
pub save_directory: std::path::PathBuf, pub save_directory: std::path::PathBuf,

View File

@ -1,6 +1,6 @@
use crate::gui::{available_languages, optional_text, text}; use crate::gui::{available_languages, optional_text, text};
use crate::log::FilenamePattern; use crate::log::FilenamePattern;
use crate::preferences::GlobalPreferences; use crate::preferences::{storage::StorageBackend, GlobalPreferences};
use cpal::traits::{DeviceTrait, HostTrait}; use cpal::traits::{DeviceTrait, HostTrait};
use egui::{Align2, Button, ComboBox, Grid, Ui, Widget, Window}; use egui::{Align2, Button, ComboBox, Grid, Ui, Widget, Window};
use ruffle_render_wgpu::clap::{GraphicsBackend, PowerPreference}; use ruffle_render_wgpu::clap::{GraphicsBackend, PowerPreference};
@ -28,6 +28,10 @@ pub struct PreferencesDialog {
log_filename_pattern: FilenamePattern, log_filename_pattern: FilenamePattern,
log_filename_pattern_changed: bool, log_filename_pattern_changed: bool,
storage_backend: StorageBackend,
storage_backend_readonly: bool,
storage_backend_changed: bool,
} }
impl PreferencesDialog { impl PreferencesDialog {
@ -64,6 +68,10 @@ impl PreferencesDialog {
log_filename_pattern: preferences.log_filename_pattern(), log_filename_pattern: preferences.log_filename_pattern(),
log_filename_pattern_changed: false, log_filename_pattern_changed: false,
storage_backend: preferences.storage_backend(),
storage_backend_readonly: preferences.cli.storage.is_some(),
storage_backend_changed: false,
preferences, preferences,
} }
} }
@ -91,6 +99,8 @@ impl PreferencesDialog {
self.show_audio_preferences(locale, ui); self.show_audio_preferences(locale, ui);
self.show_log_preferences(locale, ui); self.show_log_preferences(locale, ui);
self.show_storage_preferences(locale, &locked_text, ui);
}); });
if self.restart_required() { if self.restart_required() {
@ -119,6 +129,7 @@ impl PreferencesDialog {
|| self.power_preference != self.preferences.graphics_power_preference() || self.power_preference != self.preferences.graphics_power_preference()
|| self.output_device != self.preferences.output_device_name() || self.output_device != self.preferences.output_device_name()
|| self.log_filename_pattern != self.preferences.log_filename_pattern() || self.log_filename_pattern != self.preferences.log_filename_pattern()
|| self.storage_backend != self.preferences.storage_backend()
} }
fn show_graphics_preferences( fn show_graphics_preferences(
@ -266,6 +277,42 @@ impl PreferencesDialog {
ui.end_row(); ui.end_row();
} }
fn show_storage_preferences(
&mut self,
locale: &LanguageIdentifier,
locked_text: &str,
ui: &mut Ui,
) {
ui.label(text(locale, "storage-backend"));
if self.storage_backend_readonly {
ui.label(storage_backend_name(locale, self.storage_backend))
.on_hover_text(locked_text);
} else {
let previous = self.storage_backend;
ComboBox::from_id_source("storage-backend")
.selected_text(storage_backend_name(locale, self.storage_backend))
.show_ui(ui, |ui| {
ui.selectable_value(
&mut self.storage_backend,
StorageBackend::Disk,
storage_backend_name(locale, StorageBackend::Disk),
);
ui.selectable_value(
&mut self.storage_backend,
StorageBackend::Memory,
storage_backend_name(locale, StorageBackend::Memory),
);
});
if self.storage_backend != previous {
self.storage_backend_changed = true;
}
}
ui.end_row();
}
fn save(&mut self) { fn save(&mut self) {
if let Err(e) = self.preferences.write_preferences(|preferences| { if let Err(e) = self.preferences.write_preferences(|preferences| {
if self.graphics_backend_changed { if self.graphics_backend_changed {
@ -284,6 +331,9 @@ impl PreferencesDialog {
if self.log_filename_pattern_changed { if self.log_filename_pattern_changed {
preferences.set_log_filename_pattern(self.log_filename_pattern); preferences.set_log_filename_pattern(self.log_filename_pattern);
} }
if self.storage_backend_changed {
preferences.set_storage_backend(self.storage_backend);
}
}) { }) {
// [NA] TODO: Better error handling... everywhere in desktop, really // [NA] TODO: Better error handling... everywhere in desktop, really
tracing::error!("Could not save preferences: {e}"); tracing::error!("Could not save preferences: {e}");
@ -321,6 +371,13 @@ fn filename_pattern_name(locale: &LanguageIdentifier, pattern: FilenamePattern)
} }
} }
fn storage_backend_name(locale: &LanguageIdentifier, backend: StorageBackend) -> Cow<str> {
match backend {
StorageBackend::Disk => text(locale, "storage-backend-disk"),
StorageBackend::Memory => text(locale, "storage-backend-memory"),
}
}
fn backend_availability(instance: &wgpu::Instance, backend: wgpu::Backends) -> wgpu::Backends { fn backend_availability(instance: &wgpu::Instance, backend: wgpu::Backends) -> wgpu::Backends {
if instance.enumerate_adapters(backend).is_empty() { if instance.enumerate_adapters(backend).is_empty() {
wgpu::Backends::empty() wgpu::Backends::empty()

View File

@ -1,6 +1,6 @@
use crate::backends::{ use crate::backends::{
CpalAudioBackend, DesktopExternalInterfaceProvider, DesktopFSCommandProvider, DesktopUiBackend, CpalAudioBackend, DesktopExternalInterfaceProvider, DesktopFSCommandProvider, DesktopUiBackend,
DiskStorageBackend, ExternalNavigatorBackend, ExternalNavigatorBackend,
}; };
use crate::custom_event::RuffleEvent; use crate::custom_event::RuffleEvent;
use crate::executor::WinitAsyncExecutor; use crate::executor::WinitAsyncExecutor;
@ -159,7 +159,7 @@ impl ActivePlayer {
builder = builder builder = builder
.with_navigator(navigator) .with_navigator(navigator)
.with_renderer(renderer) .with_renderer(renderer)
.with_storage(DiskStorageBackend::new(opt.save_directory.clone())) .with_storage(preferences.storage_backend().create_backend(opt))
.with_fs_commands(Box::new(DesktopFSCommandProvider { .with_fs_commands(Box::new(DesktopFSCommandProvider {
event_loop: event_loop.clone(), event_loop: event_loop.clone(),
window: window.clone(), window: window.clone(),

View File

@ -1,6 +1,8 @@
mod read; mod read;
mod write; mod write;
pub mod storage;
use crate::cli::Opt; use crate::cli::Opt;
use crate::log::FilenamePattern; use crate::log::FilenamePattern;
use crate::preferences::read::{read_bookmarks, read_preferences}; use crate::preferences::read::{read_bookmarks, read_preferences};
@ -165,6 +167,17 @@ impl GlobalPreferences {
!bookmarks.is_empty() && !bookmarks.iter().all(|x| x.is_invalid()) !bookmarks.is_empty() && !bookmarks.iter().all(|x| x.is_invalid())
} }
pub fn storage_backend(&self) -> storage::StorageBackend {
self.cli.storage.unwrap_or_else(|| {
self.preferences
.lock()
.expect("Preferences is not reentrant")
.values
.storage
.backend
})
}
pub fn write_preferences(&self, fun: impl FnOnce(&mut PreferencesWriter)) -> Result<(), Error> { pub fn write_preferences(&self, fun: impl FnOnce(&mut PreferencesWriter)) -> Result<(), Error> {
let mut preferences = self let mut preferences = self
.preferences .preferences
@ -226,6 +239,7 @@ pub struct SavedGlobalPreferences {
pub mute: bool, pub mute: bool,
pub volume: f32, pub volume: f32,
pub log: LogPreferences, pub log: LogPreferences,
pub storage: StoragePreferences,
} }
impl Default for SavedGlobalPreferences { impl Default for SavedGlobalPreferences {
@ -242,6 +256,7 @@ impl Default for SavedGlobalPreferences {
mute: false, mute: false,
volume: 1.0, volume: 1.0,
log: Default::default(), log: Default::default(),
storage: Default::default(),
} }
} }
} }
@ -251,6 +266,11 @@ pub struct LogPreferences {
pub filename_pattern: FilenamePattern, pub filename_pattern: FilenamePattern,
} }
#[derive(PartialEq, Debug, Default)]
pub struct StoragePreferences {
pub backend: storage::StorageBackend,
}
pub static INVALID_URL: &str = "invalid:///"; pub static INVALID_URL: &str = "invalid:///";
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]

View File

@ -244,6 +244,12 @@ pub fn read_preferences(input: &str) -> (ParseResult<SavedGlobalPreferences>, Do
}; };
}); });
document.get_table_like(&mut cx, "storage", |cx, storage| {
if let Some(value) = storage.parse_from_str(cx, "backend") {
result.result.storage.backend = value;
}
});
result.warnings = cx.warnings; result.warnings = cx.warnings;
(result, document) (result, document)
} }
@ -290,7 +296,7 @@ pub fn read_bookmarks(input: &str) -> (ParseResult<Vec<Bookmark>>, DocumentMut)
mod tests { mod tests {
use super::*; use super::*;
use crate::log::FilenamePattern; use crate::log::FilenamePattern;
use crate::preferences::LogPreferences; use crate::preferences::{storage::StorageBackend, LogPreferences, StoragePreferences};
use fluent_templates::loader::langid; use fluent_templates::loader::langid;
use ruffle_render_wgpu::clap::{GraphicsBackend, PowerPreference}; use ruffle_render_wgpu::clap::{GraphicsBackend, PowerPreference};
use url::Url; use url::Url;
@ -604,6 +610,63 @@ mod tests {
); );
} }
#[test]
fn storage_backend() {
assert_eq!(
ParseResult {
result: SavedGlobalPreferences {
storage: StoragePreferences {
..Default::default()
},
..Default::default()
},
warnings: vec![
"Invalid storage.backend: expected string but found integer".to_string()
]
},
read_preferences("storage = {backend = 5}").0
);
assert_eq!(
ParseResult {
result: SavedGlobalPreferences {
storage: StoragePreferences {
..Default::default()
},
..Default::default()
},
warnings: vec!["Invalid storage.backend: unsupported value \"???\"".to_string()]
},
read_preferences("storage = {backend = \"???\"}").0
);
assert_eq!(
ParseResult {
result: SavedGlobalPreferences {
storage: StoragePreferences {
backend: StorageBackend::Memory,
},
..Default::default()
},
warnings: vec![]
},
read_preferences("storage = {backend = \"memory\"}").0
);
}
#[test]
fn storage() {
assert_eq!(
ParseResult {
result: SavedGlobalPreferences {
..Default::default()
},
warnings: vec!["Invalid storage: expected table but found string".to_string()]
},
read_preferences("storage = \"no\"").0
);
}
#[test] #[test]
fn bookmark() { fn bookmark() {
assert_eq!( assert_eq!(

View File

@ -0,0 +1,41 @@
use crate::{backends::DiskStorageBackend, player::PlayerOptions};
use ruffle_core::backend::storage::MemoryStorageBackend;
use std::str::FromStr;
#[derive(clap::ValueEnum, Copy, Clone, PartialEq, Eq, Debug, Default)]
pub enum StorageBackend {
#[default]
Disk,
Memory,
}
impl FromStr for StorageBackend {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"disk" => Ok(StorageBackend::Disk),
"memory" => Ok(StorageBackend::Memory),
_ => Err(()),
}
}
}
impl StorageBackend {
pub fn as_str(&self) -> &'static str {
match self {
StorageBackend::Disk => "disk",
StorageBackend::Memory => "memory",
}
}
pub fn create_backend(
&self,
opt: &PlayerOptions,
) -> Box<dyn ruffle_core::backend::storage::StorageBackend> {
match self {
StorageBackend::Disk => Box::new(DiskStorageBackend::new(opt.save_directory.clone())),
StorageBackend::Memory => Box::new(MemoryStorageBackend::new()),
}
}
}

View File

@ -1,4 +1,5 @@
use crate::log::FilenamePattern; use crate::log::FilenamePattern;
use crate::preferences::storage::StorageBackend;
use crate::preferences::{Bookmark, BookmarksAndDocument, PreferencesAndDocument}; use crate::preferences::{Bookmark, BookmarksAndDocument, PreferencesAndDocument};
use ruffle_render_wgpu::clap::{GraphicsBackend, PowerPreference}; use ruffle_render_wgpu::clap::{GraphicsBackend, PowerPreference};
use toml_edit::{array, value, ArrayOfTables, Table}; use toml_edit::{array, value, ArrayOfTables, Table};
@ -49,6 +50,11 @@ impl<'a> PreferencesWriter<'a> {
self.0.toml_document["log"]["filename_pattern"] = value(pattern.as_str()); self.0.toml_document["log"]["filename_pattern"] = value(pattern.as_str());
self.0.values.log.filename_pattern = pattern; self.0.values.log.filename_pattern = pattern;
} }
pub fn set_storage_backend(&mut self, backend: StorageBackend) {
self.0.toml_document["storage"]["backend"] = value(backend.as_str());
self.0.values.storage.backend = backend;
}
} }
pub struct BookmarksWriter<'a>(&'a mut BookmarksAndDocument); pub struct BookmarksWriter<'a>(&'a mut BookmarksAndDocument);
@ -242,6 +248,25 @@ mod tests {
"[log]\nfilename_pattern = \"single_file\"\n", "[log]\nfilename_pattern = \"single_file\"\n",
); );
} }
#[test]
fn set_storage_backend() {
test(
"",
|writer| writer.set_storage_backend(StorageBackend::Disk),
"storage = { backend = \"disk\" }\n",
);
test(
"storage = { backend = \"disk\" }\n",
|writer| writer.set_storage_backend(StorageBackend::Memory),
"storage = { backend = \"memory\" }\n",
);
test(
"[storage]\nbackend = \"disk\"\n",
|writer| writer.set_storage_backend(StorageBackend::Memory),
"[storage]\nbackend = \"memory\"\n",
);
}
} }
#[allow(clippy::unwrap_used)] #[allow(clippy::unwrap_used)]

View File

@ -644,7 +644,7 @@ impl Ruffle {
match window.local_storage() { match window.local_storage() {
Ok(Some(s)) => { Ok(Some(s)) => {
builder = builder.with_storage(storage::LocalStorageBackend::new(s)); builder = builder.with_storage(Box::new(storage::LocalStorageBackend::new(s)));
} }
err => { err => {
tracing::warn!("Unable to use localStorage: {:?}\nData will not save.", err); tracing::warn!("Unable to use localStorage: {:?}\nData will not save.", err);