system settings
git clone https://git.lucas.co/cce-system-interface.git
Implement atomic config writes and rolling configuration backups
src/config_manager.rs | 36 +++++++++++++++++++++++++++++++++++-
src/pages/interface.rs | 49 ++++++++++++++++++++++++++++++++++++++++++-------
2 files changed, 77 insertions(+), 8 deletions(-)
diff --git a/src/config_manager.rs b/src/config_manager.rs
index d35d5ef..5b5fda7 100644
--- a/src/config_manager.rs
+++ b/src/config_manager.rs
@@ -7,8 +7,42 @@ pub fn read_config_file() -> String {
fs::read_to_string(CONFIG_PATH).unwrap_or_default()
}
+fn perform_rolling_backup(path: &str) {
+ if path != CONFIG_PATH {
+ return;
+ }
+ if !std::path::Path::new(path).exists() {
+ return;
+ }
+ let backup_dir = "/home/lsgalante/.config/cce/backups";
+ if let Err(_) = fs::create_dir_all(backup_dir) {
+ return;
+ }
+ for i in (1..=4).rev() {
+ let src = format!("{}/config.json.{}.bak", backup_dir, i);
+ let dst = format!("{}/config.json.{}.bak", backup_dir, i + 1);
+ if std::path::Path::new(&src).exists() {
+ let _ = fs::rename(src, dst);
+ }
+ }
+ let dst = format!("{}/config.json.1.bak", backup_dir);
+ let _ = fs::copy(path, dst);
+}
+
+fn safe_write(path: &str, content: &str) -> bool {
+ perform_rolling_backup(path);
+ let temp_path = format!("{}.tmp", path);
+ if fs::write(&temp_path, content).is_ok() {
+ if fs::rename(&temp_path, path).is_ok() {
+ return true;
+ }
+ let _ = fs::remove_file(&temp_path);
+ }
+ false
+}
+
pub fn write_config_file(content: &str) -> bool {
- fs::write(CONFIG_PATH, content).is_ok()
+ safe_write(CONFIG_PATH, content)
}
pub fn update_json_value<F>(update_fn: F) -> bool
diff --git a/src/pages/interface.rs b/src/pages/interface.rs
index fe51c11..edfe803 100644
--- a/src/pages/interface.rs
+++ b/src/pages/interface.rs
@@ -48,6 +48,40 @@ fn get_config_path() -> String {
}
+fn perform_rolling_backup(path: &str) {
+ if path != CONFIG_PATH {
+ return;
+ }
+ if !std::path::Path::new(path).exists() {
+ return;
+ }
+ let backup_dir = "/home/lsgalante/.config/cce/backups";
+ if let Err(_) = fs::create_dir_all(backup_dir) {
+ return;
+ }
+ for i in (1..=4).rev() {
+ let src = format!("{}/config.json.{}.bak", backup_dir, i);
+ let dst = format!("{}/config.json.{}.bak", backup_dir, i + 1);
+ if std::path::Path::new(&src).exists() {
+ let _ = fs::rename(src, dst);
+ }
+ }
+ let dst = format!("{}/config.json.1.bak", backup_dir);
+ let _ = fs::copy(path, dst);
+}
+
+fn safe_write(path: &str, content: &str) -> bool {
+ perform_rolling_backup(path);
+ let temp_path = format!("{}.tmp", path);
+ if fs::write(&temp_path, content).is_ok() {
+ if fs::rename(&temp_path, path).is_ok() {
+ return true;
+ }
+ let _ = fs::remove_file(&temp_path);
+ }
+ false
+}
+
fn get_socket_path() -> String {
match std::env::var("WAYLAND_DISPLAY") {
Ok(display) => format!("/tmp/cce-{}.sock", display),
@@ -1214,7 +1248,7 @@ pub fn write_config_value_path(path: &str, key: &str, value: &str) -> bool {
if updated_any {
if let Ok(updated_str) = serde_json::to_string_pretty(&val) {
- if fs::write(path, updated_str).is_ok() {
+ if safe_write(path, &updated_str) {
return true;
}
}
@@ -3843,7 +3877,8 @@ pub fn parse_transparency_opacity(content: &str) -> f32 {
}
pub fn write_transparency_config_value(key: &str, value: &str) {
- let content = fs::read_to_string(CONFIG_PATH).unwrap_or_default();
+ let path = get_config_path();
+ let content = fs::read_to_string(&path).unwrap_or_default();
let mut val = parse_json(&content);
let j_val = if let Ok(parsed_val) = serde_json::from_str::<serde_json::Value>(value) {
parsed_val
@@ -3859,12 +3894,12 @@ pub fn write_transparency_config_value(key: &str, value: &str) {
transparency.insert(key.to_string(), j_val);
}
if let Ok(updated_str) = serde_json::to_string_pretty(&val) {
- let _ = fs::write(CONFIG_PATH, updated_str);
+ let _ = safe_write(&path, &updated_str);
}
}
fn write_surfaces_config_value(key: &str, value: &str) {
- write_surfaces_config_value_path(CONFIG_PATH, key, value);
+ write_surfaces_config_value_path(&get_config_path(), key, value);
}
fn write_surfaces_config_value_path(path: &str, key: &str, value: &str) {
@@ -3884,7 +3919,7 @@ fn write_surfaces_config_value_path(path: &str, key: &str, value: &str) {
surfaces.insert(key.to_string(), j_val);
}
if let Ok(updated_str) = serde_json::to_string_pretty(&val) {
- let _ = fs::write(path, updated_str);
+ let _ = safe_write(path, &updated_str);
}
}
@@ -3928,7 +3963,7 @@ fn parse_notifications_opacity(content: &str) -> f32 {
}
fn write_notifications_config_value(key: &str, value: &str) {
- write_notifications_config_value_path(CONFIG_PATH, key, value);
+ write_notifications_config_value_path(&get_config_path(), key, value);
}
fn write_notifications_config_value_path(path: &str, key: &str, value: &str) {
@@ -3948,7 +3983,7 @@ fn write_notifications_config_value_path(path: &str, key: &str, value: &str) {
notifications.insert(key.to_string(), j_val);
}
if let Ok(updated_str) = serde_json::to_string_pretty(&val) {
- let _ = fs::write(path, updated_str);
+ let _ = safe_write(path, &updated_str);
}
}