DeadAir is off for every feed until turned on for it (#171)

There was one switch for everything, with a feed kept out by hand; any
feed already subscribed would have been in it the moment the switch was
turned on. Now each feed, or the group it came in, is turned on by
itself, and a feed of a group turned on can still be kept out. The
general switch and /api/me's deadair are gone; /api/me keeps
deadair_saved.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-09 17:49:24 +00:00
parent 63e192767d
commit 16b0686aab
6 changed files with 30 additions and 40 deletions

View File

@@ -33,7 +33,7 @@ const KEEP: f64 = 0.1;
// shortcut: a file with more silences than this keeps only its longest; about 9 hours of
// conversation at the rate measured. Pass the list some other way if a feed ever gets there.
const MAX_CUTS: usize = 4000;
/// How long the maker waits when there is nothing to do: a download or a switch turned on is
/// How long the maker waits when there is nothing to do: a download or a feed turned on is
/// picked up this long after, at most.
const IDLE: std::time::Duration = std::time::Duration::from_secs(120);
@@ -130,15 +130,15 @@ pub fn saved(cuts: &[Cut]) -> i64 {
cuts.iter().map(|Cut(s, e)| e - s).sum()
}
/// Whether someone with these settings plays a feed from its copies: their switch on, and the
/// feed not kept out, by itself or by the group it came in (an OPML or a Patreon creator).
/// Whether someone with these settings plays a feed from its copies. Off for every feed until
/// they turn it on for that one, or for the group it came in (an OPML or a Patreon creator),
/// which a feed of the group can still say otherwise to.
pub fn wants(prefs: &HashMap<String, bool>, feed: &str, group: Option<&str>) -> bool {
prefs.get("").copied().unwrap_or(false)
&& prefs.get(feed).or_else(|| group.and_then(|g| prefs.get(g))).copied().unwrap_or(true)
prefs.get(feed).or_else(|| group.and_then(|g| prefs.get(g))).copied().unwrap_or(false)
}
/// Each person's settings who has the switch on, and each managed feed's group, which is what
/// `wants` needs.
/// The settings of each person with DeadAir on for something, and each managed feed's group,
/// which is what `wants` needs.
pub struct Wanting {
pub people: HashMap<i64, HashMap<String, bool>>,
pub groups: HashMap<String, String>,
@@ -150,7 +150,7 @@ impl Wanting {
for (user, feed, wanted) in ctx.db.deadair_prefs(only).await? {
people.entry(user).or_default().insert(feed, wanted);
}
people.retain(|_, p| p.get("").copied().unwrap_or(false));
people.retain(|_, p| p.values().any(|&w| w));
let groups = if people.is_empty() {
HashMap::new()
} else {
@@ -271,7 +271,7 @@ async fn have_ffmpeg() -> bool {
// shortcut: those stay on the list and are looked at again each pass, one query each; record
// them as passed over if a large library makes the pass slow.
async fn next(ctx: &Ctx) -> Result<bool> {
// Nobody with the switch on, which is most passes: nothing more to ask the database.
// Nobody with DeadAir on for anything, which is most passes: nothing more to ask the database.
let wanting = Wanting::load(ctx, None).await?;
if wanting.people.is_empty() {
return Ok(false);
@@ -447,14 +447,13 @@ mod tests {
}
#[test]
fn a_feed_kept_out_by_itself_or_its_group() {
fn off_for_every_feed_until_turned_on_for_it_or_its_group() {
let p = |v: &[(&str, bool)]| v.iter().map(|(k, w)| (k.to_string(), *w)).collect::<HashMap<_, _>>();
assert!(!wants(&p(&[]), "a", None), "off until turned on");
assert!(wants(&p(&[("", true)]), "a", None));
assert!(!wants(&p(&[("", true), ("a", false)]), "a", None));
assert!(!wants(&p(&[("", true), ("g", false)]), "a", Some("g")));
assert!(wants(&p(&[("", true), ("g", false), ("a", true)]), "a", Some("g")), "let back in");
assert!(!wants(&p(&[("", false), ("a", true)]), "a", None), "the switch rules");
assert!(!wants(&p(&[]), "a", None), "off until turned on for the feed");
assert!(!wants(&p(&[("b", true)]), "a", None), "another feed's says nothing of this one");
assert!(wants(&p(&[("a", true)]), "a", None));
assert!(wants(&p(&[("g", true)]), "a", Some("g")), "on for its group");
assert!(!wants(&p(&[("g", true), ("a", false)]), "a", Some("g")), "kept out of its group's");
}
#[test]