Cut what the audit found: dead columns, one-time upgrades, three deps

Works through TODO.md from the 2026-09-12 over-engineering audit. Drops the
entries.read/flagged/position columns (migrate() removes them from older
databases), migrate_opml_children, the legacy interval_mins key, the
contrib/ systemd units, test-only Db wrappers, a duplicate token generator,
redundant logbuf visitors, unused page state and CSS, and the infer, dirs
and tokio-stream dependencies. The icon is served once as /icon.png instead
of inlined four times, taking about 94 KB off the two pages.

The adoption's subscription half was not dead: it gives a fresh install's
first admin the config's feeds. It stays as adopt_catalogue, now tested.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TAC7sLVqfKmY6rsTLXzNgk
This commit is contained in:
2026-09-12 01:55:44 +00:00
parent 8937f35f00
commit dc63d6acaf
20 changed files with 235 additions and 298 deletions

View File

@@ -26,9 +26,6 @@ pub struct General {
/// How often to re-check feeds: "every 30m", "every 4h", "90" (minutes), "1d".
/// A feed's own `schedule` overrides this.
pub schedule: String,
/// Superseded by `schedule`. Still read so existing configs keep working.
#[serde(skip_serializing_if = "Option::is_none")]
pub interval_mins: Option<u64>,
pub organize: Organize,
/// 0 = unlimited.
pub max_total_gb: f64,
@@ -153,7 +150,6 @@ impl Default for General {
download_dir: home().join("Podcasts"),
socket: default_socket(),
schedule: "every 60m".into(),
interval_mins: None,
organize: Organize::Feed,
max_total_gb: 0.0,
max_age_days: 0,
@@ -176,8 +172,8 @@ impl Default for Torrent {
}
impl General {
/// Minutes between checks. Falls back to the legacy `interval_mins`, then to an hour.
/// A malformed value warns rather than stopping the daemon.
/// Minutes between checks, or an hour when `schedule` is empty or unreadable. A malformed
/// value warns rather than stopping the daemon.
pub fn interval(&self) -> u64 {
if let Some(n) = parse_interval(&self.schedule) {
return n;
@@ -185,7 +181,7 @@ impl General {
if !self.schedule.trim().is_empty() {
tracing::warn!(schedule = %self.schedule, "unrecognised schedule; using the default");
}
self.interval_mins.filter(|n| *n > 0).unwrap_or(60)
60
}
}
@@ -278,9 +274,7 @@ pub fn config_path() -> PathBuf {
if let Ok(p) = std::env::var("IPX_CONFIG") {
return PathBuf::from(p);
}
dirs::config_dir()
.unwrap_or_else(|| home().join(".config"))
.join("ipx/config.toml")
xdg("XDG_CONFIG_HOME", ".config").join("ipx/config.toml")
}
/// `$IPX_DATA_DIR`, else `$XDG_DATA_HOME/ipx`.
@@ -288,9 +282,7 @@ pub fn data_dir() -> PathBuf {
if let Ok(p) = std::env::var("IPX_DATA_DIR") {
return PathBuf::from(p);
}
dirs::data_dir()
.unwrap_or_else(|| home().join(".local/share"))
.join("ipx")
xdg("XDG_DATA_HOME", ".local/share").join("ipx")
}
fn default_socket() -> PathBuf {
@@ -346,8 +338,16 @@ pub fn unique_slug(text: &str, taken: &BTreeMap<String, Feed>) -> String {
(2..).map(|n| format!("{base}-{n}")).find(|s| !taken.contains_key(s)).unwrap()
}
/// `$var`, or `~/fallback` when it is unset or empty, as the XDG base directory spec says.
fn xdg(var: &str, fallback: &str) -> PathBuf {
std::env::var_os(var)
.filter(|v| !v.is_empty())
.map(PathBuf::from)
.unwrap_or_else(|| home().join(fallback))
}
fn home() -> PathBuf {
dirs::home_dir().unwrap_or_else(|| PathBuf::from("."))
std::env::var_os("HOME").map(PathBuf::from).unwrap_or_else(|| PathBuf::from("."))
}
fn expand_tilde(p: &Path) -> PathBuf {
@@ -367,6 +367,8 @@ mod tests {
r#"
[general]
download_dir = "/tmp/pods"
# A key older versions read. An old config that still has it has to load.
interval_mins = 45
[feeds.example]
url = "https://example.com/feed.xml"
@@ -403,22 +405,17 @@ mod tests {
}
#[test]
fn interval_falls_back_through_legacy_then_default() {
fn interval_falls_back_to_an_hour() {
let mut g = General::default();
assert_eq!(g.interval(), 60, "the default schedule");
g.schedule = "every 15m".into();
assert_eq!(g.interval(), 15);
// A config written before `schedule` existed still works.
// Empty or garbage must not stop the daemon.
g.schedule = String::new();
g.interval_mins = Some(45);
assert_eq!(g.interval(), 45);
// Garbage must not stop the daemon.
assert_eq!(g.interval(), 60);
g.schedule = "whenever".into();
assert_eq!(g.interval(), 45);
g.interval_mins = None;
assert_eq!(g.interval(), 60);
}