desktop: Add storage backend preference
This commit is contained in:
parent
9cab16eb68
commit
92f437ec15
|
@ -2268,8 +2268,8 @@ impl PlayerBuilder {
|
|||
|
||||
/// Sets the storage backend of the player.
|
||||
#[inline]
|
||||
pub fn with_storage(mut self, storage: impl 'static + StorageBackend) -> Self {
|
||||
self.storage = Some(Box::new(storage));
|
||||
pub fn with_storage(mut self, storage: Box<dyn StorageBackend>) -> Self {
|
||||
self.storage = Some(storage);
|
||||
self
|
||||
}
|
||||
|
||||
|
|
|
@ -17,3 +17,7 @@ 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
|
||||
|
||||
storage-backend = Save-File Location
|
||||
storage-backend-disk = Disk
|
||||
storage-backend-memory = Memory
|
|
@ -1,3 +1,4 @@
|
|||
use crate::preferences::storage::StorageBackend;
|
||||
use crate::RUFFLE_VERSION;
|
||||
use anyhow::{anyhow, Error};
|
||||
use clap::{Parser, ValueEnum};
|
||||
|
@ -54,6 +55,12 @@ pub struct Opt {
|
|||
#[clap(long, short)]
|
||||
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.
|
||||
#[clap(long, display_order = 1)]
|
||||
pub width: Option<f64>,
|
||||
|
@ -101,6 +108,8 @@ pub struct Opt {
|
|||
trace_path: Option<std::path::PathBuf>,
|
||||
|
||||
/// 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())]
|
||||
pub save_directory: std::path::PathBuf,
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::gui::{available_languages, optional_text, text};
|
||||
use crate::log::FilenamePattern;
|
||||
use crate::preferences::GlobalPreferences;
|
||||
use crate::preferences::{storage::StorageBackend, GlobalPreferences};
|
||||
use cpal::traits::{DeviceTrait, HostTrait};
|
||||
use egui::{Align2, Button, ComboBox, Grid, Ui, Widget, Window};
|
||||
use ruffle_render_wgpu::clap::{GraphicsBackend, PowerPreference};
|
||||
|
@ -28,6 +28,10 @@ pub struct PreferencesDialog {
|
|||
|
||||
log_filename_pattern: FilenamePattern,
|
||||
log_filename_pattern_changed: bool,
|
||||
|
||||
storage_backend: StorageBackend,
|
||||
storage_backend_readonly: bool,
|
||||
storage_backend_changed: bool,
|
||||
}
|
||||
|
||||
impl PreferencesDialog {
|
||||
|
@ -64,6 +68,10 @@ impl PreferencesDialog {
|
|||
log_filename_pattern: preferences.log_filename_pattern(),
|
||||
log_filename_pattern_changed: false,
|
||||
|
||||
storage_backend: preferences.storage_backend(),
|
||||
storage_backend_readonly: preferences.cli.storage.is_some(),
|
||||
storage_backend_changed: false,
|
||||
|
||||
preferences,
|
||||
}
|
||||
}
|
||||
|
@ -91,6 +99,8 @@ impl PreferencesDialog {
|
|||
self.show_audio_preferences(locale, ui);
|
||||
|
||||
self.show_log_preferences(locale, ui);
|
||||
|
||||
self.show_storage_preferences(locale, &locked_text, ui);
|
||||
});
|
||||
|
||||
if self.restart_required() {
|
||||
|
@ -119,6 +129,7 @@ impl PreferencesDialog {
|
|||
|| 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()
|
||||
|| self.storage_backend != self.preferences.storage_backend()
|
||||
}
|
||||
|
||||
fn show_graphics_preferences(
|
||||
|
@ -266,6 +277,42 @@ impl PreferencesDialog {
|
|||
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) {
|
||||
if let Err(e) = self.preferences.write_preferences(|preferences| {
|
||||
if self.graphics_backend_changed {
|
||||
|
@ -284,6 +331,9 @@ impl PreferencesDialog {
|
|||
if self.log_filename_pattern_changed {
|
||||
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
|
||||
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 {
|
||||
if instance.enumerate_adapters(backend).is_empty() {
|
||||
wgpu::Backends::empty()
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::backends::{
|
||||
CpalAudioBackend, DesktopExternalInterfaceProvider, DesktopFSCommandProvider, DesktopUiBackend,
|
||||
DiskStorageBackend, ExternalNavigatorBackend,
|
||||
ExternalNavigatorBackend,
|
||||
};
|
||||
use crate::custom_event::RuffleEvent;
|
||||
use crate::executor::WinitAsyncExecutor;
|
||||
|
@ -159,7 +159,7 @@ impl ActivePlayer {
|
|||
builder = builder
|
||||
.with_navigator(navigator)
|
||||
.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 {
|
||||
event_loop: event_loop.clone(),
|
||||
window: window.clone(),
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
mod read;
|
||||
mod write;
|
||||
|
||||
pub mod storage;
|
||||
|
||||
use crate::cli::Opt;
|
||||
use crate::log::FilenamePattern;
|
||||
use crate::preferences::read::{read_bookmarks, read_preferences};
|
||||
|
@ -165,6 +167,17 @@ impl GlobalPreferences {
|
|||
!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> {
|
||||
let mut preferences = self
|
||||
.preferences
|
||||
|
@ -226,6 +239,7 @@ pub struct SavedGlobalPreferences {
|
|||
pub mute: bool,
|
||||
pub volume: f32,
|
||||
pub log: LogPreferences,
|
||||
pub storage: StoragePreferences,
|
||||
}
|
||||
|
||||
impl Default for SavedGlobalPreferences {
|
||||
|
@ -242,6 +256,7 @@ impl Default for SavedGlobalPreferences {
|
|||
mute: false,
|
||||
volume: 1.0,
|
||||
log: Default::default(),
|
||||
storage: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -251,6 +266,11 @@ pub struct LogPreferences {
|
|||
pub filename_pattern: FilenamePattern,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Debug, Default)]
|
||||
pub struct StoragePreferences {
|
||||
pub backend: storage::StorageBackend,
|
||||
}
|
||||
|
||||
pub static INVALID_URL: &str = "invalid:///";
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
|
|
|
@ -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, document)
|
||||
}
|
||||
|
@ -290,7 +296,7 @@ pub fn read_bookmarks(input: &str) -> (ParseResult<Vec<Bookmark>>, DocumentMut)
|
|||
mod tests {
|
||||
use super::*;
|
||||
use crate::log::FilenamePattern;
|
||||
use crate::preferences::LogPreferences;
|
||||
use crate::preferences::{storage::StorageBackend, LogPreferences, StoragePreferences};
|
||||
use fluent_templates::loader::langid;
|
||||
use ruffle_render_wgpu::clap::{GraphicsBackend, PowerPreference};
|
||||
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]
|
||||
fn bookmark() {
|
||||
assert_eq!(
|
||||
|
|
|
@ -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()),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
use crate::log::FilenamePattern;
|
||||
use crate::preferences::storage::StorageBackend;
|
||||
use crate::preferences::{Bookmark, BookmarksAndDocument, PreferencesAndDocument};
|
||||
use ruffle_render_wgpu::clap::{GraphicsBackend, PowerPreference};
|
||||
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.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);
|
||||
|
@ -242,6 +248,25 @@ mod tests {
|
|||
"[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)]
|
||||
|
|
|
@ -644,7 +644,7 @@ impl Ruffle {
|
|||
|
||||
match window.local_storage() {
|
||||
Ok(Some(s)) => {
|
||||
builder = builder.with_storage(storage::LocalStorageBackend::new(s));
|
||||
builder = builder.with_storage(Box::new(storage::LocalStorageBackend::new(s)));
|
||||
}
|
||||
err => {
|
||||
tracing::warn!("Unable to use localStorage: {:?}\nData will not save.", err);
|
||||
|
|
Loading…
Reference in New Issue