viewer: proven read-only; its database check no longer creates SQLite files
check / check (push) Canceled after 0s
check / check (push) Canceled after 0s
tests/read_only.rs: with every file and folder of the books read-only on disk, the viewer loads, follows live, and renders every page (receipts included), and every file keeps its bytes, size, and mtime; on writable books nothing appears beside the database either. Found: with no writer running, the snapshot's read-only open of the WAL database had to create -wal/-shm (a write into the books) and failed on read-only books, showing a false "database unreadable". With no -wal file no writer has it open, so check_disk now reads it immutable. just mutants-gui sweeps only what the viewer must get right: the view model's numbers (books.rs) and their formatting (fmt.rs); 179 mutants. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GWVo7x3UHw5kwCUaWxiejX
This commit is contained in:
1 parent
13a52c5849
commit
fe9625502f
3 files changed
+219
-7
No files matched your search
@@ -229,10 +229,28 @@ impl Snapshot {
|
||||
if !path.exists() {
|
||||
return DiskCheck::Missing;
|
||||
}
|
||||
let mut conn = match Connection::open_with_flags(
|
||||
&path,
|
||||
OpenFlags::SQLITE_OPEN_READ_ONLY | OpenFlags::SQLITE_OPEN_NO_MUTEX,
|
||||
) {
|
||||
// A WAL reader needs the -wal and -shm files, and creates them if
|
||||
// they're missing: a write into the books, and a failure when the
|
||||
// books are read-only to this user. With no -wal file no writer
|
||||
// has the database open (SQLite deletes it when the last one
|
||||
// closes), so it can't change under us: read it as an immutable
|
||||
// file, creating nothing (found by the viewer's read-only test,
|
||||
// 2026-10-14).
|
||||
let live = path.with_extension("db-wal").exists();
|
||||
let opened = if live {
|
||||
Connection::open_with_flags(
|
||||
&path,
|
||||
OpenFlags::SQLITE_OPEN_READ_ONLY | OpenFlags::SQLITE_OPEN_NO_MUTEX,
|
||||
)
|
||||
} else {
|
||||
Connection::open_with_flags(
|
||||
immutable_uri(&path),
|
||||
OpenFlags::SQLITE_OPEN_READ_ONLY
|
||||
| OpenFlags::SQLITE_OPEN_NO_MUTEX
|
||||
| OpenFlags::SQLITE_OPEN_URI,
|
||||
)
|
||||
};
|
||||
let mut conn = match opened {
|
||||
Ok(c) => c,
|
||||
Err(e) => return DiskCheck::Unreadable(e.to_string()),
|
||||
};
|
||||
@@ -286,3 +304,18 @@ impl Snapshot {
|
||||
read_attachment_bytes(&self.conn, &self.blobs, attachment_id)
|
||||
}
|
||||
}
|
||||
|
||||
/// `file:` URI for `path`, read-only and immutable (sqlite.org/uri.html):
|
||||
/// every byte outside the unreserved set is percent-encoded.
|
||||
fn immutable_uri(path: &Path) -> String {
|
||||
let mut out = String::from("file:");
|
||||
for b in path.to_string_lossy().bytes() {
|
||||
if b.is_ascii_alphanumeric() || b"/-_.~".contains(&b) {
|
||||
out.push(b as char);
|
||||
} else {
|
||||
out.push_str(&format!("%{b:02X}"));
|
||||
}
|
||||
}
|
||||
out.push_str("?mode=ro&immutable=1");
|
||||
out
|
||||
}
|
||||
Reference in new issue
Block a user