Stability hardening: crash.log + disk-full preflight (2.5)
Two independent improvements from Phase 2.5.
## Panic hook → crash.log
GUI users launched from a .desktop file have no stderr — today panics
vanish without trace. Install a panic::set_hook early in main() that
appends a structured entry to <library_root>/yt-offline.crash.log for
every panic from every thread (UI, axum workers, downloader spawns,
tray bg thread, thumbnail decode workers).
Each entry: ISO-8601 timestamp, thread name, file:line, panic message,
and a backtrace when RUST_BACKTRACE is set. The default panic hook
still runs after ours, so dev workflows keep getting the stderr trace.
Bounded: when the log passes 256 KB the next write rotates it to .1
(one previous generation kept). No external date library — small
civil-time formula in-tree.
## Disk-full preflight
Before yt-dlp ever launches, statvfs the target filesystem. If free
space is under 500 MB, push a synthetic Failed job classified as
DiskFull instead of starting yt-dlp. That way:
- Users see the actual problem ("only 230 MB free on /mnt/…") in the
Downloads modal with the same "free up space and retry" hint the
error-classifier emits for mid-download ENOSPC.
- No half-written file lands on disk.
- No 30-second yt-dlp warmup wasted on a doomed run.
statvfs returning None (non-Unix, missing path) skips the check —
better to let yt-dlp run than to refuse on no data.
5 new tests: 3 in crash (civil-time, log shape, real-panic capture),
2 in disk_space (statvfs plumbing + missing-path None). 74 pass total.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
55b90a22b6
commit
c1bd88d800
6 changed files with 400 additions and 5 deletions
24
src/main.rs
24
src/main.rs
|
|
@ -15,7 +15,9 @@
|
|||
|
||||
mod app;
|
||||
mod config;
|
||||
mod crash;
|
||||
mod database;
|
||||
mod disk_space;
|
||||
mod download_options;
|
||||
mod downloader;
|
||||
mod error_class;
|
||||
|
|
@ -32,13 +34,25 @@ mod ytdlp_bin;
|
|||
fn main() -> eframe::Result<()> {
|
||||
let args: Vec<String> = std::env::args().collect();
|
||||
|
||||
// Load the config first — both modes share the channels_root path,
|
||||
// and the panic hook needs it before anything else runs so a crash
|
||||
// in the very early startup (DB open, tray init, ksni) still lands
|
||||
// in the crash log.
|
||||
let cwd = std::env::current_dir().unwrap_or_else(|_| std::path::PathBuf::from("."));
|
||||
let cfg_path = cwd.join("config.toml");
|
||||
let mut cfg = config::Config::load(&cfg_path)
|
||||
.unwrap_or_else(|_| config::Config::default_with_dir(cwd.join("channels")));
|
||||
|
||||
// Install the persistent panic logger. Logs go alongside the SQLite
|
||||
// database; the parent of channels_root is the same "library root"
|
||||
// the desktop UI shows.
|
||||
let crash_dir = cfg.backup.directory.parent()
|
||||
.map(std::path::Path::to_path_buf)
|
||||
.unwrap_or_else(|| cfg.backup.directory.clone());
|
||||
crash::install(&crash_dir);
|
||||
|
||||
// --web [port] → run the web interface instead of the GUI
|
||||
if let Some(pos) = args.iter().position(|a| a == "--web") {
|
||||
let mut cfg = {
|
||||
let cwd = std::env::current_dir().unwrap_or_else(|_| std::path::PathBuf::from("."));
|
||||
config::Config::load(&cwd.join("config.toml"))
|
||||
.unwrap_or_else(|_| config::Config::default_with_dir(cwd.join("channels")))
|
||||
};
|
||||
// Override port if provided after --web
|
||||
if let Some(port_str) = args.get(pos + 1) {
|
||||
if let Ok(port) = port_str.parse::<u16>() {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue