pmtiles-swarm 0.32.1 → 0.32.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,45 @@
7
7
  ### 🐞 Bug fixes
8
8
  - _...Add new stuff here..._
9
9
 
10
+ ## 0.32.3
11
+ ### ✨ Features and improvements
12
+ - _...Add new stuff here..._
13
+
14
+ ### 🐞 Bug fixes
15
+ - **Requires pmtiles-torrent 0.5.2, which deletes a torrent's resume file with its data.** Resume
16
+ data was outliving the data it described: deleting an archive to re-fetch it left the record of
17
+ the old complete file behind, so the re-add handed libtorrent a description of a finished archive
18
+ against a path holding a fresh partial one. It answered `fastresume_rejected` and rechecked, and
19
+ until that settled nothing was verified — bytes arriving at full speed against a verified-piece
20
+ count stuck at 1, and every tile read told the piece it wanted was not in the slot list. Only when
21
+ the data goes too: a removal that keeps the files is how a pause is expressed for an engine with
22
+ no pause of its own, and discarding resume data there would turn every pause into a full re-hash.
23
+
24
+ - _...Add new stuff here..._
25
+
26
+ ## 0.32.2
27
+ ### ✨ Features and improvements
28
+ - _...Add new stuff here..._
29
+
30
+ ### 🐞 Bug fixes
31
+ - **The head warmer no longer skips every archive it was built for.** It decided an archive was done
32
+ by `Boolean(entry.pmtiles?.format)` — "a summary that names a format is one a header was read
33
+ for". That is not what a format means. A feed carries format, zoom range and bounds precisely so a
34
+ subscriber can judge a 698 GiB download before starting one, so a subscribed archive arrives fully
35
+ summarised before a byte of it exists locally. Every one of them was therefore retired on the spot,
36
+ and retired silently: the runner logged "every archive has been summarised; nothing to warm" and
37
+ meant it. The archives most in need of their header were the only ones never offered one, and the
38
+ visible symptom was a readable TileJSON — served from that same feed summary, touching no bytes —
39
+ beside tile reads that failed and previews that came up empty.
40
+
41
+ Entries now record where the summary came from: `summarySource: 'header'` where an archive's own
42
+ header answered, `'feed'` where a subscription was told. An entry written before this has neither,
43
+ and is treated as unread — the self-healing direction, since a local archive re-reads its own
44
+ header off local disk for nothing, while assuming the opposite would leave every existing
45
+ subscription stuck exactly as it is.
46
+
47
+ - _...Add new stuff here..._
48
+
10
49
  ## 0.32.1
11
50
  ### ✨ Features and improvements
12
51
  - _...Add new stuff here..._
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pmtiles-swarm",
3
- "version": "0.32.1",
3
+ "version": "0.32.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",
@@ -46,7 +46,7 @@
46
46
  "maplibre-gl": "^6.2.0",
47
47
  "parse-torrent": "^11.0.24",
48
48
  "pmtiles": "^4.4.1",
49
- "pmtiles-torrent": "^0.5.1",
49
+ "pmtiles-torrent": "^0.5.2",
50
50
  "webtorrent": "^3.0.21"
51
51
  },
