settings: add a lock to read/write global settings
This commit is contained in:
parent
b696f50f8c
commit
13652e3c33
11
Cargo.lock
generated
11
Cargo.lock
generated
@ -1827,6 +1827,16 @@ dependencies = [
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fs2"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures"
|
||||
version = "0.3.31"
|
||||
@ -3053,6 +3063,7 @@ dependencies = [
|
||||
"dirs 3.0.2",
|
||||
"email_address",
|
||||
"flate2",
|
||||
"fs2",
|
||||
"hex",
|
||||
"iced",
|
||||
"iced_aw",
|
||||
|
||||
@ -54,6 +54,7 @@ bitcoin_hashes = "0.12"
|
||||
reqwest = { version = "0.11", default-features=false, features = ["json", "rustls-tls", "stream"] }
|
||||
rust-ini = "0.19.0"
|
||||
rfd = "0.15.1"
|
||||
fs2 = "0.4.3"
|
||||
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
|
||||
@ -380,13 +380,15 @@ impl std::fmt::Display for SettingsError {
|
||||
pub mod global {
|
||||
use crate::dir::LianaDirectory;
|
||||
use async_hwi::bitbox::{ConfigError, NoiseConfig, NoiseConfigData};
|
||||
use fs2::FileExt;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::io::{Read, Write};
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::{Read, Seek, SeekFrom, Write};
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub const DEFAULT_FILE_NAME: &str = "global_settings.json";
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
|
||||
pub struct WindowConfig {
|
||||
pub width: f32,
|
||||
pub height: f32,
|
||||
@ -402,40 +404,87 @@ pub mod global {
|
||||
pub fn path(global_datadir: &LianaDirectory) -> PathBuf {
|
||||
global_datadir.path().join(DEFAULT_FILE_NAME)
|
||||
}
|
||||
pub fn load(path: &PathBuf) -> Result<Option<GlobalSettings>, String> {
|
||||
if !path.exists() {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let mut file = std::fs::File::open(path).map_err(|e| e.to_string())?;
|
||||
|
||||
let mut contents = String::new();
|
||||
file.read_to_string(&mut contents)
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
let settings =
|
||||
serde_json::from_str::<GlobalSettings>(&contents).map_err(|e| e.to_string())?;
|
||||
Ok(Some(settings))
|
||||
}
|
||||
|
||||
pub fn load_window_config(path: &PathBuf) -> Option<WindowConfig> {
|
||||
Self::load(path)
|
||||
.ok()
|
||||
.flatten()
|
||||
.and_then(|s| s.window_config)
|
||||
let mut ret = None;
|
||||
if let Err(e) = Self::update(path, |s| ret = s.window_config.clone(), false) {
|
||||
tracing::error!("Failed to load window config: {e}");
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
||||
pub fn to_file(&self, path: &PathBuf) -> Result<(), String> {
|
||||
let mut file = std::fs::File::create(path).map_err(|e| e.to_string())?;
|
||||
pub fn update_window_config(
|
||||
path: &PathBuf,
|
||||
window_config: &WindowConfig,
|
||||
) -> Result<(), String> {
|
||||
Self::update(
|
||||
path,
|
||||
|s| s.window_config = Some(window_config.clone()),
|
||||
true,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn load_bitbox_settings(path: &PathBuf) -> Result<Option<BitboxSettings>, String> {
|
||||
let mut ret = None;
|
||||
Self::update(path, |s| ret = s.bitbox.clone(), false)?;
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
pub fn update_bitbox_settings(
|
||||
path: &PathBuf,
|
||||
bitbox: &BitboxSettings,
|
||||
) -> Result<(), String> {
|
||||
Self::update(path, |s| s.bitbox = Some(bitbox.clone()), true)
|
||||
}
|
||||
|
||||
pub fn update<F>(path: &PathBuf, mut update: F, write: bool) -> Result<(), String>
|
||||
where
|
||||
F: FnMut(&mut GlobalSettings),
|
||||
{
|
||||
let exists = path.is_file();
|
||||
let mut file = OpenOptions::new()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.create(true)
|
||||
.truncate(false)
|
||||
.open(path)
|
||||
.map_err(|e| format!("Opening file: {e}"))?;
|
||||
|
||||
file.lock_exclusive()
|
||||
.map_err(|e| format!("Locking file: {e}"))?;
|
||||
|
||||
let mut global_settings = if exists {
|
||||
let mut content = String::new();
|
||||
file.read_to_string(&mut content)
|
||||
.map_err(|e| format!("Reading file: {e}"))?;
|
||||
|
||||
serde_json::from_str::<GlobalSettings>(&content).map_err(|e| e.to_string())?
|
||||
} else {
|
||||
GlobalSettings::default()
|
||||
};
|
||||
|
||||
update(&mut global_settings);
|
||||
|
||||
if write {
|
||||
let content = serde_json::to_vec_pretty(&global_settings)
|
||||
.map_err(|e| format!("Failed to serialize GlobalSettings: {e}"))?;
|
||||
|
||||
file.seek(SeekFrom::Start(0))
|
||||
.map_err(|e| format!("Failed to seek file: {e}"))?;
|
||||
|
||||
file.write_all(&content)
|
||||
.map_err(|e| format!("Failed to write file: {e}"))?;
|
||||
file.set_len(content.len() as u64)
|
||||
.map_err(|e| format!("Failed to truncate file: {e}"))?;
|
||||
}
|
||||
|
||||
file.unlock().map_err(|e| format!("Unlocking file: {e}"))?;
|
||||
|
||||
let contents = serde_json::to_string_pretty(&self).map_err(|e| e.to_string())?;
|
||||
file.write_all(contents.as_bytes())
|
||||
.map_err(|e| e.to_string())?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||
pub struct BitboxSettings {
|
||||
pub noise_config: NoiseConfigData,
|
||||
}
|
||||
@ -458,23 +507,28 @@ pub mod global {
|
||||
|
||||
impl NoiseConfig for PersistedBitboxNoiseConfig {
|
||||
fn read_config(&self) -> Result<NoiseConfigData, ConfigError> {
|
||||
let settings = GlobalSettings::load(&self.file_path)
|
||||
let res = GlobalSettings::load_bitbox_settings(&self.file_path)
|
||||
.map_err(ConfigError)?
|
||||
.unwrap_or_default();
|
||||
Ok(settings
|
||||
.bitbox
|
||||
.map(|s| s.noise_config)
|
||||
.unwrap_or_else(NoiseConfigData::default))
|
||||
.unwrap_or_else(NoiseConfigData::default);
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
fn store_config(&self, conf: &NoiseConfigData) -> Result<(), ConfigError> {
|
||||
let mut cfg = GlobalSettings::load(&self.file_path)
|
||||
.map_err(ConfigError)?
|
||||
.unwrap_or_default();
|
||||
cfg.bitbox = Some(BitboxSettings {
|
||||
noise_config: conf.clone(),
|
||||
});
|
||||
cfg.to_file(&self.file_path).map_err(ConfigError)
|
||||
GlobalSettings::update(
|
||||
&self.file_path,
|
||||
|s| {
|
||||
if let Some(bitbox) = s.bitbox.as_mut() {
|
||||
bitbox.noise_config = conf.clone();
|
||||
} else {
|
||||
s.bitbox = Some(BitboxSettings {
|
||||
noise_config: conf.clone(),
|
||||
});
|
||||
}
|
||||
},
|
||||
true,
|
||||
)
|
||||
.map_err(ConfigError)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -146,15 +146,15 @@ impl GUI {
|
||||
iced::window::Settings::default().size
|
||||
};
|
||||
let path = GlobalSettings::path(&self.config.liana_directory);
|
||||
let mut settings = GlobalSettings::load(&path)
|
||||
.ok()
|
||||
.flatten()
|
||||
.unwrap_or_default();
|
||||
settings.window_config = Some(WindowConfig {
|
||||
width: new_size.width,
|
||||
height: new_size.height,
|
||||
});
|
||||
settings.to_file(&path).unwrap();
|
||||
if let Err(e) = GlobalSettings::update_window_config(
|
||||
&path,
|
||||
&WindowConfig {
|
||||
width: new_size.width,
|
||||
height: new_size.height,
|
||||
},
|
||||
) {
|
||||
tracing::error!("Failed to update the window config: {e}");
|
||||
}
|
||||
Task::batch(batch)
|
||||
}
|
||||
// we already have a record of the last window size and we update it
|
||||
@ -163,16 +163,14 @@ impl GUI {
|
||||
*width = monitor_size.width;
|
||||
*height = monitor_size.height;
|
||||
let path = GlobalSettings::path(&self.config.liana_directory);
|
||||
let mut settings = GlobalSettings::load(&path)
|
||||
.ok()
|
||||
.flatten()
|
||||
.unwrap_or_default();
|
||||
settings.window_config = Some(WindowConfig {
|
||||
width: *width,
|
||||
height: *height,
|
||||
});
|
||||
if let Err(e) = settings.to_file(&path) {
|
||||
tracing::error!("Fail to write window config to file: {e}");
|
||||
if let Err(e) = GlobalSettings::update_window_config(
|
||||
&path,
|
||||
&WindowConfig {
|
||||
width: *width,
|
||||
height: *height,
|
||||
},
|
||||
) {
|
||||
tracing::error!("Failed to update the window config: {e}");
|
||||
}
|
||||
}
|
||||
Task::none()
|
||||
|
||||
@ -108,10 +108,8 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||
};
|
||||
|
||||
let global_config_path = GlobalSettings::path(&config.liana_directory);
|
||||
let initial_size = if let Ok(Some(GlobalSettings {
|
||||
window_config: Some(WindowConfig { width, height }),
|
||||
..
|
||||
})) = GlobalSettings::load(&global_config_path)
|
||||
let initial_size = if let Some(WindowConfig { width, height }) =
|
||||
GlobalSettings::load_window_config(&global_config_path)
|
||||
{
|
||||
Size { width, height }
|
||||
} else {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user