Skip to content

Commit ddc5135

Browse files
committed
Feature: Show Tile Cache size in menu
1 parent 2f873b0 commit ddc5135

4 files changed

Lines changed: 43 additions & 9 deletions

File tree

docs/data-storage.md

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,18 @@
22

33
All data storage is cross-browser (Chrome, Firefox, Safari) — none depends on the service worker.
44

5-
| Storage | What it stores | Purpose | Cross-browser |
6-
|---|---|---|---|
7-
| **IndexedDB** | Photo objects (full-size base64 image + thumbnail + all metadata), album objects, geo caches | Primary data store — the app reads from here on every load | Yes |
8-
| **Disk (matrix-data.json)** | JSON dump of all IndexedDB content | Backup that survives browser data clearing; auto-saved by `serve.py` | Yes |
9-
| **Disk (matrix-photos/)** | Individual image files (`{id}.jpg`, `{id}_thumb.jpg`) extracted from base64 | Used by the export flow (avoids embedding huge base64 in JSON) and for lightbox display after import | Yes |
5+
| Storage | What it stores | Purpose |
6+
|---|---|---|
7+
| **matrix-photos/** | Full-size images (`{id}.jpg`) and thumbnails (`{id}_thumb.jpg`) | Source of truth for image data — written on upload, served directly for display |
8+
| **IndexedDB** | Photo metadata (coords, date, camera, notes) + file path references to `matrix-photos/`, album objects | Source of truth for metadata — the app reads from here on every load |
9+
| **matrix-data.json** | JSON backup of metadata, albums, and geo caches (file paths, not image data) | Backup that survives browser data clearing; auto-saved by `serve.py` |
1010

11-
**IndexedDB** is the source of truth during normal use. `matrix-data.json` and `matrix-photos/` are redundant backups maintained by `serve.py`. If you clear browser data, the app offers to restore from `matrix-data.json` on next load.
11+
## How it works
1212

13-
## Scalability note
13+
**Image data** lives on disk in `matrix-photos/`. IndexedDB stores only metadata and a path reference (e.g. `matrix-photos/p_123.jpg`), keeping the database lean (~50KB/photo vs ~4MB with embedded base64). This lets the app handle tens of thousands of photos without bloating browser storage.
1414

15-
IndexedDB currently stores full-size images as base64 inside each photo record. At scale (thousands of large photos), this can cause high memory usage at startup since all records are loaded into memory. A future refactor will move full-size images to `matrix-photos/` only, keeping IndexedDB lean (metadata + thumbnails). See the [planned refactor](../plans/reactive-bubbling-creek.md) for details.
15+
**On upload**, `processFiles()` saves the full-size image to disk via `POST /api/photos/{id}`, verifies the file is servable, then stores the file path in IndexedDB.
16+
17+
**On load**, the app reads metadata from IndexedDB and references images by their disk paths. If IndexedDB is empty (e.g. after clearing browser data), the app offers to restore metadata from `matrix-data.json`. The image files in `matrix-photos/` remain intact regardless.
18+
19+
**Auto-save** (`scheduleAutoSave()`) writes metadata to `matrix-data.json` via `POST /api/data` after any data-modifying action. This file contains file paths — not image data — so it stays small.

index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
</div>
3636
<div class="settings-item" onclick="emptyTileCache()">
3737
<span class="settings-item-icon">🗺</span>
38-
<div><div class="settings-item-label">Empty Map Cache</div><div class="settings-item-sub">Clear cached map tiles from disk</div></div>
38+
<div><div class="settings-item-label">Empty Map Cache</div><div class="settings-item-sub" id="cache-size-status">Clear cached map tiles from disk</div></div>
3939
</div>
4040
</div>
4141
</div>

js/data.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,15 @@ function toggleSettingsMenu(e) {
122122
const dd = document.getElementById('settings-dropdown');
123123
dd.classList.toggle('open');
124124
updateAutoSaveIndicator();
125+
if (dd.classList.contains('open')) updateCacheSizeStatus();
126+
}
127+
128+
function updateCacheSizeStatus() {
129+
fetch('/api/tiles/size').then(r => r.json()).then(d => {
130+
const mb = (d.bytes / (1024 * 1024)).toFixed(0);
131+
const el = document.getElementById('cache-size-status');
132+
if (el) el.textContent = `${mb} MB used (${d.limitMB} MB limit)`;
133+
}).catch(() => {});
125134
}
126135
document.addEventListener('click', e => {
127136
if (!e.target.closest('.settings-btn') && !e.target.closest('.settings-dropdown')) {
@@ -161,6 +170,7 @@ async function emptyTileCache() {
161170
const tileCacheName = cacheNames.find(n => n.endsWith('-tiles'));
162171
if (tileCacheName) await caches.delete(tileCacheName);
163172
showToast(`Map cache cleared — ${d.removed} file${d.removed !== 1 ? 's' : ''} removed`, 'success');
173+
updateCacheSizeStatus();
164174
} else {
165175
showToast('Failed to clear map cache', 'error');
166176
}

serve.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,8 @@ def do_GET(self):
290290
try:
291291
if self.path == "/api/data":
292292
self._serve_data()
293+
elif self.path == "/api/tiles/size":
294+
self._tile_cache_size()
293295
elif self.path.startswith("/api/tiles/proxy?"):
294296
self._proxy_tile()
295297
elif self.path.startswith("/api/video/download?"):
@@ -632,6 +634,24 @@ def _video_abort(self):
632634
self.end_headers()
633635
self.wfile.write(b'{"ok":true}')
634636

637+
def _tile_cache_size(self):
638+
total = 0
639+
count = 0
640+
if os.path.isdir(TILES_DIR):
641+
for root, _, fnames in os.walk(TILES_DIR):
642+
for fn in fnames:
643+
if fn.startswith('.'):
644+
continue
645+
try:
646+
total += os.stat(os.path.join(root, fn)).st_size
647+
count += 1
648+
except OSError:
649+
pass
650+
self.send_response(200)
651+
self.send_header('Content-Type', 'application/json')
652+
self.end_headers()
653+
self.wfile.write(json.dumps({'bytes': total, 'files': count, 'limitMB': MAX_TILES_MB}).encode())
654+
635655
def _clear_tile_cache(self):
636656
"""Wipe the entire matrix-tiles directory and recreate it empty for a clean state."""
637657
try:

0 commit comments

Comments
 (0)