web-dc-api 0.1.4 → 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,1240 +0,0 @@
|
|
|
1
|
-
import { UploadStatus, type DCConnectInfo } from "../../common/types/types";
|
|
2
|
-
import { FileClient } from "./client";
|
|
3
|
-
import type { HeliaLibp2p } from "helia";
|
|
4
|
-
import { ChainUtil } from "../../common/chain";
|
|
5
|
-
import { Errors as GErrors } from "../../common/error";
|
|
6
|
-
|
|
7
|
-
import {
|
|
8
|
-
compareByteArrays,
|
|
9
|
-
mergeUInt8Arrays,
|
|
10
|
-
sleep,
|
|
11
|
-
uint32ToLittleEndianBytes,
|
|
12
|
-
uint64ToBigEndianBytes,
|
|
13
|
-
uint64ToLittleEndianBytes,
|
|
14
|
-
} from "../../util/utils";
|
|
15
|
-
|
|
16
|
-
import { unixfs } from "@helia/unixfs";
|
|
17
|
-
import { SymmetricKey } from "../threaddb/common/key";
|
|
18
|
-
import { CID, Version } from "multiformats/cid";
|
|
19
|
-
import { BrowserType, DcUtil } from "../../common/dcutil";
|
|
20
|
-
import toBuffer from "it-to-buffer";
|
|
21
|
-
import { decryptContent } from "../../util/dccrypt";
|
|
22
|
-
import * as buffer from "buffer/";
|
|
23
|
-
import { Uint8ArrayList } from "uint8arraylist";
|
|
24
|
-
import { Libp2p, Stream } from "@libp2p/interface";
|
|
25
|
-
import { cidNeedConnect } from "../../common/constants";
|
|
26
|
-
import { SeekableFileStream } from "./seekableFileStream";
|
|
27
|
-
import { AccountClient } from "../account/client";
|
|
28
|
-
import { DCContext } from "../../../lib/interfaces/DCContext";
|
|
29
|
-
const { Buffer } = buffer;
|
|
30
|
-
|
|
31
|
-
const NonceBytes = 12;
|
|
32
|
-
const TagBytes = 16;
|
|
33
|
-
const dcFileHead = "$$dcfile$$";
|
|
34
|
-
const chunkSize = 3 * 1024 * 1024; // 3MB chunks
|
|
35
|
-
// 创建一个可以取消的信号
|
|
36
|
-
const controller = new AbortController();
|
|
37
|
-
const { signal } = controller;
|
|
38
|
-
|
|
39
|
-
// 错误定义
|
|
40
|
-
export class FileError extends Error {
|
|
41
|
-
constructor(message: string) {
|
|
42
|
-
super(message);
|
|
43
|
-
this.name = "FileError";
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
export const Errors = {
|
|
47
|
-
ErrNoDcPeerConnected: new FileError("no dc peer connected"),
|
|
48
|
-
ErrNoFileChose: new FileError("no file choose"),
|
|
49
|
-
ErrNoPeerIdIsNull: new FileError("peerId is null"),
|
|
50
|
-
ErrNoNeedUpload: new FileError("no need upload"),
|
|
51
|
-
ErrPublicKeyIsNull: new FileError("publickey is null"),
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
export interface MediaController {
|
|
55
|
-
restart(): {
|
|
56
|
-
videoElement: HTMLVideoElement;
|
|
57
|
-
mediaUrl: string;
|
|
58
|
-
controller: MediaController;
|
|
59
|
-
};
|
|
60
|
-
dispose(): void;
|
|
61
|
-
}
|
|
62
|
-
interface CustomMessage {
|
|
63
|
-
type: number; // uint8 (1字节)
|
|
64
|
-
version: number; // uint16 (2字节, 大端序)
|
|
65
|
-
payload: Uint8Array; // 二进制数据
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
export class FileManager {
|
|
69
|
-
dc: DcUtil;
|
|
70
|
-
connectedDc: DCConnectInfo = {};
|
|
71
|
-
chainUtil: ChainUtil;
|
|
72
|
-
dcNodeClient: HeliaLibp2p<Libp2p>;
|
|
73
|
-
context: DCContext;
|
|
74
|
-
constructor(
|
|
75
|
-
dc: DcUtil,
|
|
76
|
-
connectedDc: DCConnectInfo,
|
|
77
|
-
chainUtil: ChainUtil,
|
|
78
|
-
dcNodeClient: HeliaLibp2p<Libp2p>,
|
|
79
|
-
context: DCContext
|
|
80
|
-
) {
|
|
81
|
-
this.dc = dc;
|
|
82
|
-
this.connectedDc = connectedDc;
|
|
83
|
-
this.chainUtil = chainUtil;
|
|
84
|
-
this.dcNodeClient = dcNodeClient;
|
|
85
|
-
this.context = context;
|
|
86
|
-
}
|
|
87
|
-
// 处理文件头
|
|
88
|
-
async _processHeader(
|
|
89
|
-
pubkeyBytes: Uint8Array,
|
|
90
|
-
fileSize: number,
|
|
91
|
-
content: Uint8Array,
|
|
92
|
-
isFirstChunk: boolean
|
|
93
|
-
): Promise<Uint8Array> {
|
|
94
|
-
if (isFirstChunk) {
|
|
95
|
-
// 计算 pubkey 的 hash
|
|
96
|
-
const pubkeyHash = await crypto.subtle.digest("SHA-256", pubkeyBytes as any);
|
|
97
|
-
const pubkeyHashArray = new Uint8Array(pubkeyHash);
|
|
98
|
-
|
|
99
|
-
// 创建文件头
|
|
100
|
-
const headArray = new TextEncoder().encode(dcFileHead);
|
|
101
|
-
const pubkeyHashPart = pubkeyHashArray.slice(10, 24);
|
|
102
|
-
|
|
103
|
-
// 创建表示文件大小的字节数组
|
|
104
|
-
const realSizeBuffer = uint64ToBigEndianBytes(fileSize);
|
|
105
|
-
const realSizeBytes = new Uint8Array(realSizeBuffer);
|
|
106
|
-
|
|
107
|
-
// 组合所有部分
|
|
108
|
-
const result = new Uint8Array([
|
|
109
|
-
...headArray,
|
|
110
|
-
...pubkeyHashPart,
|
|
111
|
-
...realSizeBytes,
|
|
112
|
-
...content,
|
|
113
|
-
]);
|
|
114
|
-
|
|
115
|
-
return result;
|
|
116
|
-
}
|
|
117
|
-
return content;
|
|
118
|
-
}
|
|
119
|
-
async _uploadLargeFileAdvanced(
|
|
120
|
-
file: File,
|
|
121
|
-
resumeState = { offset: 0, chunkHashes: [] },
|
|
122
|
-
pubkeyBytes?: Uint8Array,
|
|
123
|
-
symKey?: SymmetricKey | null
|
|
124
|
-
): Promise<CID | null> {
|
|
125
|
-
const fs = unixfs(this.dcNodeClient);
|
|
126
|
-
|
|
127
|
-
let offset = resumeState.offset || 0;
|
|
128
|
-
const chunkHashes = resumeState.chunkHashes || {};
|
|
129
|
-
|
|
130
|
-
const _this = this;
|
|
131
|
-
|
|
132
|
-
// 创建符合流式接口的内容生成器
|
|
133
|
-
const contentStream = async function* () {
|
|
134
|
-
while (offset < file.size) {
|
|
135
|
-
// // 检查取消信号(需要补充signal参数)
|
|
136
|
-
// if (_this.signal?.aborted) {
|
|
137
|
-
// throw new AbortError('Upload cancelled')
|
|
138
|
-
// }
|
|
139
|
-
|
|
140
|
-
// 读取分块
|
|
141
|
-
const chunk = file.slice(offset, offset + chunkSize);
|
|
142
|
-
const arrayBuffer = await chunk.arrayBuffer();
|
|
143
|
-
let content = new Uint8Array(arrayBuffer);
|
|
144
|
-
|
|
145
|
-
// 加密处理
|
|
146
|
-
if (symKey) {
|
|
147
|
-
content = await symKey.encrypt(content) as any;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// 文件头处理(仅在第一个分块添加)
|
|
151
|
-
if (pubkeyBytes && offset === 0) {
|
|
152
|
-
content = await _this._processHeader(
|
|
153
|
-
pubkeyBytes,
|
|
154
|
-
file.size,
|
|
155
|
-
content,
|
|
156
|
-
true // isFirstChunk
|
|
157
|
-
) as any;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
offset += chunkSize;
|
|
161
|
-
yield content;
|
|
162
|
-
}
|
|
163
|
-
};
|
|
164
|
-
const cid = await fs.addByteStream(contentStream(), {
|
|
165
|
-
rawLeaves: false,
|
|
166
|
-
leafType: "file",
|
|
167
|
-
shardSplitThresholdBytes: 256 * 1024,
|
|
168
|
-
});
|
|
169
|
-
return cid;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
/**
|
|
173
|
-
* 检查当前连接的节点是否已经绑定到用户账户
|
|
174
|
-
* @returns 如果已绑定则返回true,否则返回false
|
|
175
|
-
*/
|
|
176
|
-
async isAccessPeerIdBinded(): Promise<boolean> {
|
|
177
|
-
const userInfo = await this.chainUtil.refreshUserInfo(
|
|
178
|
-
this.context.getPubkeyRaw()
|
|
179
|
-
);
|
|
180
|
-
|
|
181
|
-
if (userInfo.requestPeers && userInfo.requestPeers.length > 0) {
|
|
182
|
-
for (const peerId of userInfo.requestPeers) {
|
|
183
|
-
// 已经是绑定节点,直接返回true
|
|
184
|
-
if (peerId === this.connectedDc.nodeAddr?.getPeerId()) {
|
|
185
|
-
return true;
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
return false;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
// 上传文件
|
|
193
|
-
async addFile(
|
|
194
|
-
file: File,
|
|
195
|
-
enkey: string,
|
|
196
|
-
onUpdateTransmitSize: (status: UploadStatus, size: number) => void,
|
|
197
|
-
vaccount?: string
|
|
198
|
-
): Promise<[string | null, Error | null]> {
|
|
199
|
-
if (!this.connectedDc?.client) {
|
|
200
|
-
return [null, Errors.ErrNoDcPeerConnected];
|
|
201
|
-
}
|
|
202
|
-
if (!this.connectedDc || !this.connectedDc.nodeAddr) {
|
|
203
|
-
console.error("=========Errors.ErrNoDcPeerConnected");
|
|
204
|
-
return [null, Errors.ErrNoDcPeerConnected];
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
// this.dcNodeClient.libp2p.dialProtocol(this.connectedDc.nodeAddr, '/ipfs/bitswap/1.2.0')
|
|
208
|
-
const blockHeight = await this.chainUtil.getBlockHeight();
|
|
209
|
-
const peerId = this.connectedDc.nodeAddr?.getPeerId();
|
|
210
|
-
if (!peerId) {
|
|
211
|
-
return [null, Errors.ErrNoPeerIdIsNull];
|
|
212
|
-
}
|
|
213
|
-
let resCid = "";
|
|
214
|
-
try {
|
|
215
|
-
if (!(await this.isAccessPeerIdBinded())) {
|
|
216
|
-
// 检查当前连接的节点是否已经绑定到用户账户,没绑定,执行绑定
|
|
217
|
-
const accountClient = new AccountClient(this.connectedDc.client);
|
|
218
|
-
await accountClient.bindAccessPeerToUser(
|
|
219
|
-
this.context,
|
|
220
|
-
blockHeight ? blockHeight : 0,
|
|
221
|
-
peerId
|
|
222
|
-
);
|
|
223
|
-
//等待绑定信息上链,最多等待20秒
|
|
224
|
-
let waitCount = 0;
|
|
225
|
-
while (true) {
|
|
226
|
-
if (await this.isAccessPeerIdBinded()) {
|
|
227
|
-
break;
|
|
228
|
-
}
|
|
229
|
-
waitCount += 1;
|
|
230
|
-
if (waitCount > 30) {
|
|
231
|
-
return [null, new FileError("No access auth to peer")]; // 使用正确的错误返回格式
|
|
232
|
-
}
|
|
233
|
-
await sleep(1000); // 使用毫秒,等同于1秒
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
}catch (error: any) {
|
|
237
|
-
return [null, error];
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
try {
|
|
241
|
-
const fileSize = file.size;
|
|
242
|
-
const symKey =
|
|
243
|
-
enkey && enkey.length > 0 ? SymmetricKey.fromString(enkey) : null;
|
|
244
|
-
const fs = unixfs(this.dcNodeClient);
|
|
245
|
-
const pubkeyBytes = this.context.getPubkeyRaw();
|
|
246
|
-
// const peerId = "12D3KooWEGzh4AcbJrfZMfQb63wncBUpscMEEyiMemSWzEnjVCPf";
|
|
247
|
-
let nodeAddr = await this.dc?._getNodeAddr(peerId);
|
|
248
|
-
if (!nodeAddr) {
|
|
249
|
-
return [null, Errors.ErrNoDcPeerConnected];
|
|
250
|
-
}
|
|
251
|
-
const cid = await this._uploadLargeFileAdvanced(
|
|
252
|
-
file,
|
|
253
|
-
{ offset: 0, chunkHashes: [] },
|
|
254
|
-
pubkeyBytes,
|
|
255
|
-
symKey
|
|
256
|
-
);
|
|
257
|
-
if (!cid) {
|
|
258
|
-
return [resCid, Errors.ErrNoFileChose];
|
|
259
|
-
}
|
|
260
|
-
console.log("==========_uploadLargeFileAdvanced", cid.toString());
|
|
261
|
-
resCid = cid.toString();
|
|
262
|
-
console.log("=========resCid", resCid);
|
|
263
|
-
|
|
264
|
-
const stats = await fs.stat(cid);
|
|
265
|
-
const filesize = stats.unixfs?.fileSize() || 0;
|
|
266
|
-
console.log(
|
|
267
|
-
"=========stats",
|
|
268
|
-
stats.localFileSize.toString(),
|
|
269
|
-
stats.localFileSize.toString(),
|
|
270
|
-
stats.fileSize.toString(),
|
|
271
|
-
stats.dagSize.toString(),
|
|
272
|
-
filesize.toString(),
|
|
273
|
-
);
|
|
274
|
-
const dagFileSize = Number(stats.localDagSize);
|
|
275
|
-
const fileClient = new FileClient(
|
|
276
|
-
this.connectedDc.client,
|
|
277
|
-
this.dcNodeClient,
|
|
278
|
-
this.context
|
|
279
|
-
);
|
|
280
|
-
let resStatus = 0;
|
|
281
|
-
let resFlag = false;
|
|
282
|
-
let resError: Error | null = null;
|
|
283
|
-
fileClient.storeFile(
|
|
284
|
-
dagFileSize,
|
|
285
|
-
blockHeight ? blockHeight : 0,
|
|
286
|
-
resCid,
|
|
287
|
-
peerId,
|
|
288
|
-
(status: number, size: number): void => {
|
|
289
|
-
resFlag = true;
|
|
290
|
-
resStatus = status;
|
|
291
|
-
onUpdateTransmitSize(status, size);
|
|
292
|
-
},
|
|
293
|
-
async (error: Error) => {
|
|
294
|
-
resFlag = true;
|
|
295
|
-
resError = error;
|
|
296
|
-
}
|
|
297
|
-
);
|
|
298
|
-
//等待storeRes 为true
|
|
299
|
-
while (!resFlag) {
|
|
300
|
-
await sleep(100);
|
|
301
|
-
}
|
|
302
|
-
if (resError) {
|
|
303
|
-
return [null, resError];
|
|
304
|
-
}
|
|
305
|
-
if (resStatus === UploadStatus.ERROR || resStatus === UploadStatus.ABNORMAL
|
|
306
|
-
|| resStatus === UploadStatus.NOSPACE || resStatus === UploadStatus.FILECOUNTERROR
|
|
307
|
-
|| resStatus === UploadStatus.FILESIZEERROR || resStatus === UploadStatus.PULLERROR) {
|
|
308
|
-
//上传失败,不需要操作
|
|
309
|
-
return [null, Errors.ErrNoNeedUpload];
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
//创建文件主动上报流
|
|
313
|
-
await this.dc.createTransferStream(
|
|
314
|
-
this.dcNodeClient.libp2p,
|
|
315
|
-
this.dcNodeClient.blockstore,
|
|
316
|
-
nodeAddr,
|
|
317
|
-
BrowserType.File,
|
|
318
|
-
resCid
|
|
319
|
-
);
|
|
320
|
-
} catch (error) {
|
|
321
|
-
console.error("=========stream close", error);
|
|
322
|
-
throw error;
|
|
323
|
-
}
|
|
324
|
-
return [resCid, null];
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
/**
|
|
328
|
-
* Adds a folder to the DC network using browser FileList
|
|
329
|
-
* @param folderInput - Files from a directory input element
|
|
330
|
-
* @param enkey - Encryption key
|
|
331
|
-
* @param updateTransmitCount - Callback for progress updates
|
|
332
|
-
* @param vaccount - Optional virtual account
|
|
333
|
-
* @returns Promise with CID string and error if any
|
|
334
|
-
*/
|
|
335
|
-
async addFolder(
|
|
336
|
-
fileList: FileList,
|
|
337
|
-
enkey: string,
|
|
338
|
-
updateTransmitCount: (
|
|
339
|
-
status: UploadStatus,
|
|
340
|
-
total: number,
|
|
341
|
-
progress: number
|
|
342
|
-
) => void,
|
|
343
|
-
vaccount?: string
|
|
344
|
-
): Promise<[string | null, Error | null]> {
|
|
345
|
-
if (!this.connectedDc?.client) {
|
|
346
|
-
return [null, Errors.ErrNoDcPeerConnected];
|
|
347
|
-
}
|
|
348
|
-
if (!this.connectedDc || !this.connectedDc.nodeAddr) {
|
|
349
|
-
console.error("=========Errors.ErrNoDcPeerConnected");
|
|
350
|
-
return [null, Errors.ErrNoDcPeerConnected];
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
// this.dcNodeClient.libp2p.dialProtocol(this.connectedDc.nodeAddr, '/ipfs/bitswap/1.2.0')
|
|
354
|
-
const blockHeight = await this.chainUtil.getBlockHeight();
|
|
355
|
-
const peerId = this.connectedDc.nodeAddr?.getPeerId();
|
|
356
|
-
if (!peerId) {
|
|
357
|
-
return [null, Errors.ErrNoPeerIdIsNull];
|
|
358
|
-
}
|
|
359
|
-
let nodeAddr = await this.dc?._getNodeAddr(peerId);
|
|
360
|
-
if (!nodeAddr) {
|
|
361
|
-
return [null, Errors.ErrNoDcPeerConnected];
|
|
362
|
-
}
|
|
363
|
-
try {
|
|
364
|
-
if (!(await this.isAccessPeerIdBinded())) {
|
|
365
|
-
// 检查当前连接的节点是否已经绑定到用户账户,没绑定,执行绑定
|
|
366
|
-
const accountClient = new AccountClient(this.connectedDc.client);
|
|
367
|
-
await accountClient.bindAccessPeerToUser(
|
|
368
|
-
this.context,
|
|
369
|
-
blockHeight ? blockHeight : 0,
|
|
370
|
-
peerId
|
|
371
|
-
);
|
|
372
|
-
//等待绑定信息上链,最多等待20秒
|
|
373
|
-
let waitCount = 0;
|
|
374
|
-
while (true) {
|
|
375
|
-
if (await this.isAccessPeerIdBinded()) {
|
|
376
|
-
break;
|
|
377
|
-
}
|
|
378
|
-
waitCount += 1;
|
|
379
|
-
if (waitCount > 30) {
|
|
380
|
-
return [null, new FileError("No access auth to peer")]; // 使用正确的错误返回格式
|
|
381
|
-
}
|
|
382
|
-
await sleep(1000); // 使用毫秒,等同于1秒
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
} catch (error: any) {
|
|
386
|
-
return [null, error];
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
try {
|
|
390
|
-
// Create IPFS file system interface
|
|
391
|
-
const fs = unixfs(this.dcNodeClient);
|
|
392
|
-
|
|
393
|
-
// Generate user flag file (dc_ownuser)
|
|
394
|
-
let pubkeyBytes = this.context.getPubkeyRaw();
|
|
395
|
-
|
|
396
|
-
// Create hash of public key for owner file
|
|
397
|
-
const pubkeyHash = await crypto.subtle.digest("SHA-256", pubkeyBytes as any);
|
|
398
|
-
const ownerFileContent = new Uint8Array(pubkeyHash);
|
|
399
|
-
|
|
400
|
-
// Create folder structure using MFS (memory file system)
|
|
401
|
-
// Get root folder name
|
|
402
|
-
const rootFolderName = this.extractRootFolderName(fileList);
|
|
403
|
-
//排除掉 dc_ownuser 文件,然后又加上去,防止重复添加
|
|
404
|
-
const files = Array.from(fileList).filter(
|
|
405
|
-
(file) => file.name !== "dc_ownuser"
|
|
406
|
-
);
|
|
407
|
-
//将ownerFileContent作为文件内容,最后添加到根目录
|
|
408
|
-
const ownerPath = rootFolderName + "/dc_ownuser";
|
|
409
|
-
files.push(
|
|
410
|
-
new File([ownerFileContent], "dc_ownuser", { type: "text/plain" })
|
|
411
|
-
);
|
|
412
|
-
|
|
413
|
-
// 将文件路径与内容流映射
|
|
414
|
-
const source = Array.from(files).map((file) => ({
|
|
415
|
-
path: file.name === "dc_ownuser" ? ownerPath : file.webkitRelativePath, // 获取文件相对路径
|
|
416
|
-
content: this.fileToStream(file, enkey), // 使用文件流
|
|
417
|
-
}));
|
|
418
|
-
|
|
419
|
-
const results = fs.addAll(source);
|
|
420
|
-
let rootCID: CID<unknown, number, number, Version> | null = null;
|
|
421
|
-
let totalSize = 0;
|
|
422
|
-
// let fileCount = 0;
|
|
423
|
-
for await (const { path,size, cid } of results) {
|
|
424
|
-
// The entry with path equal to the root folder name is our root
|
|
425
|
-
if (path === rootFolderName) {
|
|
426
|
-
rootCID = cid;
|
|
427
|
-
totalSize = Number(size);
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
if (!rootCID) {
|
|
431
|
-
console.error("Failed to find root directory CID in IPFS results");
|
|
432
|
-
return [null, new Error("Failed to find root directory CID")];
|
|
433
|
-
}
|
|
434
|
-
// 获取rootCID下的块数量
|
|
435
|
-
|
|
436
|
-
const fileCount = await this.countDirectoryBlocks(rootCID);
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
// Get final node and CID
|
|
440
|
-
const finalCid = rootCID.toString();
|
|
441
|
-
const folderSize = totalSize;
|
|
442
|
-
|
|
443
|
-
// Sign folder data
|
|
444
|
-
const serverPidBytes = new TextEncoder().encode(peerId);
|
|
445
|
-
const sizeValue = uint64ToLittleEndianBytes(folderSize);
|
|
446
|
-
const heightValue = uint32ToLittleEndianBytes(blockHeight || 0);
|
|
447
|
-
const typeValue = uint32ToLittleEndianBytes(1); // Folder type = 1
|
|
448
|
-
|
|
449
|
-
// Create signature payload
|
|
450
|
-
let preSign = new TextEncoder().encode(finalCid);
|
|
451
|
-
preSign = mergeUInt8Arrays(preSign, sizeValue) as any;
|
|
452
|
-
preSign = mergeUInt8Arrays(preSign, heightValue) as any;
|
|
453
|
-
preSign = mergeUInt8Arrays(preSign, typeValue) as any;
|
|
454
|
-
preSign = mergeUInt8Arrays(preSign, serverPidBytes) as any;
|
|
455
|
-
|
|
456
|
-
// Sign the data
|
|
457
|
-
const signature = await this.context.sign(preSign);
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
// Create file options
|
|
463
|
-
const fileOptions = {
|
|
464
|
-
signature,
|
|
465
|
-
blockHeight: blockHeight || 0,
|
|
466
|
-
fileSize: folderSize,
|
|
467
|
-
fileCount: fileCount,
|
|
468
|
-
pubkey: this.context.getPubkeyRaw(),
|
|
469
|
-
vaccount: "",
|
|
470
|
-
};
|
|
471
|
-
|
|
472
|
-
if (vaccount) {
|
|
473
|
-
fileOptions.vaccount = vaccount;
|
|
474
|
-
}
|
|
475
|
-
let client = this.connectedDc.client;
|
|
476
|
-
if (client === null) {
|
|
477
|
-
return ["", new Error("ErrConnectToAccountPeersFail")];
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
if (client.peerAddr === null) {
|
|
481
|
-
return ["", new Error("ErrConnectToAccountPeersFail")];
|
|
482
|
-
}
|
|
483
|
-
if(!this.context.publicKey){
|
|
484
|
-
return [null, Errors.ErrPublicKeyIsNull];
|
|
485
|
-
}
|
|
486
|
-
if (client.token == "") {
|
|
487
|
-
await client.GetToken(
|
|
488
|
-
this.context.appInfo.appId || "",
|
|
489
|
-
this.context.publicKey.string(),
|
|
490
|
-
this.context.sign
|
|
491
|
-
);
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
// Store folder on DC network
|
|
495
|
-
const fileClient = new FileClient(
|
|
496
|
-
this.connectedDc.client,
|
|
497
|
-
this.dcNodeClient,
|
|
498
|
-
this.context
|
|
499
|
-
);
|
|
500
|
-
|
|
501
|
-
let resFlag = false;
|
|
502
|
-
let resStatus = 0;
|
|
503
|
-
let resError = null;
|
|
504
|
-
// Create channel for async communication
|
|
505
|
-
fileClient.storeFolder(
|
|
506
|
-
rootCID.toString(),
|
|
507
|
-
fileOptions,
|
|
508
|
-
(status: UploadStatus, total: number, processed: number): void => {
|
|
509
|
-
resFlag = true;
|
|
510
|
-
resStatus = status;
|
|
511
|
-
updateTransmitCount(status, total, processed);
|
|
512
|
-
},
|
|
513
|
-
async (error: Error) => {
|
|
514
|
-
resFlag = true;
|
|
515
|
-
resError = error;
|
|
516
|
-
}
|
|
517
|
-
);
|
|
518
|
-
while (!resFlag) {
|
|
519
|
-
await sleep(100);
|
|
520
|
-
}
|
|
521
|
-
if (resError) {
|
|
522
|
-
updateTransmitCount(UploadStatus.ERROR, 0, 0);
|
|
523
|
-
return [null, resError];
|
|
524
|
-
}
|
|
525
|
-
if (resStatus === UploadStatus.ERROR || resStatus === UploadStatus.ABNORMAL
|
|
526
|
-
|| resStatus === UploadStatus.NOSPACE || resStatus === UploadStatus.FILECOUNTERROR
|
|
527
|
-
|| resStatus === UploadStatus.FILESIZEERROR || resStatus === UploadStatus.PULLERROR) {
|
|
528
|
-
//上传失败的时候,不需要操作
|
|
529
|
-
return [null, Errors.ErrNoNeedUpload];
|
|
530
|
-
}
|
|
531
|
-
//创建文件主动上报流
|
|
532
|
-
await this.dc.createTransferStream(
|
|
533
|
-
this.dcNodeClient.libp2p,
|
|
534
|
-
this.dcNodeClient.blockstore,
|
|
535
|
-
nodeAddr,
|
|
536
|
-
BrowserType.File,
|
|
537
|
-
finalCid
|
|
538
|
-
);
|
|
539
|
-
return [finalCid, null];
|
|
540
|
-
} catch (error) {
|
|
541
|
-
console.error("Folder upload failed:", error);
|
|
542
|
-
return [null, error instanceof Error ? error : new Error(String(error))];
|
|
543
|
-
}
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
/**
|
|
547
|
-
* Creates a custom FileList object from file paths and contents
|
|
548
|
-
* @param filesMap - Map of file paths to content (string or Uint8Array)
|
|
549
|
-
* @param rootFolderName - Optional root folder name (defaults to "upload")
|
|
550
|
-
* @returns A FileList-like object that can be used with addFolder
|
|
551
|
-
*/
|
|
552
|
-
createCustomFileList(
|
|
553
|
-
filesMap:
|
|
554
|
-
| Map<string, string | Uint8Array | ArrayBuffer>
|
|
555
|
-
| Record<string, string | Uint8Array | ArrayBuffer>,
|
|
556
|
-
rootFolderName: string = "upload"
|
|
557
|
-
): FileList {
|
|
558
|
-
// Convert object to Map if needed
|
|
559
|
-
const filesMapObj =
|
|
560
|
-
filesMap instanceof Map ? filesMap : new Map(Object.entries(filesMap));
|
|
561
|
-
|
|
562
|
-
// Create File objects with proper webkitRelativePath
|
|
563
|
-
const files: File[] = [];
|
|
564
|
-
|
|
565
|
-
filesMapObj.forEach((content, path) => {
|
|
566
|
-
// Ensure path starts with rootFolderName
|
|
567
|
-
const fullPath = path.startsWith(rootFolderName)
|
|
568
|
-
? path
|
|
569
|
-
: `${rootFolderName}/${path}`;
|
|
570
|
-
|
|
571
|
-
// Convert content to proper format
|
|
572
|
-
let fileContent: Blob;
|
|
573
|
-
if (typeof content === "string") {
|
|
574
|
-
fileContent = new Blob([content], { type: "text/plain" });
|
|
575
|
-
} else {
|
|
576
|
-
fileContent = new Blob([content as any]);
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
// Create File object with webkitRelativePath
|
|
580
|
-
// const file = new File([fileContent], path.split("/").pop() || "unnamed", {
|
|
581
|
-
// type: "application/octet-stream",
|
|
582
|
-
// }) as File & { webkitRelativePath: string };
|
|
583
|
-
const file = Object.defineProperty(
|
|
584
|
-
new File([fileContent], path.split("/").pop() || "unnamed", {
|
|
585
|
-
type: "application/octet-stream",
|
|
586
|
-
}),
|
|
587
|
-
"webkitRelativePath",
|
|
588
|
-
{
|
|
589
|
-
value: fullPath,
|
|
590
|
-
writable: false
|
|
591
|
-
}
|
|
592
|
-
);
|
|
593
|
-
|
|
594
|
-
// Set webkitRelativePath property
|
|
595
|
-
// file.webkitRelativePath = fullPath;
|
|
596
|
-
|
|
597
|
-
files.push(file);
|
|
598
|
-
});
|
|
599
|
-
|
|
600
|
-
// Create a FileList-like object
|
|
601
|
-
const fileListObj: Record<string, any> = {
|
|
602
|
-
length: files.length,
|
|
603
|
-
item(index: number): File {
|
|
604
|
-
if (index < 0 || index >= files.length) {
|
|
605
|
-
return null as any; // Return null for out-of-bounds index
|
|
606
|
-
}
|
|
607
|
-
return files[index]!;
|
|
608
|
-
},
|
|
609
|
-
[Symbol.iterator](): Iterator<File> {
|
|
610
|
-
let index = 0;
|
|
611
|
-
return {
|
|
612
|
-
next(): IteratorResult<File> {
|
|
613
|
-
if (index < files.length) {
|
|
614
|
-
return { value: files[index++]!, done: false };
|
|
615
|
-
} else {
|
|
616
|
-
return { value: null as any, done: true };
|
|
617
|
-
}
|
|
618
|
-
},
|
|
619
|
-
};
|
|
620
|
-
},
|
|
621
|
-
};
|
|
622
|
-
|
|
623
|
-
// Add files with their indices as keys
|
|
624
|
-
files.forEach((file, index) => {
|
|
625
|
-
fileListObj[index] = file;
|
|
626
|
-
});
|
|
627
|
-
|
|
628
|
-
const fileList = fileListObj as unknown as FileList;
|
|
629
|
-
|
|
630
|
-
return fileList;
|
|
631
|
-
}
|
|
632
|
-
|
|
633
|
-
private fileToStream(file: File, enkey: string): ReadableStream<Uint8Array> {
|
|
634
|
-
const symKey = enkey ? SymmetricKey.fromString(enkey) : null;
|
|
635
|
-
const { readable, writable } = new TransformStream<
|
|
636
|
-
Uint8Array,
|
|
637
|
-
Uint8Array
|
|
638
|
-
>();
|
|
639
|
-
const writer = writable.getWriter();
|
|
640
|
-
const fileReader = new FileReader();
|
|
641
|
-
const chunkSize = 3 <<20; // 3MB chunks
|
|
642
|
-
let offset = 0;
|
|
643
|
-
const processFile = async () => {
|
|
644
|
-
try {
|
|
645
|
-
while (offset < file.size) {
|
|
646
|
-
// Read chunk
|
|
647
|
-
const chunk = file.slice(offset, offset + chunkSize);
|
|
648
|
-
const buffer = await new Promise<ArrayBuffer>((resolve, reject) => {
|
|
649
|
-
fileReader.onload = () => resolve(fileReader.result as ArrayBuffer);
|
|
650
|
-
fileReader.onerror = reject;
|
|
651
|
-
fileReader.readAsArrayBuffer(chunk);
|
|
652
|
-
});
|
|
653
|
-
const content = new Uint8Array(buffer);
|
|
654
|
-
// Encrypt if needed
|
|
655
|
-
if (symKey && file.name !== "dc_ownuser") {
|
|
656
|
-
const encrypted = await symKey.encrypt(content);
|
|
657
|
-
await writer.write(encrypted);
|
|
658
|
-
} else {
|
|
659
|
-
await writer.write(content);
|
|
660
|
-
}
|
|
661
|
-
offset += chunkSize;
|
|
662
|
-
}
|
|
663
|
-
} catch (error) {
|
|
664
|
-
console.error("Error processing file:", error);
|
|
665
|
-
} finally {
|
|
666
|
-
await writer.close();
|
|
667
|
-
}
|
|
668
|
-
};
|
|
669
|
-
// Start processing the file
|
|
670
|
-
processFile().catch((error) => {
|
|
671
|
-
console.error("Error in file processing:", error);
|
|
672
|
-
});
|
|
673
|
-
return readable;
|
|
674
|
-
}
|
|
675
|
-
/**
|
|
676
|
-
* Counts all blocks recursively in a directory structure
|
|
677
|
-
* @param rootCID - The CID of the root directory
|
|
678
|
-
* @returns Promise with the total block count
|
|
679
|
-
*/
|
|
680
|
-
async countDirectoryBlocks(rootCID: CID): Promise<number> {
|
|
681
|
-
try {
|
|
682
|
-
const fs = unixfs(this.dcNodeClient);
|
|
683
|
-
let totalBlocks = 0;
|
|
684
|
-
|
|
685
|
-
// Get stats for the root directory itself
|
|
686
|
-
const rootStats = await fs.stat(rootCID);
|
|
687
|
-
totalBlocks += Number(rootStats.blocks || 0);
|
|
688
|
-
|
|
689
|
-
// List all entries in the directory and process them recursively
|
|
690
|
-
for await (const entry of fs.ls(rootCID)) {
|
|
691
|
-
const { cid, type } = entry;
|
|
692
|
-
|
|
693
|
-
// Get stats for the current entry
|
|
694
|
-
const stats = await fs.stat(cid);
|
|
695
|
-
|
|
696
|
-
if (type === "directory") {
|
|
697
|
-
// Recursively count blocks in subdirectories
|
|
698
|
-
const subDirBlocks = await this.countDirectoryBlocks(cid);
|
|
699
|
-
totalBlocks += subDirBlocks;
|
|
700
|
-
} else {
|
|
701
|
-
// For files, add their block count
|
|
702
|
-
totalBlocks += Number(stats.blocks || 0);
|
|
703
|
-
}
|
|
704
|
-
}
|
|
705
|
-
|
|
706
|
-
console.log(`Directory ${rootCID.toString()} contains ${totalBlocks} blocks`);
|
|
707
|
-
return totalBlocks;
|
|
708
|
-
} catch (error) {
|
|
709
|
-
console.error("Error counting directory blocks:", error);
|
|
710
|
-
throw error;
|
|
711
|
-
}
|
|
712
|
-
}
|
|
713
|
-
/**
|
|
714
|
-
* Extract the root folder name from a FileList
|
|
715
|
-
*/
|
|
716
|
-
private extractRootFolderName(files: FileList): string {
|
|
717
|
-
if (files.length === 0) return "root";
|
|
718
|
-
|
|
719
|
-
const path = files[0]!.webkitRelativePath || files[0]!.name;
|
|
720
|
-
const parts = path.split("/");
|
|
721
|
-
|
|
722
|
-
return parts[0] || "root";
|
|
723
|
-
}
|
|
724
|
-
|
|
725
|
-
/**
|
|
726
|
-
* Create a folder in IPFS
|
|
727
|
-
*/
|
|
728
|
-
private async createFolderInIpfs(folderName: string): Promise<CID> {
|
|
729
|
-
const fs = unixfs(this.dcNodeClient);
|
|
730
|
-
const dirCid = await fs.addDirectory({
|
|
731
|
-
path: folderName,
|
|
732
|
-
});
|
|
733
|
-
return dirCid;
|
|
734
|
-
}
|
|
735
|
-
|
|
736
|
-
/**
|
|
737
|
-
* Calculate the size of all files in a folder
|
|
738
|
-
*/
|
|
739
|
-
private calculateFolderSize(files: FileList): number {
|
|
740
|
-
let totalSize = 0;
|
|
741
|
-
for (let i = 0; i < files.length; i++) {
|
|
742
|
-
totalSize += files[i]!.size;
|
|
743
|
-
}
|
|
744
|
-
return totalSize;
|
|
745
|
-
}
|
|
746
|
-
|
|
747
|
-
/**
|
|
748
|
-
* Adds a file to MFS file system folder with optional encryption
|
|
749
|
-
* @param parentDir - The parent directory in MFS
|
|
750
|
-
* @param dirPath - The directory path
|
|
751
|
-
* @param fileName - The name of the file
|
|
752
|
-
* @param enkey - Optional encryption key
|
|
753
|
-
* @returns Promise with result or error
|
|
754
|
-
*/
|
|
755
|
-
async addFileToMfsFolder(
|
|
756
|
-
parentDir: any, // MFS Directory type (replace with actual type)
|
|
757
|
-
dirPath: string,
|
|
758
|
-
fileName: File,
|
|
759
|
-
enkey: string
|
|
760
|
-
): Promise<[string | null, Error | null]> {
|
|
761
|
-
let symKey: SymmetricKey | null = null;
|
|
762
|
-
const readPath = `${dirPath}/${fileName}`; // Use path joining appropriate for your env
|
|
763
|
-
|
|
764
|
-
try {
|
|
765
|
-
// Open file (implementation depends on environment - browser vs Node.js)
|
|
766
|
-
const fileContent = await this.readFile(readPath);
|
|
767
|
-
if (!fileContent) {
|
|
768
|
-
return [null, new Error("Could not open file")];
|
|
769
|
-
}
|
|
770
|
-
|
|
771
|
-
// Create encryption key if needed
|
|
772
|
-
if (enkey !== "") {
|
|
773
|
-
symKey = SymmetricKey.fromString(enkey);
|
|
774
|
-
}
|
|
775
|
-
|
|
776
|
-
// Create readable and writable streams for processing
|
|
777
|
-
const { readable, writable } = new TransformStream<
|
|
778
|
-
Uint8Array,
|
|
779
|
-
Uint8Array
|
|
780
|
-
>();
|
|
781
|
-
const writer = writable.getWriter();
|
|
782
|
-
|
|
783
|
-
// Process the file in a separate async function (equivalent to goroutine)
|
|
784
|
-
(async () => {
|
|
785
|
-
try {
|
|
786
|
-
const file = await fetch(readPath).then((r) => r.blob());
|
|
787
|
-
const fileReader = new FileReader();
|
|
788
|
-
const chunkSize = 1024;
|
|
789
|
-
let offset = 0;
|
|
790
|
-
|
|
791
|
-
while (offset < file.size) {
|
|
792
|
-
// Check for abort signal if needed
|
|
793
|
-
// if (signal?.aborted) throw new Error("Operation cancelled");
|
|
794
|
-
|
|
795
|
-
// Read chunk
|
|
796
|
-
const chunk = file.slice(offset, offset + chunkSize);
|
|
797
|
-
const buffer = await new Promise<ArrayBuffer>((resolve, reject) => {
|
|
798
|
-
fileReader.onload = () =>
|
|
799
|
-
resolve(fileReader.result as ArrayBuffer);
|
|
800
|
-
fileReader.onerror = reject;
|
|
801
|
-
fileReader.readAsArrayBuffer(chunk);
|
|
802
|
-
});
|
|
803
|
-
|
|
804
|
-
const content = new Uint8Array(buffer);
|
|
805
|
-
|
|
806
|
-
// Encrypt if needed
|
|
807
|
-
if (symKey) {
|
|
808
|
-
const encrypted = await symKey.encrypt(content);
|
|
809
|
-
await writer.write(encrypted);
|
|
810
|
-
} else {
|
|
811
|
-
await writer.write(content);
|
|
812
|
-
}
|
|
813
|
-
|
|
814
|
-
offset += chunkSize;
|
|
815
|
-
}
|
|
816
|
-
} catch (error) {
|
|
817
|
-
console.error("Error processing file:", error);
|
|
818
|
-
} finally {
|
|
819
|
-
await writer.close();
|
|
820
|
-
}
|
|
821
|
-
})();
|
|
822
|
-
|
|
823
|
-
// Add file to IPFS
|
|
824
|
-
const fs = unixfs(this.dcNodeClient);
|
|
825
|
-
const cid = await fs.addByteStream(this.streamToAsyncIterable(readable), {
|
|
826
|
-
rawLeaves: false,
|
|
827
|
-
leafType: "file",
|
|
828
|
-
shardSplitThresholdBytes: 256 * 1024,
|
|
829
|
-
});
|
|
830
|
-
|
|
831
|
-
// Add to parent directory in MFS
|
|
832
|
-
await parentDir.addChild(fileName, cid);
|
|
833
|
-
await parentDir.flush();
|
|
834
|
-
|
|
835
|
-
return [cid.toString(), null];
|
|
836
|
-
} catch (error) {
|
|
837
|
-
return [null, error instanceof Error ? error : new Error(String(error))];
|
|
838
|
-
}
|
|
839
|
-
}
|
|
840
|
-
|
|
841
|
-
// Helper method to convert a ReadableStream to AsyncIterable
|
|
842
|
-
private async *streamToAsyncIterable(
|
|
843
|
-
stream: ReadableStream<Uint8Array>
|
|
844
|
-
): AsyncGenerator<Uint8Array> {
|
|
845
|
-
const reader = stream.getReader();
|
|
846
|
-
try {
|
|
847
|
-
while (true) {
|
|
848
|
-
const { done, value } = await reader.read();
|
|
849
|
-
if (done) break;
|
|
850
|
-
yield value;
|
|
851
|
-
}
|
|
852
|
-
} finally {
|
|
853
|
-
reader.releaseLock();
|
|
854
|
-
}
|
|
855
|
-
}
|
|
856
|
-
|
|
857
|
-
// Helper to read a file (implementation depends on environment)
|
|
858
|
-
private async readFile(path: string): Promise<Blob | null> {
|
|
859
|
-
try {
|
|
860
|
-
// Browser implementation example - replace with appropriate method
|
|
861
|
-
const response = await fetch(path);
|
|
862
|
-
return await response.blob();
|
|
863
|
-
} catch (error) {
|
|
864
|
-
console.error("Error reading file:", error);
|
|
865
|
-
return null;
|
|
866
|
-
}
|
|
867
|
-
}
|
|
868
|
-
|
|
869
|
-
private async *chunkGenerator(stream: Stream): AsyncGenerator<Uint8Array> {
|
|
870
|
-
const iterator = stream.source[Symbol.asyncIterator]();
|
|
871
|
-
while (true) {
|
|
872
|
-
try {
|
|
873
|
-
const { done, value } = await iterator.next();
|
|
874
|
-
if (done) break;
|
|
875
|
-
const res = value instanceof Uint8ArrayList ? value.subarray() : value;
|
|
876
|
-
yield res;
|
|
877
|
-
} catch (err) {
|
|
878
|
-
console.error("chunkGenerator error:", err);
|
|
879
|
-
}
|
|
880
|
-
}
|
|
881
|
-
}
|
|
882
|
-
|
|
883
|
-
/**
|
|
884
|
-
* 组装 CustomMessage 数据到 Uint8Array
|
|
885
|
-
* @param message - CustomMessage 包含消息的基本结构
|
|
886
|
-
* @returns Uint8Array - 序列化后的数据
|
|
887
|
-
*/
|
|
888
|
-
assembleCustomMessage(message: CustomMessage): Uint8Array {
|
|
889
|
-
// Step 1: header部分(1字节类型 + 2字节版本号 + 4字节payload长度)
|
|
890
|
-
const headerLength = 7; // Header固定长度:1字节Type + 2字节Version + 4字节Payload长度
|
|
891
|
-
const payloadLength = message.payload.byteLength;
|
|
892
|
-
|
|
893
|
-
const buffer = new Uint8Array(headerLength + payloadLength);
|
|
894
|
-
|
|
895
|
-
buffer[0] = message.type;
|
|
896
|
-
buffer[1] = (message.version >> 8) & 0xff;
|
|
897
|
-
buffer[2] = message.version & 0xff;
|
|
898
|
-
buffer[3] = (payloadLength >> 24) & 0xff;
|
|
899
|
-
buffer[4] = (payloadLength >> 16) & 0xff;
|
|
900
|
-
buffer[5] = (payloadLength >> 8) & 0xff;
|
|
901
|
-
buffer[6] = payloadLength & 0xff;
|
|
902
|
-
|
|
903
|
-
// Step 5: 设置 Payload 数据
|
|
904
|
-
buffer.set(message.payload, headerLength);
|
|
905
|
-
|
|
906
|
-
return buffer;
|
|
907
|
-
}
|
|
908
|
-
parseMessage(
|
|
909
|
-
data: Uint8Array
|
|
910
|
-
): { type: number; version: number; payload: Uint8Array } | null {
|
|
911
|
-
if (data.length < 7) {
|
|
912
|
-
return null;
|
|
913
|
-
}
|
|
914
|
-
|
|
915
|
-
// 第 1 字节: 消息类型
|
|
916
|
-
const type = data[0]!;
|
|
917
|
-
|
|
918
|
-
// 第 2 和 3 字节: 版本号(大端序)
|
|
919
|
-
const version = (data[1]! << 8) | data[2]!; // 手动处理大端序
|
|
920
|
-
|
|
921
|
-
// 第 4 至 7 字节: payload 长度(大端序)
|
|
922
|
-
const payloadLength =
|
|
923
|
-
(data[3]! << 24) | (data[4]! << 16) | (data[5]! << 8) | data[6]!;
|
|
924
|
-
|
|
925
|
-
// 验证数据完整性
|
|
926
|
-
if (data.length < 7 + payloadLength) {
|
|
927
|
-
return null;
|
|
928
|
-
}
|
|
929
|
-
|
|
930
|
-
// 提取 payload
|
|
931
|
-
const payload = data.slice(7, 7 + payloadLength); // 提取负载数据
|
|
932
|
-
|
|
933
|
-
return {
|
|
934
|
-
type,
|
|
935
|
-
version,
|
|
936
|
-
payload,
|
|
937
|
-
};
|
|
938
|
-
}
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
/**
|
|
943
|
-
* 获取文件夹下的所有文件,包括内容(支持多级目录递归)
|
|
944
|
-
* @param cid 根目录的CID
|
|
945
|
-
* @param decryptKey 解密密钥
|
|
946
|
-
* @param recursive 是否递归获取子目录,默认false(保持向后兼容)
|
|
947
|
-
* @returns 文件列表:[{Name:文件或目录名,Type:0-文件 1-目录,Size:大小,Hash:文件或目录cid,Path:完整路径}]
|
|
948
|
-
*/
|
|
949
|
-
async getFolderFileListWithContent(
|
|
950
|
-
cid: string,
|
|
951
|
-
decryptKey: string,
|
|
952
|
-
recursive: boolean = true
|
|
953
|
-
): Promise<[Array<{Name: string; Type: number; Size: number; Hash: string; Path: string, Content?: Uint8Array}> | null, Error | null]> {
|
|
954
|
-
const [fileList, err] = await this.getFolderFileList(cid, cidNeedConnect.NEED, recursive);
|
|
955
|
-
if (err || !fileList) return [null, err];
|
|
956
|
-
for (let i = 0; i < fileList.length; i++) {
|
|
957
|
-
const file = fileList[i];
|
|
958
|
-
if (file?.Type === 0) {
|
|
959
|
-
if (file?.Name == "dc_ownuser") {
|
|
960
|
-
continue;
|
|
961
|
-
}
|
|
962
|
-
const content = await this.getFileFromDc(file.Hash, decryptKey,cidNeedConnect.NOT_NEED,true); //和目录同节点,无需连接
|
|
963
|
-
if (!content) {
|
|
964
|
-
return [null, Errors.ErrNoFileChose];
|
|
965
|
-
}
|
|
966
|
-
file.Content = content;
|
|
967
|
-
}
|
|
968
|
-
}
|
|
969
|
-
return [fileList, null];
|
|
970
|
-
}
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
/**
|
|
975
|
-
* 获取文件夹下的文件列表(支持多级目录递归)
|
|
976
|
-
* @param cid 根目录的CID
|
|
977
|
-
* @param flag 是否需要连接节点
|
|
978
|
-
* @param recursive 是否递归获取子目录,默认false(保持向后兼容)
|
|
979
|
-
* @returns 返回JSON格式的文件列表:[{Name:文件或目录名,Type:0-文件 1-目录,Size:大小,Hash:文件或目录cid,Path:完整路径}]
|
|
980
|
-
*/
|
|
981
|
-
async getFolderFileList(
|
|
982
|
-
cid: string,
|
|
983
|
-
flag?: number,
|
|
984
|
-
recursive: boolean = true
|
|
985
|
-
): Promise<[Array<{Name: string; Type: number; Size: number; Hash: string; Path: string, Content?: Uint8Array}> | null, Error | null]> {
|
|
986
|
-
try {
|
|
987
|
-
const id = CID.parse(cid);
|
|
988
|
-
if (flag !== cidNeedConnect.NOT_NEED) {
|
|
989
|
-
const [multiAddrs, peers] = await this.dc?._connectToObjNodes(cid);
|
|
990
|
-
if (!multiAddrs && peers) {
|
|
991
|
-
// 有peers但是没有multiaddrs
|
|
992
|
-
return [null, Errors.ErrNoDcPeerConnected];
|
|
993
|
-
}
|
|
994
|
-
}
|
|
995
|
-
|
|
996
|
-
const fs = unixfs(this.dcNodeClient);
|
|
997
|
-
const fileNodes: Array<{
|
|
998
|
-
Name: string;
|
|
999
|
-
Type: number;
|
|
1000
|
-
Size: number;
|
|
1001
|
-
Hash: string;
|
|
1002
|
-
Path: string;
|
|
1003
|
-
Content?: Uint8Array; // 可选内容字段,用于存储文件内容
|
|
1004
|
-
}> = [];
|
|
1005
|
-
|
|
1006
|
-
// 递归获取目录内容的内部函数
|
|
1007
|
-
const traverseDirectory = async (dirCid: CID, currentPath: string = '') => {
|
|
1008
|
-
// 遍历当前目录内容
|
|
1009
|
-
for await (const entry of fs.ls(dirCid)) {
|
|
1010
|
-
const { name, cid, type } = entry;
|
|
1011
|
-
if (name === '.' || name === '..' || name === 'dc_ownuser') {
|
|
1012
|
-
continue; // 跳过当前目录和上级目录
|
|
1013
|
-
}
|
|
1014
|
-
// 构建完整路径
|
|
1015
|
-
const fullPath = currentPath ? `${currentPath}/${name}` : name;
|
|
1016
|
-
|
|
1017
|
-
// 获取文件/目录的统计信息
|
|
1018
|
-
const stats = await fs.stat(cid);
|
|
1019
|
-
|
|
1020
|
-
// 构造文件信息对象
|
|
1021
|
-
const fileInfo = {
|
|
1022
|
-
Name: name,
|
|
1023
|
-
Type: type === 'directory' ? 1 : 0, // 0-文件 1-目录
|
|
1024
|
-
Size: type === 'directory' ? 0 : Number(stats.fileSize || 0),
|
|
1025
|
-
Hash: cid.toString(),
|
|
1026
|
-
Path: fullPath
|
|
1027
|
-
};
|
|
1028
|
-
|
|
1029
|
-
fileNodes.push(fileInfo);
|
|
1030
|
-
|
|
1031
|
-
// 如果是目录且需要递归,则继续遍历子目录
|
|
1032
|
-
if (type === 'directory' && recursive) {
|
|
1033
|
-
await traverseDirectory(cid, fullPath);
|
|
1034
|
-
}
|
|
1035
|
-
}
|
|
1036
|
-
};
|
|
1037
|
-
|
|
1038
|
-
// 开始遍历
|
|
1039
|
-
await traverseDirectory(id);
|
|
1040
|
-
return [fileNodes, null];
|
|
1041
|
-
} catch (error) {
|
|
1042
|
-
console.error('获取文件夹列表失败:', error);
|
|
1043
|
-
return [null, error instanceof Error ? error : new Error(String(error))];
|
|
1044
|
-
}
|
|
1045
|
-
}
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
// 从dc网络获取指定文件
|
|
1050
|
-
// flag 是否需要连接节点,0-需要,1-不需要
|
|
1051
|
-
getFileFromDc = async (cid: string, decryptKey: string, flag?: number,folderFlag?:boolean) : Promise<Uint8Array | null> => {
|
|
1052
|
-
if (flag !== cidNeedConnect.NOT_NEED) {
|
|
1053
|
-
const [multiAddrs, peers] = await this.dc?._connectToObjNodes(cid);
|
|
1054
|
-
if (!multiAddrs && peers) {
|
|
1055
|
-
// 有peers但是没有multiaddrs
|
|
1056
|
-
return null;
|
|
1057
|
-
}
|
|
1058
|
-
// 没有peers
|
|
1059
|
-
if (!peers) {
|
|
1060
|
-
try {
|
|
1061
|
-
const timeoutPromise = new Promise((resolve) => setTimeout(resolve, 5000, 'Timeout'));
|
|
1062
|
-
const getPromise = this.getFileFromDcContent(cid, decryptKey,folderFlag);
|
|
1063
|
-
const result = await Promise.race([timeoutPromise, getPromise]);
|
|
1064
|
-
if(result === 'Timeout') {
|
|
1065
|
-
return null;
|
|
1066
|
-
}
|
|
1067
|
-
if(result) {
|
|
1068
|
-
return result as Uint8Array;
|
|
1069
|
-
}
|
|
1070
|
-
return null;
|
|
1071
|
-
} catch (error) {
|
|
1072
|
-
return null;
|
|
1073
|
-
}
|
|
1074
|
-
}
|
|
1075
|
-
}
|
|
1076
|
-
return this.getFileFromDcContent(cid, decryptKey,folderFlag);
|
|
1077
|
-
};
|
|
1078
|
-
|
|
1079
|
-
getFileFromDcContent = async (cid: string, decryptKey: string, folderFlag?:boolean): Promise<Uint8Array | null> => {
|
|
1080
|
-
const fs = unixfs(this.dcNodeClient);
|
|
1081
|
-
let headDealed = false;
|
|
1082
|
-
let waitBuffer = new Uint8Array(0);
|
|
1083
|
-
let fileContent = new Uint8Array(0);
|
|
1084
|
-
|
|
1085
|
-
const encryptextLen = (3 << 20) + NonceBytes + TagBytes; //每段密文长度(最后一段可能会短一点)
|
|
1086
|
-
const catOptions = {
|
|
1087
|
-
offset: 0,
|
|
1088
|
-
length: 32,
|
|
1089
|
-
// signal: AbortSignal.timeout(5000),
|
|
1090
|
-
};
|
|
1091
|
-
let readCount = 0;
|
|
1092
|
-
try {
|
|
1093
|
-
for (;;) {
|
|
1094
|
-
if (!headDealed && !folderFlag) {
|
|
1095
|
-
const headBuf = await toBuffer(fs.cat(CID.parse(cid), catOptions));
|
|
1096
|
-
readCount += headBuf.length;
|
|
1097
|
-
if (headBuf.length > 0) {
|
|
1098
|
-
waitBuffer = mergeUInt8Arrays(waitBuffer, headBuf) as any;
|
|
1099
|
-
if (waitBuffer.length < 32) {
|
|
1100
|
-
catOptions.offset = waitBuffer.length;
|
|
1101
|
-
catOptions.length = 32 - waitBuffer.length;
|
|
1102
|
-
continue;
|
|
1103
|
-
} else {
|
|
1104
|
-
//判断是否是dc网络存储的文件头
|
|
1105
|
-
headDealed = true;
|
|
1106
|
-
if (
|
|
1107
|
-
compareByteArrays(
|
|
1108
|
-
waitBuffer.subarray(0, 10),
|
|
1109
|
-
Buffer.from("$$dcfile$$")
|
|
1110
|
-
)
|
|
1111
|
-
) {
|
|
1112
|
-
//判断是否是dc网络存储的文件头
|
|
1113
|
-
waitBuffer = waitBuffer.subarray(32, waitBuffer.length);
|
|
1114
|
-
}
|
|
1115
|
-
}
|
|
1116
|
-
} else {
|
|
1117
|
-
if (waitBuffer.length > 0) {
|
|
1118
|
-
if (decryptKey != "") {
|
|
1119
|
-
const decrypted = await decryptContent(waitBuffer, decryptKey);
|
|
1120
|
-
fileContent = mergeUInt8Arrays(fileContent, decrypted) as any;
|
|
1121
|
-
} else {
|
|
1122
|
-
fileContent = mergeUInt8Arrays(fileContent, waitBuffer) as any;
|
|
1123
|
-
}
|
|
1124
|
-
}
|
|
1125
|
-
break;
|
|
1126
|
-
}
|
|
1127
|
-
continue;
|
|
1128
|
-
}
|
|
1129
|
-
catOptions.offset = readCount;
|
|
1130
|
-
catOptions.length = encryptextLen;
|
|
1131
|
-
const buf = await toBuffer(fs.cat(CID.parse(cid), catOptions));
|
|
1132
|
-
if (buf.length > 0) {
|
|
1133
|
-
readCount += buf.length;
|
|
1134
|
-
}
|
|
1135
|
-
if (buf.length > 0) {
|
|
1136
|
-
waitBuffer = mergeUInt8Arrays(waitBuffer, buf) as any;
|
|
1137
|
-
while (waitBuffer.length >= encryptextLen) {
|
|
1138
|
-
const encryptBuffer = waitBuffer.subarray(0, encryptextLen);
|
|
1139
|
-
waitBuffer = waitBuffer.subarray(encryptextLen, waitBuffer.length);
|
|
1140
|
-
if (decryptKey == "") {
|
|
1141
|
-
fileContent = mergeUInt8Arrays(fileContent, encryptBuffer) as any;
|
|
1142
|
-
continue;
|
|
1143
|
-
}
|
|
1144
|
-
//解密
|
|
1145
|
-
const decrypted = await decryptContent(encryptBuffer, decryptKey);
|
|
1146
|
-
fileContent = mergeUInt8Arrays(fileContent, decrypted) as any;
|
|
1147
|
-
}
|
|
1148
|
-
} else {
|
|
1149
|
-
if (waitBuffer.length > 0) {
|
|
1150
|
-
if (decryptKey != "") {
|
|
1151
|
-
const decrypted = await decryptContent(waitBuffer, decryptKey);
|
|
1152
|
-
fileContent = mergeUInt8Arrays(fileContent, decrypted) as any;
|
|
1153
|
-
} else {
|
|
1154
|
-
fileContent = mergeUInt8Arrays(fileContent, waitBuffer) as any;
|
|
1155
|
-
}
|
|
1156
|
-
}
|
|
1157
|
-
break;
|
|
1158
|
-
}
|
|
1159
|
-
}
|
|
1160
|
-
return fileContent;
|
|
1161
|
-
} catch (error) {
|
|
1162
|
-
console.error("getFileFromDc error", error);
|
|
1163
|
-
return null;
|
|
1164
|
-
}
|
|
1165
|
-
}
|
|
1166
|
-
/**
|
|
1167
|
-
* 创建可随机访问的文件流
|
|
1168
|
-
*/
|
|
1169
|
-
async createSeekableFileStream(
|
|
1170
|
-
cid: string,
|
|
1171
|
-
decryptKey: string,
|
|
1172
|
-
flag?: number
|
|
1173
|
-
): Promise<SeekableFileStream | null> {
|
|
1174
|
-
// 连接到节点
|
|
1175
|
-
if (flag !== cidNeedConnect.NOT_NEED) {
|
|
1176
|
-
const [multiAddrs, peers] = await this.dc?._connectToObjNodes(cid);
|
|
1177
|
-
if (!multiAddrs && peers) {
|
|
1178
|
-
// 有peers但是没有multiaddrs
|
|
1179
|
-
return null;
|
|
1180
|
-
}
|
|
1181
|
-
}
|
|
1182
|
-
|
|
1183
|
-
const fs = unixfs(this.dcNodeClient);
|
|
1184
|
-
|
|
1185
|
-
try {
|
|
1186
|
-
// 读取头信息
|
|
1187
|
-
const headerData = await toBuffer(
|
|
1188
|
-
fs.cat(CID.parse(cid), {
|
|
1189
|
-
offset: 0,
|
|
1190
|
-
length: 32,
|
|
1191
|
-
})
|
|
1192
|
-
);
|
|
1193
|
-
|
|
1194
|
-
// 检查是否有DC文件头
|
|
1195
|
-
const hasHeader = compareByteArrays(
|
|
1196
|
-
headerData.subarray(0, 10),
|
|
1197
|
-
Buffer.from(dcFileHead)
|
|
1198
|
-
);
|
|
1199
|
-
|
|
1200
|
-
// 获取文件大小
|
|
1201
|
-
// const stats = await fs.stat(CID.parse(cid));
|
|
1202
|
-
// const totalSize = Number(stats.fileSize);
|
|
1203
|
-
const fileSize = this.readUint64BE(headerData, 24);
|
|
1204
|
-
|
|
1205
|
-
const fileInfo = {
|
|
1206
|
-
size: fileSize,
|
|
1207
|
-
hasHeader,
|
|
1208
|
-
headerSize: hasHeader ? 32 : 0,
|
|
1209
|
-
};
|
|
1210
|
-
|
|
1211
|
-
// 创建并返回流对象
|
|
1212
|
-
return new SeekableFileStream({
|
|
1213
|
-
fileInfo,
|
|
1214
|
-
fs,
|
|
1215
|
-
cid: CID.parse(cid),
|
|
1216
|
-
decryptKey: decryptKey || "",
|
|
1217
|
-
encryptChunkSize: (3 << 20) + NonceBytes + TagBytes,
|
|
1218
|
-
});
|
|
1219
|
-
} catch (err) {
|
|
1220
|
-
console.error("Failed to create seekable file stream:", err);
|
|
1221
|
-
return null;
|
|
1222
|
-
}
|
|
1223
|
-
}
|
|
1224
|
-
|
|
1225
|
-
readUint64BE(buffer: Uint8Array, offset: number): number {
|
|
1226
|
-
// JavaScript中Number可以安全表示的最大整数是2^53-1
|
|
1227
|
-
const high =
|
|
1228
|
-
buffer[offset]! * 2 ** 24 +
|
|
1229
|
-
buffer[offset + 1]! * 2 ** 16 +
|
|
1230
|
-
buffer[offset + 2]! * 2 ** 8 +
|
|
1231
|
-
buffer[offset + 3]!;
|
|
1232
|
-
const low =
|
|
1233
|
-
buffer[offset + 4]! * 2 ** 24 +
|
|
1234
|
-
buffer[offset + 5]! * 2 ** 16 +
|
|
1235
|
-
buffer[offset + 6]! * 2 ** 8 +
|
|
1236
|
-
buffer[offset + 7]!;
|
|
1237
|
-
|
|
1238
|
-
return high * 2 ** 32 + low;
|
|
1239
|
-
}
|
|
1240
|
-
}
|