amule-ec-client 0.6.1 → 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/README.md +10 -2
- package/dist/{AddLinkRequest-D2IME4eb.js → AddLinkRequest-CGv1H3PC.js} +2 -3
- package/dist/{ClientQueueRequest-DFfCiNcz.js → ClientQueueRequest-mklt-KLi.js} +2 -3
- package/dist/{ClientQueueResponse-JTrBqrr1.js → ClientQueueResponse-RfXh2JGa.js} +8 -5
- package/dist/{CreateCategoryRequest-CRUMcPo5.js → CreateCategoryRequest-Cyp9G7jU.js} +2 -3
- package/dist/{DeleteCategoryRequest-C8v9oGZ-.js → DeleteCategoryRequest-DbLJ4mvi.js} +2 -3
- package/dist/{DownloadCommandRequest-B3KV_bvm.js → DownloadCommandRequest-DLU7RtYs.js} +2 -3
- package/dist/{DownloadFileRequest-BjrroO3h.js → DownloadFileRequest-Db7oijyu.js} +2 -3
- package/dist/{DownloadQueueRequest-Dil5hl3D.js → DownloadQueueRequest-DdHRjl4L.js} +2 -3
- package/dist/{GetPreferencesRequest-DsxlghqT.js → GetPreferencesRequest-BlWGYNCP.js} +2 -3
- package/dist/{PreferencesDetailsResponse-DapscVQi.js → PreferencesDetailsResponse-BX6qUoga.js} +1 -2
- package/dist/{PreferencesResponse-8n1STWUZ.js → PreferencesResponse-migFtIG7.js} +1 -2
- package/dist/{ReloadSharedFilesRequest-kt0Pxs8A.js → ReloadSharedFilesRequest-WD9iYLje.js} +2 -2
- package/dist/{Request-C0UUuQIX.js → Request-YQ0nIM6b.js} +3 -9
- package/dist/{SearchProgressResponse-C1CdzKnz.js → SearchProgressResponse-CkEgZGFE.js} +1 -2
- package/dist/{SearchRequest-DQnKfA5Z.js → SearchRequest-DaAKS4IO.js} +2 -3
- package/dist/{SearchResultsRequest-2ejFGQmQ.js → SearchResultsRequest-B53jwjRH.js} +2 -2
- package/dist/{SearchResultsResponse-DwmvbQop.js → SearchResultsResponse-C1l-bBja.js} +1 -2
- package/dist/{SearchStatusRequest-B_xr4-72.js → SearchStatusRequest-DuEa5ZYu.js} +2 -2
- package/dist/{SearchStopRequest-8hwXobty.js → SearchStopRequest-B5TZdMiB.js} +2 -2
- package/dist/{ServerConnectRequest-BhNpGiEJ.js → ServerConnectRequest-BeB8RSBN.js} +2 -3
- package/dist/{ServerDisconnectRequest-BjCitLtU.js → ServerDisconnectRequest-CSkUHETP.js} +2 -2
- package/dist/{ServerListRequest-BWXLGJib.js → ServerListRequest-B82686jD.js} +2 -2
- package/dist/{ServerListResponse-BUJ7sz5p.js → ServerListResponse-M3C2OcfZ.js} +5 -4
- package/dist/{ServerUpdateFromUrlRequest-K8C_d0UQ.js → ServerUpdateFromUrlRequest-BcvhRKgH.js} +2 -3
- package/dist/{SetFileCategoryRequest-VwroLBTV.js → SetFileCategoryRequest-C1jWsYO5.js} +2 -3
- package/dist/{SetPreferencesRequest-KZ23VaBY.js → SetPreferencesRequest-CNJVmeQl.js} +2 -3
- package/dist/{SharedFilePriorityRequest-7BriqAcr.js → SharedFilePriorityRequest-CBqkaXaK.js} +2 -3
- package/dist/{SharedFilesRequest-B72XyRcA.js → SharedFilesRequest-5mKKjzgt.js} +2 -3
- package/dist/SharedFilesResponse-CFl0NuO-.js +45 -0
- package/dist/{StatsRequest-CGGumOOx.js → StatsRequest-C9gBUvSq.js} +2 -3
- package/dist/{StatsResponse-DxQk686g.js → StatsResponse-KOk-xT6_.js} +25 -2
- package/dist/{Codes-CjiW7Pdl.js → Tag-D_Ybe5RQ.js} +280 -1
- package/dist/{UpdateCategoryRequest-CzKJa_rC.js → UpdateCategoryRequest-BOYfb6Vn.js} +2 -3
- package/dist/{UpdateRequest-g2ReOQ5m.js → UpdateRequest-csLQz7JO.js} +2 -3
- package/dist/UpdateState-LhdtiATk.js +443 -0
- package/dist/index.d.ts +381 -337
- package/dist/index.js +116 -55
- package/dist/rolldown-runtime-D7D4PA-g.js +13 -0
- package/dist/{utils-DBnUQnZ-.js → utils-CNoUroOI.js} +11 -1
- package/package.json +1 -1
- package/dist/DownloadQueueResponse-DUam_zNf.js +0 -259
- package/dist/SharedFilesResponse-CkqvYd3N.js +0 -39
- package/dist/Tag-CZF6BRMY.js +0 -260
- package/dist/UpdateResponse-CGwVjn0Y.js +0 -45
|
@@ -0,0 +1,443 @@
|
|
|
1
|
+
import { f as findNumericTag, p as findTag } from "./Tag-D_Ybe5RQ.js";
|
|
2
|
+
import { n as PARTSIZE, t as ChunkStatus } from "./download-details-OM0FJUhZ.js";
|
|
3
|
+
import { a as toOptionalNumber, i as toOptionalIp, n as tagOwnNumericValue, r as toOptionalBool } from "./utils-CNoUroOI.js";
|
|
4
|
+
import { r as parseKnownFileFields, t as SharedFilesResponseParser } from "./SharedFilesResponse-CFl0NuO-.js";
|
|
5
|
+
import { t as ClientQueueResponseParser } from "./ClientQueueResponse-RfXh2JGa.js";
|
|
6
|
+
import { t as ServerListResponseParser } from "./ServerListResponse-M3C2OcfZ.js";
|
|
7
|
+
//#region src/response/DownloadDetailsResponse.ts
|
|
8
|
+
/**
|
|
9
|
+
* Decode an aMule EC RLE-compressed buffer.
|
|
10
|
+
* Format: [value, value, count] repeats value count times; other bytes pass through.
|
|
11
|
+
*/
|
|
12
|
+
function rleDecode(buf) {
|
|
13
|
+
let size = 0;
|
|
14
|
+
let index = 0;
|
|
15
|
+
while (index < buf.length) if (index < buf.length - 2 && buf[index + 1] === buf[index]) {
|
|
16
|
+
size += buf[index + 2];
|
|
17
|
+
index += 3;
|
|
18
|
+
} else {
|
|
19
|
+
size += 1;
|
|
20
|
+
index += 1;
|
|
21
|
+
}
|
|
22
|
+
const output = Buffer.alloc(size);
|
|
23
|
+
let outIndex = 0;
|
|
24
|
+
index = 0;
|
|
25
|
+
while (index < buf.length) if (index < buf.length - 2 && buf[index + 1] === buf[index]) {
|
|
26
|
+
const count = buf[index + 2];
|
|
27
|
+
output.fill(buf[index], outIndex, outIndex + count);
|
|
28
|
+
outIndex += count;
|
|
29
|
+
index += 3;
|
|
30
|
+
} else {
|
|
31
|
+
output[outIndex++] = buf[index];
|
|
32
|
+
index += 1;
|
|
33
|
+
}
|
|
34
|
+
return output;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Reconstruct the current state from the previous state and an RLE-decoded XOR diff,
|
|
38
|
+
* mirroring aMule's RLE_Data: resize previous buffer to the diff size (zero-extend on
|
|
39
|
+
* grow, truncate on shrink), then XOR byte-wise.
|
|
40
|
+
*/
|
|
41
|
+
function xorReconstruct(previous, decodedDiff) {
|
|
42
|
+
const result = Buffer.alloc(decodedDiff.length, 0);
|
|
43
|
+
previous.copy(result, 0, 0, Math.min(previous.length, decodedDiff.length));
|
|
44
|
+
for (let i = 0; i < decodedDiff.length; i++) result[i] ^= decodedDiff[i];
|
|
45
|
+
return result;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Decode an RLE-decoded buffer of byte-interleaved (column-major) little-endian uint64 values.
|
|
49
|
+
*/
|
|
50
|
+
function decodeInterleavedUint64(decoded) {
|
|
51
|
+
if (decoded.length === 0 || decoded.length % 8 !== 0) return [];
|
|
52
|
+
const valueCount = decoded.length / 8;
|
|
53
|
+
const values = new Array(valueCount);
|
|
54
|
+
for (let i = 0; i < valueCount; i++) {
|
|
55
|
+
let value = 0;
|
|
56
|
+
for (let byte = 7; byte >= 0; byte--) value = value * 256 + decoded[i + byte * valueCount];
|
|
57
|
+
values[i] = value;
|
|
58
|
+
}
|
|
59
|
+
return values;
|
|
60
|
+
}
|
|
61
|
+
function stringValue(tag) {
|
|
62
|
+
if (!tag) return "";
|
|
63
|
+
const value = tag.getValue();
|
|
64
|
+
return typeof value === "string" ? value : "";
|
|
65
|
+
}
|
|
66
|
+
function bufferValue(tag) {
|
|
67
|
+
if (!tag) return;
|
|
68
|
+
const value = tag.getValue();
|
|
69
|
+
return Buffer.isBuffer(value) ? value : void 0;
|
|
70
|
+
}
|
|
71
|
+
function nestedTag(parent, tagName) {
|
|
72
|
+
return parent.nestedTags ? findTag(parent.nestedTags, tagName) : void 0;
|
|
73
|
+
}
|
|
74
|
+
function nestedNumeric(parent, tagName) {
|
|
75
|
+
if (!parent.nestedTags) return 0;
|
|
76
|
+
const numeric = findNumericTag(parent.nestedTags, tagName);
|
|
77
|
+
if (numeric) return Number(numeric.getValue());
|
|
78
|
+
return tagOwnNumericValue(findTag(parent.nestedTags, tagName)) ?? 0;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Extract the raw (RLE-encoded) part/gap/req status buffers of a partfile tag.
|
|
82
|
+
*/
|
|
83
|
+
function extractPartFileStatusBuffers(fileTag) {
|
|
84
|
+
return {
|
|
85
|
+
partStatus: bufferValue(nestedTag(fileTag, 786)),
|
|
86
|
+
gapStatus: bufferValue(nestedTag(fileTag, 787)),
|
|
87
|
+
reqStatus: bufferValue(nestedTag(fileTag, 788))
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Compute per-chunk status from RLE-decoded part/gap/req status buffers.
|
|
92
|
+
*/
|
|
93
|
+
function computeChunkInfo(sizeFull, buffers) {
|
|
94
|
+
const partCount = sizeFull > 0 ? Math.ceil(sizeFull / PARTSIZE) : 0;
|
|
95
|
+
const availability = new Array(partCount).fill(0);
|
|
96
|
+
if (buffers.partStatus && buffers.partStatus.length > 0) for (let i = 0; i < Math.min(buffers.partStatus.length, partCount); i++) availability[i] = buffers.partStatus[i];
|
|
97
|
+
const chunks = new Array(partCount).fill(ChunkStatus.COMPLETE);
|
|
98
|
+
if (partCount > 0 && buffers.gapStatus && buffers.gapStatus.length > 0) {
|
|
99
|
+
const gapValues = decodeInterleavedUint64(buffers.gapStatus);
|
|
100
|
+
for (let i = 0; i + 1 < gapValues.length; i += 2) {
|
|
101
|
+
const gapStart = gapValues[i];
|
|
102
|
+
const gapEnd = gapValues[i + 1];
|
|
103
|
+
if (gapStart > sizeFull || gapEnd > sizeFull || gapStart >= gapEnd) continue;
|
|
104
|
+
const startPart = Math.floor(gapStart / PARTSIZE);
|
|
105
|
+
const endPart = Math.min(Math.floor(gapEnd / PARTSIZE) + 1, partCount);
|
|
106
|
+
for (let part = startPart; part < endPart; part++) {
|
|
107
|
+
const chunkStart = part * PARTSIZE;
|
|
108
|
+
if (gapStart < Math.min((part + 1) * 9728e3, sizeFull) && gapEnd > chunkStart) chunks[part] = availability[part] > 0 ? ChunkStatus.AVAILABLE : ChunkStatus.UNAVAILABLE;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
if (partCount > 0 && buffers.reqStatus && buffers.reqStatus.length > 0) {
|
|
113
|
+
const reqValues = decodeInterleavedUint64(buffers.reqStatus);
|
|
114
|
+
for (let i = 0; i + 1 < reqValues.length; i += 2) {
|
|
115
|
+
const reqStart = reqValues[i];
|
|
116
|
+
const reqEnd = reqValues[i + 1];
|
|
117
|
+
if (reqStart > sizeFull || reqEnd > sizeFull || reqStart >= reqEnd) continue;
|
|
118
|
+
const startPart = Math.floor(reqStart / PARTSIZE);
|
|
119
|
+
const endPart = Math.min(Math.floor(reqEnd / PARTSIZE) + 1, partCount);
|
|
120
|
+
for (let part = startPart; part < endPart; part++) {
|
|
121
|
+
if (chunks[part] === ChunkStatus.COMPLETE) continue;
|
|
122
|
+
const chunkStart = part * PARTSIZE;
|
|
123
|
+
if (reqStart < Math.min((part + 1) * 9728e3, sizeFull) && reqEnd > chunkStart) chunks[part] = ChunkStatus.DOWNLOADING;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return {
|
|
128
|
+
chunks,
|
|
129
|
+
availability,
|
|
130
|
+
partCount,
|
|
131
|
+
sizeFull
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Decode the status buffers of a file tag encoded after a daemon-side encoder reset,
|
|
136
|
+
* i.e. a full snapshot: EC_OP_GET_DLOAD_QUEUE / EC_OP_GET_SHARED_FILES responses at any
|
|
137
|
+
* detail level other than EC_DETAIL_UPDATE (see Get_EC_Response_GetDownloadQueue in
|
|
138
|
+
* aMule's ExternalConn.cpp). After the reset, a present buffer holds the complete
|
|
139
|
+
* current state and an absent one means the state is empty (the daemon skips the tag
|
|
140
|
+
* when there is nothing to encode), so every key is always defined.
|
|
141
|
+
*/
|
|
142
|
+
function decodeFullPartFileStatusBuffers(fileTag) {
|
|
143
|
+
const raw = extractPartFileStatusBuffers(fileTag);
|
|
144
|
+
return {
|
|
145
|
+
partStatus: raw.partStatus ? rleDecode(raw.partStatus) : Buffer.alloc(0),
|
|
146
|
+
gapStatus: raw.gapStatus ? rleDecode(raw.gapStatus) : Buffer.alloc(0),
|
|
147
|
+
reqStatus: raw.reqStatus ? rleDecode(raw.reqStatus) : Buffer.alloc(0)
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Raw diff entries of the source-names map of a partfile tag, or undefined when the
|
|
152
|
+
* packet carries no source-names changes for this file.
|
|
153
|
+
*
|
|
154
|
+
* The daemon sends source names (the filenames under which the sources share the file)
|
|
155
|
+
* as a per-connection incremental map (see CPartFile_Encoder::Encode in aMule's
|
|
156
|
+
* ExternalConn.cpp): an EC_TAG_PARTFILE_SOURCE_NAMES container whose children are
|
|
157
|
+
* integer-valued EC_TAG_PARTFILE_SOURCE_NAMES tags (the map key), each carrying a
|
|
158
|
+
* nested count (0 removes the entry) and, only when the entry is new, a nested name
|
|
159
|
+
* string. The map is never reset while the connection lives, not even for
|
|
160
|
+
* EC_DETAIL_FULL requests.
|
|
161
|
+
*/
|
|
162
|
+
function sourceNameEntriesFromFileTag(fileTag) {
|
|
163
|
+
const container = nestedTag(fileTag, 789);
|
|
164
|
+
if (!container) return;
|
|
165
|
+
const entries = [];
|
|
166
|
+
for (const child of container.nestedTags || []) {
|
|
167
|
+
if (child.name !== 789) continue;
|
|
168
|
+
const id = tagOwnNumericValue(child);
|
|
169
|
+
if (id === void 0) continue;
|
|
170
|
+
const name = stringValue(nestedTag(child, 789)) || void 0;
|
|
171
|
+
const count = nestedNumeric(child, 796);
|
|
172
|
+
entries.push({
|
|
173
|
+
id,
|
|
174
|
+
name,
|
|
175
|
+
count
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
return entries;
|
|
179
|
+
}
|
|
180
|
+
//#endregion
|
|
181
|
+
//#region src/response/DownloadQueueResponse.ts
|
|
182
|
+
var DownloadQueueResponseParser = class {
|
|
183
|
+
static fromPacket(packet) {
|
|
184
|
+
const files = [];
|
|
185
|
+
const partfileTags = packet.tags.filter((tag) => tag.name === 768);
|
|
186
|
+
for (const fileTag of partfileTags) {
|
|
187
|
+
const tags = fileTag.nestedTags || [];
|
|
188
|
+
let a4afSources = void 0;
|
|
189
|
+
const a4afSourcesTag = findTag(tags, 802);
|
|
190
|
+
if (a4afSourcesTag) {
|
|
191
|
+
a4afSources = [];
|
|
192
|
+
a4afSourcesTag.nestedTags = a4afSourcesTag.nestedTags || [];
|
|
193
|
+
for (const sourceTag of a4afSourcesTag.nestedTags) if (sourceTag.name === 15) a4afSources.push(sourceTag.getValue());
|
|
194
|
+
}
|
|
195
|
+
const file = {
|
|
196
|
+
...parseKnownFileFields(fileTag),
|
|
197
|
+
partMetID: findNumericTag(tags, 770)?.getShort(),
|
|
198
|
+
sizeXfer: toOptionalNumber(findNumericTag(tags, 772)?.getLong()),
|
|
199
|
+
sizeDone: toOptionalNumber(findNumericTag(tags, 774)?.getLong()),
|
|
200
|
+
fileStatus: findNumericTag(tags, 776)?.getInt(),
|
|
201
|
+
stopped: toOptionalBool(findNumericTag(tags, 791)?.getInt()),
|
|
202
|
+
sourceCount: findNumericTag(tags, 778)?.getShort(),
|
|
203
|
+
sourceNotCurrCount: findNumericTag(tags, 780)?.getShort(),
|
|
204
|
+
sourceXferCount: findNumericTag(tags, 781)?.getShort(),
|
|
205
|
+
sourceCountA4AF: findNumericTag(tags, 779)?.getShort(),
|
|
206
|
+
speed: toOptionalNumber(findNumericTag(tags, 775)?.getLong()),
|
|
207
|
+
downPrio: findNumericTag(tags, 777)?.getInt(),
|
|
208
|
+
fileCat: toOptionalNumber(findNumericTag(tags, 783)?.getLong()),
|
|
209
|
+
lastSeenComplete: toOptionalNumber(findNumericTag(tags, 785)?.getLong()),
|
|
210
|
+
lastDateChanged: toOptionalNumber(findNumericTag(tags, 784)?.getLong()),
|
|
211
|
+
downloadActiveTime: findNumericTag(tags, 792)?.getInt(),
|
|
212
|
+
availablePartCount: findNumericTag(tags, 797)?.getShort(),
|
|
213
|
+
a4AFAuto: toOptionalBool(findNumericTag(tags, 801)?.getInt()),
|
|
214
|
+
hashedPartCount: findNumericTag(tags, 800)?.getInt(),
|
|
215
|
+
getLostDueToCorruption: toOptionalNumber(findNumericTag(tags, 793)?.getLong()),
|
|
216
|
+
getGainDueToCompression: toOptionalNumber(findNumericTag(tags, 794)?.getLong()),
|
|
217
|
+
totalPacketsSavedDueToICH: findNumericTag(tags, 795)?.getInt(),
|
|
218
|
+
a4afSources
|
|
219
|
+
};
|
|
220
|
+
files.push(file);
|
|
221
|
+
}
|
|
222
|
+
return { files };
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
//#endregion
|
|
226
|
+
//#region src/response/UpdateResponse.ts
|
|
227
|
+
var UpdateResponseParser = class {
|
|
228
|
+
static fromPacket(packet) {
|
|
229
|
+
const sharedFiles = SharedFilesResponseParser.fromPacket(packet).files;
|
|
230
|
+
const downloadQueue = DownloadQueueResponseParser.fromPacket(packet).files;
|
|
231
|
+
const clientContainer = findTag(packet.tags, 1536);
|
|
232
|
+
const clients = clientContainer && clientContainer.nestedTags ? ClientQueueResponseParser.fromTags(clientContainer.nestedTags).clients : [];
|
|
233
|
+
const serverContainer = findTag(packet.tags, 1280);
|
|
234
|
+
const servers = serverContainer && serverContainer.nestedTags ? ServerListResponseParser.fromTags(serverContainer.nestedTags).servers : [];
|
|
235
|
+
const friendContainer = findTag(packet.tags, 2048);
|
|
236
|
+
return {
|
|
237
|
+
sharedFiles,
|
|
238
|
+
downloadQueue,
|
|
239
|
+
clients,
|
|
240
|
+
servers,
|
|
241
|
+
friends: friendContainer && friendContainer.nestedTags ? this.parseFriends(friendContainer.nestedTags) : []
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
static parseFriends(tags) {
|
|
245
|
+
const friends = [];
|
|
246
|
+
const friendTags = tags.filter((t) => t.name === 2048);
|
|
247
|
+
for (const friendTag of friendTags) {
|
|
248
|
+
const nested = friendTag.nestedTags || [];
|
|
249
|
+
friends.push({
|
|
250
|
+
name: findTag(nested, 2049)?.getValue(),
|
|
251
|
+
userHashHexString: findTag(nested, 2050)?.getValue()?.toString("hex"),
|
|
252
|
+
ip: toOptionalIp(findNumericTag(nested, 2051)?.getInt()),
|
|
253
|
+
port: findNumericTag(nested, 2052)?.getInt(),
|
|
254
|
+
friendSlot: toOptionalBool(findNumericTag(nested, 2056)?.getInt()),
|
|
255
|
+
shared: toOptionalBool(findNumericTag(nested, 2057)?.getInt()),
|
|
256
|
+
client: ClientQueueResponseParser.fromTags(nested).clients[0]
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
return friends;
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
//#endregion
|
|
263
|
+
//#region src/client/UpdateState.ts
|
|
264
|
+
const STATUS_BUFFER_KEYS = [
|
|
265
|
+
"partStatus",
|
|
266
|
+
"gapStatus",
|
|
267
|
+
"reqStatus"
|
|
268
|
+
];
|
|
269
|
+
/**
|
|
270
|
+
* Merge update fields into the existing object. Fields absent from an incremental
|
|
271
|
+
* response are undefined after parsing and must not clobber previously known values.
|
|
272
|
+
*/
|
|
273
|
+
function mergeDefined(existing, updates) {
|
|
274
|
+
const result = { ...existing };
|
|
275
|
+
for (const [key, value] of Object.entries(updates)) if (value !== void 0) result[key] = value;
|
|
276
|
+
return result;
|
|
277
|
+
}
|
|
278
|
+
var UpdateState = class {
|
|
279
|
+
sessionGeneration;
|
|
280
|
+
downloads = /* @__PURE__ */ new Map();
|
|
281
|
+
sharedFiles = /* @__PURE__ */ new Map();
|
|
282
|
+
clients = /* @__PURE__ */ new Map();
|
|
283
|
+
statusBuffers = /* @__PURE__ */ new Map();
|
|
284
|
+
sourceNames = /* @__PURE__ */ new Map();
|
|
285
|
+
/**
|
|
286
|
+
* @param sessionGeneration Connection generation this state belongs to (see
|
|
287
|
+
* AmuleConnection.getSessionGeneration). The state is only valid while the
|
|
288
|
+
* connection that produced it is alive, since the daemon diffs per connection.
|
|
289
|
+
*/
|
|
290
|
+
constructor(sessionGeneration) {
|
|
291
|
+
this.sessionGeneration = sessionGeneration;
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Apply an incremental EC_OP_GET_UPDATE response.
|
|
295
|
+
*/
|
|
296
|
+
apply(packet) {
|
|
297
|
+
const parsed = UpdateResponseParser.fromPacket(packet);
|
|
298
|
+
const downloadQueue = this.mergeCollection(this.downloads, parsed.downloadQueue);
|
|
299
|
+
const sharedFiles = this.mergeCollection(this.sharedFiles, parsed.sharedFiles);
|
|
300
|
+
const clients = this.mergeCollection(this.clients, parsed.clients);
|
|
301
|
+
for (const ecid of [...this.statusBuffers.keys()]) if (!this.downloads.has(ecid)) this.statusBuffers.delete(ecid);
|
|
302
|
+
for (const ecid of [...this.sourceNames.keys()]) if (!this.downloads.has(ecid)) this.sourceNames.delete(ecid);
|
|
303
|
+
this.reconstructFileDetails(packet);
|
|
304
|
+
this.attachSources();
|
|
305
|
+
return {
|
|
306
|
+
downloadQueue,
|
|
307
|
+
sharedFiles,
|
|
308
|
+
clients,
|
|
309
|
+
servers: parsed.servers,
|
|
310
|
+
friends: parsed.friends
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* Apply a full (non-EC_DETAIL_UPDATE) EC_OP_GET_DLOAD_QUEUE response.
|
|
315
|
+
*
|
|
316
|
+
* The daemon resets each file's diff encoder before encoding it, so the buffers
|
|
317
|
+
* are complete snapshots that become the connection's new diff baseline; they are
|
|
318
|
+
* stored here as-is (replace, not XOR) to keep later incremental updates decodable.
|
|
319
|
+
* Source-name diffs are applied to the shared per-connection map.
|
|
320
|
+
*/
|
|
321
|
+
applyDownloadQueue(packet) {
|
|
322
|
+
const parsed = DownloadQueueResponseParser.fromPacket(packet);
|
|
323
|
+
const partfileTags = packet.tags.filter((tag) => tag.name === 768);
|
|
324
|
+
const seen = /* @__PURE__ */ new Set();
|
|
325
|
+
for (const fileTag of partfileTags) {
|
|
326
|
+
const ecid = tagOwnNumericValue(fileTag);
|
|
327
|
+
if (ecid === void 0) continue;
|
|
328
|
+
seen.add(ecid);
|
|
329
|
+
this.statusBuffers.set(ecid, decodeFullPartFileStatusBuffers(fileTag));
|
|
330
|
+
this.applySourceNameEntries(ecid, sourceNameEntriesFromFileTag(fileTag));
|
|
331
|
+
}
|
|
332
|
+
for (const ecid of [...this.statusBuffers.keys()]) if (!seen.has(ecid)) this.statusBuffers.delete(ecid);
|
|
333
|
+
for (const ecid of [...this.sourceNames.keys()]) if (!seen.has(ecid)) this.sourceNames.delete(ecid);
|
|
334
|
+
for (const file of parsed.files) {
|
|
335
|
+
if (file.ecid === void 0) continue;
|
|
336
|
+
const buffers = this.statusBuffers.get(file.ecid);
|
|
337
|
+
if (buffers && file.sizeFull && file.sizeFull > 0) file.chunkInfo = computeChunkInfo(file.sizeFull, buffers);
|
|
338
|
+
const nameMap = this.sourceNames.get(file.ecid);
|
|
339
|
+
if (nameMap) file.sourceNames = [...nameMap.values()];
|
|
340
|
+
}
|
|
341
|
+
return parsed.files;
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Track the encoder side effects of a full (non-EC_DETAIL_UPDATE)
|
|
345
|
+
* EC_OP_GET_SHARED_FILES response.
|
|
346
|
+
*
|
|
347
|
+
* Shared partfiles use the same per-connection encoder as the download paths: the
|
|
348
|
+
* daemon resets it and re-encodes part/gap/req status (and consumes source-name
|
|
349
|
+
* diffs) into each EC_TAG_KNOWNFILE tag, so the new baselines must be recorded here
|
|
350
|
+
* or the next incremental update decodes into garbage.
|
|
351
|
+
*/
|
|
352
|
+
applySharedFiles(packet) {
|
|
353
|
+
const knownfileTags = packet.tags.filter((tag) => tag.name === 1024);
|
|
354
|
+
for (const fileTag of knownfileTags) {
|
|
355
|
+
const ecid = tagOwnNumericValue(fileTag);
|
|
356
|
+
if (ecid === void 0) continue;
|
|
357
|
+
this.statusBuffers.set(ecid, decodeFullPartFileStatusBuffers(fileTag));
|
|
358
|
+
this.applySourceNameEntries(ecid, sourceNameEntriesFromFileTag(fileTag));
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
mergeCollection(state, items) {
|
|
362
|
+
const seen = /* @__PURE__ */ new Set();
|
|
363
|
+
const result = [];
|
|
364
|
+
for (const item of items) {
|
|
365
|
+
if (item.ecid === void 0) {
|
|
366
|
+
result.push(item);
|
|
367
|
+
continue;
|
|
368
|
+
}
|
|
369
|
+
seen.add(item.ecid);
|
|
370
|
+
const existing = state.get(item.ecid);
|
|
371
|
+
const merged = existing ? mergeDefined(existing, item) : item;
|
|
372
|
+
state.set(item.ecid, merged);
|
|
373
|
+
result.push(merged);
|
|
374
|
+
}
|
|
375
|
+
for (const ecid of [...state.keys()]) if (!seen.has(ecid)) state.delete(ecid);
|
|
376
|
+
return result;
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* Reconstruct the incremental per-file details of each partfile in the packet:
|
|
380
|
+
* RLE-decode and XOR-reconstruct the status buffers (recomputing chunk info from the
|
|
381
|
+
* accumulated state), and apply the source-names map diffs.
|
|
382
|
+
*/
|
|
383
|
+
reconstructFileDetails(packet) {
|
|
384
|
+
const partfileTags = packet.tags.filter((tag) => tag.name === 768);
|
|
385
|
+
for (const fileTag of partfileTags) {
|
|
386
|
+
const ecid = tagOwnNumericValue(fileTag);
|
|
387
|
+
if (ecid === void 0) continue;
|
|
388
|
+
const file = this.downloads.get(ecid);
|
|
389
|
+
if (!file) continue;
|
|
390
|
+
const raw = extractPartFileStatusBuffers(fileTag);
|
|
391
|
+
const state = this.statusBuffers.get(ecid) || {};
|
|
392
|
+
for (const key of STATUS_BUFFER_KEYS) {
|
|
393
|
+
const rawBuffer = raw[key];
|
|
394
|
+
if (!rawBuffer) continue;
|
|
395
|
+
const decoded = rleDecode(rawBuffer);
|
|
396
|
+
const previous = state[key];
|
|
397
|
+
state[key] = previous ? xorReconstruct(previous, decoded) : decoded;
|
|
398
|
+
}
|
|
399
|
+
this.statusBuffers.set(ecid, state);
|
|
400
|
+
if (file.sizeFull && file.sizeFull > 0) file.chunkInfo = computeChunkInfo(file.sizeFull, state);
|
|
401
|
+
this.applySourceNameEntries(ecid, sourceNameEntriesFromFileTag(fileTag));
|
|
402
|
+
const nameMap = this.sourceNames.get(ecid);
|
|
403
|
+
if (nameMap) file.sourceNames = [...nameMap.values()];
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
/**
|
|
407
|
+
* Apply source-names map diffs: new entry (carries the name), count change
|
|
408
|
+
* (no name resent) or removal (count 0).
|
|
409
|
+
*/
|
|
410
|
+
applySourceNameEntries(ecid, entries) {
|
|
411
|
+
if (!entries) return;
|
|
412
|
+
const nameMap = this.sourceNames.get(ecid) || /* @__PURE__ */ new Map();
|
|
413
|
+
for (const entry of entries) {
|
|
414
|
+
if (entry.count === 0) {
|
|
415
|
+
nameMap.delete(entry.id);
|
|
416
|
+
continue;
|
|
417
|
+
}
|
|
418
|
+
const existing = nameMap.get(entry.id);
|
|
419
|
+
if (existing) {
|
|
420
|
+
existing.count = entry.count;
|
|
421
|
+
if (entry.name !== void 0) existing.name = entry.name;
|
|
422
|
+
} else if (entry.name !== void 0) nameMap.set(entry.id, {
|
|
423
|
+
name: entry.name,
|
|
424
|
+
count: entry.count
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
this.sourceNames.set(ecid, nameMap);
|
|
428
|
+
}
|
|
429
|
+
/**
|
|
430
|
+
* Group clients by the download they are requesting (client.requestFileId -> file.ecid).
|
|
431
|
+
*/
|
|
432
|
+
attachSources() {
|
|
433
|
+
for (const file of this.downloads.values()) file.sources = [];
|
|
434
|
+
for (const client of this.clients.values()) {
|
|
435
|
+
const requestFileId = toOptionalNumber(client.requestFileId);
|
|
436
|
+
if (requestFileId === void 0) continue;
|
|
437
|
+
const file = this.downloads.get(requestFileId);
|
|
438
|
+
if (file) file.sources.push(client);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
};
|
|
442
|
+
//#endregion
|
|
443
|
+
export { UpdateState };
|