diff --git a/app.rb b/app.rb index 12f840b..0246b79 100644 --- a/app.rb +++ b/app.rb @@ -291,6 +291,15 @@ module Maptool json ok: true end + # What's actually in the per-country download cache right now (not the + # same as offline.pmtiles' own meta.sources — see cached_extracts) — + # read-only, same tier as GET /api/offline-regions above, so the region + # picker can show a size + delete affordance next to each cached + # country regardless of whether it's still selected or already built. + get "/api/offline-tiles/cache" do + json extracts: PmtilesBuilder.cached_extracts(DATA_DIR) + end + # Deletes one country's cached Geofabrik extract only — e.g. to force a # clean re-download of just that one, or reclaim space for a country # that's no longer selected (the cache otherwise keeps it indefinitely). diff --git a/docs/development.adoc b/docs/development.adoc index 7538f4b..13ce45a 100644 --- a/docs/development.adoc +++ b/docs/development.adoc @@ -147,7 +147,15 @@ which only changes what a *future* build covers and never frees anything already on disk. `DELETE /api/offline-tiles/cache/:code` removes just one country's cached extract, e.g. to force a clean re-download of it without touching the -rest of the cache. +rest of the cache. `GET /api/offline-tiles/cache` lists what's +actually in that cache right now (code/size/Last-Modified per file) — +not the same thing as offline.pmtiles' own `meta.sources`, which only +reflects the last successful build: a country can be cached without +ever having been built (ticked, not yet Applied) or built from without +still being selected (unticked since). The region picker shows this +next to each country's checkbox, with its own delete button, rather +than only offering "delete everything" on the whole offline.pmtiles +row. Each successful build also writes a small sidecar (`offline.pmtiles. meta.json` — each source country's code/Geofabrik slug/Last-Modified; diff --git a/lib/maptool/pmtiles_builder.rb b/lib/maptool/pmtiles_builder.rb index a4844fa..cccf2e9 100644 --- a/lib/maptool/pmtiles_builder.rb +++ b/lib/maptool/pmtiles_builder.rb @@ -448,6 +448,26 @@ module Maptool File.delete("#{path}.last_modified") if File.exist?("#{path}.last_modified") end + # What's actually sitting in the per-country download cache right now — + # not the same thing as offline_meta's "sources", which only reflects + # whatever the *last successful build* used. A country can be cached + # without ever having been built (selected, then deselected again + # before Apply) or built from without still being selected, and this is + # what lets the region picker show/clear either case. + def self.cached_extracts(data_dir) + cache_dir = geofabrik_cache_dir(data_dir) + return [] unless Dir.exist?(cache_dir) + + Dir.glob(File.join(cache_dir, "*.osm.pbf")).map do |path| + stamp_path = "#{path}.last_modified" + { + "code" => File.basename(path, ".osm.pbf"), + "size" => File.size(path), + "last_modified" => File.exist?(stamp_path) ? File.read(stamp_path) : nil, + } + end.sort_by { |s| s["code"] } + end + # --- terrain.pmtiles --- # planet.pmtiles (z0-12, always) plus whichever regional chunks diff --git a/public/css/style.css b/public/css/style.css index 4679802..b3fc4e9 100644 --- a/public/css/style.css +++ b/public/css/style.css @@ -416,11 +416,20 @@ html, body { border-radius: 4px; } +.offline-data-panel__region-list li { + display: flex; + align-items: center; + justify-content: space-between; + gap: 6px; +} + .offline-data-panel__region-list label { display: flex; align-items: center; gap: 6px; padding: 4px 8px; + min-width: 0; + flex: 1; cursor: pointer; } @@ -428,6 +437,34 @@ html, body { background: #f0f0f0; } +.offline-data-panel__cache-info { + display: flex; + align-items: center; + gap: 4px; + padding-right: 8px; + color: #888; + font-size: 11px; + white-space: nowrap; +} + +.offline-data-panel__cache-delete { + border: none; + background: none; + padding: 0 2px; + color: #a33; + font-size: 11px; + cursor: pointer; +} + +.offline-data-panel__cache-delete:hover { + text-decoration: underline; +} + +.offline-data-panel__cache-delete:disabled { + opacity: 0.5; + cursor: default; +} + /* Same rounded-square badge ratio as icons.js's own MAP_MARKER_ICON (rx 5 of a 22-wide square) — a consistent frame rather than however each flag's own artwork happens to fill an unbordered square. */ diff --git a/public/js/offline_data.js b/public/js/offline_data.js index b4e2852..3fc4a2e 100644 --- a/public/js/offline_data.js +++ b/public/js/offline_data.js @@ -81,26 +81,72 @@ class RegionPicker { container.appendChild(section); let data; + let cache; try { - data = await (await fetch("/api/offline-regions")).json(); + [data, cache] = await Promise.all([ + fetch("/api/offline-regions").then((r) => r.json()), + fetch("/api/offline-tiles/cache").then((r) => r.json()), + ]); } catch { this._list.innerHTML = `