settings: add a lock to read/write global settings

This commit is contained in:
pythcoiner 2025-06-24 05:56:53 +02:00
parent b696f50f8c
commit 13652e3c33
5 changed files with 124 additions and 62 deletions

11
Cargo.lock generated
View File

@ -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",

View File

@ -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]

View File

@ -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)
}
}
}

View File

@ -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()

View File

@ -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 {