pmtiles-swarm 0.54.0 → 0.54.2

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,34 @@
7
7
  ### 🐞 Bug fixes
8
8
  - _...Add new stuff here..._
9
9
 
10
+ ## 0.54.2
11
+ ### ✨ Features and improvements
12
+ - **Written down and tested: a range read is cached exactly as a tile read is**, because it is the
13
+ same read. `serveArchiveFromSwarm` goes through the same acquisition, the same `TorrentSource` and
14
+ therefore the same piece cache, so `tiles.pieceCacheBytes`, `tiles.maxOpenArchives` and
15
+ `tiles.directoryCacheEntries` all bound it unchanged. The two doors warm each other: pulling the
16
+ header over HTTP leaves the tile endpoint warm for free, and a tile already read costs a range
17
+ request nothing. Five tests now hold that in place by counting what actually reaches the swarm —
18
+ including one that shrinks the byte budget to a single piece and watches the eviction happen.
19
+
20
+ ### 🐞 Bug fixes
21
+
22
+ ## 0.54.1
23
+ ### ✨ Features and improvements
24
+
25
+ ### 🐞 Bug fixes
26
+ - **The public page offered a download of an archive the node did not hold.** With `publicDownload`
27
+ on, a cache-mode archive got a link labelled **download** that answers `409` — or, with
28
+ `serveArchiveFromSwarm` on, refuses a request carrying no `Range`, which reads as a broken node
29
+ rather than as a deliberate limit. `publicDownload` now needs the file to be here. `serveArchive`
30
+ is deliberately not conditioned that way, since a bounded range can be fetched from the swarm, and
31
+ neither setting is stored as `false` on this account — so both take effect on their own the moment
32
+ a download finishes.
33
+
34
+ - **A range request with no `Range` header answered `411`.** That code is about a missing
35
+ `Content-Length` on the way in; the request here is well formed and it is the state of the resource
36
+ that makes it unanswerable, so it is now a `409` like the other "not here" refusals beside it.
37
+
10
38
  ## 0.54.0
11
39
  ### ✨ Features and improvements
12
40
  - **`serveArchiveFromSwarm` — a byte range for an archive this node does not hold.** Experimental,
@@ -373,6 +373,17 @@ The response carries the same `ETag` and the same year-long `immutable` caching
373
373
  complete copy would give, because it is the same content: the infohash names
374
374
  those bytes wherever they were read from.
375
375
 
376
+ **The pieces are cached exactly as a tile read caches them**, because it is the
377
+ same read. A range goes through the same acquisition, the same `TorrentSource`
378
+ and therefore the same piece cache, so `tiles.pieceCacheBytes`,
379
+ `tiles.maxOpenArchives` and `tiles.directoryCacheEntries` all apply to it
380
+ unchanged — and a node tuned for its memory stays tuned when this endpoint is
381
+ used. It also means the two doors warm each other: somebody pulling the header
382
+ over HTTP leaves the tile endpoint warm for free, and a tile already read costs
383
+ a range request nothing. Underneath, in cache mode, the pieces land in
384
+ [`cacheSavePath`](#cachesavepath) the same way, so what a reader asks for over
385
+ HTTP hydrates the partial archive on disk just as a tile request does.
386
+
376
387
  **A node cannot be a web seed for an archive it does not hold**, whatever this is
377
388
  set to. [`selfWebSeed`](#selfwebseed) is refused on an incomplete archive and
378
389
  offered again when the download finishes — answering from the swarm and then
@@ -464,6 +475,15 @@ is off, whatever the file says. A web seed URL that answers `403` is worse than
464
475
  web seed, because a client spends its retries on it, and a download link that
465
476
  `403`s is worse than no link.
466
477
 
478
+ **`publicDownload` also needs the file to actually be here.** A cache-mode
479
+ archive is offered no download link, because there is nothing to download: the
480
+ node holds none of it, and a link labelled "download" that answers `409` reads as
481
+ a broken node rather than as a deliberate limit. `serveArchive` is deliberately
482
+ _not_ conditioned that way — a bounded range can be fetched from the swarm, which
483
+ is what [`serveArchiveFromSwarm`](#servearchivefromswarm) is for — and neither
484
+ setting is stored as `false` on that account, so both take effect on their own the
485
+ moment a download finishes.
486
+
467
487
  ### Per archive, per folder, per source
468
488
 
469
489
  The node's setting is the default. A [watched folder](#watched-folders), a
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pmtiles-swarm",
3
- "version": "0.54.0",
3
+ "version": "0.54.2",
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
@@ -2382,7 +2382,11 @@ export function createApp({
2382
2382
  const asked = req.headers.range;
2383
2383
  if (!asked) {
2384
2384
  res.setHeader('accept-ranges', 'bytes');
2385
- return res.status(411).json({
2385
+ // 409 rather than 411. The request is well formed -- 411 is about a
2386
+ // missing Content-Length on the way in -- and what is wrong is the state
2387
+ // of the thing being asked for: the whole file is not here, and fetching
2388
+ // it through the swarm to stream back out is not an answer.
2389
+ return res.status(409).json({
2386
2390
  error:
2387
2391
  'this node does not hold this archive, so it can only answer a ' +
2388
2392
  'byte range. Send a Range header.',
package/src/catalog.js CHANGED
@@ -82,12 +82,26 @@ export function normalizeCategories(source) {
82
82
  */
83
83
  export function publishingFor(entry, config) {
84
84
  const serveArchive = entry?.serveArchive ?? config?.serveArchive ?? false;
85
+ // Whether the whole file is here. `serveArchive` does not depend on it —
86
+ // `serveArchiveFromSwarm` exists precisely so a node holding nothing can
87
+ // still answer a bounded range — but a download link does. A link labelled
88
+ // "download" on a public page has to hand over the file, and a cache-mode
89
+ // node cannot: it answers 409, or refuses a request with no Range, which
90
+ // reads as a broken node rather than as a deliberate limit.
91
+ //
92
+ // `selfWebSeed` is guarded separately, in setPublishing, because it has a
93
+ // lifecycle rather than a state: the URL is written into a .torrent and has
94
+ // to be added and withdrawn at the right moments, not merely reported as
95
+ // off. Resolving it to false here would withdraw a published seed the first
96
+ // time an archive was rechecked.
97
+ const held = entry?.complete !== false;
85
98
  return {
86
99
  serveArchive,
87
100
  selfWebSeed:
88
101
  serveArchive && (entry?.selfWebSeed ?? config?.selfWebSeed ?? false),
89
102
  publicDownload:
90
103
  serveArchive &&
104
+ held &&
91
105
  (entry?.publicDownload ?? config?.publicDownload ?? false),
92
106
  };
93
107
  }