pmtiles-swarm 0.4.3 → 0.4.4

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,17 @@
7
7
  ### 🐞 Bug fixes
8
8
  - _...Add new stuff here..._
9
9
 
10
+ ## 0.4.4
11
+ ### 🐞 Bug fixes
12
+ - **The console no longer claims an `.incomplete` file that is not there.** The marker was a
13
+ literal in the page, drawn beside every unfinished archive, with a tooltip naming the file it
14
+ was supposedly on disk as. libtorrent renames nothing — the rename would have to happen in the
15
+ sidecar — so on the engine most people run, that named a file which did not exist, next to one
16
+ sitting under its final name at 25% downloaded. Each engine now says whether it marks
17
+ incomplete files, the composite answers for its primary since that is the engine writing the
18
+ bytes, and `/api/status` combines that with `incompleteSuffix` — which can also be empty — to
19
+ report the marker actually in use, or none. The console draws only what it is told.
20
+
10
21
  ## 0.4.3
11
22
  ### 🐞 Bug fixes
12
23
  - **The lock file keeps the optional native builds `ws` asks for.** `bufferutil` and
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pmtiles-swarm",
3
- "version": "0.4.3",
3
+ "version": "0.4.4",
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",
package/src/api.js CHANGED
@@ -408,6 +408,14 @@ export function createApp({
408
408
  }
409
409
  res.json({
410
410
  version: VERSION,
411
+ // What an unfinished archive is actually called on disk, or null when
412
+ // nothing renames it. Both halves matter: the setting can be empty,
413
+ // and an engine can ignore it entirely. The console showed the marker
414
+ // for every unfinished archive regardless, which on libtorrent was a
415
+ // filename that did not exist.
416
+ incompleteMarker: engine.marksIncomplete
417
+ ? config.incompleteSuffix || null
418
+ : null,
411
419
  engine: { name: engine.name, ok: engineOk, error: engineError },
412
420
  archives: catalog.list().length,
413
421
  categories: catalog.categories(),
@@ -58,6 +58,9 @@ export class CompositeEngine {
58
58
  this.shareIntervalMs = Math.max(5, shareIntervalSeconds) * 1000;
59
59
  this.shareTimeoutMs = Math.max(60, shareTimeoutSeconds) * 1000;
60
60
  this.name = [primary.name, ...this.#secondaries.map((e) => e.name)].join('+');
61
+ // The primary is the one that writes the file. A secondary only ever
62
+ // receives an archive that is already whole, so it never marks anything.
63
+ this.marksIncomplete = primary.marksIncomplete ?? false;
61
64
  }
62
65
 
63
66
  /** The engine that owns the data. @returns {object} - The primary. */
@@ -78,6 +78,12 @@ export class LibtorrentEngine {
78
78
  throw new Error('libtorrent engine requires a savePath');
79
79
  }
80
80
  this.name = 'libtorrent';
81
+ // Whether `incompleteSuffix` means anything here: it does not. The rename
82
+ // would have to happen in the sidecar, so a partial archive sits under its
83
+ // final name from the first byte — which is why a web server must never be
84
+ // pointed at this engine's save path, and why the console must not claim
85
+ // a marker that is not there.
86
+ this.marksIncomplete = false;
81
87
  if (options.listen !== undefined && typeof options.listen !== 'string') {
82
88
  // Caught here because the alternative is a C++ converter error four
83
89
  // frames into a Python traceback, which says nothing about which setting
@@ -54,6 +54,9 @@ export class WebTorrentSeedEngine {
54
54
  throw new Error('WebTorrent engine requires a savePath');
55
55
  }
56
56
  this.name = 'webtorrent';
57
+ // Honoured, by replacing the store — the only thing that decides where
58
+ // bytes land.
59
+ this.marksIncomplete = true;
57
60
  this.#options = { readyTimeoutMs: 300000, ...options };
58
61
  }
59
62
 
@@ -725,6 +725,9 @@
725
725
  // ── Archives ──────────────────────────────────────────────────────────
726
726
  let archives = [];
727
727
  let selected = null;
728
+ // Null until /api/status says otherwise, so nothing claims a marker
729
+ // before the node has said whether its engine applies one.
730
+ let incompleteMarker = null;
728
731
  let activeTab = 'general';
729
732
 
730
733
  // What the header switch shows, so a click knows what it is toggling to.
@@ -882,6 +885,9 @@
882
885
  renderSpeed(speed);
883
886
  renderFetching(adds?.running);
884
887
  if (status.version) $('version').textContent = `v${status.version}`;
888
+ // What an unfinished archive is really called on disk, or null when
889
+ // nothing renames it. The engine decides, not the setting alone.
890
+ incompleteMarker = status.incompleteMarker ?? null;
885
891
  const engine = status.engine;
886
892
  $('status').innerHTML =
887
893
  `engine <b>${engine.name}</b> ${engine.ok ? 'ready' : 'unavailable'}` +
@@ -994,8 +1000,8 @@
994
1000
  <td class="sub">${
995
1001
  entry.paused ? 'paused' : escapeHtml(s.state ?? '—')
996
1002
  }${
997
- entry.complete === false && progress < 1
998
- ? `<div class="sub" title="on disk as ${escapeHtml(entry.name)}.incomplete until it is whole">.incomplete</div>`
1003
+ entry.complete === false && progress < 1 && incompleteMarker
1004
+ ? `<div class="sub" title="on disk as ${escapeHtml(entry.name)}${escapeHtml(incompleteMarker)} until it is whole">${escapeHtml(incompleteMarker)}</div>`
999
1005
  : ''
1000
1006
  }</td>`;
1001
1007
  tr.onclick = () => {