52
52
  "engines": {
package/src/api.js CHANGED
@@ -2168,6 +2168,7 @@ export function createApp({
2168
2168
  entry = await catalog.put({
2169
2169
  infoHash: entry.infoHash,
2170
2170
  pmtiles: summary,
2171
+ summarySource: 'header',
2171
2172
  });
2172
2173
  } catch (error) {
2173
2174
  // The content had its say. Record it so this is answered from the
@@ -2383,6 +2384,7 @@ export function createApp({
2383
2384
  entry = await catalog.put({
2384
2385
  infoHash: entry.infoHash,
2385
2386
  pmtiles: summary,
2387
+ summarySource: 'header',
2386
2388
  });
2387
2389
  }
2388
2390
  }
package/src/library.js CHANGED
@@ -204,6 +204,10 @@ export class Library {
204
204
  // The torrent names the file, so the save path is its parent directory.
205
205
  savePath: path.dirname(absolute),
206
206
  pmtiles: summary,
207
+ // Read out of the archive, not taken from anybody's word for it. The
208
+ // prewarmer needs the difference: a summary that arrived in a feed says
209
+ // nothing about whether the header is on this disk. See prewarm.due().
210
+ summarySource: 'header',
207
211
  kind: identified.kind,
208
212
  sparse: options.sparse,
209
213
  md5: created.md5,
@@ -747,6 +751,10 @@ export class Library {
747
751
  buildDate: options.buildDate,
748
752
  savePath,
749
753
  pmtiles: summary,
754
+ // Read out of the archive, not taken from anybody's word for it. The
755
+ // prewarmer needs the difference: a summary that arrived in a feed says
756
+ // nothing about whether the header is on this disk. See prewarm.due().
757
+ summarySource: 'header',
750
758
  kind: identified.kind,
751
759
  sparse: options.sparse,
752
760
  md5: created.md5,
@@ -1212,6 +1220,10 @@ export class Library {
1212
1220
  magnet,
1213
1221
  webSeeds: [],
1214
1222
  pmtiles: summary,
1223
+ // Read out of the archive, not taken from anybody's word for it. The
1224
+ // prewarmer needs the difference: a summary that arrived in a feed says
1225
+ // nothing about whether the header is on this disk. See prewarm.due().
1226
+ summarySource: 'header',
1215
1227
  kind: guessKind(torrent.name ?? ''),
1216
1228
  mode: 'mirror',
1217
1229
  // The engine's own account of it. It is on disk under its real name
package/src/prewarm.js CHANGED
@@ -157,12 +157,23 @@ export class HeadWarmer {
157
157
  const kind = entry.kind ?? guessKind(entry.name ?? '');
158
158
  if (kind !== 'pmtiles') return false;
159
159
 
160
- // A summary that names a format is one a header was actually read for.
161
- // Anything else — an empty object, or one left behind by a read that raced
162
- // its deadline — is not an answer, and treating it as one retired the
163
- // archive permanently: no logs, no retries, nothing to explain the silence.
160
+ // A summary is only an answer about *this disk* if a header on this disk
161
+ // produced it. `format` was standing in for that and does not mean it.
162
+ //
163
+ // The feed carries format, zoom range and bounds precisely so a subscriber
164
+ // can judge a 698 GiB download before starting one -- so a subscribed
165
+ // archive arrives fully summarised, before a byte of it exists here. Read
166
+ // as "a header was read", that retired every subscribed archive on the spot
167
+ // and did it silently, since as far as this was concerned there was nothing
168
+ // to do. The archives most in need of their header were the only ones never
169
+ // offered one, and the log said "nothing to warm".
170
+ //
171
+ // An entry from before this was recorded has no source, and is treated as
172
+ // unread. That is the self-healing direction: a local archive re-reads its
173
+ // own header off local disk and costs nothing, where the other way round
174
+ // leaves every existing subscription stuck exactly as it was.
164
175
  const summary = entry.pmtiles;
165
- const read = Boolean(summary?.format);
176
+ const read = entry.summarySource === 'header' && Boolean(summary?.format);
166
177
  if (read && (summary.format !== 'pbf' || summary.vectorLayers)) {
167
178
  return false;
168
179
  }
@@ -257,6 +268,9 @@ export class HeadWarmer {
257
268
  const stored = await this.#catalog.put({
258
269
  infoHash: entry.infoHash,
259
270
  pmtiles: { ...entry.pmtiles, ...summary },
271
+ // The whole point of this pass: from here on the entry's summary is
272
+ // one the header answered for, so due() stops choosing it.
273
+ summarySource: 'header',
260
274
  });
261
275
  // Said accurately, because the two halves arrive separately and the
262
276
  // difference matters: the header is at byte zero, while the JSON
@@ -419,6 +419,10 @@ export class SubscriptionManager {
419
419
  // with what the archive itself says; this only means the wait is not
420
420
  // spent unservable.
421
421
  pmtiles: item.pmtiles,
422
+ // Told, not read. The feed carries this so a subscriber can judge a
423
+ // 698 GiB download before starting one, which means it is populated
424
+ // before a single byte exists here.
425
+ summarySource: 'feed',
422
426
  };
423
427
 
424
428
  // The .torrent is preferred where there is one: it carries the trackers