xrootd 0.2.3 → 1.0.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +24 -0
- package/LICENSE +189 -0
- package/README.md +334 -101
- package/dist/index.d.mts +620 -821
- package/dist/index.mjs +1554 -723
- package/package.json +64 -83
- package/LICENSE-GPLv3 +0 -674
- package/LICENSE-MIT +0 -7
- package/dist/index.cjs +0 -817
- package/dist/index.d.cts +0 -864
- package/libs/darwin-arm64/libXrdCl.6.dylib +0 -0
- package/libs/darwin-arm64/libXrdCrypto.6.dylib +0 -0
- package/libs/darwin-arm64/libXrdCryptossl-6.so +0 -0
- package/libs/darwin-arm64/libXrdSec-6.so +0 -0
- package/libs/darwin-arm64/libXrdSecProt-6.so +0 -0
- package/libs/darwin-arm64/libXrdSeckrb5-6.so +0 -0
- package/libs/darwin-arm64/libXrdSecpwd-6.so +0 -0
- package/libs/darwin-arm64/libXrdSecsss-6.so +0 -0
- package/libs/darwin-arm64/libXrdSecunix-6.so +0 -0
- package/libs/darwin-arm64/libXrdSecztn-6.so +0 -0
- package/libs/darwin-arm64/libXrdUtils.6.dylib +0 -0
- package/libs/darwin-arm64/libXrdXml.6.dylib +0 -0
- package/libs/darwin-x64/libXrdCl.6.dylib +0 -0
- package/libs/darwin-x64/libXrdCrypto.6.dylib +0 -0
- package/libs/darwin-x64/libXrdCryptossl-6.so +0 -0
- package/libs/darwin-x64/libXrdSec-6.so +0 -0
- package/libs/darwin-x64/libXrdSecProt-6.so +0 -0
- package/libs/darwin-x64/libXrdSeckrb5-6.so +0 -0
- package/libs/darwin-x64/libXrdSecpwd-6.so +0 -0
- package/libs/darwin-x64/libXrdSecsss-6.so +0 -0
- package/libs/darwin-x64/libXrdSecunix-6.so +0 -0
- package/libs/darwin-x64/libXrdSecztn-6.so +0 -0
- package/libs/darwin-x64/libXrdUtils.6.dylib +0 -0
- package/libs/darwin-x64/libXrdXml.6.dylib +0 -0
- package/libs/linux-arm64/libXrdCl.so.6 +0 -0
- package/libs/linux-arm64/libXrdCrypto.so.6 +0 -0
- package/libs/linux-arm64/libXrdCryptossl-6.so +0 -0
- package/libs/linux-arm64/libXrdSec-6.so +0 -0
- package/libs/linux-arm64/libXrdSecProt-6.so +0 -0
- package/libs/linux-arm64/libXrdSeckrb5-6.so +0 -0
- package/libs/linux-arm64/libXrdSecpwd-6.so +0 -0
- package/libs/linux-arm64/libXrdSecsss-6.so +0 -0
- package/libs/linux-arm64/libXrdSecunix-6.so +0 -0
- package/libs/linux-arm64/libXrdSecztn-6.so +0 -0
- package/libs/linux-arm64/libXrdUtils.so.6 +0 -0
- package/libs/linux-arm64/libXrdXml.so.6 +0 -0
- package/libs/linux-x64/libXrdCl.so.6 +0 -0
- package/libs/linux-x64/libXrdCrypto.so.6 +0 -0
- package/libs/linux-x64/libXrdCryptossl-6.so +0 -0
- package/libs/linux-x64/libXrdSec-6.so +0 -0
- package/libs/linux-x64/libXrdSecProt-6.so +0 -0
- package/libs/linux-x64/libXrdSeckrb5-6.so +0 -0
- package/libs/linux-x64/libXrdSecpwd-6.so +0 -0
- package/libs/linux-x64/libXrdSecsss-6.so +0 -0
- package/libs/linux-x64/libXrdSecunix-6.so +0 -0
- package/libs/linux-x64/libXrdSecztn-6.so +0 -0
- package/libs/linux-x64/libXrdUtils.so.6 +0 -0
- package/libs/linux-x64/libXrdXml.so.6 +0 -0
- package/prebuilds/darwin-arm64/xrootd.node +0 -0
- package/prebuilds/darwin-x64/xrootd.node +0 -0
- package/prebuilds/linux-arm64/xrootd.node +0 -0
- package/prebuilds/linux-x64/xrootd.node +0 -0
package/dist/index.cjs
DELETED
|
@@ -1,817 +0,0 @@
|
|
|
1
|
-
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
//#region \0rolldown/runtime.js
|
|
3
|
-
var __create = Object.create;
|
|
4
|
-
var __defProp = Object.defineProperty;
|
|
5
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
-
var __copyProps = (to, from, except, desc) => {
|
|
10
|
-
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
11
|
-
key = keys[i];
|
|
12
|
-
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
13
|
-
get: ((k) => from[k]).bind(null, key),
|
|
14
|
-
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
15
|
-
});
|
|
16
|
-
}
|
|
17
|
-
return to;
|
|
18
|
-
};
|
|
19
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
20
|
-
value: mod,
|
|
21
|
-
enumerable: true
|
|
22
|
-
}) : target, mod));
|
|
23
|
-
//#endregion
|
|
24
|
-
let node_stream = require("node:stream");
|
|
25
|
-
let node_gyp_build = require("node-gyp-build");
|
|
26
|
-
node_gyp_build = __toESM(node_gyp_build, 1);
|
|
27
|
-
let node_path = require("node:path");
|
|
28
|
-
node_path = __toESM(node_path, 1);
|
|
29
|
-
let node_url = require("node:url");
|
|
30
|
-
//#region lib/native.ts
|
|
31
|
-
const nativeAddon = (0, node_gyp_build.default)(node_path.default.resolve(__dirname, ".."));
|
|
32
|
-
//#endregion
|
|
33
|
-
//#region lib/enums.ts
|
|
34
|
-
/**
|
|
35
|
-
* XrdCl::OpenFlags - 文件打开选项
|
|
36
|
-
* @see src/XProtocol/XProtocol.hh:XOpenRequestOption
|
|
37
|
-
* @see src/XrdCl/XrdClFileSystem.hh:OpenFlags::Flags
|
|
38
|
-
*/
|
|
39
|
-
const OpenFlags = {
|
|
40
|
-
None: 0,
|
|
41
|
-
Compress: 1,
|
|
42
|
-
Delete: 2,
|
|
43
|
-
Force: 4,
|
|
44
|
-
New: 8,
|
|
45
|
-
Read: 16,
|
|
46
|
-
Update: 32,
|
|
47
|
-
Refresh: 128,
|
|
48
|
-
MakePath: 256,
|
|
49
|
-
IntentDirList: 1024,
|
|
50
|
-
Replica: 2048,
|
|
51
|
-
POSC: 4096,
|
|
52
|
-
NoWait: 8192,
|
|
53
|
-
SeqIO: 16384,
|
|
54
|
-
Write: 32768,
|
|
55
|
-
PrefName: 256,
|
|
56
|
-
Dup: 65536,
|
|
57
|
-
Samefs: 131072
|
|
58
|
-
};
|
|
59
|
-
/**
|
|
60
|
-
* XrdCl::Access - 文件访问权限模式
|
|
61
|
-
* @see src/XProtocol/XProtocol.hh:XOpenRequestMode
|
|
62
|
-
* @see src/XrdCl/XrdClFileSystem.hh:Access::Mode
|
|
63
|
-
*/
|
|
64
|
-
const AccessMode = {
|
|
65
|
-
None: 0,
|
|
66
|
-
UR: 256,
|
|
67
|
-
UW: 128,
|
|
68
|
-
UX: 64,
|
|
69
|
-
GR: 32,
|
|
70
|
-
GW: 16,
|
|
71
|
-
GX: 8,
|
|
72
|
-
OR: 4,
|
|
73
|
-
OW: 2,
|
|
74
|
-
OX: 1
|
|
75
|
-
};
|
|
76
|
-
/**
|
|
77
|
-
* XrdCl::MkDirFlags - 目录创建选项
|
|
78
|
-
* @see src/XrdCl/XrdClFileSystem.hh:MkDirFlags::Flags
|
|
79
|
-
*/
|
|
80
|
-
const MkDirFlags = {
|
|
81
|
-
None: 0,
|
|
82
|
-
MakePath: 1
|
|
83
|
-
};
|
|
84
|
-
const StatFlags = {
|
|
85
|
-
XBitSet: 1,
|
|
86
|
-
//!< Executable/searchable bit set
|
|
87
|
-
IsDir: 2,
|
|
88
|
-
//!< This is a directory
|
|
89
|
-
Other: 4,
|
|
90
|
-
//!< Neither a file nor a directory
|
|
91
|
-
Offline: 8,
|
|
92
|
-
//!< File is not online (ie. on disk)
|
|
93
|
-
POSCPending: 64,
|
|
94
|
-
//!< File opened with POST flag, not yet successfully closed
|
|
95
|
-
IsReadable: 16,
|
|
96
|
-
//!< Read access is allowed
|
|
97
|
-
IsWritable: 32,
|
|
98
|
-
//!< Write access is allowed
|
|
99
|
-
BackUpExists: 128
|
|
100
|
-
};
|
|
101
|
-
//!< Back up copy exists
|
|
102
|
-
//#endregion
|
|
103
|
-
//#region lib/file.ts
|
|
104
|
-
/**
|
|
105
|
-
* XRootD File 客户端
|
|
106
|
-
* 提供对远程文件的异步读写操作及 Node.js 风格的流式接口。
|
|
107
|
-
*/
|
|
108
|
-
var File = class {
|
|
109
|
-
_internal;
|
|
110
|
-
constructor(internalInstance) {
|
|
111
|
-
if (internalInstance) this._internal = internalInstance;
|
|
112
|
-
else {
|
|
113
|
-
if (!nativeAddon || !nativeAddon.File) throw new Error("Native addon not loaded properly.");
|
|
114
|
-
this._internal = new nativeAddon.File();
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
/**
|
|
118
|
-
* 打开远程文件
|
|
119
|
-
* @param url 目标地址 (如 root://server//path/to/file)
|
|
120
|
-
* @param flags 打开标志位
|
|
121
|
-
* @param mode 访问权限模式
|
|
122
|
-
*/
|
|
123
|
-
async open(url, flags = OpenFlags.None, mode = AccessMode.None) {
|
|
124
|
-
return this._internal.Open(url, flags, mode);
|
|
125
|
-
}
|
|
126
|
-
/**
|
|
127
|
-
* 关闭文件
|
|
128
|
-
*/
|
|
129
|
-
async close() {
|
|
130
|
-
return this._internal.Close();
|
|
131
|
-
}
|
|
132
|
-
/**
|
|
133
|
-
* 获取文件状态
|
|
134
|
-
*/
|
|
135
|
-
async stat() {
|
|
136
|
-
return this._internal.Stat();
|
|
137
|
-
}
|
|
138
|
-
/**
|
|
139
|
-
* 读取文件块 (Zero-Copy from C++)
|
|
140
|
-
* @param offset 偏移量 (支持 >2GB)
|
|
141
|
-
* @param size 读取字节数
|
|
142
|
-
* @returns 包含数据的 Node.js Buffer
|
|
143
|
-
*/
|
|
144
|
-
async read(offset, size) {
|
|
145
|
-
return this._internal.Read(BigInt(offset), size);
|
|
146
|
-
}
|
|
147
|
-
async write(offset, arg1, arg2, arg3) {
|
|
148
|
-
if (Buffer.isBuffer(arg1)) return this._internal.Write(BigInt(offset), arg1);
|
|
149
|
-
else if (typeof arg1 === "number" && typeof arg2 === "number") return this._internal.WriteFd(BigInt(offset), arg1, arg2, arg3 !== void 0 ? BigInt(arg3) : void 0);
|
|
150
|
-
else throw new TypeError("Invalid arguments for write");
|
|
151
|
-
}
|
|
152
|
-
/**
|
|
153
|
-
* 从本地 fd 写入文件块 (直接映射)
|
|
154
|
-
* @param offset 写入的起始字节偏移量。
|
|
155
|
-
* @param size 写入大小。
|
|
156
|
-
* @param fd 本地文件描述符 (fd)。
|
|
157
|
-
* @param fdoff 可选,从本地 fd 中读取的起始偏移。
|
|
158
|
-
*/
|
|
159
|
-
async writeFd(offset, size, fd, fdoff) {
|
|
160
|
-
return this._internal.WriteFd(BigInt(offset), size, fd, fdoff !== void 0 ? BigInt(fdoff) : void 0);
|
|
161
|
-
}
|
|
162
|
-
/**
|
|
163
|
-
* 同步文件缓冲区到磁盘
|
|
164
|
-
*/
|
|
165
|
-
async sync() {
|
|
166
|
-
return this._internal.Sync();
|
|
167
|
-
}
|
|
168
|
-
/**
|
|
169
|
-
* 截断文件
|
|
170
|
-
* @param size 目标大小
|
|
171
|
-
*/
|
|
172
|
-
async truncate(size) {
|
|
173
|
-
return this._internal.Truncate(BigInt(size));
|
|
174
|
-
}
|
|
175
|
-
/**
|
|
176
|
-
* 检查本地实例状态 (同步操作)
|
|
177
|
-
*/
|
|
178
|
-
isOpen() {
|
|
179
|
-
return this._internal.IsOpen();
|
|
180
|
-
}
|
|
181
|
-
async getProperty(name) {
|
|
182
|
-
const ret = this._internal.GetProperty(name);
|
|
183
|
-
if (ret.success) return ret.value;
|
|
184
|
-
throw new Error("TODO");
|
|
185
|
-
}
|
|
186
|
-
async setProperty(name, value) {
|
|
187
|
-
return this._internal.SetProperty(name, value);
|
|
188
|
-
}
|
|
189
|
-
/**
|
|
190
|
-
* 向量化读取 (Vector Read)
|
|
191
|
-
* 在单个请求中从文件的多个非连续区域读取数据,极大地减少网络往返开销。
|
|
192
|
-
* @param chunks 包含 offset 和 size 的读取请求数组
|
|
193
|
-
* @returns 与请求数组顺序对应的 Buffer 数组
|
|
194
|
-
*/
|
|
195
|
-
async vectorRead(chunks) {
|
|
196
|
-
const normalizedChunks = chunks.map((c) => ({
|
|
197
|
-
offset: BigInt(c.offset),
|
|
198
|
-
size: c.size
|
|
199
|
-
}));
|
|
200
|
-
return this._internal.VectorRead(normalizedChunks);
|
|
201
|
-
}
|
|
202
|
-
/**
|
|
203
|
-
* 读取块 (Read Chunks)
|
|
204
|
-
* 类似于 VectorRead,但底层实现可能利用更高级的预读或多路复用策略。
|
|
205
|
-
*/
|
|
206
|
-
async readChunks(chunks) {
|
|
207
|
-
const normalizedChunks = chunks.map((c) => ({
|
|
208
|
-
offset: BigInt(c.offset),
|
|
209
|
-
size: c.size
|
|
210
|
-
}));
|
|
211
|
-
return this._internal.ReadChunks(normalizedChunks);
|
|
212
|
-
}
|
|
213
|
-
/**
|
|
214
|
-
* 设置文件的扩展属性
|
|
215
|
-
*/
|
|
216
|
-
async setXAttrs(attrs) {
|
|
217
|
-
return this._internal.SetXAttr(attrs);
|
|
218
|
-
}
|
|
219
|
-
/**
|
|
220
|
-
* 设置文件的扩展属性
|
|
221
|
-
*/
|
|
222
|
-
async setXAttr(key, value) {
|
|
223
|
-
return (await this._internal.SetXAttr({ [key]: value }))[0].ok;
|
|
224
|
-
}
|
|
225
|
-
/**
|
|
226
|
-
* 获取文件的扩展属性
|
|
227
|
-
*/
|
|
228
|
-
async getXAttrs(keys) {
|
|
229
|
-
return this._internal.GetXAttr(keys);
|
|
230
|
-
}
|
|
231
|
-
async getXAttr(key) {
|
|
232
|
-
return (await this._internal.GetXAttr([key]))[key];
|
|
233
|
-
}
|
|
234
|
-
/**
|
|
235
|
-
* 删除文件的扩展属性
|
|
236
|
-
*/
|
|
237
|
-
async delXAttrs(keys) {
|
|
238
|
-
return this._internal.DelXAttr(keys);
|
|
239
|
-
}
|
|
240
|
-
async delXAttr(key) {
|
|
241
|
-
return (await this._internal.DelXAttr([key]))[0].ok;
|
|
242
|
-
}
|
|
243
|
-
/**
|
|
244
|
-
* 列出文件所有的扩展属性和内容
|
|
245
|
-
*/
|
|
246
|
-
async listXAttrs() {
|
|
247
|
-
return this._internal.ListXAttr();
|
|
248
|
-
}
|
|
249
|
-
/**
|
|
250
|
-
* 将其他文件的指定区间在服务器端克隆到当前文件
|
|
251
|
-
* 当前文件必须以写入/更新模式打开
|
|
252
|
-
*/
|
|
253
|
-
clone(locations) {
|
|
254
|
-
return this._internal.Clone(locations);
|
|
255
|
-
}
|
|
256
|
-
/**
|
|
257
|
-
* 创建一个可读流 (Readable Stream)
|
|
258
|
-
* 使得 XRootD 文件可以无缝 pipe 到其他 Node.js 流 (如本地 fs, HTTP response)
|
|
259
|
-
*/
|
|
260
|
-
createReadStream(options = {}) {
|
|
261
|
-
let currentOffset = options.start ?? 0n;
|
|
262
|
-
const endOffset = options.end;
|
|
263
|
-
const chunkSize = options.highWaterMark ?? 64 * 1024;
|
|
264
|
-
const self = this;
|
|
265
|
-
return new node_stream.Readable({
|
|
266
|
-
highWaterMark: chunkSize,
|
|
267
|
-
async read(size) {
|
|
268
|
-
try {
|
|
269
|
-
let bytesToRead = size;
|
|
270
|
-
if (endOffset !== void 0) {
|
|
271
|
-
const remaining = endOffset - currentOffset + 1n;
|
|
272
|
-
if (remaining <= 0n) {
|
|
273
|
-
this.push(null);
|
|
274
|
-
return;
|
|
275
|
-
}
|
|
276
|
-
if (remaining < BigInt(bytesToRead)) bytesToRead = Number(remaining);
|
|
277
|
-
}
|
|
278
|
-
const buffer = await self.read(currentOffset, bytesToRead);
|
|
279
|
-
if (buffer.length === 0) this.push(null);
|
|
280
|
-
else {
|
|
281
|
-
currentOffset += BigInt(buffer.length);
|
|
282
|
-
this.push(buffer);
|
|
283
|
-
}
|
|
284
|
-
} catch (err) {
|
|
285
|
-
this.destroy(err instanceof Error ? err : new Error(String(err)));
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
});
|
|
289
|
-
}
|
|
290
|
-
/**
|
|
291
|
-
* 创建一个可写流 (Writable Stream)
|
|
292
|
-
* 支持通过 pipe 将大量数据流式写入 XRootD 服务器
|
|
293
|
-
*/
|
|
294
|
-
createWriteStream(options = {}) {
|
|
295
|
-
let currentOffset = options.start ?? 0n;
|
|
296
|
-
const self = this;
|
|
297
|
-
return new node_stream.Writable({
|
|
298
|
-
async write(chunk, encoding, callback) {
|
|
299
|
-
try {
|
|
300
|
-
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk, encoding);
|
|
301
|
-
await self.write(currentOffset, buffer);
|
|
302
|
-
currentOffset += BigInt(buffer.length);
|
|
303
|
-
callback();
|
|
304
|
-
} catch (err) {
|
|
305
|
-
callback(err instanceof Error ? err : new Error(String(err)));
|
|
306
|
-
}
|
|
307
|
-
},
|
|
308
|
-
async writev(chunks, callback) {
|
|
309
|
-
try {
|
|
310
|
-
const buffers = chunks.map((c) => Buffer.isBuffer(c.chunk) ? c.chunk : Buffer.from(c.chunk, c.encoding));
|
|
311
|
-
const masterBuffer = Buffer.concat(buffers);
|
|
312
|
-
await self.write(currentOffset, masterBuffer);
|
|
313
|
-
currentOffset += BigInt(masterBuffer.length);
|
|
314
|
-
callback();
|
|
315
|
-
} catch (err) {
|
|
316
|
-
callback(err instanceof Error ? err : new Error(String(err)));
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
});
|
|
320
|
-
}
|
|
321
|
-
};
|
|
322
|
-
//#endregion
|
|
323
|
-
//#region lib/utils.ts
|
|
324
|
-
function reverseStr(str) {
|
|
325
|
-
let reversed = "";
|
|
326
|
-
for (let i = str.length - 1; i >= 0; i--) reversed += str.charAt(i);
|
|
327
|
-
return reversed;
|
|
328
|
-
}
|
|
329
|
-
function isXrdError(e) {
|
|
330
|
-
return e instanceof Error && "xrdStatus" in e && typeof e.xrdStatus === "number" && e.xrdStatus > 0;
|
|
331
|
-
}
|
|
332
|
-
//#endregion
|
|
333
|
-
//#region lib/filesystem.ts
|
|
334
|
-
/**
|
|
335
|
-
* XRootD FileSystem 客户端
|
|
336
|
-
* 提供对远程服务器目录和文件的通用管理操作。
|
|
337
|
-
*/
|
|
338
|
-
var FileSystem = class {
|
|
339
|
-
_internal;
|
|
340
|
-
serverUrl;
|
|
341
|
-
/**
|
|
342
|
-
* 实例化一个 FileSystem 客户端
|
|
343
|
-
* @param url 服务器地址 (例如: 'root://eospublic.cern.ch')
|
|
344
|
-
*/
|
|
345
|
-
constructor(url) {
|
|
346
|
-
if (!nativeAddon || !nativeAddon.FileSystem) throw new Error("Native addon not loaded properly. Cannot instantiate FileSystem.");
|
|
347
|
-
this.serverUrl = url;
|
|
348
|
-
this._internal = new nativeAddon.FileSystem(url);
|
|
349
|
-
}
|
|
350
|
-
/**
|
|
351
|
-
* 内部辅助方法:确保路径是标准的 Unix 绝对路径
|
|
352
|
-
*/
|
|
353
|
-
_normalize(targetPath) {
|
|
354
|
-
const posixPath = node_path.posix.normalize(targetPath);
|
|
355
|
-
return posixPath.startsWith("/") ? posixPath : "/" + posixPath;
|
|
356
|
-
}
|
|
357
|
-
/**
|
|
358
|
-
* 定位文件在集群中的具体数据节点
|
|
359
|
-
* @param filePath 目标文件路径
|
|
360
|
-
* @param flags 定位标志位 (默认 0)
|
|
361
|
-
* @returns 包含主机、端口等信息的数组
|
|
362
|
-
*/
|
|
363
|
-
async locate(filePath, flags = 0) {
|
|
364
|
-
return this._internal.Locate(this._normalize(filePath), flags);
|
|
365
|
-
}
|
|
366
|
-
/**
|
|
367
|
-
* 获取文件或目录的状态信息
|
|
368
|
-
* @param targetPath 目标路径
|
|
369
|
-
*/
|
|
370
|
-
async stat(targetPath) {
|
|
371
|
-
const rawStat = await this._internal.Stat(this._normalize(targetPath));
|
|
372
|
-
return {
|
|
373
|
-
...rawStat,
|
|
374
|
-
get modeOctal() {
|
|
375
|
-
return reverseStr(rawStat.modeAsOctString);
|
|
376
|
-
},
|
|
377
|
-
get modeString() {
|
|
378
|
-
return reverseStr(rawStat.modeAsString);
|
|
379
|
-
},
|
|
380
|
-
get isFile() {
|
|
381
|
-
return (rawStat.flags & StatFlags.IsDir) === 0 && (rawStat.flags & StatFlags.Other) === 0;
|
|
382
|
-
},
|
|
383
|
-
get isDir() {
|
|
384
|
-
return (rawStat.flags & StatFlags.IsDir) !== 0;
|
|
385
|
-
},
|
|
386
|
-
get isOther() {
|
|
387
|
-
return (rawStat.flags & StatFlags.Other) !== 0;
|
|
388
|
-
},
|
|
389
|
-
get isOffline() {
|
|
390
|
-
return (rawStat.flags & StatFlags.Offline) !== 0;
|
|
391
|
-
},
|
|
392
|
-
get isPOSCPending() {
|
|
393
|
-
return (rawStat.flags & StatFlags.POSCPending) !== 0;
|
|
394
|
-
},
|
|
395
|
-
get isReadable() {
|
|
396
|
-
return (rawStat.flags & StatFlags.IsReadable) !== 0;
|
|
397
|
-
},
|
|
398
|
-
get isWritable() {
|
|
399
|
-
return (rawStat.flags & StatFlags.IsWritable) !== 0;
|
|
400
|
-
},
|
|
401
|
-
get isBackUpExists() {
|
|
402
|
-
return (rawStat.flags & StatFlags.BackUpExists) !== 0;
|
|
403
|
-
}
|
|
404
|
-
};
|
|
405
|
-
}
|
|
406
|
-
/**
|
|
407
|
-
* 删除远程文件
|
|
408
|
-
* @param filePath 要删除的文件路径
|
|
409
|
-
*/
|
|
410
|
-
async rm(filePath) {
|
|
411
|
-
return this._internal.Rm(this._normalize(filePath));
|
|
412
|
-
}
|
|
413
|
-
/**
|
|
414
|
-
* 创建远程目录
|
|
415
|
-
* @param dirPath 目录路径
|
|
416
|
-
* @param flags 标志位 (例如 MakePath,允许创建多级父目录)
|
|
417
|
-
* @param mode 访问权限模式 (默认 0755 对应的 AccessMode)
|
|
418
|
-
*/
|
|
419
|
-
async mkdir(dirPath, flags = MkDirFlags.None, mode = AccessMode.UR | AccessMode.UW | AccessMode.UX | AccessMode.GR | AccessMode.GX | AccessMode.OR | AccessMode.OX) {
|
|
420
|
-
return this._internal.MkDir(this._normalize(dirPath), flags, mode);
|
|
421
|
-
}
|
|
422
|
-
/**
|
|
423
|
-
* 删除远程目录 (目录必须为空)
|
|
424
|
-
* @param dirPath 要删除的目录路径
|
|
425
|
-
*/
|
|
426
|
-
async rmdir(dirPath) {
|
|
427
|
-
return this._internal.RmDir(this._normalize(dirPath));
|
|
428
|
-
}
|
|
429
|
-
/**
|
|
430
|
-
* 移动或重命名文件/目录
|
|
431
|
-
* @param source 源路径
|
|
432
|
-
* @param dest 目标路径
|
|
433
|
-
*/
|
|
434
|
-
async mv(source, dest) {
|
|
435
|
-
return this._internal.Mv(this._normalize(source), this._normalize(dest));
|
|
436
|
-
}
|
|
437
|
-
/**
|
|
438
|
-
* 列出目录下的所有文件和子目录名
|
|
439
|
-
* @param dirPath 目录路径
|
|
440
|
-
* @param flags 标志位 (例如是否显示隐藏文件)
|
|
441
|
-
*/
|
|
442
|
-
async dirList(dirPath, flags = 0) {
|
|
443
|
-
return this._internal.DirList(this._normalize(dirPath), flags);
|
|
444
|
-
}
|
|
445
|
-
/**
|
|
446
|
-
* 检查文件或目录是否存在
|
|
447
|
-
* (通过捕获 stat 的错误来实现,类似于老版本 Node 的 fs.exists)
|
|
448
|
-
* @param targetPath 目标路径
|
|
449
|
-
*/
|
|
450
|
-
async exists(targetPath) {
|
|
451
|
-
try {
|
|
452
|
-
await this.stat(targetPath);
|
|
453
|
-
return true;
|
|
454
|
-
} catch (err) {
|
|
455
|
-
debugger;
|
|
456
|
-
if (isXrdError(err)) {
|
|
457
|
-
if (err.xrdErrNo === 3011) return false;
|
|
458
|
-
}
|
|
459
|
-
throw err;
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
/**
|
|
463
|
-
* 确保目录存在。如果目录不存在,则会自动创建它及其所有父目录。
|
|
464
|
-
* (类似于 fs-extra 的 ensureDir 或 mkdir -p)
|
|
465
|
-
* @param dirPath 目标目录
|
|
466
|
-
*/
|
|
467
|
-
async ensureDir(dirPath) {
|
|
468
|
-
if (!await this.exists(dirPath)) await this.mkdir(dirPath, MkDirFlags.MakePath);
|
|
469
|
-
}
|
|
470
|
-
/**
|
|
471
|
-
* 深度定位:返回包含所有数据节点副本的详细物理位置信息
|
|
472
|
-
*/
|
|
473
|
-
async deepLocate(filePath, flags = 0) {
|
|
474
|
-
return this._internal.DeepLocate(this._normalize(filePath), flags);
|
|
475
|
-
}
|
|
476
|
-
/**
|
|
477
|
-
* 在不打开文件的情况下,直接截断目标文件
|
|
478
|
-
*/
|
|
479
|
-
async truncate(filePath, size) {
|
|
480
|
-
return this._internal.Truncate(this._normalize(filePath), size);
|
|
481
|
-
}
|
|
482
|
-
/**
|
|
483
|
-
* 更改远程文件或目录的访问权限
|
|
484
|
-
*/
|
|
485
|
-
async chmod(targetPath, mode) {
|
|
486
|
-
return this._internal.ChMod(this._normalize(targetPath), mode);
|
|
487
|
-
}
|
|
488
|
-
/**
|
|
489
|
-
* 探活:检查远端文件系统服务是否响应
|
|
490
|
-
*/
|
|
491
|
-
async ping() {
|
|
492
|
-
return this._internal.Ping();
|
|
493
|
-
}
|
|
494
|
-
/**
|
|
495
|
-
* 获取虚拟文件系统(VFS)的状态(如磁盘总容量、剩余可用空间)
|
|
496
|
-
*/
|
|
497
|
-
async statVFS(targetPath) {
|
|
498
|
-
return this._internal.StatVFS(this._normalize(targetPath));
|
|
499
|
-
}
|
|
500
|
-
/**
|
|
501
|
-
* 获取当前连接协议的详细属性
|
|
502
|
-
*/
|
|
503
|
-
async protocol() {
|
|
504
|
-
return this._internal.Protocol();
|
|
505
|
-
}
|
|
506
|
-
/**
|
|
507
|
-
* 发送带外查询指令到数据节点 (通常用于 XRootD 的高级自定义插件)
|
|
508
|
-
*/
|
|
509
|
-
async query(queryCode, args) {
|
|
510
|
-
return this._internal.Query(queryCode, args);
|
|
511
|
-
}
|
|
512
|
-
/**
|
|
513
|
-
* 发送通用信息到服务器
|
|
514
|
-
*/
|
|
515
|
-
async sendInfo(info) {
|
|
516
|
-
return this._internal.SendInfo(info);
|
|
517
|
-
}
|
|
518
|
-
/**
|
|
519
|
-
* 发送缓存操作信息给集群
|
|
520
|
-
*/
|
|
521
|
-
async sendCache(info) {
|
|
522
|
-
return this._internal.SendCache(info);
|
|
523
|
-
}
|
|
524
|
-
/**
|
|
525
|
-
* 数据预热/暂存 (Staging):
|
|
526
|
-
* 在处理海量物理数据时,通知存储集群将特定的冷数据(如磁带上的文件)提前拉取到磁盘缓存。
|
|
527
|
-
* @param targetPaths 需要预热的路径数组
|
|
528
|
-
* @param flags 预热策略标志
|
|
529
|
-
* @param priority 优先级
|
|
530
|
-
*/
|
|
531
|
-
async prepare(targetPaths, flags = 0, priority = 0) {
|
|
532
|
-
const normalizedPaths = targetPaths.map((p) => this._normalize(p));
|
|
533
|
-
return this._internal.Prepare(normalizedPaths, flags, priority);
|
|
534
|
-
}
|
|
535
|
-
/**
|
|
536
|
-
* 获取文件系统属性
|
|
537
|
-
*/
|
|
538
|
-
getProperty(name) {
|
|
539
|
-
return this._internal.GetProperty(name);
|
|
540
|
-
}
|
|
541
|
-
/**
|
|
542
|
-
* 设置文件系统属性
|
|
543
|
-
*/
|
|
544
|
-
setProperty(name, value) {
|
|
545
|
-
return this._internal.SetProperty(name, value);
|
|
546
|
-
}
|
|
547
|
-
/**
|
|
548
|
-
* 设置扩展属性
|
|
549
|
-
* @param targetPath 目标路径
|
|
550
|
-
* @param attrs 扩展属性键值对记录
|
|
551
|
-
*/
|
|
552
|
-
async setXAttrs(targetPath, attrs) {
|
|
553
|
-
return this._internal.SetXAttr(this._normalize(targetPath), attrs);
|
|
554
|
-
}
|
|
555
|
-
async setXAttr(targetPath, key, value) {
|
|
556
|
-
return (await this.setXAttrs(targetPath, { [key]: value }))[0].ok;
|
|
557
|
-
}
|
|
558
|
-
/**
|
|
559
|
-
* 获取扩展属性
|
|
560
|
-
* @param targetPath 目标路径
|
|
561
|
-
* @param keys 需要获取的属性名数组
|
|
562
|
-
*/
|
|
563
|
-
async getXAttrs(targetPath, keys) {
|
|
564
|
-
return this._internal.GetXAttr(this._normalize(targetPath), keys);
|
|
565
|
-
}
|
|
566
|
-
async getXAttr(targetPath, key) {
|
|
567
|
-
return (await this.getXAttrs(targetPath, [key]))[key];
|
|
568
|
-
}
|
|
569
|
-
/**
|
|
570
|
-
* 删除指定的扩展属性
|
|
571
|
-
* @param targetPath 目标路径
|
|
572
|
-
* @param keys 需要删除的属性名数组
|
|
573
|
-
*/
|
|
574
|
-
async delXAttrs(targetPath, keys) {
|
|
575
|
-
return this._internal.DelXAttr(this._normalize(targetPath), keys);
|
|
576
|
-
}
|
|
577
|
-
async delXAttr(targetPath, key) {
|
|
578
|
-
return (await this.delXAttrs(targetPath, [key]))[0].ok;
|
|
579
|
-
}
|
|
580
|
-
/**
|
|
581
|
-
* 列出目标文件或目录的所有扩展属性
|
|
582
|
-
*/
|
|
583
|
-
async listXAttr(targetPath) {
|
|
584
|
-
return this._internal.ListXAttr(this._normalize(targetPath));
|
|
585
|
-
}
|
|
586
|
-
};
|
|
587
|
-
//#endregion
|
|
588
|
-
//#region lib/copy.ts
|
|
589
|
-
var CopyProcess = class {
|
|
590
|
-
nativeCp;
|
|
591
|
-
constructor() {
|
|
592
|
-
this.nativeCp = new nativeAddon.CopyProcess();
|
|
593
|
-
}
|
|
594
|
-
/**
|
|
595
|
-
* Add a job to the copy process synchronously
|
|
596
|
-
*/
|
|
597
|
-
addJob(config) {
|
|
598
|
-
this.nativeCp.AddJob(config);
|
|
599
|
-
}
|
|
600
|
-
/**
|
|
601
|
-
* Asynchronously prepare the jobs (resolve DNS, check endpoints, etc.)
|
|
602
|
-
*/
|
|
603
|
-
async prepare() {
|
|
604
|
-
return this.nativeCp.Prepare();
|
|
605
|
-
}
|
|
606
|
-
/**
|
|
607
|
-
* Run the copy process.
|
|
608
|
-
* @param onProgress Optional progress callback
|
|
609
|
-
* @returns Array of results corresponding to the jobs added
|
|
610
|
-
*/
|
|
611
|
-
async run(onProgress) {
|
|
612
|
-
if (onProgress) this.nativeCp.SetEventListener("progress", onProgress);
|
|
613
|
-
return this.nativeCp.Run();
|
|
614
|
-
}
|
|
615
|
-
/**
|
|
616
|
-
* Abort the running copy process.
|
|
617
|
-
*/
|
|
618
|
-
abort() {
|
|
619
|
-
this.nativeCp.CancelJob();
|
|
620
|
-
}
|
|
621
|
-
/**
|
|
622
|
-
* Abort the running copy process. (alias for abort)
|
|
623
|
-
*/
|
|
624
|
-
cancelJob() {
|
|
625
|
-
this.nativeCp.CancelJob();
|
|
626
|
-
}
|
|
627
|
-
};
|
|
628
|
-
//#endregion
|
|
629
|
-
//#region lib/url.ts
|
|
630
|
-
/**
|
|
631
|
-
* 纯 TypeScript 实现的 XRootD URL 解析器
|
|
632
|
-
* 安全、轻量,避免跨越 C++ N-API 边界
|
|
633
|
-
*/
|
|
634
|
-
var XRootDUrl = class XRootDUrl {
|
|
635
|
-
_url;
|
|
636
|
-
isValid;
|
|
637
|
-
constructor(urlString) {
|
|
638
|
-
this._url = new node_url.URL(urlString);
|
|
639
|
-
this.isValid = true;
|
|
640
|
-
if (![
|
|
641
|
-
"root",
|
|
642
|
-
"xrootd",
|
|
643
|
-
"xroot",
|
|
644
|
-
"file"
|
|
645
|
-
].includes(this.protocol)) {
|
|
646
|
-
console.error("Invalid XRootD URL protocol.");
|
|
647
|
-
this.isValid = false;
|
|
648
|
-
} else if (this.hostName.length === 0) this.isValid = false;
|
|
649
|
-
}
|
|
650
|
-
get protocol() {
|
|
651
|
-
return this._url.protocol.replace(/:$/, "");
|
|
652
|
-
}
|
|
653
|
-
set protocol(protocol) {
|
|
654
|
-
this._url.protocol = protocol;
|
|
655
|
-
}
|
|
656
|
-
get hostName() {
|
|
657
|
-
return this._url.hostname;
|
|
658
|
-
}
|
|
659
|
-
set hostName(hostName) {
|
|
660
|
-
this._url.hostname = hostName;
|
|
661
|
-
}
|
|
662
|
-
get port() {
|
|
663
|
-
return this._url.port ? parseInt(this._url.port, 10) : 1094;
|
|
664
|
-
}
|
|
665
|
-
set port(port) {
|
|
666
|
-
this._url.port = port.toString();
|
|
667
|
-
}
|
|
668
|
-
get userName() {
|
|
669
|
-
return this._url.username;
|
|
670
|
-
}
|
|
671
|
-
set userName(userName) {
|
|
672
|
-
this._url.username = userName;
|
|
673
|
-
}
|
|
674
|
-
get password() {
|
|
675
|
-
return this._url.password;
|
|
676
|
-
}
|
|
677
|
-
set password(password) {
|
|
678
|
-
this._url.password = password;
|
|
679
|
-
}
|
|
680
|
-
get hostId() {
|
|
681
|
-
const u = this._url.username;
|
|
682
|
-
if (u.length === 0) return this._url.host;
|
|
683
|
-
else if (this._url.password.length > 0) return `${u}:${this._url.password}@${this._url.host}`;
|
|
684
|
-
else return `${u}@${this._url.host}`;
|
|
685
|
-
}
|
|
686
|
-
set pathWithParams(path) {
|
|
687
|
-
const [p, s] = path.split("?", 2);
|
|
688
|
-
this._url.pathname = "/" + p;
|
|
689
|
-
this._url.search = s ?? "";
|
|
690
|
-
}
|
|
691
|
-
get pathWithParams() {
|
|
692
|
-
return this.path + this._url.search;
|
|
693
|
-
}
|
|
694
|
-
set searchParams(params) {
|
|
695
|
-
this._url.search = params;
|
|
696
|
-
}
|
|
697
|
-
get searchParams() {
|
|
698
|
-
return this._url.search;
|
|
699
|
-
}
|
|
700
|
-
set anchor(anchor) {
|
|
701
|
-
this._url.hash = anchor;
|
|
702
|
-
}
|
|
703
|
-
get anchor() {
|
|
704
|
-
return this._url.hash;
|
|
705
|
-
}
|
|
706
|
-
set path(path) {
|
|
707
|
-
this._url.pathname = "/" + path;
|
|
708
|
-
}
|
|
709
|
-
get path() {
|
|
710
|
-
return this._url.pathname.replace(/^\/+/, "/");
|
|
711
|
-
}
|
|
712
|
-
getParams() {
|
|
713
|
-
const params = {};
|
|
714
|
-
for (const [key, value] of this._url.searchParams.entries()) params[key] = value;
|
|
715
|
-
return params;
|
|
716
|
-
}
|
|
717
|
-
toString() {
|
|
718
|
-
return this._url.toString();
|
|
719
|
-
}
|
|
720
|
-
static isValid(urlString) {
|
|
721
|
-
try {
|
|
722
|
-
return new XRootDUrl(urlString).isValid;
|
|
723
|
-
} catch {
|
|
724
|
-
return false;
|
|
725
|
-
}
|
|
726
|
-
}
|
|
727
|
-
};
|
|
728
|
-
//#endregion
|
|
729
|
-
//#region lib/env.ts
|
|
730
|
-
function checkIntRange(key, value) {
|
|
731
|
-
const num = Number(value);
|
|
732
|
-
if (num < -2147483648 || num > 2147483647) console.warn(`[xrootd] Warning: value ${value} for key "${key}" is out of 32-bit signed integer range [-2147483648, 2147483647]. It will be truncated.`);
|
|
733
|
-
}
|
|
734
|
-
var XRootDEnvironment = class {
|
|
735
|
-
constructor() {}
|
|
736
|
-
/**
|
|
737
|
-
* 设置整数配置项,返回是否设置成功。
|
|
738
|
-
*/
|
|
739
|
-
putInt(key, intVal) {
|
|
740
|
-
checkIntRange(key, intVal);
|
|
741
|
-
const success = nativeAddon.Env.PutInt(key, Number(intVal));
|
|
742
|
-
if (!success) console.warn(`[xrootd] Warning: Failed to set integer configuration "${key}"=${intVal}. It might have been overridden by a system environment variable.`);
|
|
743
|
-
return success;
|
|
744
|
-
}
|
|
745
|
-
putString(key, strVal) {
|
|
746
|
-
if (key === "SecProtocol") {
|
|
747
|
-
process.env.XrdSecPROTOCOL = strVal;
|
|
748
|
-
process.env.XRD_SECPROTOCOL = strVal;
|
|
749
|
-
return true;
|
|
750
|
-
}
|
|
751
|
-
const success = nativeAddon.Env.PutString(key, strVal);
|
|
752
|
-
if (!success) console.warn(`[xrootd] Warning: Failed to set string configuration "${key}"="${strVal}". It might have been overridden by a system environment variable.`);
|
|
753
|
-
return success;
|
|
754
|
-
}
|
|
755
|
-
/**
|
|
756
|
-
* 设置布尔配置项,返回是否设置成功。
|
|
757
|
-
*/
|
|
758
|
-
putBoolean(key, boolVal) {
|
|
759
|
-
if (typeof boolVal === "boolean") boolVal = boolVal ? "true" : "false";
|
|
760
|
-
return this.putString(key, boolVal);
|
|
761
|
-
}
|
|
762
|
-
/**
|
|
763
|
-
* 获取整数配置项。
|
|
764
|
-
*/
|
|
765
|
-
getInt(key) {
|
|
766
|
-
return nativeAddon.Env.GetInt(key) ?? void 0;
|
|
767
|
-
}
|
|
768
|
-
/**
|
|
769
|
-
* 获取字符串配置项。
|
|
770
|
-
*/
|
|
771
|
-
getString(key) {
|
|
772
|
-
if (key === "SecProtocol") return process.env.XrdSecPROTOCOL ?? process.env.XRD_SECPROTOCOL ?? void 0;
|
|
773
|
-
return nativeAddon.Env.GetString(key) ?? void 0;
|
|
774
|
-
}
|
|
775
|
-
/**
|
|
776
|
-
* 批量安全地设置 XRootD 底层参数
|
|
777
|
-
*/
|
|
778
|
-
configure(config) {
|
|
779
|
-
for (const [key, value] of Object.entries(config)) {
|
|
780
|
-
if (value === void 0 || value === null) continue;
|
|
781
|
-
let success = true;
|
|
782
|
-
if (key === "SecProtocol") {
|
|
783
|
-
process.env.XrdSecPROTOCOL = String(value);
|
|
784
|
-
process.env.XRD_SECPROTOCOL = String(value);
|
|
785
|
-
continue;
|
|
786
|
-
}
|
|
787
|
-
if (typeof value === "number" || typeof value === "bigint") {
|
|
788
|
-
checkIntRange(key, value);
|
|
789
|
-
success = nativeAddon.Env.PutInt(key, Number(value));
|
|
790
|
-
} else if (typeof value === "string") success = nativeAddon.Env.PutString(key, value);
|
|
791
|
-
else if (typeof value === "boolean") success = nativeAddon.Env.PutString(key, value ? "true" : "false");
|
|
792
|
-
else {
|
|
793
|
-
console.warn(`[xrootd] Unhandled type ${typeof value} for key ${key}.`);
|
|
794
|
-
success = nativeAddon.Env.PutString(key, value.toString());
|
|
795
|
-
}
|
|
796
|
-
if (!success) console.warn(`[xrootd] Warning: Failed to set configuration "${key}"=${value}. It might have been overridden by a system environment variable.`);
|
|
797
|
-
}
|
|
798
|
-
}
|
|
799
|
-
get(key) {
|
|
800
|
-
if (key === "SecProtocol") return process.env.XrdSecPROTOCOL ?? process.env.XRD_SECPROTOCOL ?? void 0;
|
|
801
|
-
return nativeAddon.Env.GetInt(key) ?? nativeAddon.Env.GetString(key) ?? void 0;
|
|
802
|
-
}
|
|
803
|
-
};
|
|
804
|
-
const Env = new XRootDEnvironment();
|
|
805
|
-
Env.configure({
|
|
806
|
-
RequestTimeout: 30,
|
|
807
|
-
WorkerThreads: 4
|
|
808
|
-
});
|
|
809
|
-
//#endregion
|
|
810
|
-
exports.AccessMode = AccessMode;
|
|
811
|
-
exports.CopyProcess = CopyProcess;
|
|
812
|
-
exports.Env = Env;
|
|
813
|
-
exports.File = File;
|
|
814
|
-
exports.FileSystem = FileSystem;
|
|
815
|
-
exports.MkDirFlags = MkDirFlags;
|
|
816
|
-
exports.OpenFlags = OpenFlags;
|
|
817
|
-
exports.URL = XRootDUrl;
|