pmtiles-swarm 0.98.1 β†’ 0.98.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,37 @@
7
7
  ### 🐞 Bug fixes
8
8
  - _...Add new stuff here..._
9
9
 
10
+ ## 0.98.3
11
+ ### ✨ Features and improvements
12
+
13
+ ### 🐞 Bug fixes
14
+ - **A stack could not be pointed at a completed MBTiles archive.** The source picker offered only
15
+ PMTiles, so an MBTiles the node was perfectly able to serve tiles from was simply absent from the
16
+ menu. It offers a complete one now, by the same rule the tile route enforces β€” and still hides one
17
+ that is arriving, since a stack naming that would answer 503 for every tile until the download
18
+ finished.
19
+ - **A planet dump was offered as a stack source.** The same filter read an archive with no recorded
20
+ kind as PMTiles, which is not a harmless default: a mirrored `.osm.pbf` appeared in the menu, and
21
+ naming it would have produced a recipe that failed at its first tile. The kind now falls back to
22
+ the filename before anything is assumed, which is how `prewarm.js` resolves it after the identical
23
+ mistake sent every mirrored dump round the header-read backoff for ever. Such an archive is also
24
+ labelled `unknown Β· not servable` in the list, where it previously said nothing at all.
25
+
26
+ ## 0.98.2
27
+ ### ✨ Features and improvements
28
+
29
+ ### 🐞 Bug fixes
30
+ - **The console called a complete MBTiles archive "not servable".** It is, and has been since the
31
+ tile store learned to open one: PMTiles always, MBTiles from a complete local copy. The console
32
+ never followed. It tested `kind === 'pmtiles'` in two places, so an MBTiles that had finished
33
+ hashing was labelled `mbtiles Β· not servable` in the list and had its TileJSON, preview and tile
34
+ endpoint hidden in the panel β€” all of which the node was answering perfectly well.
35
+
36
+ One rule now, matching what the tile route actually enforces, and an archive still arriving reads
37
+ as `servable once complete` rather than as one that never will be. Warming stays PMTiles-only: it
38
+ fetches the pieces a region's tiles live in, and an MBTiles is read whole or not at all, so there
39
+ is never a region of one to warm.
40
+
10
41
  ## 0.98.1
11
42
  ### ✨ Features and improvements
12
43
  - **The two archive readers no longer import each other.** Summarising an MBTiles archive reached
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pmtiles-swarm",
3
- "version": "0.98.1",
3
+ "version": "0.98.3",
4
4
  "description": "BitTorrent distribution for PMTiles map archives: create torrents, watch folders, publish and subscribe to RSS feeds, and seed through qBittorrent or an embedded client",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -1823,6 +1823,63 @@
1823
1823
  }
1824
1824
  };
1825
1825
 
