Step 1: repo skeleton
Rust rewrite of ipodderx-core: cargo project, pinned dependency set, MIT license carrying the original 2010 copyright, README lineage note, and PROGRESS.md as the resumable build record. Dependency tree is rustls-only: librqbit's default features pull in native-tls and an OpenSSL sha1 backend, so it is built with rust-tls. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RPyeapneuXrCdojsaiXGbe
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
/target
|
||||||
4178
Cargo.lock
generated
Normal file
4178
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
22
Cargo.toml
Normal file
22
Cargo.toml
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
[package]
|
||||||
|
name = "ipx"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
anyhow = "1.0.104"
|
||||||
|
atom_syndication = "0.12.10"
|
||||||
|
clap = { version = "4.6.6", features = ["derive"] }
|
||||||
|
dirs = "7.0.0"
|
||||||
|
infer = "0.22.0"
|
||||||
|
librqbit = { version = "9.0.1", default-features = false, features = ["rust-tls", "http-api-client"] }
|
||||||
|
opml = "1.1.6"
|
||||||
|
reqwest = { version = "0.13.5", default-features = false, features = ["rustls", "http2", "gzip", "stream", "json", "charset", "system-proxy"] }
|
||||||
|
rss = "2.1.1"
|
||||||
|
rusqlite = { version = "0.40.2", features = ["bundled"] }
|
||||||
|
serde = { version = "1.0.229", features = ["derive"] }
|
||||||
|
serde_json = "1.0.151"
|
||||||
|
tokio = { version = "1.53.1", features = ["rt-multi-thread", "macros", "fs", "io-util", "net", "sync", "time", "signal"] }
|
||||||
|
toml = "1.1.5"
|
||||||
|
tracing = "0.1.44"
|
||||||
|
tracing-subscriber = { version = "0.3.23", features = ["env-filter"] }
|
||||||
25
LICENSE
Normal file
25
LICENSE
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (C) 2010 by Ray Slakinski & August Trometer
|
||||||
|
Copyright (C) 2026 by Ray Slakinski
|
||||||
|
|
||||||
|
This project is a Rust rewrite of ipodderx-core, the Python engine behind
|
||||||
|
iPodderX (2004-2008), which was released under the MIT License in 2010.
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
65
PROGRESS.md
Normal file
65
PROGRESS.md
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
# Progress
|
||||||
|
|
||||||
|
Running record of what has actually landed. Newest entry first.
|
||||||
|
The full design and step list live in the plan file at
|
||||||
|
`/config/.claude/plans/i-want-to-create-playful-quiche.md`.
|
||||||
|
|
||||||
|
## Build order
|
||||||
|
|
||||||
|
- [x] **1. Repo skeleton** — git init (`main`), `cargo init --name ipx`, deps pinned, LICENSE,
|
||||||
|
README, this file.
|
||||||
|
- [ ] **2. `config.rs` + `db.rs`** — TOML config structs + SQLite schema.
|
||||||
|
*Done when:* `ipx list` prints feeds from a hand-written config; DB created once.
|
||||||
|
- [ ] **3. `feed.rs`** — conditional GET, RSS-then-Atom parse, persist entries.
|
||||||
|
*Done when:* `ipx fetch` populates `entries`, second run 304s.
|
||||||
|
- [ ] **4. `download.rs`** — filename derivation + sanitizer, streaming download, `infer` sniff,
|
||||||
|
HTML rejection, move into place, dedupe, keyword/explicit filters, `max_new_per_check`.
|
||||||
|
*Done when:* smoke 1, 2, 4 pass.
|
||||||
|
- [ ] **5. `retention.rs`** — oldest-first quota + age reaper, `ipx reap [--dry-run]`.
|
||||||
|
*Done when:* smoke 6 passes.
|
||||||
|
- [ ] **6. `ipc.rs` + daemon** — broadcast event bus, UDS JSON-lines server, TTL scheduler,
|
||||||
|
CLI-proxies-to-daemon. *Done when:* smoke 3 passes.
|
||||||
|
- [ ] **7. `torrent.rs`** — librqbit, seed to ratio/time, stall abort. *Done when:* smoke 5 passes.
|
||||||
|
- [ ] **8. OPML + polish** — import/export, add/rm/status, tracing setup, systemd unit, README.
|
||||||
|
|
||||||
|
## Smoke tests
|
||||||
|
|
||||||
|
1. `ipx add <feed>` + `ipx fetch` → file in `download_dir/<Show>/`, row in `enclosures`.
|
||||||
|
2. `ipx fetch` again → no re-download, feed skipped for TTL.
|
||||||
|
3. `ipx daemon &` + `nc -U $XDG_RUNTIME_DIR/ipx.sock`, send `{"cmd":"fetch"}` → JSON events;
|
||||||
|
a concurrent `ipx fetch` proxies to the daemon instead of downloading in parallel.
|
||||||
|
4. Delete a downloaded file by hand, `ipx fetch` → NOT re-downloaded.
|
||||||
|
5. Torrent enclosure → downloads, moves, stops seeding at the configured ratio/time.
|
||||||
|
6. `ipx reap --dry-run` under quota pressure → oldest-first hit list; real run flips rows to
|
||||||
|
`reaped`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2026-09-09 — Step 1: repo skeleton
|
||||||
|
|
||||||
|
Repo created at `/src/ipodderx-rs`, default branch `main`, `cargo init --name ipx` (edition 2024,
|
||||||
|
rustc 1.95.0). LICENSE (MIT, carrying the 2010 copyright), README with the lineage note, and this
|
||||||
|
file. Hello-world `main.rs` builds.
|
||||||
|
|
||||||
|
Dependency versions pinned today — the later steps are written against these APIs:
|
||||||
|
|
||||||
|
| crate | version | notes |
|
||||||
|
|---|---|---|
|
||||||
|
| tokio | 1.53.1 | rt-multi-thread, macros, fs, io-util, net, sync, time, signal |
|
||||||
|
| reqwest | 0.13.5 | `default-features = false`; features **`rustls`** (not `rustls-tls` — renamed in 0.13), http2, gzip, stream, json, charset, **`system-proxy`** (env-var proxy pickup is opt-in in 0.13) |
|
||||||
|
| rss | 2.1.1 | default features; the `with-syndication` feature name in my notes does not exist — Atom is handled by the separate crate |
|
||||||
|
| atom_syndication | 0.12.10 | |
|
||||||
|
| rusqlite | 0.40.2 | `bundled` |
|
||||||
|
| librqbit | 9.0.1 | `default-features = false`; features `rust-tls`, `http-api-client`. The default `default-tls` feature pulls reqwest/native-tls **and** an OpenSSL sha1 backend (`crypto-hash`), which fails to build without pkg-config/OpenSSL headers. API not yet exercised — step 7 |
|
||||||
|
| serde 1.0.229 / serde_json 1.0.151 / toml 1.1.5 | | |
|
||||||
|
| clap | 4.6.6 | derive |
|
||||||
|
| infer 0.22.0 / dirs 7.0.0 / anyhow 1.0.104 / opml 1.1.6 | | |
|
||||||
|
| tracing 0.1.44 / tracing-subscriber 0.3.23 | | env-filter |
|
||||||
|
|
||||||
|
Deferred: nothing.
|
||||||
|
|
||||||
|
Gotcha worth keeping: three feature names in the plan were wrong against current crate versions —
|
||||||
|
`reqwest/rustls-tls` is now `rustls`, env-var proxy support moved behind `system-proxy`, and
|
||||||
|
`rss/with-syndication` does not exist. `librqbit`'s default features drag in OpenSSL; `rust-tls`
|
||||||
|
is the fix. Whole tree is rustls-only now, no C TLS dependency.
|
||||||
|
Next: step 2 — `config.rs` + `db.rs`.
|
||||||
32
README.md
Normal file
32
README.md
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
# ipodderx-rs
|
||||||
|
|
||||||
|
A headless podcatcher: scans RSS/Atom feeds, downloads enclosures (HTTP and BitTorrent),
|
||||||
|
files them into per-feed folders, and reaps old episodes to stay under a disk quota.
|
||||||
|
Runs as a one-shot CLI or as a daemon with a Unix-socket JSON event stream for a UI to attach to.
|
||||||
|
|
||||||
|
## Lineage
|
||||||
|
|
||||||
|
This is a modern Rust rewrite of [ipodderx-core](https://git.sdf1.net/rays/ipodderx-core), the
|
||||||
|
Python 2 engine behind **iPodderX** (2004-2008, Ray Slakinski & August Trometer), which was
|
||||||
|
open-sourced under the MIT License in 2010.
|
||||||
|
|
||||||
|
What carries over: the feed scan and TTL handling, GUID/URL dedupe, per-feed and per-date download
|
||||||
|
folders, keyword filters, the explicit-content filter, torrent enclosures, and "SmartSpace" — the
|
||||||
|
oldest-first disk quota reaper.
|
||||||
|
|
||||||
|
What does not: iTunes and iPhoto export via AppleScript, text-to-speech enclosures, the Windows
|
||||||
|
WMP/COM paths, XML plists and Python pickles for state, the `directory.iPodderX.com` survey ping,
|
||||||
|
3DES-encrypted preferences, and the `printMSG` stdout protocol (replaced by a JSON-lines socket).
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
Under construction. See [PROGRESS.md](PROGRESS.md) for what works today.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
`~/.config/ipx/config.toml`, state in `~/.local/share/ipx/state.db`. See PROGRESS.md until the
|
||||||
|
usage section lands.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT. See [LICENSE](LICENSE).
|
||||||
3
src/main.rs
Normal file
3
src/main.rs
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
fn main() {
|
||||||
|
println!("ipx {}", env!("CARGO_PKG_VERSION"));
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user