use super::{McPaths, settings::SettingsScope, settings_storage};
const SEEN_KEY: &str = "welcome_release_notes_seen";
const PROGRESS_KEY: &str = "welcome_release_notes_progress";
#[derive(Clone, serde::Serialize, serde::Deserialize)]
pub(crate) struct UnreadStart {
version: String,
inclusive: bool,
}
impl UnreadStart {
pub(crate) fn contains(&self, version: &str, current: &str) -> bool {
let (Ok(version), Ok(current), Ok(start)) = (
semver::Version::parse(version),
semver::Version::parse(current),
semver::Version::parse(&self.version),
) else {
return false;
};
version <= current && (version > start || (self.inclusive && version == start))
}
}
#[derive(serde::Serialize, serde::Deserialize)]
struct Progress {
unread_from: UnreadStart,
}
pub(crate) fn record_launch(paths: &McPaths, current: &str) -> anyhow::Result<UnreadStart> {
let current_version = semver::Version::parse(current)?;
let mut unread = None;
settings_storage::update_settings_json(paths, SettingsScope::Global, |raw| {
let seen = raw
.get(SEEN_KEY)
.map(|value| {
value
.as_object()
.ok_or_else(|| anyhow::anyhow!("{SEEN_KEY} must be an object"))
})
.transpose()?;
let progress = if let Some(value) = raw.get(PROGRESS_KEY) {
let progress: Progress = serde_json::from_value(value.clone())?;
semver::Version::parse(&progress.unread_from.version)?;
progress
} else {
let baseline = seen
.into_iter()
.flat_map(|seen| seen.iter())
.filter(|(_, value)| value.as_bool() == Some(true))
.filter_map(|(version, _)| semver::Version::parse(version).ok())
.filter(|version| version <= ¤t_version)
.max();
Progress {
unread_from: UnreadStart {
inclusive: baseline.is_none(),
version: baseline
.map_or_else(|| current.to_owned(), |version| version.to_string()),
},
}
};
unread = Some(progress.unread_from.clone());
raw[PROGRESS_KEY] = serde_json::to_value(progress)?;
Ok(())
})?;
Ok(unread.expect("successful settings update selects unread range"))
}
pub(crate) fn mark_seen(paths: &McPaths, version: &str) -> anyhow::Result<()> {
settings_storage::update_settings_json(paths, SettingsScope::Global, |raw| {
if let Some(value) = raw.get(PROGRESS_KEY) {
let mut progress: Progress = serde_json::from_value(value.clone())?;
if semver::Version::parse(version)?
>= semver::Version::parse(&progress.unread_from.version)?
{
progress.unread_from = UnreadStart {
version: version.to_owned(),
inclusive: false,
};
raw[PROGRESS_KEY] = serde_json::to_value(progress)?;
}
}
let seen = raw
.as_object_mut()
.expect("storage validates object")
.entry(SEEN_KEY)
.or_insert_with(|| serde_json::json!({}));
let seen = seen
.as_object_mut()
.ok_or_else(|| anyhow::anyhow!("{SEEN_KEY} must be an object"))?;
seen.insert(version.to_owned(), serde_json::Value::Bool(true));
Ok(())
})?;
Ok(())
}