1826
+ /**
1827
+ * Whether this archive has a tile endpoint at all.
1828
+ *
1829
+ * The same rule the tile route applies, rather than a second opinion
1830
+ * about it: PMTiles always, MBTiles once the file is whole. MBTiles is
1831
+ * SQLite, so it cannot be read a byte range at a time out of the swarm β€”
1832
+ * but a complete one on local disk is an ordinary database holding the
1833
+ * same tiles, and refusing to serve it is refusing something that works.
1834
+ * See docs/internals.md β€” "Serving an MBTiles archive".
1835
+ * @param {object} entry - A catalog entry.
1836
+ * @returns {boolean} - Whether to offer the tile endpoints.
1837
+ */
1838
+ const isServable = (entry) => {
1839
+ const kind = kindOf(entry);
1840
+ if (kind === 'pmtiles') return true;
1841
+ return kind === 'mbtiles' && entry.complete === true;
1842
+ };
1843
+
1844
+ /**
1845
+ * What kind of archive this is, from the record or failing that the name.
1846
+ *
1847
+ * An entry from before the kind was recorded carries none, and reading
1848
+ * that as PMTiles is not a harmless default: it is how a planet dump ends
1849
+ * up labelled servable and offered as a stack source, where naming it
1850
+ * would produce a recipe that fails at its first tile. `prewarm.js`
1851
+ * resolves it exactly this way, after the same mistake sent every
1852
+ * mirrored .osm.pbf round the header-read backoff for ever.
1853
+ * @param {object} entry - A catalog entry.
1854
+ * @returns {string} - 'pmtiles', 'mbtiles', or 'unknown'.
1855
+ */
1856
+ const kindOf = (entry) => {
1857
+ if (entry.kind) return entry.kind;
1858
+ const name = entry.name ?? '';
1859
+ if (/\.pmtiles$/i.test(name)) return 'pmtiles';
1860
+ if (/\.mbtiles$/i.test(name)) return 'mbtiles';
1861
+ return 'unknown';
1862
+ };
1863
+
1864
+ /**
1865
+ * What to say under the size about an archive that is not PMTiles.
1866
+ *
1867
+ * An MBTiles that is here in full is servable, and saying "not servable"
1868
+ * beside a finished file was simply wrong. One that is still arriving is
1869
+ * not servable *yet*, which is a different thing to tell somebody: it
1870
+ * becomes one by waiting, where an .osm.pbf never will.
1871
+ * @param {object} entry - A catalog entry.
1872
+ * @returns {string} - The note, or empty for a PMTiles archive.
1873
+ */
1874
+ const kindNote = (entry) => {
1875
+ const kind = kindOf(entry);
1876
+ if (kind === 'pmtiles') return '';
1877
+ if (kind !== 'mbtiles') return `${escapeHtml(kind)} Β· not servable`;
1878
+ return entry.complete === true
1879
+ ? escapeHtml(kind)
1880
+ : `${escapeHtml(kind)} Β· servable once complete`;
1881
+ };
1882
+
1826
1883
  /**
1827
1884
  * Closes the details, panel and row together.
1828
1885
  *
@@ -1891,8 +1948,8 @@
1891
1948
  .join(' Β· ')}</div>
1892
1949
  </td>
1893
1950
  <td>${bytes(entry.size)}${
1894
- (entry.kind ?? 'pmtiles') !== 'pmtiles'
1895
- ? `<div class="sub">${escapeHtml(entry.kind)} Β· not servable</div>`
1951
+ kindNote(entry)
1952
+ ? `<div class="sub">${kindNote(entry)}</div>`
1896
1953
  : ''
1897
1954
  }</td>
1898
1955
  <td>${added(entry.createdAt)}</td>
@@ -2518,10 +2575,15 @@
2518
2575
 
2519
2576
  const base = location.origin;
2520
2577
  const tileJson = `${base}/archives/${entry.infoHash}/tiles.json`;
2521
- // Only PMTiles can be a tile endpoint. Offering the URL for anything
2522
- // else hands out a link that will never work and reads as a fault in
2523
- // the server rather than a fact about the archive.
2524
- const servable = (entry.kind ?? 'pmtiles') === 'pmtiles';
2578
+ // Offering the URL for something that cannot serve tiles hands out a
2579
+ // link that will never work and reads as a fault in the server rather
2580
+ // than a fact about the archive.
2581
+ const servable = isServable(entry);
2582
+ // Warming pulls the pieces a region's tiles live in, so it only means
2583
+ // anything for an archive read out of the swarm. An MBTiles is read
2584
+ // whole or not at all β€” once it is here there is nothing left to
2585
+ // fetch, and before that there is no tile to warm.
2586
+ const warmable = kindOf(entry) === 'pmtiles';
2525
2587
  const torrentUrl = `${base}/archives/${entry.infoHash}/archive.torrent`;
2526
2588
  const summary = entry.pmtiles ?? {};
2527
2589
  const mode = entry.mode ?? 'mirror';
@@ -2606,11 +2668,18 @@
2606
2668
  ${
2607
2669
  servable
2608
2670
  ? `<div class="sub" style="margin-bottom:1rem"><code>${escapeHtml(tileJson)}</code></div>`
2609
- : `<div class="sub" style="margin-bottom:1rem">
2610
- This is ${escapeHtml(entry.kind ?? 'not a PMTiles')} archive, so it has no tile
2611
- endpoint β€” only PMTiles can be read a byte range at a time, which is what
2612
- serving tiles from a swarm needs. It is still distributed and seeded normally.
2613
- </div>`
2671
+ : kindOf(entry) === 'mbtiles'
2672
+ ? `<div class="sub" style="margin-bottom:1rem">
2673
+ This MBTiles archive is still arriving, so it has no tile endpoint yet. It is
2674
+ SQLite, which cannot be read a byte range at a time out of the swarm the way
2675
+ PMTiles can β€” so it becomes servable when the download finishes rather than as
2676
+ pieces land. It is distributed and seeded normally in the meantime.
2677
+ </div>`
2678
+ : `<div class="sub" style="margin-bottom:1rem">
2679
+ This is ${escapeHtml(entry.kind ?? 'not a PMTiles')} archive, so it has no tile
2680
+ endpoint β€” only PMTiles and MBTiles can be served as tiles. It is still
2681
+ distributed and seeded normally.
2682
+ </div>`
2614
2683
  }
2615
2684
 
2616
2685
  <h2>Completion hook</h2>
@@ -2665,7 +2734,7 @@
2665
2734
 
2666
2735
  <h2>Actions</h2>
2667
2736
  <div class="actions">
2668
- ${servable ? '<button id="warm">Warm region</button>' : ''}
2737
+ ${warmable ? '<button id="warm">Warm region</button>' : ''}
2669
2738
  <button id="set-location">Set location…</button>
2670
2739
  <button id="clear-cache" ${mode === 'cache' ? '' : 'disabled title="only cache-mode archives have a cache to clear"'}>Clear cache</button>
2671
2740
  <button id="recheck" title="Hash the files on disk again and believe the result. For an archive that reads 0% next to a file that is plainly there, or one that claims to be complete and is not β€” every other figure comes from something written down earlier, and this is the only thing that goes and looks.">Recheck files</button>
@@ -2895,7 +2964,7 @@
2895
2964
  }
2896
2965
  $('copy-hash').onclick = () => copy(entry.infoHash, 'Infohash');
2897
2966
 
2898
- if (servable) {
2967
+ if (warmable) {
2899
2968
  $('warm').onclick = () => openWarmDialog(entry);
2900
2969
  }
2901
2970
 
@@ -8126,10 +8195,12 @@ Every piece is hashed against the ` +
8126
8195
  // missing from a menu the operator is looking at. This is the same
8127
8196
  // endpoint the Archives view reads, and it answers a bare array.
8128
8197
  const held = await api('/api/torrents');
8198
+ // The same rule the tile route enforces, because a stack source is
8199
+ // read through exactly that path: a complete MBTiles belongs here,
8200
+ // and one still arriving does not -- a stack naming it would answer
8201
+ // 503 for every tile until the download finished.
8129
8202
  const free = (held ?? []).filter(
8130
- (entry) =>
8131
- !taken.has(entry.infoHash) &&
8132
- (entry.kind ?? 'pmtiles') === 'pmtiles',
8203
+ (entry) => !taken.has(entry.infoHash) && isServable(entry),
8133
8204
  );
8134
8205
  if (free.length) {
8135
8206
  groups.push(