Announce a followed move as a feed_moved event (#115)
A feed moved to its new address was only logged by follow_move. It is now an event, feed_moved with the feed and its old and new addresses, so it goes where every other event goes: the log, with from and to as fields, the admin page's Scans view, `ipx fetch`, and the page, which gets the feed's new row. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -32,12 +32,13 @@ lines had no trace id, the access log's time was `ms`, and each event was logged
|
|||||||
| target | fields | what |
|
| target | fields | what |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `ipx::http` | `method`, `path`, `route`, `status`, `duration_ms` | one per web request; `route` is the pattern, empty for an unrouted path |
|
| `ipx::http` | `method`, `path`, `route`, `status`, `duration_ms` | one per web request; `route` is the pattern, empty for an unrouted path |
|
||||||
| `ipx::scan` | `ev` and the event's own: `feed`, `new`, `downloaded`, `failed`, `bytes`, `msg`, `url`, `feeds`, `reason`; on a failure `error.type` and, from an HTTP error, `http.response.status_code` | the daemon's events, one line each, in words; warnings are feed and download failures |
|
| `ipx::scan` | `ev` and the event's own: `feed`, `new`, `downloaded`, `failed`, `bytes`, `msg`, `url`, `feeds`, `reason`, `from`, `to`; on a failure `error.type` and, from an HTTP error, `http.response.status_code` | the daemon's events, one line each, in words; warnings are feed and download failures |
|
||||||
| `ipx::io` | `ev`, `feeds`, `pending`, `downloaded` on the `status` reply | commands arriving (`-> {...}`) and the healthcheck's answer |
|
| `ipx::io` | `ev`, `feeds`, `pending`, `downloaded` on the `status` reply | commands arriving (`-> {...}`) and the healthcheck's answer |
|
||||||
| `ipx` | message, sometimes fields | start-up, shutdown, account and config messages |
|
| `ipx` | message, sometimes fields | start-up, shutdown, account and config messages |
|
||||||
|
|
||||||
Events (`ev`): `feed_start`, `feed_done` (new, downloaded, failed, torrents), `feed_skip` (not due,
|
Events (`ev`): `feed_start`, `feed_done` (new, downloaded, failed, torrents), `feed_skip` (not due,
|
||||||
routine), `feed_error` (msg), `download_done` (bytes), `download_error` (msg, url),
|
routine), `feed_error` (msg), `feed_moved` (from, to: a permanent redirect followed, the address
|
||||||
|
updated), `download_done` (bytes), `download_error` (msg, url),
|
||||||
`torrent_deferred`, `reaped`, `scan_done` (feeds checked), `reap_done`, `status` (feeds, pending,
|
`torrent_deferred`, `reaped`, `scan_done` (feeds checked), `reap_done`, `status` (feeds, pending,
|
||||||
downloaded: the healthcheck's, every 30s), `error` (msg).
|
downloaded: the healthcheck's, every 30s), `error` (msg).
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- A feed that has moved for good (a permanent redirect) is followed to its new address, which iPX then reads from. A temporary redirect changes nothing.
|
- A feed that has moved for good (a permanent redirect) is followed to its new address, which iPX then reads from, and says so in the log as `feed_moved`. A temporary redirect changes nothing.
|
||||||
- The daemon sleeps until the next feed is due, at most ten minutes, instead of looking every minute; refreshing or adding a feed still wakes it at once.
|
- The daemon sleeps until the next feed is due, at most ten minutes, instead of looking every minute; refreshing or adding a feed still wakes it at once.
|
||||||
- A pinned feed's pin sits on the corner of its artwork, as a failing feed's mark does, instead of before its name.
|
- A pinned feed's pin sits on the corner of its artwork, as a failing feed's mark does, instead of before its name.
|
||||||
- The Directory lists feeds nobody subscribes to yet; Popular still lists what people subscribe to.
|
- The Directory lists feeds nobody subscribes to yet; Popular still lists what people subscribe to.
|
||||||
|
|||||||
14
src/ipc.rs
14
src/ipc.rs
@@ -15,6 +15,9 @@ pub enum Event {
|
|||||||
FeedSkip { feed: String, reason: String },
|
FeedSkip { feed: String, reason: String },
|
||||||
FeedDone { feed: String, new: usize, downloaded: usize, failed: usize, torrents: usize },
|
FeedDone { feed: String, new: usize, downloaded: usize, failed: usize, torrents: usize },
|
||||||
FeedError { feed: String, msg: String },
|
FeedError { feed: String, msg: String },
|
||||||
|
/// The feed answered from a new address after redirects that all said it moved for good,
|
||||||
|
/// and the catalogue now has that address (#115).
|
||||||
|
FeedMoved { feed: String, from: String, to: String },
|
||||||
Progress {
|
Progress {
|
||||||
feed: String,
|
feed: String,
|
||||||
/// Which enclosure this is about. Without it a UI cannot tell one download's
|
/// Which enclosure this is about. Without it a UI cannot tell one download's
|
||||||
@@ -50,6 +53,7 @@ impl Event {
|
|||||||
"{feed}: {new} new entries, {downloaded} downloaded, {failed} failed, {torrents} torrents deferred"
|
"{feed}: {new} new entries, {downloaded} downloaded, {failed} failed, {torrents} torrents deferred"
|
||||||
),
|
),
|
||||||
Event::FeedError { feed, msg } => format!("{feed}: error: {msg}"),
|
Event::FeedError { feed, msg } => format!("{feed}: error: {msg}"),
|
||||||
|
Event::FeedMoved { feed, from, to } => format!("{feed}: moved for good from {from} to {to}; following it"),
|
||||||
Event::Progress { file, done, total, .. } => match total {
|
Event::Progress { file, done, total, .. } => match total {
|
||||||
Some(t) if *t > 0 => format!(
|
Some(t) if *t > 0 => format!(
|
||||||
" {file}: {:.1}% ({:.1}/{:.1} MB)",
|
" {file}: {:.1}% ({:.1}/{:.1} MB)",
|
||||||
@@ -172,7 +176,7 @@ fn log_event(e: &Event, wire: bool) {
|
|||||||
($level:ident, $target:literal, $text:expr) => {
|
($level:ident, $target:literal, $text:expr) => {
|
||||||
tracing::$level!(
|
tracing::$level!(
|
||||||
target: $target,
|
target: $target,
|
||||||
ev = s("ev"), feed = s("feed"), msg = s("msg"), url = s("url"), reason = s("reason"),
|
ev = s("ev"), feed = s("feed"), msg = s("msg"), url = s("url"), reason = s("reason"), from = s("from"), to = s("to"),
|
||||||
new = n("new"), downloaded = n("downloaded"), failed = n("failed"),
|
new = n("new"), downloaded = n("downloaded"), failed = n("failed"),
|
||||||
torrents = n("torrents"), bytes = n("bytes"), feeds = n("feeds"),
|
torrents = n("torrents"), bytes = n("bytes"), feeds = n("feeds"),
|
||||||
pending = n("pending"), enclosure = n("enclosure"), files = n("files"),
|
pending = n("pending"), enclosure = n("enclosure"), files = n("files"),
|
||||||
@@ -328,6 +332,14 @@ pub async fn proxy(path: &Path, cmd: &Command) -> Result<()> {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_feed_that_moved_says_so_on_the_wire_and_in_words() {
|
||||||
|
let e = Event::FeedMoved { feed: "x".into(), from: "http://a/f".into(), to: "https://a/f".into() };
|
||||||
|
let wire = serde_json::to_value(&e).unwrap();
|
||||||
|
assert_eq!((wire["ev"].as_str(), wire["from"].as_str(), wire["to"].as_str()), (Some("feed_moved"), Some("http://a/f"), Some("https://a/f")));
|
||||||
|
assert_eq!(e.human().unwrap(), "x: moved for good from http://a/f to https://a/f; following it");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn commands_parse_from_the_wire_form() {
|
fn commands_parse_from_the_wire_form() {
|
||||||
let got: Command = serde_json::from_str(r#"{"cmd":"fetch"}"#).unwrap();
|
let got: Command = serde_json::from_str(r#"{"cmd":"fetch"}"#).unwrap();
|
||||||
|
|||||||
@@ -1374,6 +1374,7 @@ enum Outcome {
|
|||||||
/// is left alone, since the OPML would only put the old address back; so is a move onto an
|
/// is left alone, since the OPML would only put the old address back; so is a move onto an
|
||||||
/// address another feed already has.
|
/// address another feed already has.
|
||||||
async fn follow_move(ctx: &Ctx, id: &str, to: &str) -> Result<()> {
|
async fn follow_move(ctx: &Ctx, id: &str, to: &str) -> Result<()> {
|
||||||
|
// Said in the event below, not logged here: the event is the log line.
|
||||||
if subscriptions(ctx).await?.iter().any(|s| s.id != id && feed::same_feed(&s.cfg.url, to)) {
|
if subscriptions(ctx).await?.iter().any(|s| s.id != id && feed::same_feed(&s.cfg.url, to)) {
|
||||||
tracing::info!(feed = id, to, "the feed moved to an address another feed already has; leaving it");
|
tracing::info!(feed = id, to, "the feed moved to an address another feed already has; leaving it");
|
||||||
return Ok(());
|
return Ok(());
|
||||||
@@ -1382,7 +1383,7 @@ async fn follow_move(ctx: &Ctx, id: &str, to: &str) -> Result<()> {
|
|||||||
let Some(f) = cfg.feeds.get_mut(id) else { return Ok(()) };
|
let Some(f) = cfg.feeds.get_mut(id) else { return Ok(()) };
|
||||||
let from = std::mem::replace(&mut f.url, to.to_owned());
|
let from = std::mem::replace(&mut f.url, to.to_owned());
|
||||||
ctx.store_cfg(cfg).await?;
|
ctx.store_cfg(cfg).await?;
|
||||||
tracing::info!(feed = id, from, to, "the feed moved for good; following it to its new address");
|
ctx.out.emit(Event::FeedMoved { feed: id.to_owned(), from, to: to.to_owned() });
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1701,7 +1701,10 @@ async fn events(
|
|||||||
/// routine skip of a feed not due, dozens a minute that change nothing.
|
/// routine skip of a feed not due, dozens a minute that change nothing.
|
||||||
fn changed_feed(ev: &Event) -> Option<&str> {
|
fn changed_feed(ev: &Event) -> Option<&str> {
|
||||||
match ev {
|
match ev {
|
||||||
Event::FeedDone { feed, .. } | Event::FeedError { feed, .. } | Event::DownloadDone { feed, .. } => Some(feed),
|
Event::FeedDone { feed, .. }
|
||||||
|
| Event::FeedError { feed, .. }
|
||||||
|
| Event::FeedMoved { feed, .. }
|
||||||
|
| Event::DownloadDone { feed, .. } => Some(feed),
|
||||||
Event::FeedSkip { feed, reason } if !reason.starts_with("not due") => Some(feed),
|
Event::FeedSkip { feed, reason } if !reason.starts_with("not due") => Some(feed),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user