1
Fork 0
mirror of https://github.com/thegeneralist01/archivr synced 2026-07-21 18:55:36 +02:00

feat: precompute cached_bytes per entry in DB (#13)

Store how many bytes of each entry's artifacts are already on disk from
an earlier entry (content-addressed blob deduplication means shared
blobs are only stored once).

Design
------
- Add `cached_bytes INTEGER NOT NULL DEFAULT 0` to `archived_entries`
- Precompute at capture time via `database::refresh_entry_cached_bytes`
  called after all artifacts are saved for every capture path
  (web page, generic URL, tweet, yt-dlp/local)
- One-time migration in `initialize_schema`: detects missing column via
  PRAGMA table_info, ALTERs the table, then back-fills all existing rows
  with the correlated subquery
- `database::cascade_cached_bytes_after_delete` ready for when entry
  deletion is implemented; designed to run asynchronously after the
  delete is acknowledged to the user
- `cached_bytes` included in `EntrySummary` and all four SELECT paths
  (list_root_entries, search_entries, list_entries_for_collection,
  entries_for_tag) via the shared ENTRY_SELECT_COLS constant

Frontend
--------
- `EntryRow` shows a `% cached` sub-line under the size when non-zero,
  with a tooltip showing the raw cached byte count
- No separate API endpoint or extra fetch — value rides in the existing
  entries list response at zero extra query cost per read
This commit is contained in:
TheGeneralist 2026-07-03 11:56:55 +02:00 committed by GitHub
parent c85cce579b
commit 90d17c9d25
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 161 additions and 21 deletions

View file

@ -241,7 +241,7 @@ export default function App() {
</div>
)}
{view === 'archive' && (
<EntriesView
<EntriesView
entries={entries}
selectedEntryUid={selectedEntryUid}
onSelectEntry={selectEntry}

View file

@ -39,7 +39,14 @@ export default function EntryRow({ entry, archiveId, isSelected, onSelect }) {
<div className="col-type">
<span className="type-pill">{valueText(entry.entity_kind)}</span>
</div>
<div className="col-size">{formatBytes(entry.total_artifact_bytes)}</div>
<div className="col-size">
<span className="size-total">{formatBytes(entry.total_artifact_bytes)}</span>
{entry.cached_bytes > 0 && entry.total_artifact_bytes > 0 && (
<span className="size-cached-pct" title={`${formatBytes(entry.cached_bytes)} already on disk from an earlier entry`}>
{Math.round(entry.cached_bytes / entry.total_artifact_bytes * 100)}% cached
</span>
)}
</div>
<div className="url-cell col-url">{valueText(entry.original_url)}</div>
</div>
);

View file

@ -310,7 +310,16 @@ select {
.col-added { width: 162px; color: var(--muted); }
.col-title { flex: 1 1 0; min-width: 0; overflow: hidden; display: flex; align-items: center; gap: 0.42em; }
.col-type { width: 116px; }
.col-size { width: 96px; }
.col-size { width: 100px; display: flex; flex-direction: column; justify-content: center; gap: 1px; }
.size-total { font-variant-numeric: tabular-nums; }
.size-cached-pct {
font-size: 10px;
color: var(--muted);
opacity: 0.8;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.col-url { flex: 0 0 30%; min-width: 0; overflow: hidden; }
.entry-header-row > div:first-child,