feat(config): support for fallback to previous config version

This commit is contained in:
Ashley Wulber 2026-03-26 15:51:34 -04:00
parent a44cff8011
commit bec679efc9

View file

@ -162,6 +162,7 @@ pub trait ConfigSet {
pub struct Config { pub struct Config {
system_path: Option<PathBuf>, system_path: Option<PathBuf>,
user_path: Option<PathBuf>, user_path: Option<PathBuf>,
previous: Option<Box<Config>>,
} }
/// Check that the name is relative and doesn't contain . or .. /// Check that the name is relative and doesn't contain . or ..
@ -180,9 +181,13 @@ fn sanitize_name(name: &str) -> Result<&Path, Error> {
impl Config { impl Config {
/// Get a system config for the given name and config version /// Get a system config for the given name and config version
pub fn system(name: &str, version: u64) -> Result<Self, Error> { pub fn system(name: &str, version: u64) -> Result<Self, Error> {
Self::system_inner(name, version, true)
}
fn system_inner(name: &str, version: u64, look_for_previous: bool) -> Result<Self, Error> {
let path = sanitize_name(name)?.join(format!("v{version}")); let path = sanitize_name(name)?.join(format!("v{version}"));
#[cfg(unix)] #[cfg(unix)]
let system_path = xdg::BaseDirectories::with_prefix("cosmic").find_data_file(path); let system_path = xdg::BaseDirectories::with_prefix("cosmic").find_data_file(&path);
#[cfg(windows)] #[cfg(windows)]
let system_path = let system_path =
@ -192,6 +197,13 @@ impl Config {
Ok(Self { Ok(Self {
system_path, system_path,
user_path: None, user_path: None,
previous: if version > 1 && look_for_previous {
Self::system_inner(name, version - 1, false)
.ok()
.map(Box::new)
} else {
None
},
}) })
} }
@ -199,6 +211,10 @@ impl Config {
// Use folder at XDG config/name for config storage, return Config if successful // Use folder at XDG config/name for config storage, return Config if successful
//TODO: fallbacks for flatpak (HOST_XDG_CONFIG_HOME, xdg-desktop settings proxy) //TODO: fallbacks for flatpak (HOST_XDG_CONFIG_HOME, xdg-desktop settings proxy)
pub fn new(name: &str, version: u64) -> Result<Self, Error> { pub fn new(name: &str, version: u64) -> Result<Self, Error> {
Self::new_inner(name, version, true)
}
fn new_inner(name: &str, version: u64, look_for_previous: bool) -> Result<Self, Error> {
// Look for [name]/v[version] // Look for [name]/v[version]
let path = sanitize_name(name)?.join(format!("v{}", version)); let path = sanitize_name(name)?.join(format!("v{}", version));
@ -223,15 +239,29 @@ impl Config {
Ok(Self { Ok(Self {
system_path, system_path,
user_path: Some(user_path), user_path: Some(user_path),
previous: if version > 1 && look_for_previous {
Self::new_inner(name, version - 1, false).ok().map(Box::new)
} else {
None
},
}) })
} }
/// Get config for the given application name and config version and custom path. /// Get config for the given application name and config version and custom path.
pub fn with_custom_path(name: &str, version: u64, custom_path: PathBuf) -> Result<Self, Error> { pub fn with_custom_path(name: &str, version: u64, custom_path: PathBuf) -> Result<Self, Error> {
Self::with_custom_path_inner(name, version, custom_path, true)
}
fn with_custom_path_inner(
name: &str,
version: u64,
custom_path: PathBuf,
look_for_previous: bool,
) -> Result<Self, Error> {
// Look for [name]/v[version] // Look for [name]/v[version]
let path = sanitize_name(name)?.join(format!("v{version}")); let path = sanitize_name(name)?.join(format!("v{version}"));
let mut user_path = custom_path; let mut user_path = custom_path.clone();
user_path.push("cosmic"); user_path.push("cosmic");
user_path.push(path); user_path.push(path);
// Create new configuration directory if not found. // Create new configuration directory if not found.
@ -241,6 +271,13 @@ impl Config {
Ok(Self { Ok(Self {
system_path: None, system_path: None,
user_path: Some(user_path), user_path: Some(user_path),
previous: if version > 1 && look_for_previous {
Self::with_custom_path_inner(name, version - 1, custom_path.clone(), false)
.ok()
.map(Box::new)
} else {
None
},
}) })
} }
@ -250,6 +287,10 @@ impl Config {
// Use folder at XDG config/name for config storage, return Config if successful // Use folder at XDG config/name for config storage, return Config if successful
//TODO: fallbacks for flatpak (HOST_XDG_CONFIG_HOME, xdg-desktop settings proxy) //TODO: fallbacks for flatpak (HOST_XDG_CONFIG_HOME, xdg-desktop settings proxy)
pub fn new_state(name: &str, version: u64) -> Result<Self, Error> { pub fn new_state(name: &str, version: u64) -> Result<Self, Error> {
Self::new_state_inner(name, version, true)
}
fn new_state_inner(name: &str, version: u64, look_for_previous: bool) -> Result<Self, Error> {
// Look for [name]/v[version] // Look for [name]/v[version]
let path = sanitize_name(name)?.join(format!("v{}", version)); let path = sanitize_name(name)?.join(format!("v{}", version));
@ -263,6 +304,13 @@ impl Config {
Ok(Self { Ok(Self {
system_path: None, system_path: None,
user_path: Some(user_path), user_path: Some(user_path),
previous: if version > 1 && look_for_previous {
Self::new_state_inner(name, version - 1, false)
.ok()
.map(Box::new)
} else {
None
},
}) })
} }
@ -373,7 +421,13 @@ impl ConfigGet for Config {
Ok(ron::from_str(&data)?) Ok(ron::from_str(&data)?)
} }
_ => Err(Error::NotFound), _ => {
if let Some(previous) = self.previous.as_ref() {
previous.get_local(key)
} else {
Err(Error::NotFound)
}
}
} }
} }