pmtiles-swarm 0.7.0 → 0.7.1
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 +28 -0
- package/README.md +19 -7
- package/docs/subscribing.md +19 -3
- package/package.json +1 -1
- package/src/retention.js +16 -0
- package/src/subscriptions.js +22 -12
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,34 @@
|
|
|
7
7
|
### 🐞 Bug fixes
|
|
8
8
|
- _...Add new stuff here..._
|
|
9
9
|
|
|
10
|
+
## 0.7.1
|
|
11
|
+
### 🐞 Bug fixes
|
|
12
|
+
- **A feed no longer deletes its only complete copy.** Retention was written for a watched folder
|
|
13
|
+
and a scheduled source, where the archive it is handed is already whole — the file was there, or
|
|
14
|
+
the fetch finished. A subscription is not like that: it joins a torrent, and the data arrives
|
|
15
|
+
hours later. So `keep: 1` on a feed removed last week's complete copy the moment this week's
|
|
16
|
+
torrent was announced, leaving nothing complete at all for the length of an 88 GiB download.
|
|
17
|
+
`keepDays` had the same exposure, a copy ageing out while its replacement was still arriving.
|
|
18
|
+
|
|
19
|
+
Retention on a subscription now waits for the newest copy to be whole, which makes `keep: 1`
|
|
20
|
+
mean *the last complete copy* — the only reading of it that is safe there. It also runs on every
|
|
21
|
+
poll rather than only on polls that took something, because what is being waited for is a
|
|
22
|
+
download finishing rather than a poll happening. Watched folders and scheduled sources are
|
|
23
|
+
unchanged: they hand over a finished archive, and asking them for a completion marker they never
|
|
24
|
+
set would have stopped their retention working.
|
|
25
|
+
|
|
26
|
+
### 📚 Documentation
|
|
27
|
+
- **`prune` does not apply to an RSS subscription**, which the documentation did not say and a
|
|
28
|
+
reader would reasonably have assumed otherwise — it is accepted there and quietly does nothing.
|
|
29
|
+
Absence from a bounded feed is not evidence that anything was withdrawn, so pruning needs a
|
|
30
|
+
catalogue. The two questions are now separated where they are described: whether the publisher
|
|
31
|
+
still offers an archive, and whether you still want it on your disk.
|
|
32
|
+
- **Feed retention is documented**, along with the claim it replaces. The subscribing guide said a
|
|
33
|
+
node following a feed "accumulates and never sheds", which was true when it was written and is
|
|
34
|
+
what `keep` and `keepDays` on a subscription now answer.
|
|
35
|
+
- **The README describes the two settings sections** rather than the single table they replaced,
|
|
36
|
+
and covers `newest`, `keep` and `keepDays` — none of which it mentioned.
|
|
37
|
+
|
|
10
38
|
## 0.7.0
|
|
11
39
|
### ✨ Features and improvements
|
|
12
40
|
- **A feed can be told how long to keep what it brings in.** `keep` and `keepDays` now work on a
|
package/README.md
CHANGED
|
@@ -488,16 +488,28 @@ restart.
|
|
|
488
488
|
|
|
489
489
|
### Following other nodes
|
|
490
490
|
|
|
491
|
-
`subscriptions` are
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
491
|
+
`subscriptions` are what this node takes archives from, editable in Settings as two sections:
|
|
492
|
+
**RSS feeds** and **Remote nodes**. They are not one thing in two costumes. A feed says "here is
|
|
493
|
+
what is new" and is bounded by the publisher's `feedMaxItems`, so a node offline long enough
|
|
494
|
+
misses things permanently, and an absence from one proves nothing; a `/api/catalog` URL says
|
|
495
|
+
"here is everything", which is what makes reconciling — and pruning — possible. A row belongs to
|
|
496
|
+
whichever section it sits in, and is saved with that protocol rather than leaving it to be
|
|
497
|
+
inferred from the URL.
|
|
495
498
|
|
|
496
499
|
`mode` decides what following one costs: `cache` joins the swarm and fetches only what is read,
|
|
497
500
|
`mirror` commits to a whole copy of every archive the peer lists. `token` is presented to the peer,
|
|
498
|
-
which may then publish more than it publishes to the world.
|
|
499
|
-
|
|
500
|
-
|
|
501
|
+
which may then publish more than it publishes to the world.
|
|
502
|
+
|
|
503
|
+
`newest` caps how many items one check of a **feed** may take, counting from the newest, and
|
|
504
|
+
defaults to 1 — planet.openstreetmap.org lists five planet dumps, and taking the lot is four
|
|
505
|
+
hundred gigabytes nobody asked for. It means nothing to a catalogue, which lists everything.
|
|
506
|
+
|
|
507
|
+
`keep` and `keepDays` retire what a subscription has brought in, exactly as they do for a watched
|
|
508
|
+
folder or a scheduled source: only after something new has landed, and never the newest copy. They
|
|
509
|
+
answer for your disk. `prune` answers for the publisher — it is off unless chosen, only ever
|
|
510
|
+
considers archives that peer sent, never acts on a filtered or partial view, and **applies to a
|
|
511
|
+
catalogue only**, since absence from a bounded feed is not evidence that anything was withdrawn.
|
|
512
|
+
Start a new peer on `"report"` and watch it before trusting it with more.
|
|
501
513
|
|
|
502
514
|
Peer tokens are redacted from `GET /api/config` like any other credential, and a save that echoes
|
|
503
515
|
the placeholder back keeps the stored one.
|
package/docs/subscribing.md
CHANGED
|
@@ -117,9 +117,16 @@ poor trade for the archive itself.
|
|
|
117
117
|
|
|
118
118
|
## Removing what a peer no longer offers
|
|
119
119
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
120
|
+
Two different questions, with two different answers, and it is worth keeping
|
|
121
|
+
them apart: whether the **publisher** still offers an archive, and whether
|
|
122
|
+
**you** still want it on your disk.
|
|
123
|
+
|
|
124
|
+
### The publisher stopped offering it
|
|
125
|
+
|
|
126
|
+
A feed can only ever say "here is something new". Nothing about it distinguishes
|
|
127
|
+
"withdrawn" from "further down a list that only carries five items", so absence
|
|
128
|
+
from a feed is not evidence of anything and **`prune` does not apply to an RSS
|
|
129
|
+
subscription at all** — setting it there is quietly inert.
|
|
123
130
|
|
|
124
131
|
The catalogue API can say "here is everything", which is the only way a
|
|
125
132
|
consumer can notice an absence. `prune` acts on that, and it is deliberately
|
|
@@ -289,6 +296,15 @@ OpenStreetMap publishes for the planet dumps:
|
|
|
289
296
|
it is **1 by default**. That feed lists five planet dumps, so without a cap the
|
|
290
297
|
first poll is roughly four hundred gigabytes. `0` takes everything it lists.
|
|
291
298
|
|
|
299
|
+
`keepDays` is how long to keep what it brings in. A feed publishing weekly
|
|
300
|
+
leaves a copy behind every week and the publisher goes on listing all of them,
|
|
301
|
+
so without it a subscription grows for ever — which is the case pruning cannot
|
|
302
|
+
help with, because nothing was ever withdrawn. `keep` says the same thing as a
|
|
303
|
+
count. Both are the rules a watched folder and a scheduled source retire under,
|
|
304
|
+
applied by the same code and with the same guards: **only after something new
|
|
305
|
+
has landed**, and **never the newest copy**, so a feed that goes quiet cannot
|
|
306
|
+
empty your disk.
|
|
307
|
+
|
|
292
308
|
The items are `.osm.pbf`, not map archives, and that is fine: joining an
|
|
293
309
|
existing torrent does not require the content to be anything in particular —
|
|
294
310
|
only *creating* one does. The archive simply is not servable as tiles, and
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pmtiles-swarm",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.1",
|
|
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/retention.js
CHANGED
|
@@ -99,6 +99,7 @@ export function expired({ family, keep, keepDays, now = Date.now() }) {
|
|
|
99
99
|
* @param {number} [options.keep] - How many of the newest to hold.
|
|
100
100
|
* @param {number} [options.keepDays] - How old a build may get, in days.
|
|
101
101
|
* @param {string} options.label - How to name this family in the log.
|
|
102
|
+
* @param {boolean} [options.requireComplete] - Wait for the newest to be whole.
|
|
102
103
|
* @param {number} [options.now] - The current time, for testing.
|
|
103
104
|
* @returns {Promise<string[]>} - The infohashes removed.
|
|
104
105
|
*/
|
|
@@ -109,8 +110,23 @@ export async function retire({
|
|
|
109
110
|
keep,
|
|
110
111
|
keepDays,
|
|
111
112
|
label,
|
|
113
|
+
requireComplete = false,
|
|
112
114
|
now,
|
|
113
115
|
}) {
|
|
116
|
+
// Where the new copy is a download rather than a file that already exists.
|
|
117
|
+
//
|
|
118
|
+
// A watched folder and a scheduled source hand over an archive that is
|
|
119
|
+
// whole: the file was there, or the fetch finished. A subscription does not
|
|
120
|
+
// — it joins a torrent, and the data arrives hours later. Retiring on the
|
|
121
|
+
// join would delete last week's complete copy the moment this week's was
|
|
122
|
+
// announced, leaving nothing complete for the length of the download.
|
|
123
|
+
//
|
|
124
|
+
// So `keep: 1` here means "the last complete copy", which is the only
|
|
125
|
+
// reading of it that is safe.
|
|
126
|
+
if (requireComplete && entry?.complete !== true) {
|
|
127
|
+
return [];
|
|
128
|
+
}
|
|
129
|
+
|
|
114
130
|
const doomed = expired({ family, keep, keepDays, now });
|
|
115
131
|
if (doomed.length === 0) return [];
|
|
116
132
|
|
package/src/subscriptions.js
CHANGED
|
@@ -180,7 +180,7 @@ export class SubscriptionManager {
|
|
|
180
180
|
// from a bounded feed proves nothing — but age applies whatever the list
|
|
181
181
|
// is, which is what makes this the answer for a feed that publishes
|
|
182
182
|
// weekly for ever.
|
|
183
|
-
await this.#retire(subscription
|
|
183
|
+
await this.#retire(subscription);
|
|
184
184
|
return added;
|
|
185
185
|
}
|
|
186
186
|
|
|
@@ -259,7 +259,7 @@ export class SubscriptionManager {
|
|
|
259
259
|
}
|
|
260
260
|
|
|
261
261
|
await this.#prune(subscription, document, archives);
|
|
262
|
-
await this.#retire(subscription
|
|
262
|
+
await this.#retire(subscription);
|
|
263
263
|
return added;
|
|
264
264
|
}
|
|
265
265
|
|
|
@@ -279,23 +279,33 @@ export class SubscriptionManager {
|
|
|
279
279
|
* Only after something new has landed, and never the newest copy, which are
|
|
280
280
|
* the same guards a scheduled source retires under. See `retire`.
|
|
281
281
|
* @param {object} subscription - The subscription.
|
|
282
|
-
* @param {object[]} added - What this pass took, newest first.
|
|
283
282
|
* @returns {Promise<void>} - Resolves once anything due has gone.
|
|
284
283
|
*/
|
|
285
|
-
async #retire(subscription
|
|
286
|
-
if (
|
|
287
|
-
|
|
284
|
+
async #retire(subscription) {
|
|
285
|
+
if (!retains(subscription)) return;
|
|
286
|
+
|
|
287
|
+
// Only what this feed brought in. An archive built here, added by hand, or
|
|
288
|
+
// taken from another peer is not this subscription's to remove.
|
|
289
|
+
const family = this.#library.catalog
|
|
290
|
+
.list()
|
|
291
|
+
.filter((entry) => entry.source?.subscription === subscription.url);
|
|
292
|
+
if (family.length === 0) return;
|
|
293
|
+
|
|
294
|
+
// Every poll, not only the polls that took something. What is being waited
|
|
295
|
+
// for is the newest copy *finishing*, which happens hours after the poll
|
|
296
|
+
// that started it — so retiring only on the way in would either act far
|
|
297
|
+
// too early or never act at all.
|
|
288
298
|
await retire({
|
|
289
299
|
library: this.#library,
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
family: this.#library.catalog
|
|
293
|
-
.list()
|
|
294
|
-
.filter((entry) => entry.source?.subscription === subscription.url),
|
|
295
|
-
entry: added[0],
|
|
300
|
+
family,
|
|
301
|
+
entry: family[0],
|
|
296
302
|
keep: subscription.keep,
|
|
297
303
|
keepDays: subscription.keepDays,
|
|
298
304
|
label: `[sync] ${subscription.url}`,
|
|
305
|
+
// The newest copy here is a download rather than a file that was already
|
|
306
|
+
// there, so nothing goes until it is whole. That is what makes `keep: 1`
|
|
307
|
+
// mean "the last complete copy".
|
|
308
|
+
requireComplete: true,
|
|
299
309
|
});
|
|
300
310
|
}
|
|
301
311
|
|