Keep two changes to the catalogue made at once from undoing each other (#153)
Each change to the catalogue or the server settings stored the whole thing from a copy it took earlier, and nothing kept two from overlapping. Adding a feed copied it first and then spent up to a minute finding and reading the feed, so another person's new feed, an unsubscribe, an admin's settings, or the scan's own changes (a moved feed's address, a category) made in that minute were stored over when it finished, with no error. A feed dropped that way left its subscriber's list. Every change now takes its copy through Ctx::edit_cfg, which holds a lock until store_cfg keeps it, and store_cfg takes nothing else, so no writer can skip it. Adding a feed takes its copy once it has found the feed, and before the duplicate check, so two people adding one feed add it once. reload takes the lock too. `ipx add`, `rm` and `import`, in a process of their own, are outside it, as the comment on the lock says. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1457,12 +1457,13 @@ async fn add_feed(
|
||||
user: crate::db::User,
|
||||
Json(body): Json<NewFeed>,
|
||||
) -> Result<Json<serde_json::Value>, ApiError> {
|
||||
let mut cfg = (*state.ctx.cfg()).clone();
|
||||
// Before the duplicate check, so a site's page finds the feed someone already has. What is
|
||||
// not a feed and links none is refused here, with why, and nothing is added.
|
||||
let url = crate::feed::find_feed(&state.ctx.client, &crate::feed::expand_input(&body.url))
|
||||
.await
|
||||
.map_err(|e| ApiError::bad_request(format!("{e:#}")))?;
|
||||
// Taken before the duplicate check, so two people adding the same feed at once add it once.
|
||||
let mut cfg = state.ctx.edit_cfg().await;
|
||||
// Someone else may already have it. Then adding costs nothing: no second fetch, no
|
||||
// second copy on disk, just another name against the same feed.
|
||||
if let Some(existing) = crate::subscriptions(&state.ctx).await?
|
||||
@@ -1600,7 +1601,7 @@ async fn patch_feed(
|
||||
"the feed's address, folder, schedule and category are the same for everyone, so only an admin changes them",
|
||||
));
|
||||
}
|
||||
let mut cfg = (*state.ctx.cfg()).clone();
|
||||
let mut cfg = state.ctx.edit_cfg().await;
|
||||
|
||||
// Derived feeds have no config entry. Editing one is the moment it earns a real
|
||||
// entry: promote it, so the config holds your decisions and nothing else.
|
||||
@@ -1677,7 +1678,7 @@ async fn remove_feed(
|
||||
// Nobody is left: the feed stops being scanned. Its files and history stay, so if
|
||||
// someone subscribes again they do not pull the back catalogue a second time. A feed an
|
||||
// admin listed stays in the Directory, for the next person.
|
||||
let mut cfg = (*state.ctx.cfg()).clone();
|
||||
let mut cfg = state.ctx.edit_cfg().await;
|
||||
if cfg.feeds.get(&id).is_some_and(|f| f.listed) {
|
||||
return Ok(StatusCode::NO_CONTENT);
|
||||
}
|
||||
@@ -2156,7 +2157,7 @@ async fn patch_settings(
|
||||
if !user.is_admin {
|
||||
return Err(ApiError::forbidden("only an admin changes these settings"));
|
||||
}
|
||||
let mut cfg = (*state.ctx.cfg()).clone();
|
||||
let mut cfg = state.ctx.edit_cfg().await;
|
||||
if let Some(sched) = body.schedule {
|
||||
let sched = sched.trim().to_owned();
|
||||
if crate::config::parse_interval(&sched).is_none() {
|
||||
|
||||
Reference in New Issue
Block a user