pmtiles-swarm 0.55.2 ā 0.55.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 +18 -0
- package/docs/serving-tiles.md +12 -3
- package/package.json +1 -1
- package/src/api.js +48 -24
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,24 @@
|
|
|
7
7
|
### š Bug fixes
|
|
8
8
|
- _...Add new stuff here..._
|
|
9
9
|
|
|
10
|
+
## 0.55.3
|
|
11
|
+
### š Bug fixes
|
|
12
|
+
- **A `/latest/` document could never be updated once a client had cached it.** The `ETag` was the
|
|
13
|
+
infohash, which says which _build_ a category resolved to ā and these documents carry more than
|
|
14
|
+
that. A TileJSON also carries the archive's summary; a magnet also carries its web seeds and
|
|
15
|
+
trackers; the feed carries both. So enriching a summary (0.55.1 reading an `encoding` an older
|
|
16
|
+
prober had missed) or adding a web seed changed the body while the infohash stayed put: every cache
|
|
17
|
+
in the path revalidated, was told `304`, and went on serving the old document indefinitely. That is
|
|
18
|
+
not a cache being stale for a minute; it is a document that can never change again.
|
|
19
|
+
|
|
20
|
+
These are now tagged over what is actually sent. `/latest/<category>/archive.pmtiles` and the
|
|
21
|
+
`.torrent` redirect keep the infohash, because for those the infohash really is the whole content.
|
|
22
|
+
|
|
23
|
+
- **A successful metadata re-read was logged as a failure.** The line counting vector layers ran for
|
|
24
|
+
raster archives too, where there are none, and the `TypeError` went to the catch beside it ā which
|
|
25
|
+
reported a backfill that had already written its result as "no vector layers yet". A misleading log
|
|
26
|
+
is worse than none when it is what somebody is reading to find out whether the thing works.
|
|
27
|
+
|
|
10
28
|
## 0.55.2
|
|
11
29
|
### š Bug fixes
|
|
12
30
|
- **The stale-summary re-read never ran for a category URL.** 0.55.1 added it to
|
package/docs/serving-tiles.md
CHANGED
|
@@ -157,14 +157,23 @@ answers 404 for them exactly as the feeds do.
|
|
|
157
157
|
|
|
158
158
|
### How a client knows the build moved
|
|
159
159
|
|
|
160
|
-
Every one of these carries an `ETag
|
|
161
|
-
it resolved to:
|
|
160
|
+
Every one of these carries an `ETag` over the document it is sending:
|
|
162
161
|
|
|
163
162
|
```
|
|
164
|
-
ETag: "
|
|
163
|
+
ETag: "6b8f1c2dā¦"
|
|
165
164
|
Cache-Control: public, max-age=60, must-revalidate
|
|
166
165
|
```
|
|
167
166
|
|
|
167
|
+
**Over the document, not over the infohash.** The infohash is the obvious
|
|
168
|
+
choice and it is wrong here: it says which _build_ a category resolved to, and
|
|
169
|
+
these documents carry more than that. A TileJSON also carries the archive's
|
|
170
|
+
summary; a magnet also carries its web seeds and trackers. Enrich a summary or
|
|
171
|
+
add a web seed and the body changes while the infohash does not ā so every cache
|
|
172
|
+
in the path revalidates, is told `304`, and goes on serving the old document.
|
|
173
|
+
Not stale for a minute: unable to be updated at all. Only
|
|
174
|
+
`/latest/{category}/archive.pmtiles` and the `.torrent` redirect are tagged by
|
|
175
|
+
infohash, because for those the infohash really is the whole content.
|
|
176
|
+
|
|
168
177
|
A short TTL on its own is a guess. At five minutes, every client and every proxy
|
|
169
178
|
in front of one serves the previous build for up to five minutes after a rollover
|
|
170
179
|
and not one of them can tell it is doing so. The infohash is the honest answer:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pmtiles-swarm",
|
|
3
|
-
"version": "0.55.
|
|
3
|
+
"version": "0.55.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",
|
package/src/api.js
CHANGED
|
@@ -414,9 +414,16 @@ export function createApp({
|
|
|
414
414
|
infoHash: entry.infoHash,
|
|
415
415
|
pmtiles: { ...entry.pmtiles, ...summary },
|
|
416
416
|
});
|
|
417
|
+
// Guarded, because this now runs for raster archives too, where there
|
|
418
|
+
// are no layers to count -- and the TypeError went to the catch below,
|
|
419
|
+
// which reported a backfill that had already succeeded as a failure.
|
|
420
|
+
// A misleading log is worse than none when it is the thing somebody is
|
|
421
|
+
// reading to find out whether the backfill works.
|
|
417
422
|
console.log(
|
|
418
|
-
|
|
419
|
-
|
|
423
|
+
summary.vectorLayers
|
|
424
|
+
? `[tiles] read ${summary.vectorLayers.length} vector layers for ` +
|
|
425
|
+
`${entry.name} out of the swarm`
|
|
426
|
+
: `[tiles] re-read the metadata for ${entry.name}`,
|
|
420
427
|
);
|
|
421
428
|
})
|
|
422
429
|
.catch((error) => {
|
|
@@ -2123,10 +2130,28 @@ export function createApp({
|
|
|
2123
2130
|
* @param {import('express').Response} res - The response to tag.
|
|
2124
2131
|
* @param {object} entry - The archive this URL resolved to.
|
|
2125
2132
|
*/
|
|
2126
|
-
const tagAsLatest = (res,
|
|
2133
|
+
const tagAsLatest = (res, content) => {
|
|
2134
|
+
// Hashed from what is actually being sent, not from the infohash it
|
|
2135
|
+
// resolved to. The infohash was the obvious choice and it was wrong: it
|
|
2136
|
+
// says which build this is, and these documents carry more than that. A
|
|
2137
|
+
// TileJSON also carries the archive's summary, a magnet also carries the
|
|
2138
|
+
// web seeds and trackers, a feed carries both. Enrich a summary or add a
|
|
2139
|
+
// web seed and the body changes while the infohash does not -- so every
|
|
2140
|
+
// cache in the path revalidates, is told 304, and serves the old body for
|
|
2141
|
+
// ever. That is not a cache being stale for a minute; it is a document
|
|
2142
|
+
// that can never be updated again.
|
|
2143
|
+
//
|
|
2144
|
+
// A hash of the body has the property the infohash was picked for anyway:
|
|
2145
|
+
// two nodes answering identically produce identical tags, and two nodes
|
|
2146
|
+
// answering differently should not claim otherwise.
|
|
2147
|
+
const text =
|
|
2148
|
+
typeof content === 'string' ? content : JSON.stringify(content);
|
|
2127
2149
|
// Quoted, because that is what an entity-tag is. Unquoted, a validator
|
|
2128
2150
|
// never matches an If-None-Match and every revalidation misses silently.
|
|
2129
|
-
res.setHeader(
|
|
2151
|
+
res.setHeader(
|
|
2152
|
+
'etag',
|
|
2153
|
+
`"${crypto.createHash('sha1').update(text).digest('hex')}"`,
|
|
2154
|
+
);
|
|
2130
2155
|
// must-revalidate because serving this one stale is not merely "slightly
|
|
2131
2156
|
// old": a reader holding part of one build and asking for the rest after
|
|
2132
2157
|
// a rollover assembles a file that never existed. A minute of cache with
|
|
@@ -2287,8 +2312,7 @@ export function createApp({
|
|
|
2287
2312
|
// makes that re-read cheap ā a client already holding the current build
|
|
2288
2313
|
// gets a 304 and no body, which Express does on its own once an ETag is
|
|
2289
2314
|
// set before the response goes out.
|
|
2290
|
-
|
|
2291
|
-
res.json({
|
|
2315
|
+
const doc = {
|
|
2292
2316
|
...buildTileJson(entry, baseUrl(req)),
|
|
2293
2317
|
// Names what it resolved to, so a consumer can tell one build from the
|
|
2294
2318
|
// next without diffing the tile URLs.
|
|
@@ -2298,7 +2322,9 @@ export function createApp({
|
|
|
2298
2322
|
name: entry.name,
|
|
2299
2323
|
createdAt: entry.createdAt,
|
|
2300
2324
|
},
|
|
2301
|
-
}
|
|
2325
|
+
};
|
|
2326
|
+
tagAsLatest(res, doc);
|
|
2327
|
+
res.json(doc);
|
|
2302
2328
|
}),
|
|
2303
2329
|
);
|
|
2304
2330
|
|
|
@@ -2319,18 +2345,17 @@ export function createApp({
|
|
|
2319
2345
|
// this one says so, and names what it resolved to while it is at it. The
|
|
2320
2346
|
// tag is the infohash being redirected to, which is precisely the thing
|
|
2321
2347
|
// that changes when this redirect starts pointing somewhere else.
|
|
2322
|
-
|
|
2323
|
-
res
|
|
2324
|
-
|
|
2325
|
-
`${baseUrl(req)}/archives/${entry.infoHash}/archive.torrent`,
|
|
2326
|
-
);
|
|
2348
|
+
const target = `${baseUrl(req)}/archives/${entry.infoHash}/archive.torrent`;
|
|
2349
|
+
tagAsLatest(res, target);
|
|
2350
|
+
res.redirect(302, target);
|
|
2327
2351
|
});
|
|
2328
2352
|
|
|
2329
2353
|
app.get('/latest/:category/magnet', (req, res) => {
|
|
2330
2354
|
const entry = newestIn(req.params.category, req);
|
|
2331
2355
|
if (!entry) return res.status(404).json({ error: 'no such category' });
|
|
2332
|
-
|
|
2333
|
-
res
|
|
2356
|
+
const magnet = entry.magnet ?? '';
|
|
2357
|
+
tagAsLatest(res, magnet);
|
|
2358
|
+
res.type('text/plain').send(magnet);
|
|
2334
2359
|
});
|
|
2335
2360
|
|
|
2336
2361
|
/**
|
|
@@ -2566,16 +2591,15 @@ export function createApp({
|
|
|
2566
2591
|
// validator: a tag meaning "still empty" is indistinguishable from one
|
|
2567
2592
|
// meaning "still this build", and a reader would cache the emptiness past
|
|
2568
2593
|
// the arrival of the thing it was waiting for.
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
);
|
|
2594
|
+
const body = renderFeed(entry ? [entry] : [], {
|
|
2595
|
+
title: `${config.feedTitle ?? 'PMTiles archives'} ā ${category}, latest`,
|
|
2596
|
+
baseUrl: baseUrl(req),
|
|
2597
|
+
copyright: config.feedCopyright,
|
|
2598
|
+
category,
|
|
2599
|
+
maxItems: 1,
|
|
2600
|
+
});
|
|
2601
|
+
if (entry) tagAsLatest(res, body);
|
|
2602
|
+
res.type('application/rss+xml').send(body);
|
|
2579
2603
|
});
|
|
2580
2604
|
|
|
2581
2605
|
app.get('/feed.xml', (req, res) => {
|