Step 2: TOML config and SQLite state
config.rs replaces iPXSettings.py and feeds.plist; db.rs replaces the per-feed .ipxd plists, history.dat and qmcache.dat. enclosures.url is UNIQUE, which is the dedupe key the old pickle history provided. ipx list is the first working subcommand. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RPyeapneuXrCdojsaiXGbe
This commit is contained in:
64
src/main.rs
64
src/main.rs
@@ -1,3 +1,63 @@
|
||||
fn main() {
|
||||
println!("ipx {}", env!("CARGO_PKG_VERSION"));
|
||||
mod config;
|
||||
mod db;
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::{Parser, Subcommand};
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(name = "ipx", version, about = "A headless podcatcher")]
|
||||
struct Cli {
|
||||
/// Config file (default: $XDG_CONFIG_HOME/ipx/config.toml)
|
||||
#[arg(long, global = true)]
|
||||
config: Option<std::path::PathBuf>,
|
||||
|
||||
#[command(subcommand)]
|
||||
command: Command,
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
enum Command {
|
||||
/// Show configured feeds and their state
|
||||
List,
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let cli = Cli::parse();
|
||||
let config_path = cli.config.unwrap_or_else(config::config_path);
|
||||
let cfg = config::Config::load(&config_path)?;
|
||||
let db = db::Db::open(&config::data_dir().join("state.db"))?;
|
||||
|
||||
match cli.command {
|
||||
Command::List => list(&cfg, &db),
|
||||
}
|
||||
}
|
||||
|
||||
fn list(cfg: &config::Config, db: &db::Db) -> Result<()> {
|
||||
if cfg.feeds.is_empty() {
|
||||
println!("No feeds configured in {}", config::config_path().display());
|
||||
return Ok(());
|
||||
}
|
||||
for (id, feed) in &cfg.feeds {
|
||||
let s = db.feed_summary(id)?;
|
||||
let title = s.title.as_deref().unwrap_or("-");
|
||||
println!("{id} {title}");
|
||||
println!(" url {}", feed.url);
|
||||
println!(" last checked {}", ago(s.last_checked));
|
||||
println!(" entries {} ({} downloaded)", s.entries, s.downloaded);
|
||||
if let Some(err) = &s.last_error {
|
||||
println!(" last error {err}");
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn ago(t: Option<i64>) -> String {
|
||||
let Some(t) = t else { return "never".into() };
|
||||
let secs = (db::now() - t).max(0);
|
||||
match secs {
|
||||
s if s < 90 => format!("{s}s ago"),
|
||||
s if s < 5400 => format!("{}m ago", s / 60),
|
||||
s if s < 172_800 => format!("{}h ago", s / 3600),
|
||||
s => format!("{}d ago", s / 86_400),
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user