Steps 7 and 8: torrents, OPML, and polish
librqbit replaces the vendored BitTorrent 4.2.1 tree. Torrents download in place because seeding serves the files it downloaded, so the planned stage-then-move would have broken it. The stall budget now also covers magnet metadata resolution, which otherwise never returns against a dead swarm and wedged the scan. Adds add/rm/import/export, tracing setup, systemd units and README. A successful swarm download is unverified: no reachable peers here. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RPyeapneuXrCdojsaiXGbe
This commit is contained in:
@@ -188,6 +188,33 @@ fn default_socket() -> PathBuf {
|
||||
}
|
||||
}
|
||||
|
||||
/// Feed ids are the TOML table key, so they must be readable and punctuation-free.
|
||||
pub fn slug(text: &str) -> String {
|
||||
let mut out = String::new();
|
||||
for c in text.chars() {
|
||||
if c.is_ascii_alphanumeric() {
|
||||
out.push(c.to_ascii_lowercase());
|
||||
} else if c.is_alphanumeric() {
|
||||
out.push(c); // Keep non-ASCII letters; TOML bare keys are stricter, but we quote.
|
||||
} else if !out.ends_with('-') {
|
||||
out.push('-');
|
||||
}
|
||||
}
|
||||
let out = out.trim_matches('-').to_owned();
|
||||
let out: String = out.chars().take(40).collect();
|
||||
let out = out.trim_matches('-').to_owned();
|
||||
if out.is_empty() { "feed".into() } else { out }
|
||||
}
|
||||
|
||||
/// `slug`, with a numeric suffix if that id is taken.
|
||||
pub fn unique_slug(text: &str, taken: &BTreeMap<String, Feed>) -> String {
|
||||
let base = slug(text);
|
||||
if !taken.contains_key(&base) {
|
||||
return base;
|
||||
}
|
||||
(2..).map(|n| format!("{base}-{n}")).find(|s| !taken.contains_key(s)).unwrap()
|
||||
}
|
||||
|
||||
fn home() -> PathBuf {
|
||||
dirs::home_dir().unwrap_or_else(|| PathBuf::from("."))
|
||||
}
|
||||
@@ -241,6 +268,24 @@ mod tests {
|
||||
assert_eq!(t.ports(), (6881, 6889), "reversed range is not a range");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn slugs_are_readable_and_unique() {
|
||||
assert_eq!(slug("Accidental Tech Podcast"), "accidental-tech-podcast");
|
||||
assert_eq!(slug(" The Daily!! "), "the-daily");
|
||||
assert_eq!(slug("99% Invisible"), "99-invisible");
|
||||
assert_eq!(slug("///"), "feed");
|
||||
assert_eq!(slug("").len(), 4);
|
||||
assert!(slug(&"x".repeat(100)).len() <= 40);
|
||||
|
||||
let mut taken = BTreeMap::new();
|
||||
taken.insert("the-daily".to_string(), Feed {
|
||||
url: "u".into(), folder: None, keywords: vec![], allow_explicit: false,
|
||||
auto_download: true, max_new_per_check: None, username: None,
|
||||
password: None, password_env: None,
|
||||
});
|
||||
assert_eq!(unique_slug("The Daily", &taken), "the-daily-2");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn password_env_wins_over_literal() {
|
||||
let mut f = Feed {
|
||||
|
||||
Reference in New Issue
Block a user