@torrent-tv/proxy 2.83.1 → 2.83.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
@@ -1,3 +1,9 @@
1
+ ## 2.83.2
2
+
3
+ - **Fix**: 2.83.1 COULD NOT START PLAYBACK AT ALL, and the cause was its own new rule. A torrent nobody has stated anything about leaves its swarm — and a torrent that has just been added has stated nothing yet, because the read of the file's edges is still in flight and the playback plan is still being built. Field 2026-09-11: added at 20:57:17, first peer at 20:57:18, out of the swarm at 20:57:21 with 741 connections let go. Nothing ever rejoined it: rejoining waits for a reader, and the reader was waiting for the header the swarm had been fetching. `[stats]` then read `peers=0 connected of 0 known (94 queued, 5 tracker(s) offered up to 683 seeders)` for as long as the viewer was willing to wait.
4
+ - **Fix**: The condition is a state rather than a period: a torrent is left only once it has been read at least once. That is what separates a film somebody left from a film being opened right now, and a torrent nobody ever reads still goes by the pool's own idle removal, which is where that belongs.
5
+ - **Chore**: Two checks that asserted the old behaviour now read a torrent through its real sequence — read once, then left — and two more cover the state this release is about.
6
+
1
7
  ## 2.83.1
2
8
 
3
9
  - **Fix**: THE PIECE STORE COULD END A TORRENT, AND ON 2026-09-11 IT DID. Switching to the second episode of a five-file torrent reached a store that had shrunk to three blocks of 4 MB while the machine offered 4.3 GB; a claim for a block failed after five seconds, the failure travelled out through the torrent client's own write callback, and the client destroyed the torrent. For the rest of the process every read answered `File 1 not found in torrent:d4022ff4...`, `/stats` said `peers=0 connected of 1186 known`, the header of the new episode never downloaded, and the viewer waited on a plan that could not be built until the addon was restarted. Memory is no longer a reason to refuse anything: an arriving piece that cannot have a block is written straight to disk, which is where it was going anyway.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@torrent-tv/proxy",
3
- "version": "2.83.1",
3
+ "version": "2.83.2",
4
4
  "description": "Torrent proxy client that exposes webseed-like HTTP stream endpoint.",
5
5
  "license": "GPL-3.0-or-later",
6
6
  "publishConfig": {
@@ -971,7 +971,28 @@ export class TorrentPool {
971
971
  }
972
972
  const usage = this.fileUsageByTorrent.get(torrent);
973
973
  const isRead = Boolean(usage && usage.size > 0);
974
+ if (isRead) {
975
+ // Recorded on the torrent, as `hasActiveReader` and `hasUnmetDemand`
976
+ // beside it are: it is a fact about this torrent and it lives as long as
977
+ // the torrent does.
978
+ torrent.hasBeenRead = true;
979
+ }
974
980
  const isWanted = isRead || demandFor(torrent).register.windows().length > 0;
981
+ // A TORRENT NOBODY HAS READ YET IS BEING SET UP, NOT ABANDONED. Field
982
+ // 2026-09-11, and it broke playback outright: a torrent was added at
983
+ // 20:57:17, its first peer connected at 20:57:18, and this pass took it out
984
+ // of the swarm at 20:57:21 — while the read of the file's edges was still
985
+ // in flight and the playback plan was being built. Nothing rejoined it,
986
+ // because rejoining waits for a reader and a reader cannot arrive: the
987
+ // header it needs is downloaded by the swarm that was just let go.
988
+ //
989
+ // The condition is a state and not a period: having been read at least once
990
+ // is what tells a film somebody left from a film nobody has opened yet. One
991
+ // that is never read at all goes by the pool's own idle removal, which is
992
+ // where that belongs.
993
+ if (!isWanted && torrent.hasBeenRead !== true) {
994
+ return;
995
+ }
975
996
  if (isWanted && torrent.paused === true) {
976
997
  torrent.resume();
977
998
  logger.info(
@@ -67,6 +67,11 @@ test("a torrent nobody is reading is let go of, and its data is not", () => {
67
67
  const torrent = torrentWith(6);
68
68
  const pool = poolWith(torrent);
69
69
  try {
70
+ // Read once and left, which is the state this is about: a film nobody has
71
+ // opened yet is being set up, and the test below covers that.
72
+ pool.fileUsageByTorrent.set(torrent, new Map([[0, 1]]));
73
+ pool.followTheReaders(torrent);
74
+ pool.fileUsageByTorrent.set(torrent, new Map());
70
75
  pool.followTheReaders(torrent);
71
76
 
72
77
  assert.equal(torrent.paused, true, "the swarm was not left");
@@ -114,6 +119,9 @@ test("the swarm is rejoined when a reader comes back", () => {
114
119
  const torrent = torrentWith(3);
115
120
  const pool = poolWith(torrent);
116
121
  try {
122
+ pool.fileUsageByTorrent.set(torrent, new Map([[0, 1]]));
123
+ pool.followTheReaders(torrent);
124
+ pool.fileUsageByTorrent.set(torrent, new Map());
117
125
  pool.followTheReaders(torrent);
118
126
  assert.equal(torrent.paused, true);
119
127
 
@@ -124,3 +132,37 @@ test("the swarm is rejoined when a reader comes back", () => {
124
132
  forgetTorrent(torrent);
125
133
  }
126
134
  });
135
+
136
+ test("a torrent nobody has read yet is being set up, and is left alone", () => {
137
+ // Field 2026-09-11, and it broke playback outright: a torrent added at
138
+ // 20:57:17 had its first peer at 20:57:18 and was taken out of the swarm at
139
+ // 20:57:21 — while the read of the file's edges was still in flight. Nothing
140
+ // rejoined it, because rejoining waits for a reader and the reader was
141
+ // waiting for the header the swarm was fetching.
142
+ const torrent = torrentWith(103);
143
+ const pool = poolWith(torrent);
144
+ try {
145
+ pool.followTheReaders(torrent);
146
+ assert.equal(torrent.paused, false, "a torrent being opened was taken out of its swarm");
147
+ assert.equal(torrent._peers.size, 103, "and its connections were let go");
148
+ } finally {
149
+ forgetTorrent(torrent);
150
+ }
151
+ });
152
+
153
+ test("once it has been read, leaving is allowed again", () => {
154
+ const torrent = torrentWith(4);
155
+ const pool = poolWith(torrent);
156
+ try {
157
+ // A reader arrives and goes.
158
+ pool.fileUsageByTorrent.set(torrent, new Map([[0, 1]]));
159
+ pool.followTheReaders(torrent);
160
+ assert.equal(torrent.paused, false);
161
+
162
+ pool.fileUsageByTorrent.set(torrent, new Map());
163
+ pool.followTheReaders(torrent);
164
+ assert.equal(torrent.paused, true, "a film somebody left keeps its swarm for nobody");
165
+ } finally {
166
+ forgetTorrent(torrent);
167
+ }
168
+ });