pmtiles-swarm 0.52.0 → 0.53.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 +19 -0
- package/docs/configuration.md +17 -6
- package/package.json +1 -1
- package/src/library.js +38 -0
- package/src/subscriptions.js +8 -0
- package/src/web/index.html +32 -0
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,25 @@
|
|
|
7
7
|
### 🐞 Bug fixes
|
|
8
8
|
- _...Add new stuff here..._
|
|
9
9
|
|
|
10
|
+
## 0.53.0
|
|
11
|
+
### ✨ Features and improvements
|
|
12
|
+
- **The three switches are now settable on every import, from the console.** **Serve file**, **Web
|
|
13
|
+
seed** and **Listed** columns on monitored folders, watched web locations, RSS feeds and remote
|
|
14
|
+
nodes, each offering `node` as well as yes and no — because unset is a real answer here, meaning
|
|
15
|
+
the archive follows the node rather than being switched off.
|
|
16
|
+
|
|
17
|
+
### 🐞 Bug fixes
|
|
18
|
+
- **An RSS feed could not ask for any of them.** 0.50.0 wired these through watched folders and
|
|
19
|
+
scheduled sources and stopped there, and an archive adopted from a feed takes a different path
|
|
20
|
+
through the library — which is the path a mirror node actually uses. A subscription now carries all
|
|
21
|
+
three, and there was no way to set them from the console on any import at all.
|
|
22
|
+
|
|
23
|
+
- **`selfWebSeed` on an import waits for the download to finish.** Publishing a web seed URL for an
|
|
24
|
+
archive that is still arriving would advertise an address answering `409`, and a peer handed a URL
|
|
25
|
+
that refuses spends its retries on it — worse than no web seed, and unfixable afterwards because by
|
|
26
|
+
then the URL is in every copy of the `.torrent`. The intention is recorded when the archive joins
|
|
27
|
+
and acted on at the first moment this node holds the whole file.
|
|
28
|
+
|
|
10
29
|
## 0.52.0
|
|
11
30
|
### ✨ Features and improvements
|
|
12
31
|
- **`publishingUrl`, for the URLs that have to be permanent.** Almost every URL this node emits is
|
package/docs/configuration.md
CHANGED
|
@@ -433,12 +433,23 @@ web seed, because a client spends its retries on it, and a download link that
|
|
|
433
433
|
|
|
434
434
|
### Per archive, per folder, per source
|
|
435
435
|
|
|
436
|
-
The
|
|
437
|
-
[
|
|
438
|
-
may carry
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
436
|
+
The node's setting is the default. A [watched folder](#watched-folders), a
|
|
437
|
+
[scheduled source](#scheduled-sources), an [RSS feed](#subscriptions) and a
|
|
438
|
+
remote node may each carry their own — the **Serve file**, **Web seed** and
|
|
439
|
+
**Listed** columns on those tables, where `node` means "no opinion" rather than
|
|
440
|
+
"off". And any individual archive can be switched in the console, under **HTTP
|
|
441
|
+
sources** in its details.
|
|
442
|
+
|
|
443
|
+
An archive that says nothing goes on following the node, so changing the node's
|
|
444
|
+
answer reaches every archive that never had one of its own.
|
|
445
|
+
|
|
446
|
+
Unlike `md5`, these do apply to a subscription. `md5` is a hashing pass that only
|
|
447
|
+
happens where a torrent is built, and a subscription adopts one somebody else
|
|
448
|
+
built; this is about what happens to the archive afterwards, which is this node's
|
|
449
|
+
business whoever made it. **`selfWebSeed` waits for the download to finish**: a
|
|
450
|
+
web seed URL for an archive still arriving answers `409`, and a peer handed a URL
|
|
451
|
+
that refuses spends its retries on it — worse than no web seed, and unfixable
|
|
452
|
+
afterwards, because by then the URL is in every copy of the `.torrent`.
|
|
442
453
|
|
|
443
454
|
## Trackers
|
|
444
455
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pmtiles-swarm",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.53.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/library.js
CHANGED
|
@@ -526,6 +526,39 @@ export class Library {
|
|
|
526
526
|
* @returns {Promise<object>} - The updated catalog entry.
|
|
527
527
|
*/
|
|
528
528
|
async finalize(infoHash) {
|
|
529
|
+
const settled = await this.#finalizeOnce(infoHash);
|
|
530
|
+
|
|
531
|
+
// Only now, and never at import. A web seed URL for an archive that is
|
|
532
|
+
// still arriving answers 409, and a peer handed a URL that refuses spends
|
|
533
|
+
// its retries on it — worse than no web seed at all, and unfixable
|
|
534
|
+
// afterwards, because the URL is in the .torrent every peer holds. So a
|
|
535
|
+
// subscription records the intention when it joins and it is acted on
|
|
536
|
+
// here, at the first moment this node actually holds the whole file.
|
|
537
|
+
//
|
|
538
|
+
// Safe to reach on an archive that was already complete: setPublishing
|
|
539
|
+
// works from what is on record rather than from a transition, so this does
|
|
540
|
+
// nothing the second time — and does the right thing the first time for an
|
|
541
|
+
// archive that finished before the setting existed.
|
|
542
|
+
if (settled && publishingFor(settled, this.#config).selfWebSeed) {
|
|
543
|
+
try {
|
|
544
|
+
await this.setPublishing(infoHash, {});
|
|
545
|
+
return this.#catalog.get(infoHash) ?? settled;
|
|
546
|
+
} catch (error) {
|
|
547
|
+
console.warn(
|
|
548
|
+
`[web seed] ${settled.name} is not published as a web seed by this ` +
|
|
549
|
+
`node: ${error.message}`,
|
|
550
|
+
);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
return settled;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
/**
|
|
557
|
+
* The rename and the bookkeeping, without the publishing that follows it.
|
|
558
|
+
* @param {string} infoHash - The archive that finished.
|
|
559
|
+
* @returns {Promise<object>} - The updated catalog entry.
|
|
560
|
+
*/
|
|
561
|
+
async #finalizeOnce(infoHash) {
|
|
529
562
|
const entry = this.#catalog.get(infoHash);
|
|
530
563
|
if (!entry) throw new Error('unknown archive');
|
|
531
564
|
if (entry.complete) return entry;
|
|
@@ -1145,6 +1178,11 @@ export class Library {
|
|
|
1145
1178
|
complete,
|
|
1146
1179
|
// Held until the download finishes, which may be hours away.
|
|
1147
1180
|
originMtime: options.originMtime,
|
|
1181
|
+
// Recorded now, applied when the download finishes. Unset leaves the
|
|
1182
|
+
// archive following the node, which is the rule everywhere else.
|
|
1183
|
+
serveArchive: options.serveArchive,
|
|
1184
|
+
selfWebSeed: options.selfWebSeed,
|
|
1185
|
+
publicDownload: options.publicDownload,
|
|
1148
1186
|
// What the peer that offered this says it holds, where it said anything.
|
|
1149
1187
|
// The head warmer replaces it with what the archive's own header says as
|
|
1150
1188
|
// soon as it can read one; until then this is what makes the archive
|
package/src/subscriptions.js
CHANGED
|
@@ -423,6 +423,14 @@ export class SubscriptionManager {
|
|
|
423
423
|
// 698 GiB download before starting one, which means it is populated
|
|
424
424
|
// before a single byte exists here.
|
|
425
425
|
summarySource: 'feed',
|
|
426
|
+
// What this node will offer of the archive once it holds it. Unset here
|
|
427
|
+
// means the node's own answer applies, the same rule as everywhere else.
|
|
428
|
+
// Nothing takes effect until the download finishes: a web seed URL for
|
|
429
|
+
// an archive that is still arriving answers 409, which is worse than no
|
|
430
|
+
// web seed because peers spend their retries on it.
|
|
431
|
+
serveArchive: subscription.serveArchive,
|
|
432
|
+
selfWebSeed: subscription.selfWebSeed,
|
|
433
|
+
publicDownload: subscription.publicDownload,
|
|
426
434
|
};
|
|
427
435
|
|
|
428
436
|
// The .torrent is preferred where there is one: it carries the trackers
|
package/src/web/index.html
CHANGED
|
@@ -4654,6 +4654,34 @@ Every piece is hashed against the ` +
|
|
|
4654
4654
|
await renderTokenEditor(body);
|
|
4655
4655
|
renderHookEditor(body, config, restartKeys);
|
|
4656
4656
|
|
|
4657
|
+
// The three switches the details panel offers, as import defaults.
|
|
4658
|
+
// Each has a blank option as well as yes and no, because unset is a
|
|
4659
|
+
// real answer here: it means the archive follows the node, and a row
|
|
4660
|
+
// that has never said anything must keep not saying it. See
|
|
4661
|
+
// publishingFor() -- the node's setting reaches everything that never
|
|
4662
|
+
// had one of its own, and that only works if "no opinion" is storable.
|
|
4663
|
+
const publishing = ['', 'node'];
|
|
4664
|
+
const publishingColumns = [
|
|
4665
|
+
{
|
|
4666
|
+
field: 'serveArchive',
|
|
4667
|
+
label: 'Serve file',
|
|
4668
|
+
boolean: true,
|
|
4669
|
+
options: [publishing, ['true', 'yes'], ['false', 'no']],
|
|
4670
|
+
},
|
|
4671
|
+
{
|
|
4672
|
+
field: 'selfWebSeed',
|
|
4673
|
+
label: 'Web seed',
|
|
4674
|
+
boolean: true,
|
|
4675
|
+
options: [publishing, ['true', 'yes'], ['false', 'no']],
|
|
4676
|
+
},
|
|
4677
|
+
{
|
|
4678
|
+
field: 'publicDownload',
|
|
4679
|
+
label: 'Listed',
|
|
4680
|
+
boolean: true,
|
|
4681
|
+
options: [publishing, ['true', 'yes'], ['false', 'no']],
|
|
4682
|
+
},
|
|
4683
|
+
];
|
|
4684
|
+
|
|
4657
4685
|
renderRowEditor({
|
|
4658
4686
|
into: body,
|
|
4659
4687
|
key: 'locations',
|
|
@@ -4747,6 +4775,7 @@ Every piece is hashed against the ` +
|
|
|
4747
4775
|
placeholder: 'for ever',
|
|
4748
4776
|
number: true,
|
|
4749
4777
|
},
|
|
4778
|
+
...publishingColumns,
|
|
4750
4779
|
],
|
|
4751
4780
|
rows: config.watch ?? [],
|
|
4752
4781
|
footnote:
|
|
@@ -4855,6 +4884,7 @@ Every piece is hashed against the ` +
|
|
|
4855
4884
|
placeholder: 'for ever',
|
|
4856
4885
|
number: true,
|
|
4857
4886
|
},
|
|
4887
|
+
...publishingColumns,
|
|
4858
4888
|
],
|
|
4859
4889
|
rows: config.sources ?? [],
|
|
4860
4890
|
checkNow: '/api/sources/check',
|
|
@@ -4979,6 +5009,7 @@ Every piece is hashed against the ` +
|
|
|
4979
5009
|
['false', 'no'],
|
|
4980
5010
|
],
|
|
4981
5011
|
},
|
|
5012
|
+
...publishingColumns,
|
|
4982
5013
|
],
|
|
4983
5014
|
rows: following.filter((row) => !isPeer(row)),
|
|
4984
5015
|
checkNow: '/api/subscriptions/refresh',
|
|
@@ -5053,6 +5084,7 @@ Every piece is hashed against the ` +
|
|
|
5053
5084
|
['false', 'no'],
|
|
5054
5085
|
],
|
|
5055
5086
|
},
|
|
5087
|
+
...publishingColumns,
|
|
5056
5088
|
],
|
|
5057
5089
|
rows: following.filter(isPeer),
|
|
5058
5090
|
checkNow: '/api/subscriptions/refresh',
|