web-dc-api 0.1.5 → 0.1.6
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/dist/cjs/index.js +1 -1
- package/dist/dc.min.js +1 -1
- package/dist/esm/index.js +1 -1
- package/dist/index.d.ts +934 -878
- package/package.json +4 -8
- package/dist/cjs/helia-core-B1Xqha7a.js +0 -1
- package/dist/cjs/helia-core-D8Uv1KjQ.js +0 -1
- package/dist/cjs/polkadot-api-7PhQf3ws.js +0 -1
- package/dist/cjs/polkadot-api-CtrJVWuZ.js +0 -1
- package/dist/esm/chunks/helia-core-BxMqyK2Y.js +0 -1
- package/dist/esm/chunks/helia-core-DMXRpcO-.js +0 -1
- package/dist/esm/chunks/polkadot-api-5Y9Bw8VT.js +0 -1
- package/dist/esm/chunks/polkadot-api-D69Ioun_.js +0 -1
- package/lib/common/blowfish/block.ts +0 -259
- package/lib/common/blowfish/cipher.ts +0 -144
- package/lib/common/blowfish/const.ts +0 -195
- package/lib/common/chain.ts +0 -469
- package/lib/common/commonclient.ts +0 -202
- package/lib/common/constants.ts +0 -55
- package/lib/common/dc-key/ed25519.ts +0 -343
- package/lib/common/dc-key/keyManager.ts +0 -424
- package/lib/common/dcapi.ts +0 -98
- package/lib/common/dcutil.ts +0 -627
- package/lib/common/define.ts +0 -70
- package/lib/common/error.ts +0 -67
- package/lib/common/grpc-dc.ts +0 -104
- package/lib/common/module-system.ts +0 -184
- package/lib/common/service-worker.ts +0 -234
- package/lib/common/types/types.ts +0 -344
- package/lib/dc.ts +0 -701
- package/lib/implements/account/client.ts +0 -185
- package/lib/implements/account/manager.ts +0 -683
- package/lib/implements/aiproxy/client.ts +0 -357
- package/lib/implements/aiproxy/manager.ts +0 -670
- package/lib/implements/cache/client.ts +0 -105
- package/lib/implements/cache/manager.ts +0 -127
- package/lib/implements/comment/client.ts +0 -982
- package/lib/implements/comment/manager.ts +0 -1151
- package/lib/implements/dc/client.ts +0 -51
- package/lib/implements/dc/manager.ts +0 -33
- package/lib/implements/file/client.ts +0 -253
- package/lib/implements/file/file-cache-manager.ts +0 -142
- package/lib/implements/file/manager.ts +0 -1240
- package/lib/implements/file/seekableFileStream.ts +0 -344
- package/lib/implements/file/streamwriter.ts +0 -322
- package/lib/implements/keyvalue/client.ts +0 -376
- package/lib/implements/keyvalue/manager.ts +0 -759
- package/lib/implements/message/client.ts +0 -250
- package/lib/implements/message/manager.ts +0 -215
- package/lib/implements/threaddb/cbor/coding.ts +0 -62
- package/lib/implements/threaddb/cbor/event.ts +0 -336
- package/lib/implements/threaddb/cbor/node.ts +0 -542
- package/lib/implements/threaddb/cbor/record.ts +0 -398
- package/lib/implements/threaddb/common/AsyncMutex.ts +0 -24
- package/lib/implements/threaddb/common/addrinfo.ts +0 -135
- package/lib/implements/threaddb/common/dispatcher.ts +0 -81
- package/lib/implements/threaddb/common/idbstore-adapter.ts +0 -260
- package/lib/implements/threaddb/common/json-patcher.ts +0 -204
- package/lib/implements/threaddb/common/key.ts +0 -290
- package/lib/implements/threaddb/common/level-adapter.ts +0 -235
- package/lib/implements/threaddb/common/lineReader.ts +0 -79
- package/lib/implements/threaddb/common/logstore.ts +0 -215
- package/lib/implements/threaddb/common/transformed-datastore.ts +0 -308
- package/lib/implements/threaddb/core/app.ts +0 -206
- package/lib/implements/threaddb/core/core.ts +0 -230
- package/lib/implements/threaddb/core/db.ts +0 -249
- package/lib/implements/threaddb/core/event.ts +0 -54
- package/lib/implements/threaddb/core/head.ts +0 -89
- package/lib/implements/threaddb/core/identity.ts +0 -171
- package/lib/implements/threaddb/core/logstore.ts +0 -137
- package/lib/implements/threaddb/core/options.ts +0 -14
- package/lib/implements/threaddb/core/record.ts +0 -54
- package/lib/implements/threaddb/db/collection.ts +0 -1910
- package/lib/implements/threaddb/db/db.ts +0 -698
- package/lib/implements/threaddb/db/json2Query.ts +0 -192
- package/lib/implements/threaddb/db/query.ts +0 -524
- package/lib/implements/threaddb/dbclient.ts +0 -543
- package/lib/implements/threaddb/dbmanager.ts +0 -1906
- package/lib/implements/threaddb/lsstoreds/addr_book.ts +0 -549
- package/lib/implements/threaddb/lsstoreds/cache.ts +0 -36
- package/lib/implements/threaddb/lsstoreds/cyclic_batch.ts +0 -87
- package/lib/implements/threaddb/lsstoreds/global.ts +0 -151
- package/lib/implements/threaddb/lsstoreds/headbook.ts +0 -373
- package/lib/implements/threaddb/lsstoreds/keybook.ts +0 -297
- package/lib/implements/threaddb/lsstoreds/logstore.ts +0 -29
- package/lib/implements/threaddb/lsstoreds/metadata.ts +0 -223
- package/lib/implements/threaddb/net/define.ts +0 -149
- package/lib/implements/threaddb/net/grpcClient.ts +0 -589
- package/lib/implements/threaddb/net/grpcserver.ts +0 -146
- package/lib/implements/threaddb/net/net.ts +0 -2047
- package/lib/implements/threaddb/pb/lstore.proto +0 -38
- package/lib/implements/threaddb/pb/lstore.ts +0 -393
- package/lib/implements/threaddb/pb/lstore_pb.d.ts +0 -433
- package/lib/implements/threaddb/pb/lstore_pb.js +0 -1085
- package/lib/implements/threaddb/pb/net.proto +0 -194
- package/lib/implements/threaddb/pb/net_pb.d.ts +0 -2349
- package/lib/implements/threaddb/pb/net_pb.js +0 -5525
- package/lib/implements/threaddb/pb/proto-custom-types.ts +0 -212
- package/lib/implements/util/client.ts +0 -72
- package/lib/implements/util/manager.ts +0 -146
- package/lib/implements/wallet/manager.ts +0 -671
- package/lib/index.ts +0 -57
- package/lib/interfaces/DCContext.ts +0 -51
- package/lib/interfaces/aiproxy-interface.ts +0 -145
- package/lib/interfaces/auth-interface.ts +0 -118
- package/lib/interfaces/cache-interface.ts +0 -22
- package/lib/interfaces/client-interface.ts +0 -11
- package/lib/interfaces/comment-interface.ts +0 -167
- package/lib/interfaces/components/news-component.ts +0 -0
- package/lib/interfaces/database-interface.ts +0 -169
- package/lib/interfaces/file-interface.ts +0 -120
- package/lib/interfaces/index.ts +0 -10
- package/lib/interfaces/keyvalue-interface.ts +0 -156
- package/lib/interfaces/message-interface.ts +0 -22
- package/lib/interfaces/util-interface.ts +0 -31
- package/lib/modules/aiproxy-module.ts +0 -246
- package/lib/modules/auth-module.ts +0 -753
- package/lib/modules/cache-module.ts +0 -99
- package/lib/modules/client-module.ts +0 -71
- package/lib/modules/comment-module.ts +0 -429
- package/lib/modules/components/news-components.ts +0 -390
- package/lib/modules/database-module.ts +0 -598
- package/lib/modules/file-module.ts +0 -291
- package/lib/modules/index.ts +0 -13
- package/lib/modules/keyvalue-module.ts +0 -379
- package/lib/modules/message-module.ts +0 -107
- package/lib/modules/util-module.ts +0 -148
- package/lib/polyfills/process-env-browser.ts +0 -1
- package/lib/proto/datasource.ts +0 -93
- package/lib/proto/dcnet.proto +0 -1601
- package/lib/proto/dcnet_proto.d.ts +0 -22857
- package/lib/proto/dcnet_proto.js +0 -55204
- package/lib/proto/dcnet_proto_sparse.js +0 -55166
- package/lib/proto/oidfetch.proto +0 -25
- package/lib/proto/oidfetch_proto.d.ts +0 -585
- package/lib/proto/oidfetch_proto.js +0 -1247
- package/lib/serverless/babel-browser.ts +0 -39
- package/lib/serverless/base_entity.ts +0 -78
- package/lib/serverless/base_repository.ts +0 -414
- package/lib/serverless/browser_schema_extractor.ts +0 -283
- package/lib/serverless/decorator_factory.ts +0 -322
- package/lib/util/BrowserLineReader.ts +0 -73
- package/lib/util/base64.ts +0 -105
- package/lib/util/bcrypt.ts +0 -206
- package/lib/util/curve25519Encryption.ts +0 -418
- package/lib/util/dccrypt.ts +0 -73
- package/lib/util/logger.ts +0 -104
- package/lib/util/utils.ts +0 -289
|
@@ -1,344 +0,0 @@
|
|
|
1
|
-
import { UnixFS } from "@helia/unixfs";
|
|
2
|
-
import { CID } from "multiformats/cid";
|
|
3
|
-
import toBuffer from "it-to-buffer";
|
|
4
|
-
import { decryptContent } from "../../util/dccrypt";
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* 可定位文件流类
|
|
8
|
-
* 支持随机访问和流式读取加密和未加密的IPFS文件
|
|
9
|
-
*/
|
|
10
|
-
export class SeekableFileStream {
|
|
11
|
-
private position: number = 0;
|
|
12
|
-
private fs: UnixFS;
|
|
13
|
-
private cid: CID;
|
|
14
|
-
private decryptKey: string;
|
|
15
|
-
private fileInfo: { size: number; hasHeader: boolean; headerSize: number };
|
|
16
|
-
private encryptChunkSize: number;
|
|
17
|
-
|
|
18
|
-
constructor(options: {
|
|
19
|
-
fileInfo: { size: number; hasHeader: boolean; headerSize: number };
|
|
20
|
-
fs: UnixFS;
|
|
21
|
-
cid: CID;
|
|
22
|
-
decryptKey: string;
|
|
23
|
-
encryptChunkSize: number;
|
|
24
|
-
}) {
|
|
25
|
-
this.fileInfo = options.fileInfo;
|
|
26
|
-
this.fs = options.fs;
|
|
27
|
-
this.cid = options.cid;
|
|
28
|
-
this.decryptKey = options.decryptKey;
|
|
29
|
-
this.encryptChunkSize = options.encryptChunkSize;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* 获取文件大小
|
|
34
|
-
*/
|
|
35
|
-
getSize(): number {
|
|
36
|
-
return this.fileInfo.size;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* 定位到文件指定位置
|
|
41
|
-
* @param position 目标位置
|
|
42
|
-
*/
|
|
43
|
-
seek(position: number): void {
|
|
44
|
-
if (position < 0) {
|
|
45
|
-
position = 0;
|
|
46
|
-
} else if (position > this.fileInfo.size) {
|
|
47
|
-
position = this.fileInfo.size;
|
|
48
|
-
}
|
|
49
|
-
this.position = position;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* 获取当前位置
|
|
54
|
-
*/
|
|
55
|
-
getPosition(): number {
|
|
56
|
-
return this.position;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* 读取指定长度的数据
|
|
61
|
-
* @param length 要读取的字节数
|
|
62
|
-
*/
|
|
63
|
-
async read(length: number): Promise<Uint8Array> {
|
|
64
|
-
// 使用局部变量存储当前位置,避免并发问题
|
|
65
|
-
const startPosition = this.position;
|
|
66
|
-
|
|
67
|
-
if (startPosition >= this.fileInfo.size) {
|
|
68
|
-
// 已到达文件末尾
|
|
69
|
-
return new Uint8Array(0);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// 计算实际读取长度(不超过文件末尾)
|
|
73
|
-
const remainingBytes = this.fileInfo.size - startPosition;
|
|
74
|
-
const actualLength = Math.min(length, remainingBytes);
|
|
75
|
-
|
|
76
|
-
let result: Uint8Array;
|
|
77
|
-
console.log("**************read actualLength:", actualLength);
|
|
78
|
-
// 根据是否加密选择不同读取策略
|
|
79
|
-
if (this.decryptKey) {
|
|
80
|
-
result = await this.readEncrypted(startPosition, actualLength);
|
|
81
|
-
} else {
|
|
82
|
-
result = await this.readPlain(startPosition, actualLength);
|
|
83
|
-
}
|
|
84
|
-
console.log("**************read result length:", result.length);
|
|
85
|
-
// 仅在成功读取后更新位置
|
|
86
|
-
if(this.position == startPosition) { //表示没有seek过
|
|
87
|
-
this.position = startPosition + result.length;
|
|
88
|
-
console.log("**************read this.position:", this.position);
|
|
89
|
-
}
|
|
90
|
-
return result;
|
|
91
|
-
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* 读取未加密的数据
|
|
96
|
-
* @param startPosition 开始位置
|
|
97
|
-
* @param length 读取长度
|
|
98
|
-
*/
|
|
99
|
-
private async readPlain(startPosition: number, length: number): Promise<Uint8Array> {
|
|
100
|
-
try {
|
|
101
|
-
// 计算实际文件中的偏移量(考虑文件头)
|
|
102
|
-
const actualOffset = startPosition + this.fileInfo.headerSize;
|
|
103
|
-
|
|
104
|
-
// 直接读取指定长度
|
|
105
|
-
const data = await toBuffer(this.fs.cat(this.cid, {
|
|
106
|
-
offset: actualOffset,
|
|
107
|
-
length: length
|
|
108
|
-
}));
|
|
109
|
-
|
|
110
|
-
return data;
|
|
111
|
-
} catch (err) {
|
|
112
|
-
console.error("Error reading plain data:", err);
|
|
113
|
-
return new Uint8Array(0);
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
/**
|
|
117
|
-
* 读取并解密加密数据,无音视频帧保护
|
|
118
|
-
* @param startPosition 开始位置
|
|
119
|
-
* @param length 读取长度
|
|
120
|
-
*/
|
|
121
|
-
private async readEncrypted(startPosition: number, length: number): Promise<Uint8Array> {
|
|
122
|
-
// 计算块相关参数
|
|
123
|
-
const blockSize = 3 << 20; // 3MB 原始块大小
|
|
124
|
-
const blockIndex = Math.floor(startPosition / blockSize);
|
|
125
|
-
const offsetInBlock = startPosition % blockSize ;
|
|
126
|
-
|
|
127
|
-
// 计算需要读取的块数
|
|
128
|
-
const endPosition = startPosition + length ;
|
|
129
|
-
const endBlockIndex = Math.floor(endPosition / blockSize);
|
|
130
|
-
const neededBlocks = endBlockIndex - blockIndex + 1;
|
|
131
|
-
|
|
132
|
-
// 分配适当大小的内存
|
|
133
|
-
const totalSize = Math.min(neededBlocks * blockSize, offsetInBlock + length);
|
|
134
|
-
let decryptedData = new Uint8Array(totalSize);
|
|
135
|
-
|
|
136
|
-
// 计算文件中的起始位置
|
|
137
|
-
let readStartPosition = this.fileInfo.headerSize + blockIndex * this.encryptChunkSize;
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
const readTotalSize = neededBlocks * this.encryptChunkSize;
|
|
141
|
-
|
|
142
|
-
try {
|
|
143
|
-
// 读取加密块
|
|
144
|
-
const encryptedBlocks = await toBuffer(this.fs.cat(this.cid, {
|
|
145
|
-
offset: readStartPosition,
|
|
146
|
-
length: readTotalSize
|
|
147
|
-
}));
|
|
148
|
-
|
|
149
|
-
// 确保获取到了数据
|
|
150
|
-
if (encryptedBlocks.length === 0) {
|
|
151
|
-
console.warn("无法读取加密数据");
|
|
152
|
-
return new Uint8Array(0);
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
// 解密所有块并合并
|
|
156
|
-
let currentOffset = 0;
|
|
157
|
-
for (let i = 0; i < neededBlocks; i++) {
|
|
158
|
-
const start = i * this.encryptChunkSize;
|
|
159
|
-
if (start >= encryptedBlocks.byteLength){
|
|
160
|
-
console.warn("*******加密数据块超出范围");
|
|
161
|
-
break;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
const end = Math.min((i + 1) * this.encryptChunkSize, encryptedBlocks.length);
|
|
165
|
-
const encryptedBlock = encryptedBlocks.slice(start, end);
|
|
166
|
-
|
|
167
|
-
if (encryptedBlock.byteLength === 0){
|
|
168
|
-
console.warn("*******加密数据块为空");
|
|
169
|
-
continue;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
// 解密块
|
|
173
|
-
const decryptedBlock = await decryptContent(encryptedBlock, this.decryptKey);
|
|
174
|
-
|
|
175
|
-
// 合并到结果中
|
|
176
|
-
const bytesToCopy = Math.min(decryptedBlock.length, decryptedData.length - currentOffset);
|
|
177
|
-
decryptedData.set(decryptedBlock.subarray(0, bytesToCopy), currentOffset);
|
|
178
|
-
currentOffset += bytesToCopy;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
// 从解密数据中提取请求的范围
|
|
182
|
-
let result = new Uint8Array(length);
|
|
183
|
-
result.set(decryptedData.subarray(offsetInBlock, offsetInBlock + length), 0);
|
|
184
|
-
return result;
|
|
185
|
-
} catch (err) {
|
|
186
|
-
console.error("Error reading encrypted data:", err);
|
|
187
|
-
return new Uint8Array(0);
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
/**
|
|
192
|
-
* 创建标准的可读流
|
|
193
|
-
* 使用独立缓冲区,不影响随机访问读取
|
|
194
|
-
*/
|
|
195
|
-
createReadableStream(options?: { start?: number, end?: number }): ReadableStream<Uint8Array> {
|
|
196
|
-
// 为流创建独立的状态变量,不使用实例变量
|
|
197
|
-
let streamPosition = options?.start !== undefined ? options.start : this.position;
|
|
198
|
-
let streamBuffer = new Uint8Array(0);
|
|
199
|
-
let streamBufferPosition = streamPosition;
|
|
200
|
-
|
|
201
|
-
// 确保位置有效
|
|
202
|
-
if (streamPosition < 0) {
|
|
203
|
-
streamPosition = 0;
|
|
204
|
-
} else if (streamPosition > this.fileInfo.size) {
|
|
205
|
-
streamPosition = this.fileInfo.size;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
const end = options?.end ?? this.fileInfo.size;
|
|
209
|
-
const chunkSize = 64 * 1024; // 64KB 客户端块
|
|
210
|
-
const bufferSize = 256 * 1024; // 256KB 预读缓冲区
|
|
211
|
-
|
|
212
|
-
/**
|
|
213
|
-
* 流式读取专用的预读函数
|
|
214
|
-
* 使用独立缓冲区
|
|
215
|
-
*/
|
|
216
|
-
const prefetchStreamData = async (size: number): Promise<void> => {
|
|
217
|
-
const maxSize = Math.min(size, this.fileInfo.size - streamPosition);
|
|
218
|
-
|
|
219
|
-
if (maxSize <= 0) return;
|
|
220
|
-
|
|
221
|
-
try {
|
|
222
|
-
// 读取数据到独立的流缓冲区
|
|
223
|
-
let prefetchedData: Uint8Array;
|
|
224
|
-
if (this.decryptKey) {
|
|
225
|
-
prefetchedData = await this.readEncrypted(streamPosition, maxSize);
|
|
226
|
-
} else {
|
|
227
|
-
prefetchedData = await this.readPlain(streamPosition, maxSize);
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
// 更新流缓冲区和位置
|
|
231
|
-
streamBuffer = prefetchedData;
|
|
232
|
-
streamBufferPosition = streamPosition;
|
|
233
|
-
} catch (err) {
|
|
234
|
-
console.error("Error prefetching stream data:", err);
|
|
235
|
-
streamBuffer = new Uint8Array(0);
|
|
236
|
-
}
|
|
237
|
-
};
|
|
238
|
-
|
|
239
|
-
/**
|
|
240
|
-
* 流式读取专用的读取函数
|
|
241
|
-
* 使用独立缓冲区和位置
|
|
242
|
-
*/
|
|
243
|
-
const streamRead = async (length: number): Promise<Uint8Array> => {
|
|
244
|
-
if (streamPosition >= this.fileInfo.size) {
|
|
245
|
-
return new Uint8Array(0);
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
const remainingBytes = this.fileInfo.size - streamPosition;
|
|
249
|
-
const actualLength = Math.min(length, remainingBytes);
|
|
250
|
-
|
|
251
|
-
let result: Uint8Array;
|
|
252
|
-
|
|
253
|
-
// 检查流缓冲区中是否有足够数据
|
|
254
|
-
if (streamPosition >= streamBufferPosition &&
|
|
255
|
-
streamPosition + actualLength <= streamBufferPosition + streamBuffer.length) {
|
|
256
|
-
|
|
257
|
-
// 从流缓冲区获取数据
|
|
258
|
-
const bufferOffset = streamPosition - streamBufferPosition;
|
|
259
|
-
result = streamBuffer.slice(bufferOffset, bufferOffset + actualLength);
|
|
260
|
-
} else {
|
|
261
|
-
// 缓冲区中没有所需数据,直接读取
|
|
262
|
-
if (this.decryptKey) {
|
|
263
|
-
result = await this.readEncrypted(streamPosition, actualLength);
|
|
264
|
-
} else {
|
|
265
|
-
result = await this.readPlain(streamPosition, actualLength);
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
// 更新流位置
|
|
270
|
-
streamPosition += result.length;
|
|
271
|
-
return result;
|
|
272
|
-
};
|
|
273
|
-
|
|
274
|
-
return new ReadableStream({
|
|
275
|
-
start: async (controller) => {
|
|
276
|
-
// 初始预读
|
|
277
|
-
try {
|
|
278
|
-
await prefetchStreamData(bufferSize);
|
|
279
|
-
} catch (err) {
|
|
280
|
-
console.warn("Initial stream prefetch failed:", err);
|
|
281
|
-
}
|
|
282
|
-
},
|
|
283
|
-
|
|
284
|
-
pull: async (controller) => {
|
|
285
|
-
try {
|
|
286
|
-
if (streamPosition >= end) {
|
|
287
|
-
controller.close();
|
|
288
|
-
return;
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
const bytesToRead = Math.min(chunkSize, end - streamPosition);
|
|
292
|
-
|
|
293
|
-
// 检查流缓冲区中是否有足够数据
|
|
294
|
-
if (streamPosition >= streamBufferPosition &&
|
|
295
|
-
streamPosition + bytesToRead <= streamBufferPosition + streamBuffer.length) {
|
|
296
|
-
|
|
297
|
-
// 从流缓冲区获取数据
|
|
298
|
-
const bufferOffset = streamPosition - streamBufferPosition;
|
|
299
|
-
const chunk = streamBuffer.slice(bufferOffset, bufferOffset + bytesToRead);
|
|
300
|
-
streamPosition += chunk.length;
|
|
301
|
-
controller.enqueue(chunk);
|
|
302
|
-
|
|
303
|
-
// 如果接近缓冲区末尾,预读取更多数据
|
|
304
|
-
const prefetchThreshold = chunkSize * 2;
|
|
305
|
-
if (streamPosition + prefetchThreshold > streamBufferPosition + streamBuffer.length &&
|
|
306
|
-
streamPosition < end) {
|
|
307
|
-
prefetchStreamData(bufferSize)
|
|
308
|
-
.catch(err => console.warn("Stream prefetch failed:", err));
|
|
309
|
-
}
|
|
310
|
-
} else {
|
|
311
|
-
// 缓冲区中没有所需数据,直接读取并同时更新缓冲区
|
|
312
|
-
await prefetchStreamData(Math.max(bufferSize, bytesToRead));
|
|
313
|
-
|
|
314
|
-
// 从新缓冲区获取数据
|
|
315
|
-
const bufferOffset = streamPosition - streamBufferPosition;
|
|
316
|
-
if (bufferOffset >= 0 && bufferOffset < streamBuffer.length) {
|
|
317
|
-
const chunk = streamBuffer.slice(bufferOffset,
|
|
318
|
-
Math.min(bufferOffset + bytesToRead, streamBuffer.length));
|
|
319
|
-
streamPosition += chunk.length;
|
|
320
|
-
controller.enqueue(chunk);
|
|
321
|
-
} else {
|
|
322
|
-
// 缓冲区没有预期的数据,直接读取
|
|
323
|
-
const chunk = await streamRead(bytesToRead);
|
|
324
|
-
if (chunk.length > 0) {
|
|
325
|
-
controller.enqueue(chunk);
|
|
326
|
-
} else {
|
|
327
|
-
controller.close();
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
} catch (err) {
|
|
332
|
-
console.error("Stream read error:", err);
|
|
333
|
-
controller.error(err);
|
|
334
|
-
}
|
|
335
|
-
},
|
|
336
|
-
|
|
337
|
-
cancel: () => {
|
|
338
|
-
// 清理资源
|
|
339
|
-
streamBuffer = new Uint8Array(0);
|
|
340
|
-
console.log("ReadableStream cancelled");
|
|
341
|
-
}
|
|
342
|
-
});
|
|
343
|
-
}
|
|
344
|
-
}
|
|
@@ -1,322 +0,0 @@
|
|
|
1
|
-
import { pipe } from 'it-pipe'
|
|
2
|
-
import { pushable, Pushable } from 'it-pushable'
|
|
3
|
-
|
|
4
|
-
interface StreamWriterOptions {
|
|
5
|
-
/** 分块大小(默认1MB) */
|
|
6
|
-
chunkSize?: number
|
|
7
|
-
/** 背压缓冲区最大值(默认5MB) */
|
|
8
|
-
bufferSize?: number
|
|
9
|
-
/** 失败重试次数(默认3次) */
|
|
10
|
-
retries?: number
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
interface BackpressureEventDetail {
|
|
14
|
-
currentSize: number
|
|
15
|
-
averageSize: number
|
|
16
|
-
threshold: number
|
|
17
|
-
waitingTime: number
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
interface EnhancedPushable<T> extends Pushable<T> {
|
|
21
|
-
// 使用类型合并增加自定义方法
|
|
22
|
-
_originalPush: Pushable<T>['push']
|
|
23
|
-
_originalNext: Pushable<T>['next']
|
|
24
|
-
_queueSize: number
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
const MaxChunkSize = 4*1024 -5
|
|
29
|
-
export class StreamWriter {
|
|
30
|
-
private p: EnhancedPushable<Uint8Array>
|
|
31
|
-
//private p = pushable({ objectMode: false })
|
|
32
|
-
|
|
33
|
-
private bytesWritten = 0
|
|
34
|
-
private abortController = new AbortController()
|
|
35
|
-
|
|
36
|
-
// 背压控制相关属性
|
|
37
|
-
private backpressureHistory: number[] = []
|
|
38
|
-
private isBackpressure = false
|
|
39
|
-
private writeQueue: (() => Promise<void>)[] = []
|
|
40
|
-
private isProcessingQueue = false
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
constructor(
|
|
44
|
-
private sink: any,
|
|
45
|
-
private options: StreamWriterOptions = {}
|
|
46
|
-
) {
|
|
47
|
-
|
|
48
|
-
if (options){
|
|
49
|
-
this.options = {
|
|
50
|
-
chunkSize: options.chunkSize ?? MaxChunkSize,
|
|
51
|
-
bufferSize: options.bufferSize ?? 5 * 1024 * 1024,
|
|
52
|
-
retries: options.retries ?? 3
|
|
53
|
-
}
|
|
54
|
-
}else{
|
|
55
|
-
this.options = {
|
|
56
|
-
chunkSize: MaxChunkSize,
|
|
57
|
-
bufferSize: 5 * 1024 * 1024,
|
|
58
|
-
retries: 3
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
if (this.options.chunkSize && this.options.chunkSize > MaxChunkSize) {
|
|
63
|
-
this.options.chunkSize = MaxChunkSize
|
|
64
|
-
}
|
|
65
|
-
const basePushable = pushable<Uint8Array>({ objectMode: false }) as EnhancedPushable<Uint8Array>
|
|
66
|
-
|
|
67
|
-
// 保留原始方法引用
|
|
68
|
-
basePushable._originalPush = basePushable.push.bind(basePushable)
|
|
69
|
-
basePushable._originalNext = basePushable.next.bind(basePushable)
|
|
70
|
-
basePushable._queueSize = 0
|
|
71
|
-
// 重写 next 方法
|
|
72
|
-
Object.defineProperty(basePushable, 'next', {
|
|
73
|
-
value: async () => {
|
|
74
|
-
const result = await basePushable._originalNext();
|
|
75
|
-
if (!result.done && result.value) {
|
|
76
|
-
basePushable._queueSize -= result.value.byteLength;
|
|
77
|
-
}
|
|
78
|
-
return result;
|
|
79
|
-
},
|
|
80
|
-
writable: false,
|
|
81
|
-
configurable: false
|
|
82
|
-
});
|
|
83
|
-
// 安全重写 push 方法
|
|
84
|
-
Object.defineProperty(basePushable, 'push', {
|
|
85
|
-
value: (chunk: Uint8Array) => {
|
|
86
|
-
basePushable._queueSize += chunk.byteLength
|
|
87
|
-
return basePushable._originalPush(chunk)
|
|
88
|
-
},
|
|
89
|
-
writable: false,
|
|
90
|
-
configurable: false
|
|
91
|
-
})
|
|
92
|
-
this.p = basePushable
|
|
93
|
-
this.startPipeline()
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
get queueSize():number {
|
|
98
|
-
return this.p._queueSize
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// 在 next() 操作时更新队列大小
|
|
102
|
-
private async safeNext() {
|
|
103
|
-
const result = await this.p.next()
|
|
104
|
-
if (!result.done && result.value) {
|
|
105
|
-
this.p._queueSize -= result.value.byteLength
|
|
106
|
-
}
|
|
107
|
-
return result
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
private handleError(err: Error) {
|
|
111
|
-
this.dispatchEvent(new CustomEvent('error', { detail: err }))
|
|
112
|
-
this.abort(err.message)
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
private startPipeline() {
|
|
117
|
-
pipe(
|
|
118
|
-
this.p,
|
|
119
|
-
this.createTransform(),
|
|
120
|
-
this.sink
|
|
121
|
-
).catch((err: Error) => this.handleError(err)) // 正确绑定this
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
private createTransform() {
|
|
125
|
-
return async function* (source: AsyncIterable<Uint8Array>) {
|
|
126
|
-
for await (const chunk of source) {
|
|
127
|
-
yield chunk
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
async write(data: ArrayBuffer | Blob | string): Promise<void> {
|
|
133
|
-
if (this.abortController.signal.aborted) return
|
|
134
|
-
|
|
135
|
-
return new Promise((resolve, reject) => {
|
|
136
|
-
const task = async () => {
|
|
137
|
-
try {
|
|
138
|
-
const buffer = await this.convertToBuffer(data)
|
|
139
|
-
await this.writeChunks(buffer)
|
|
140
|
-
resolve()
|
|
141
|
-
} catch (err) {
|
|
142
|
-
reject(err)
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
this.writeQueue.push(task)
|
|
147
|
-
this.processQueue()
|
|
148
|
-
})
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
private async convertToBuffer(data: ArrayBuffer | Blob | string): Promise<ArrayBuffer> {
|
|
152
|
-
if (data instanceof Blob) return data.arrayBuffer()
|
|
153
|
-
if (typeof data === 'string') return new TextEncoder().encode(data).buffer
|
|
154
|
-
return data
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
private async writeChunks(buffer: ArrayBuffer) {
|
|
158
|
-
for (let offset = 0; offset < buffer.byteLength; offset += this.options.chunkSize!) {
|
|
159
|
-
const end = Math.min(offset + this.options.chunkSize!, buffer.byteLength)
|
|
160
|
-
const chunk = new Uint8Array( end - offset)
|
|
161
|
-
chunk.set(new Uint8Array(buffer.slice(offset, end)))
|
|
162
|
-
|
|
163
|
-
await this.retryableWrite(chunk)
|
|
164
|
-
this.updateProgress(chunk.byteLength)
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
private async retryableWrite(chunk: Uint8Array, attempt = 0): Promise<void> {
|
|
169
|
-
try {
|
|
170
|
-
await this.monitorBackpressure()
|
|
171
|
-
|
|
172
|
-
await new Promise<void>((resolve, reject) => {
|
|
173
|
-
try {
|
|
174
|
-
this.p.push(chunk)
|
|
175
|
-
}catch(err){
|
|
176
|
-
reject(err)
|
|
177
|
-
}
|
|
178
|
-
resolve()
|
|
179
|
-
})
|
|
180
|
-
} catch (err) {
|
|
181
|
-
if (attempt < this.options.retries!) {
|
|
182
|
-
const delay = this.calculateRetryDelay(attempt)
|
|
183
|
-
await new Promise(r => setTimeout(r, delay))
|
|
184
|
-
return this.retryableWrite(chunk, attempt + 1)
|
|
185
|
-
}
|
|
186
|
-
throw err
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
private async monitorBackpressure(): Promise<void> {
|
|
191
|
-
const checkInterval = 50
|
|
192
|
-
const historySize = 10
|
|
193
|
-
|
|
194
|
-
while (true) {
|
|
195
|
-
const currentSize = this.queueSize // 空值合并运算符
|
|
196
|
-
if (currentSize>0){
|
|
197
|
-
this.backpressureHistory.push(currentSize)
|
|
198
|
-
}
|
|
199
|
-
if (this.backpressureHistory.length > historySize) {
|
|
200
|
-
this.backpressureHistory.shift()
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
const avg = this.backpressureHistory.reduce((a, b) => a + b, 0) / historySize
|
|
204
|
-
const dynamicThreshold = Math.min(
|
|
205
|
-
this.options.bufferSize! * 0.8,
|
|
206
|
-
avg * 1.5
|
|
207
|
-
)
|
|
208
|
-
|
|
209
|
-
if (currentSize < dynamicThreshold || currentSize === 0 ) {
|
|
210
|
-
if (this.isBackpressure) {
|
|
211
|
-
this.isBackpressure = false
|
|
212
|
-
this.dispatchEvent(new CustomEvent('backpressure', {
|
|
213
|
-
detail: {
|
|
214
|
-
currentSize,
|
|
215
|
-
averageSize: avg,
|
|
216
|
-
threshold: dynamicThreshold,
|
|
217
|
-
waitingTime: 0
|
|
218
|
-
}
|
|
219
|
-
}))
|
|
220
|
-
}
|
|
221
|
-
return
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
if (!this.isBackpressure) {
|
|
225
|
-
this.isBackpressure = true
|
|
226
|
-
this.dispatchEvent(new CustomEvent('backpressure', {
|
|
227
|
-
detail: {
|
|
228
|
-
currentSize,
|
|
229
|
-
averageSize: avg,
|
|
230
|
-
threshold: dynamicThreshold,
|
|
231
|
-
waitingTime: 0
|
|
232
|
-
}
|
|
233
|
-
}))
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
const waitTime = Math.min(
|
|
237
|
-
10,
|
|
238
|
-
checkInterval * Math.pow(2, currentSize / dynamicThreshold)
|
|
239
|
-
)
|
|
240
|
-
await new Promise(r => setTimeout(r, waitTime))
|
|
241
|
-
|
|
242
|
-
if (this.abortController.signal.aborted) break
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
private calculateRetryDelay(attempt: number): number {
|
|
248
|
-
const baseDelay = 10
|
|
249
|
-
const maxDelay = 100
|
|
250
|
-
return Math.min(
|
|
251
|
-
baseDelay * Math.pow(2, attempt) + Math.random() * 100,
|
|
252
|
-
maxDelay
|
|
253
|
-
)
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
private async processQueue() {
|
|
257
|
-
if (this.isProcessingQueue || this.abortController.signal.aborted) return
|
|
258
|
-
this.isProcessingQueue = true
|
|
259
|
-
|
|
260
|
-
while (this.writeQueue.length > 0) {
|
|
261
|
-
if (this.abortController.signal.aborted) break
|
|
262
|
-
await this.monitorBackpressure()
|
|
263
|
-
const task = this.writeQueue.shift()!
|
|
264
|
-
try {
|
|
265
|
-
await task()
|
|
266
|
-
} catch (err) {
|
|
267
|
-
continue // 继续处理下一个任务
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
this.isProcessingQueue = false
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
private updateProgress(bytes: number) {
|
|
276
|
-
this.bytesWritten += bytes
|
|
277
|
-
this.dispatchEvent(new CustomEvent('progress', {
|
|
278
|
-
detail: { loaded: this.bytesWritten }
|
|
279
|
-
}))
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
async end(): Promise<void> {
|
|
283
|
-
this.p.end()
|
|
284
|
-
await this.sink.return?.()
|
|
285
|
-
this.cleanup()
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
abort(reason = 'User aborted') {
|
|
289
|
-
this.abortController.abort(reason)
|
|
290
|
-
this.cleanup()
|
|
291
|
-
this.dispatchEvent(new CustomEvent('abort', { detail: reason }))
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
private cleanup() {
|
|
295
|
-
this.p.end()
|
|
296
|
-
this.abortController.abort()
|
|
297
|
-
this.writeQueue = []
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
// 事件系统
|
|
301
|
-
private listeners = new Map<string, Function[]>()
|
|
302
|
-
|
|
303
|
-
addEventListener(type: string, callback: (event: CustomEvent) => void) {
|
|
304
|
-
const handlers = this.listeners.get(type) || []
|
|
305
|
-
handlers.push(callback)
|
|
306
|
-
this.listeners.set(type, handlers)
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
// 修复后的代码片段
|
|
310
|
-
private dispatchEvent(event: CustomEvent) {
|
|
311
|
-
const handlers = this.listeners.get(event.type) || []
|
|
312
|
-
handlers.forEach(handler => handler(event))
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
// 明确指定事件类型
|
|
316
|
-
private dispatchBackpressureEvent(detail: BackpressureEventDetail) {
|
|
317
|
-
this.dispatchEvent(new CustomEvent<BackpressureEventDetail>('backpressure', {
|
|
318
|
-
detail
|
|
319
|
-
}))
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
}
|