Settings gains a file picker that reads an exported Hot Tub database and merges its favorites into this client's. The file never leaves the device: sqlite.js is a small read-only reader -- header, schema, table b-trees, record decoding, and the overflow pages that real rows here spill onto -- which is all it takes to walk one table, and avoids putting a wasm SQLite behind a CDN fetch. The two sides don't agree on what identifies a video. The app keys one by a hash it computes locally (a 64-hex string); the server, and so this client, keys it as something like "reddit-1rdudss". So the merge matches on normalized URL: entries already saved here are left exactly as they are, keeping the server id that makes a listing card's heart light up, and only genuinely new videos are appended. That means an imported favorite has no server id, so hearts now also match by URL (`data-fav-url` on the card, feed slide and favorites bar). Without it an imported favorite would look unsaved on its own card, and clicking the heart would file a second copy of the same video. Only the columns a favorite needs are read. `allFormats` is deliberately left behind: it holds resolved, signed URLs, which is exactly what favorites must not store (they expire -- see App.favorites.normalize). Tests (scratchpad): the reader checked against Python's sqlite3 on a real 12MB backup -- table list, every table's row count, all 515 favorites with their fields and order, and the 25 longest records byte-for-byte, which is where a wrong overflow split shows up; and the Settings control driven end-to-end, covering the merge, an existing favorite keeping its id, a re-import adding nothing, and a listing card recognising an imported favorite and unfavoriting it cleanly. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QBDkEXP4htyXTCZUwMLphd
320 lines
14 KiB
JavaScript
320 lines
14 KiB
JavaScript
window.App = window.App || {};
|
||
App.sqlite = App.sqlite || {};
|
||
|
||
// A read-only SQLite file reader, just big enough to walk a table and hand back
|
||
// its rows. It exists so a Hot Tub backup can be opened in the browser with no
|
||
// dependency and no upload: the file stays on the device, and a 12MB database
|
||
// costs one ArrayBuffer.
|
||
//
|
||
// What it covers: the file header, the schema table, table b-trees (interior and
|
||
// leaf), record decoding, and payloads that spill onto overflow pages -- which
|
||
// real rows here do. What it deliberately doesn't: writes, indexes, WITHOUT
|
||
// ROWID tables, encryption, and a WAL sidecar (an exported backup is a single
|
||
// checkpointed file; see readTable's error for the case that isn't true).
|
||
//
|
||
// Reference: https://www.sqlite.org/fileformat2.html
|
||
(function() {
|
||
const HEADER_MAGIC = 'SQLite format 3 |