Add a log view, and Docker packaging
The Log button shows the running daemon live: feed scans, downloads, torrents and every HTTP request. It reads a ring buffer filled by a tracing layer rather than tailing a file, so it works under Docker where logs go to stdout. The access-log middleware skips /api/logs, or the panel's poll would log itself forever. Detached torrents could leave a row stuck in 'downloading' across a restart, where nothing would ever revisit it; those are requeued at startup. Dockerfile, entrypoint and compose: 114 MB runtime, config bound to 0.0.0.0 on first run since container loopback is unreachable, drops to PUID:PGID for Unraid, and a healthcheck that goes through the control socket so a wedged worker reads as unhealthy. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AdXho5tTkjFLeUXKbEjKBh
This commit is contained in:
32
src/db.rs
32
src/db.rs
@@ -703,6 +703,17 @@ impl Db {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Nothing can be in flight the moment the daemon starts, so any row still marked
|
||||
/// `downloading` is a leftover from a restart or a crash. Left alone it would sit
|
||||
/// there forever: the pending queue skips it and nothing else ever revisits it.
|
||||
pub fn requeue_interrupted(&self) -> Result<usize> {
|
||||
let conn = self.conn.lock().unwrap();
|
||||
Ok(conn.execute(
|
||||
"UPDATE enclosures SET state = 'pending' WHERE state = 'downloading' AND path IS NULL",
|
||||
[],
|
||||
)?)
|
||||
}
|
||||
|
||||
/// Puts an enclosure back in the queue so the next scan picks it up. This is how a
|
||||
/// `skipped` verdict (from a filter that has since been changed) gets revisited.
|
||||
pub fn requeue(&self, id: i64) -> Result<()> {
|
||||
@@ -782,6 +793,27 @@ mod tests {
|
||||
"search is case-insensitive and covers the description");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_restart_requeues_interrupted_downloads() {
|
||||
let db = Db::memory().unwrap();
|
||||
db.exec_for_test(
|
||||
"INSERT INTO enclosures (id, feed_id, guid, url, state, path) VALUES
|
||||
(1,'f','a','u1','downloading',NULL),
|
||||
(2,'f','b','u2','pending',NULL),
|
||||
(3,'f','c','u3','downloading','/tmp/already-here'),
|
||||
(4,'f','d','u4','done','/tmp/x');",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(db.requeue_interrupted().unwrap(), 1, "only the in-flight, fileless one");
|
||||
let conn = db.conn.lock().unwrap();
|
||||
let state = |id: i64| -> String {
|
||||
conn.query_row("SELECT state FROM enclosures WHERE id = ?1", [id], |r| r.get(0)).unwrap()
|
||||
};
|
||||
assert_eq!(state(1), "pending");
|
||||
assert_eq!(state(3), "downloading", "it has a file; leave it alone");
|
||||
assert_eq!(state(4), "done");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn enclosure_url_is_the_dedupe_key() {
|
||||
let db = Db::memory().unwrap();
|
||||
|
||||
Reference in New Issue
Block a user