pmtiles-swarm 0.25.0 → 0.26.0

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,29 @@
7
7
  ### 🐞 Bug fixes
8
8
  - _...Add new stuff here..._
9
9
 
10
+ ## 0.26.0
11
+ ### 🐞 Bug fixes
12
+ - **The add dialog stayed on screen for the length of a download.** `POST /api/torrents` awaited
13
+ the entire transfer before answering, so for a URL the response arrived hours after the request
14
+ — and the console, which closes the dialog when the response lands, sat there over an archive
15
+ visibly appearing behind it.
16
+
17
+ The console was always written for the other arrangement: it says "fetching — watch the log" as
18
+ it closes, polls `/api/adds` for progress, and offers `DELETE /api/adds` to cancel. Only the
19
+ route was missing. It now answers **202** as soon as the URL has been checked — it answers, it is
20
+ an archive of a publishable kind, it is not a credential about to be broadcast — and lets the
21
+ transfer run behind it. Everything a person can correct is still reported in the dialog, because
22
+ all of it is found before the first byte moves.
23
+
24
+ Both shortcuts inside `addRemoteArchive` had to be taught the same signal. A URL already in the
25
+ catalog, or one already being fetched by somebody else, returns without ever reaching the checks
26
+ — so a response waiting on them would have waited for something that had already happened, or
27
+ for the whole of a download another caller had started.
28
+
29
+ `url` bodies now answer 202 with an acknowledgement rather than 201 with the finished entry.
30
+ Paths, magnets and `.torrent` URLs are unchanged: they were always fast, and still answer 201
31
+ with the entry.
32
+
10
33
  ## 0.25.0
11
34
  ### ✨ Features and improvements
12
35
  - **A stopped download is kept, and adding the same URL again resumes it.** Staging directories
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pmtiles-swarm",
3
- "version": "0.25.0",
3
+ "version": "0.26.0",
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
@@ -1387,7 +1387,40 @@ export function createApp({
1387
1387
  if (body.path) {
1388
1388
  entry = await library.addLocalArchive(body.path, options);
1389
1389
  } else if (body.url) {
1390
- entry = await library.addRemoteArchive(body.url, options);
1390
+ // Answered as soon as the URL has been checked, not when the transfer
1391
+ // finishes. Awaiting the whole thing held the response open for the
1392
+ // length of the download -- hours for a planet archive -- so the
1393
+ // console's add dialog stayed on screen throughout, over an archive
1394
+ // that was visibly appearing behind it.
1395
+ //
1396
+ // Progress has its own route already: runningAdds() feeds /api/adds,
1397
+ // the console polls it, and DELETE /api/adds cancels one. This is the
1398
+ // piece that was missing rather than a new mechanism.
1399
+ const validated = Promise.withResolvers();
1400
+ const running = library
1401
+ .addRemoteArchive(body.url, {
1402
+ ...options,
1403
+ onValidated: validated.resolve,
1404
+ })
1405
+ // A failure after validation has nowhere to be reported: the
1406
+ // response has gone. It is logged where the rest of the fetch is,
1407
+ // and swallowed here so it cannot take the process down as an
1408
+ // unhandled rejection.
1409
+ .catch((error) => {
1410
+ validated.reject(error);
1411
+ console.error(`[fetch] ${body.url}: ${error.message}`);
1412
+ });
1413
+
1414
+ // Whichever comes first: the checks passing, or the whole attempt
1415
+ // failing. A URL that does not answer, or is not an archive, still
1416
+ // reports itself in the dialog where somebody can correct it.
1417
+ await validated.promise;
1418
+ void running;
1419
+ return res.status(202).json({
1420
+ accepted: true,
1421
+ url: body.url,
1422
+ message: 'fetching; progress is reported by /api/adds',
1423
+ });
1391
1424
  } else if (body.magnet) {
1392
1425
  entry = await library.addExistingTorrent(
1393
1426
  { magnet: body.magnet },
package/src/library.js CHANGED
@@ -541,8 +541,15 @@ export class Library {
541
541
  * @returns {Promise<object>} - The catalog entry.
542
542
  */
543
543
  async addRemoteArchive(url, options = {}) {
544
+ // Both shortcuts below have to fire onValidated before returning, and for
545
+ // the same reason: a caller waiting on it to answer a request would
546
+ // otherwise wait for something that has already happened, or -- worse --
547
+ // for the whole of a download somebody else started.
544
548
  const existing = this.#catalog.findBySource(url);
545
- if (existing) return existing;
549
+ if (existing) {
550
+ options.onValidated?.({ url, kind: existing.kind, held: true });
551
+ return existing;
552
+ }
546
553
 
547
554
  // A second request for a URL already being fetched joins the first; the
548
555
  // catalog cannot answer this, since an entry exists only once the download
@@ -550,6 +557,7 @@ export class Library {
550
557
  const inFlight = this.#inFlight.get(url);
551
558
  if (inFlight) {
552
559
  console.log(`[fetch] ${url} is already being fetched; joining that one`);
560
+ options.onValidated?.({ url, joined: true });
553
561
  return inFlight;
554
562
  }
555
563
 
@@ -590,6 +598,18 @@ export class Library {
590
598
  allowUnknown: options.allowUnknown ?? this.#config.allowUnknownArchives,
591
599
  });
592
600
 
601
+ // Everything a caller can do something about has now been checked: the URL
602
+ // answers, it is an archive of a kind this will publish, and it is not a
603
+ // credential being broadcast to a swarm. What remains is the transfer,
604
+ // which for a planet archive is hours.
605
+ //
606
+ // A caller that wants to stop waiting there says so with onValidated. That
607
+ // is the difference between a dialog that closes on a bad URL with the
608
+ // reason in it, and one that sits open for the length of the download --
609
+ // and the console was always written for the former, since it reports
610
+ // progress through runningAdds() and says "watch the log" as it closes.
611
+ options.onValidated?.({ url, kind: identified.kind });
612
+
593
613
  const summary =
594
614
  identified.kind === 'pmtiles'
595
615
  ? await probePMTiles(url).catch(() => undefined)