Everything this application knew about a failure went to stderr on the desktop and to logcat on Android, and both are gone the moment the terminal closes or the ring buffer wraps. That is fine when the person debugging is sitting at the machine. It is useless for the case NFR-OPS-1 actually describes, and the one Android makes normal: somebody reproduces a bug on a tablet, and then sends us a file. A large amount of Android behaviour has never run on a device — image intents read over JNI, an ExportProvider, a class loaded through the activity's class loader, memory-pressure eviction, lost-root recovery — and the single most likely failure of the lot, the activity's loader not resolving our classes from android_main's thread, produces one line that scrolls past. That line is now in a file, with the thread that emitted it named beside it. dr_plat::state answers "where does this platform keep state for this app": $XDG_STATE_HOME/darkroom on Linux, and on Android whatever the entry point declares. Separate from configuration and from the catalog for the reason XDG separates them — state is the thing nobody backs up and the user may delete without consequence. dr_plat::diagnostics is the sink. Two files of 4 MiB, so the worst case is a number rather than a discovery on a full phone; one line per write with no BufWriter anywhere, because on Android processes are killed rather than ended and a buffered log loses exactly the line it was kept for; and redaction applied at the sink rather than at the call sites, since a rule every author has to remember is not a rule. It tees the platform's own logger rather than replacing it, so logcat is unchanged — losing that while debugging would have made this a downgrade. Android logs to external_data_path, not internal. Both are app-private and both survive backgrounding; what separates them is that /data/data/<pkg>/files needs run-as against a debuggable build to read and /sdcard/Android/data/<pkg>/files is a plain adb pull from any build. A log nobody can retrieve is not a diagnostic. The consequence is that anyone holding the tablet can read it, which is why the redaction is where it is, and why configuration stays on internal_data_path. What is redacted is what NFR-SEC-2 and NFR-OPS-1 name: credentials and tokens, found by the keyword that nearly always sits next to them, plus the two forms that carry one with no keyword at all — an Authorization scheme and a URL's userinfo. What is deliberately not redacted is filesystem paths and the names of the user's photographs. They are in neither requirement's list, and "failed to decode <redacted>" is not a diagnostic; the preview-and-consent step NFR-OPS-1 asks for governs those better than scrubbing would, because it lets the user look. The over-redaction failure is tested as carefully as the under-redaction one. A scrubber that eats "using basic sRGB as the fallback" makes a log useless without ever being caught.
45 lines
1.8 KiB
Rust
45 lines
1.8 KiB
Rust
//! DarkRoom desktop entry point.
|
|
//!
|
|
//! darkroom-desktop <file-or-directory>...
|
|
|
|
use std::path::PathBuf;
|
|
|
|
use dr_plat::diagnostics::Installed;
|
|
|
|
fn main() -> anyhow::Result<()> {
|
|
// Built rather than `init`ed, so the same logger can be handed to the
|
|
// diagnostics tee: `env_logger` keeps writing to stderr exactly as before,
|
|
// and every record it accepts is also appended to the on-disk log
|
|
// (NFR-OPS-1). `filter()` is asked afterwards because the environment may
|
|
// have overridden the default below, and the file must not be quieter than
|
|
// the terminal.
|
|
let console = env_logger::Builder::from_env(env_logger::Env::default().default_filter_or(
|
|
"info,wgpu_core=warn,wgpu_hal=warn,zbus=warn,tracing=warn,calloop=warn,rawler=warn",
|
|
))
|
|
.build();
|
|
let level = console.filter();
|
|
let logging = dr_plat::diagnostics::install(Box::new(console), level);
|
|
|
|
log::info!("DarkRoom v{}", env!("CARGO_PKG_VERSION"));
|
|
// First thing in the file, so a user asked for "the log" can find it
|
|
// without being told a path over the phone.
|
|
match &logging {
|
|
Installed::ToFile(path) => log::info!("logging to {}", path.display()),
|
|
Installed::ConsoleOnly(why) => log::warn!("no log file this session: {why}"),
|
|
}
|
|
|
|
let paths: Vec<PathBuf> = std::env::args().skip(1).map(PathBuf::from).collect();
|
|
if paths.is_empty() {
|
|
eprintln!("usage: darkroom-desktop <file-or-directory>...");
|
|
}
|
|
|
|
dr_ui::run(paths)?;
|
|
|
|
// Skip Rust's normal static/thread-local teardown on the way out: a
|
|
// background zbus/keyring connection opened by dr_ui::launch_ui can
|
|
// still be alive here, and unwinding through it races its async-io
|
|
// reactor thread, panicking with "thread local ... during or after
|
|
// destruction" when the window is closed.
|
|
std::process::exit(0);
|
|
}
|