@yvhitxcel/opencode-remote 0.15.0

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.
Files changed (102) hide show
  1. package/README.md +82 -0
  2. package/bin/opencode-remote.js +70 -0
  3. package/bin/opencode-weixin.js +10 -0
  4. package/dist/AGENTS.md +20 -0
  5. package/dist/MEMORY.md +21 -0
  6. package/dist/bot-runner.js +180 -0
  7. package/dist/cli.js +256 -0
  8. package/dist/core/approval.js +95 -0
  9. package/dist/core/auth.js +119 -0
  10. package/dist/core/config.js +61 -0
  11. package/dist/core/notifications.js +134 -0
  12. package/dist/core/qiniu.js +267 -0
  13. package/dist/core/registry.js +86 -0
  14. package/dist/core/router.js +344 -0
  15. package/dist/core/session.js +403 -0
  16. package/dist/core/setup.js +418 -0
  17. package/dist/core/types.js +16 -0
  18. package/dist/feishu/adapter.js +72 -0
  19. package/dist/feishu/bot.js +168 -0
  20. package/dist/feishu/commands.js +601 -0
  21. package/dist/feishu/handler.js +380 -0
  22. package/dist/index.js +60 -0
  23. package/dist/opencode/client.js +823 -0
  24. package/dist/package-lock.json +762 -0
  25. package/dist/patch_spawn.js +28 -0
  26. package/dist/plugins/agents/acp/acp-adapter.js +42 -0
  27. package/dist/plugins/agents/claude-code/index.js +69 -0
  28. package/dist/plugins/agents/codex/index.js +44 -0
  29. package/dist/plugins/agents/copilot/index.js +44 -0
  30. package/dist/plugins/agents/opencode/index.js +66 -0
  31. package/dist/telegram/bot.js +288 -0
  32. package/dist/utils/message-split.js +38 -0
  33. package/dist/web/code-viewer.js +266 -0
  34. package/dist/weixin/adapter.js +135 -0
  35. package/dist/weixin/api.js +179 -0
  36. package/dist/weixin/bot.js +183 -0
  37. package/dist/weixin/commands.js +758 -0
  38. package/dist/weixin/handler.js +577 -0
  39. package/dist/weixin/node_modules/encodeurl/LICENSE +22 -0
  40. package/dist/weixin/node_modules/encodeurl/README.md +109 -0
  41. package/dist/weixin/node_modules/encodeurl/index.js +60 -0
  42. package/dist/weixin/node_modules/encodeurl/package.json +40 -0
  43. package/dist/weixin/node_modules/qiniu/.claude/settings.local.json +7 -0
  44. package/dist/weixin/node_modules/qiniu/.github/workflows/ci-test.yml +36 -0
  45. package/dist/weixin/node_modules/qiniu/.github/workflows/npm-publish.yml +20 -0
  46. package/dist/weixin/node_modules/qiniu/.github/workflows/version-check.yml +19 -0
  47. package/dist/weixin/node_modules/qiniu/.idea/MarsCodeWorkspaceAppSettings.xml +7 -0
  48. package/dist/weixin/node_modules/qiniu/.idea/codeStyles/Project.xml +44 -0
  49. package/dist/weixin/node_modules/qiniu/.idea/codeStyles/codeStyleConfig.xml +5 -0
  50. package/dist/weixin/node_modules/qiniu/.idea/git_toolbox_blame.xml +6 -0
  51. package/dist/weixin/node_modules/qiniu/.idea/inspectionProfiles/Project_Default.xml +6 -0
  52. package/dist/weixin/node_modules/qiniu/.idea/jsLibraryMappings.xml +6 -0
  53. package/dist/weixin/node_modules/qiniu/.idea/modules.xml +8 -0
  54. package/dist/weixin/node_modules/qiniu/.idea/nodejs-sdk.iml +12 -0
  55. package/dist/weixin/node_modules/qiniu/.idea/vcs.xml +6 -0
  56. package/dist/weixin/node_modules/qiniu/CHANGELOG.md +292 -0
  57. package/dist/weixin/node_modules/qiniu/README.md +56 -0
  58. package/dist/weixin/node_modules/qiniu/StorageResponseInterface.d.ts +239 -0
  59. package/dist/weixin/node_modules/qiniu/codecov.yml +28 -0
  60. package/dist/weixin/node_modules/qiniu/index.d.ts +1995 -0
  61. package/dist/weixin/node_modules/qiniu/index.js +32 -0
  62. package/dist/weixin/node_modules/qiniu/node_modules/encodeurl/HISTORY.md +14 -0
  63. package/dist/weixin/node_modules/qiniu/node_modules/encodeurl/LICENSE +22 -0
  64. package/dist/weixin/node_modules/qiniu/node_modules/encodeurl/README.md +128 -0
  65. package/dist/weixin/node_modules/qiniu/node_modules/encodeurl/index.js +60 -0
  66. package/dist/weixin/node_modules/qiniu/node_modules/encodeurl/package.json +40 -0
  67. package/dist/weixin/node_modules/qiniu/package.json +80 -0
  68. package/dist/weixin/node_modules/qiniu/qiniu/auth/digest.js +13 -0
  69. package/dist/weixin/node_modules/qiniu/qiniu/cdn.js +149 -0
  70. package/dist/weixin/node_modules/qiniu/qiniu/conf.js +254 -0
  71. package/dist/weixin/node_modules/qiniu/qiniu/fop.js +112 -0
  72. package/dist/weixin/node_modules/qiniu/qiniu/httpc/client.js +253 -0
  73. package/dist/weixin/node_modules/qiniu/qiniu/httpc/endpoint.js +66 -0
  74. package/dist/weixin/node_modules/qiniu/qiniu/httpc/endpointsProvider.js +27 -0
  75. package/dist/weixin/node_modules/qiniu/qiniu/httpc/endpointsRetryPolicy.js +76 -0
  76. package/dist/weixin/node_modules/qiniu/qiniu/httpc/middleware/base.js +31 -0
  77. package/dist/weixin/node_modules/qiniu/qiniu/httpc/middleware/index.js +9 -0
  78. package/dist/weixin/node_modules/qiniu/qiniu/httpc/middleware/qiniuAuth.js +53 -0
  79. package/dist/weixin/node_modules/qiniu/qiniu/httpc/middleware/retryDomains.js +101 -0
  80. package/dist/weixin/node_modules/qiniu/qiniu/httpc/middleware/ua.js +36 -0
  81. package/dist/weixin/node_modules/qiniu/qiniu/httpc/region.js +349 -0
  82. package/dist/weixin/node_modules/qiniu/qiniu/httpc/regionsProvider.js +788 -0
  83. package/dist/weixin/node_modules/qiniu/qiniu/httpc/regionsRetryPolicy.js +242 -0
  84. package/dist/weixin/node_modules/qiniu/qiniu/httpc/responseWrapper.js +40 -0
  85. package/dist/weixin/node_modules/qiniu/qiniu/retry/index.js +4 -0
  86. package/dist/weixin/node_modules/qiniu/qiniu/retry/retrier.js +99 -0
  87. package/dist/weixin/node_modules/qiniu/qiniu/retry/retryPolicy.js +55 -0
  88. package/dist/weixin/node_modules/qiniu/qiniu/rpc.js +237 -0
  89. package/dist/weixin/node_modules/qiniu/qiniu/rtc/app.js +123 -0
  90. package/dist/weixin/node_modules/qiniu/qiniu/rtc/credentials.js +57 -0
  91. package/dist/weixin/node_modules/qiniu/qiniu/rtc/room.js +118 -0
  92. package/dist/weixin/node_modules/qiniu/qiniu/rtc/util.js +16 -0
  93. package/dist/weixin/node_modules/qiniu/qiniu/sms/message.js +58 -0
  94. package/dist/weixin/node_modules/qiniu/qiniu/storage/form.js +442 -0
  95. package/dist/weixin/node_modules/qiniu/qiniu/storage/internal.js +214 -0
  96. package/dist/weixin/node_modules/qiniu/qiniu/storage/resume.js +1272 -0
  97. package/dist/weixin/node_modules/qiniu/qiniu/storage/rs.js +1764 -0
  98. package/dist/weixin/node_modules/qiniu/qiniu/util.js +382 -0
  99. package/dist/weixin/node_modules/qiniu/qiniu/zone.js +230 -0
  100. package/dist/weixin/node_modules/qiniu/tsconfig.json +112 -0
  101. package/dist/weixin/types.js +25 -0
  102. package/package.json +56 -0
@@ -0,0 +1,1272 @@
1
+ const os = require('os');
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+ const crypto = require('crypto');
5
+
6
+ const mime = require('mime');
7
+ const getCrc32 = require('crc32');
8
+ const destroy = require('destroy');
9
+ const BlockStream = require('block-stream2');
10
+ // use the native option `recursive` when min version of Node.js update to ≥ v10.12.0
11
+ const mkdirp = require('mkdirp');
12
+
13
+ const conf = require('../conf');
14
+ const util = require('../util');
15
+ const rpc = require('../rpc');
16
+ const libUtil = require('util');
17
+
18
+ const { SERVICE_NAME } = require('../httpc/region');
19
+ const { ResponseWrapper } = require('../httpc/responseWrapper');
20
+ const { Endpoint } = require('../httpc/endpoint');
21
+ const { StaticRegionsProvider } = require('../httpc/regionsProvider');
22
+ const { EndpointsRetryPolicy } = require('../httpc/endpointsRetryPolicy');
23
+ const { RegionsRetryPolicy } = require('../httpc/regionsRetryPolicy');
24
+ const { Retrier } = require('../retry');
25
+
26
+ const {
27
+ AccUnavailableRetryPolicy,
28
+ TokenExpiredRetryPolicy,
29
+ getNoNeedRetryError,
30
+ handleReqCallback
31
+ } = require('./internal');
32
+
33
+ exports.ResumeUploader = ResumeUploader;
34
+ exports.PutExtra = libUtil.deprecate(
35
+ PutExtra,
36
+ 'PutExtra constructor is deprecated. Use PutExtra.create() for v2 uploads or explicitly specify version parameter.'
37
+ );
38
+ exports.createResumeRecorder = createResumeRecorder;
39
+ exports.createResumeRecorderSync = createResumeRecorderSync;
40
+
41
+ /**
42
+ * @param {conf.Config} [config]
43
+ * @constructor
44
+ */
45
+ function ResumeUploader (config) {
46
+ this.config = config || new conf.Config();
47
+ }
48
+
49
+ /**
50
+ * @callback reqCallback
51
+ *
52
+ * @param {Error} err
53
+ * @param {Object} ret
54
+ * @param {http.IncomingMessage} info
55
+ */
56
+
57
+ /**
58
+ * @callback progressCallback
59
+ *
60
+ * @param {number} uploadBytes
61
+ * @param {number} totalBytes
62
+ */
63
+
64
+ /**
65
+ * 上传可选参数,分片上传默认使用 v1 版本
66
+ * @class
67
+ * @constructor
68
+ * @param {string} [fname] 请求体中的文件的名称
69
+ * @param {Object} [params] 额外参数设置,参数名称必须以x:开头
70
+ * @param {string | null} [mimeType] 指定文件的mimeType
71
+ * @param {string | null} [resumeRecordFile] DEPRECATED: 使用 `resumeRecorder` 与 `resumeKey` 代替;断点续传的已上传的部分信息记录文件路径
72
+ * @param {function(number, number):void} [progressCallback] 上传进度回调,回调参数为 (uploadBytes, totalBytes)
73
+ * @param {number} [partSize] 分片上传v2必传字段 默认大小为4MB 分片大小范围为1 MB - 1 GB
74
+ * @param {'v1' | 'v2'} [version] 分片上传版本 目前支持v1/v2版本 默认v1
75
+ * @param {Object} [metadata] 元数据设置,参数名称必须以 x-qn-meta-${name}: 开头
76
+ * @param {JsonFileRecorder} [resumeRecorder] 通过 `createResumeRecorder` 或 `createResumeRecorderSync` 获取,优先级比 `resumeRecordFile` 低
77
+ * @param {string} [resumeKey] 断点续传记录文件的具体文件名,不设置时会由当次上传自动生成
78
+ */
79
+ function PutExtra (
80
+ fname,
81
+ params,
82
+ mimeType,
83
+ resumeRecordFile,
84
+ progressCallback,
85
+ partSize,
86
+ version,
87
+ metadata,
88
+ resumeRecorder,
89
+ resumeKey
90
+ ) {
91
+ this.fname = fname || '';
92
+ this.params = params || {};
93
+ this.mimeType = mimeType || null;
94
+ // @deprecated use resumeRecorder and resumeKey instead
95
+ this.resumeRecordFile = resumeRecordFile || null;
96
+ this.progressCallback = progressCallback || null;
97
+ this.partSize = partSize || conf.BLOCK_SIZE;
98
+ this.version = version || 'v1';
99
+ this.metadata = metadata || {};
100
+ this.resumeRecorder = resumeRecorder || null;
101
+ this.resumeKey = resumeKey || null;
102
+ }
103
+
104
+ /**
105
+ * 上传可选参数,分片上传默认使用 v2 版本
106
+ * @class
107
+ * @constructor
108
+ * @param {string} [fname] 请求体中的文件的名称
109
+ * @param {Object} [params] 额外参数设置,参数名称必须以x:开头
110
+ * @param {string | null} [mimeType] 指定文件的mimeType
111
+ * @param {string | null} [resumeRecordFile] DEPRECATED: 使用 `resumeRecorder` 与 `resumeKey` 代替;断点续传的已上传的部分信息记录文件路径
112
+ * @param {function(number, number):void} [progressCallback] 上传进度回调,回调参数为 (uploadBytes, totalBytes)
113
+ * @param {number} [partSize] 分片上传v2必传字段 默认大小为4MB 分片大小范围为1 MB - 1 GB
114
+ * @param {'v1' | 'v2'} [version] 分片上传版本 目前支持v1/v2版本 默认v2
115
+ * @param {Object} [metadata] 元数据设置,参数名称必须以 x-qn-meta-${name}: 开头
116
+ * @param {JsonFileRecorder} [resumeRecorder] 通过 `createResumeRecorder` 或 `createResumeRecorderSync` 获取,优先级比 `resumeRecordFile` 低
117
+ * @param {string} [resumeKey] 断点续传记录文件的具体文件名,不设置时会由当次上传自动生成
118
+ */
119
+ PutExtra.create = function (
120
+ fname,
121
+ params,
122
+ mimeType,
123
+ resumeRecordFile,
124
+ progressCallback,
125
+ partSize,
126
+ version,
127
+ metadata,
128
+ resumeRecorder,
129
+ resumeKey) {
130
+ return new PutExtra(
131
+ fname, params, mimeType, resumeRecordFile,
132
+ progressCallback, partSize, version || 'v2',
133
+ metadata, resumeRecorder, resumeKey
134
+ );
135
+ };
136
+
137
+ /**
138
+ * @private
139
+ * @param {Object} options
140
+ * @param {string} options.accessKey
141
+ * @param {string} options.bucketName
142
+ * @param {string} [options.key]
143
+ * @param {string} [options.filePath]
144
+ * @param {PutExtra} options.putExtra
145
+ *
146
+ * @returns Retrier
147
+ */
148
+ function _getRegionsRetrier (options) {
149
+ const {
150
+ accessKey,
151
+ bucketName,
152
+ key,
153
+ filePath,
154
+
155
+ putExtra
156
+ } = options;
157
+
158
+ const preferredScheme = this.config.useHttpsDomain ? 'https' : 'http';
159
+
160
+ let regionsProviderPromise = this.config.getRegionsProvider({
161
+ accessKey,
162
+ bucketName
163
+ });
164
+
165
+ // generate resume key, if there is a recorder but not resume key
166
+ if (putExtra.resumeRecorder && !putExtra.resumeKey) {
167
+ regionsProviderPromise = regionsProviderPromise
168
+ .then(regionsProvider => regionsProvider.getRegions())
169
+ .then(regions => {
170
+ if (!regions || !regions.length) {
171
+ return Promise.reject(new Error(`no region available for the bucket "${bucketName}"`));
172
+ }
173
+ const upAccEndpoints = regions[0].services[SERVICE_NAME.UP_ACC] || [];
174
+ const upEndpoints = regions[0].services[SERVICE_NAME.UP] || [];
175
+ const upHosts = upAccEndpoints.concat(upEndpoints).map(e => e.host);
176
+ putExtra.resumeKey = putExtra.resumeRecorder.generateKeySync({
177
+ hosts: upHosts,
178
+ accessKey: accessKey,
179
+ bucketName: bucketName,
180
+ key: key,
181
+ filePath: filePath,
182
+ version: putExtra.version,
183
+ partSize: putExtra.partSize
184
+ });
185
+ return new StaticRegionsProvider(regions);
186
+ });
187
+ }
188
+
189
+ return regionsProviderPromise
190
+ .then(regionsProvider => {
191
+ // handle preferred endpoints
192
+ let preferredEndpoints;
193
+ if (putExtra.resumeRecorder && putExtra.resumeKey) {
194
+ const resumeInfo = putExtra.resumeRecorder.getSync(putExtra.resumeKey);
195
+ if (resumeInfo && Array.isArray(resumeInfo.upDomains)) {
196
+ preferredEndpoints = resumeInfo.upDomains.map(d =>
197
+ new Endpoint(d, { defaultScheme: preferredScheme }));
198
+ }
199
+ }
200
+
201
+ const serviceNames = this.config.accelerateUploading
202
+ ? [SERVICE_NAME.UP_ACC, SERVICE_NAME.UP]
203
+ : [SERVICE_NAME.UP];
204
+ const retryPolicies = [
205
+ new AccUnavailableRetryPolicy(),
206
+ new TokenExpiredRetryPolicy({
207
+ uploadApiVersion: putExtra.version,
208
+ recordExistsHandler: () => {
209
+ if (!putExtra.resumeRecorder || !putExtra.resumeKey) {
210
+ return;
211
+ }
212
+ putExtra.resumeRecorder.hasSync(putExtra.resumeKey);
213
+ },
214
+ recordDeleteHandler: () => {
215
+ if (!putExtra.resumeRecorder || !putExtra.resumeKey) {
216
+ return;
217
+ }
218
+ putExtra.resumeRecorder.deleteSync(putExtra.resumeKey);
219
+ }
220
+ }),
221
+ new EndpointsRetryPolicy({
222
+ skipInitContext: true
223
+ }),
224
+ new RegionsRetryPolicy({
225
+ regionsProvider,
226
+ serviceNames,
227
+ onChangedRegion: () => {
228
+ if (!putExtra.resumeRecorder || !putExtra.resumeKey) {
229
+ return;
230
+ }
231
+ putExtra.resumeRecorder.deleteSync(putExtra.resumeKey);
232
+ },
233
+ preferredEndpoints
234
+ })
235
+ ];
236
+
237
+ return new Retrier({
238
+ retryPolicies,
239
+ onBeforeRetry: (context, policy) => {
240
+ if (context.error) {
241
+ if (context.error.noNeedRetry) {
242
+ return false;
243
+ }
244
+ return true;
245
+ }
246
+ if (policy instanceof AccUnavailableRetryPolicy) {
247
+ return true;
248
+ }
249
+ return context.result && context.result.needRetry();
250
+ }
251
+ });
252
+ });
253
+ }
254
+
255
+ /**
256
+ * @typedef UploadResult
257
+ * @property {any} data
258
+ * @property {http.IncomingMessage} resp
259
+ */
260
+
261
+ /**
262
+ * @deprecated use putStreamV2 instead
263
+ * @param {string} uploadToken
264
+ * @param {string | null} key
265
+ * @param {stream.Readable} rsStream
266
+ * @param {number} rsStreamLen
267
+ * @param {PutExtra} putExtra
268
+ * @param {reqCallback} [callbackFunc]
269
+ * @returns {Promise<UploadResult>}
270
+ */
271
+ ResumeUploader.prototype.putStream = function (
272
+ uploadToken,
273
+ key,
274
+ rsStream,
275
+ rsStreamLen,
276
+ putExtra,
277
+ callbackFunc
278
+ ) {
279
+ // PutExtra
280
+ putExtra = getDefaultPutExtra(
281
+ putExtra,
282
+ {
283
+ key
284
+ }
285
+ );
286
+ return this.putStreamV2(
287
+ uploadToken,
288
+ key,
289
+ rsStream,
290
+ rsStreamLen,
291
+ putExtra,
292
+ callbackFunc
293
+ );
294
+ };
295
+
296
+ /**
297
+ * @param {string} uploadToken
298
+ * @param {string | null} key
299
+ * @param {stream.Readable} rsStream
300
+ * @param {number} rsStreamLen
301
+ * @param {PutExtra} putExtra
302
+ * @param {reqCallback} [callbackFunc]
303
+ * @returns {Promise<UploadResult>}
304
+ */
305
+ ResumeUploader.prototype.putStreamV2 = function (
306
+ uploadToken,
307
+ key,
308
+ rsStream,
309
+ rsStreamLen,
310
+ putExtra,
311
+ callbackFunc
312
+ ) {
313
+ const preferredScheme = this.config.useHttpsDomain ? 'https' : 'http';
314
+
315
+ // PutExtra
316
+ putExtra = getDefaultPutExtraV2(
317
+ putExtra,
318
+ {
319
+ key
320
+ }
321
+ );
322
+
323
+ const bucketName = util.getBucketFromUptoken(uploadToken);
324
+ const accessKey = util.getAKFromUptoken(uploadToken);
325
+
326
+ const result = this.config.getRegionsProvider({
327
+ bucketName,
328
+ accessKey
329
+ })
330
+ .then(regionsProvider => regionsProvider.getRegions())
331
+ .then(regions => {
332
+ if (!regions || !regions.length) {
333
+ return Promise.reject(new Error('no region available for the bucket', bucketName));
334
+ }
335
+ const preferService = this.config.accelerateUploading
336
+ ? SERVICE_NAME.UP_ACC
337
+ : SERVICE_NAME.UP;
338
+ if (
339
+ !regions[0].services ||
340
+ !regions[0].services[preferService] ||
341
+ !regions[0].services[preferService].length
342
+ ) {
343
+ return Promise.reject(new Error('no endpoint available for the bucket', bucketName));
344
+ }
345
+ const endpoint = regions[0].services[preferService][0];
346
+ return putReq(
347
+ endpoint,
348
+ preferredScheme,
349
+ uploadToken,
350
+ key,
351
+ rsStream,
352
+ rsStreamLen,
353
+ putExtra
354
+ );
355
+ });
356
+
357
+ handleReqCallback(result, callbackFunc);
358
+
359
+ return result;
360
+ };
361
+
362
+ /**
363
+ * @param {Endpoint} upEndpoint
364
+ * @param {string} uploadToken
365
+ * @param {string} preferredScheme
366
+ * @param {string | null} key
367
+ * @param {Readable} rsStream
368
+ * @param {number} rsStreamLen
369
+ * @param {PutExtra} putExtra
370
+ */
371
+ function putReq (
372
+ upEndpoint,
373
+ preferredScheme,
374
+ uploadToken,
375
+ key,
376
+ rsStream,
377
+ rsStreamLen,
378
+ putExtra
379
+ ) {
380
+ // make block stream
381
+ const blkStream = rsStream.pipe(new BlockStream({
382
+ size: putExtra.partSize,
383
+ zeroPadding: false
384
+ }));
385
+
386
+ // get resume record info
387
+ const blkputRets = putExtra.resumeRecorder && putExtra.resumeRecorder.getSync(putExtra.resumeKey);
388
+ const totalBlockNum = Math.ceil(rsStreamLen / putExtra.partSize);
389
+
390
+ // select upload version
391
+ /**
392
+ * @type {function(SourceOptions, UploadOptions, reqCallback)}
393
+ */
394
+ let doPutReq;
395
+ if (putExtra.version === 'v1') {
396
+ doPutReq = putReqV1;
397
+ } else if (putExtra.version === 'v2') {
398
+ doPutReq = putReqV2;
399
+ } else {
400
+ throw new Error('part upload version number error');
401
+ }
402
+
403
+ // upload parts
404
+ return new Promise((resolve, reject) => {
405
+ doPutReq(
406
+ {
407
+ blkputRets,
408
+ rsStream,
409
+ rsStreamLen,
410
+ blkStream,
411
+ totalBlockNum
412
+ },
413
+ {
414
+ upEndpoint,
415
+ preferredScheme,
416
+ uploadToken,
417
+ key,
418
+ putExtra
419
+ },
420
+ function (err, ret, info) {
421
+ if (err) {
422
+ err.resp = info;
423
+ reject(err);
424
+ return;
425
+ }
426
+ if (info.statusCode === 200 && putExtra.resumeRecorder && putExtra.resumeKey) {
427
+ putExtra.resumeRecorder.deleteSync(putExtra.resumeKey);
428
+ }
429
+ resolve(new ResponseWrapper({
430
+ data: ret,
431
+ resp: info
432
+ }));
433
+ }
434
+ );
435
+ });
436
+ }
437
+
438
+ /**
439
+ * @typedef SourceOptions
440
+ * @property {Object.<string, any> | undefined} blkputRets
441
+ * @property {Readable} rsStream
442
+ * @property {BlockStream} blkStream
443
+ * @property {number} rsStreamLen
444
+ * @property {number} totalBlockNum
445
+ */
446
+
447
+ /**
448
+ * @typedef UploadOptions
449
+ * @property {string | null} key
450
+ * @property {Endpoint} upEndpoint
451
+ * @property {string} preferredScheme
452
+ * @property {string} uploadToken
453
+ * @property {PutExtra} putExtra
454
+ */
455
+
456
+ /**
457
+ * @param {SourceOptions} sourceOptions
458
+ * @param {UploadOptions} uploadOptions
459
+ * @param {reqCallback} callbackFunc
460
+ * @returns {void}
461
+ */
462
+ function putReqV1 (sourceOptions, uploadOptions, callbackFunc) {
463
+ const {
464
+ rsStream,
465
+ blkStream,
466
+ rsStreamLen,
467
+ totalBlockNum
468
+ } = sourceOptions;
469
+ let blkputRets = sourceOptions.blkputRets;
470
+ const {
471
+ upEndpoint,
472
+ preferredScheme,
473
+ key,
474
+ uploadToken,
475
+ putExtra
476
+ } = uploadOptions;
477
+
478
+ // initial state
479
+ const finishedCtxList = [];
480
+ const finishedBlkPutRets = {
481
+ upDomains: [],
482
+ parts: []
483
+ };
484
+ // backward compatibility with ≤ 7.9.0
485
+ if (Array.isArray(blkputRets)) {
486
+ blkputRets = {
487
+ upDomains: [],
488
+ parts: []
489
+ };
490
+ }
491
+ if (blkputRets && Array.isArray(blkputRets.upDomains)) {
492
+ finishedBlkPutRets.upDomains = blkputRets.upDomains;
493
+ }
494
+ finishedBlkPutRets.upDomains.push(upEndpoint.host);
495
+
496
+ // TODO: test what will happen when stream.on('error')
497
+ // upload parts
498
+ const upDomain = upEndpoint.getValue({ scheme: preferredScheme });
499
+ let readLen = 0;
500
+ let curBlock = 0;
501
+ let isSent = false;
502
+ blkStream.on('data', function (chunk) {
503
+ readLen += chunk.length;
504
+ let needUploadBlk = true;
505
+ // check uploaded parts
506
+ if (
507
+ blkputRets &&
508
+ blkputRets.parts &&
509
+ blkputRets.parts.length > 0 &&
510
+ blkputRets.parts[curBlock]
511
+ ) {
512
+ const blkputRet = blkputRets.parts[curBlock];
513
+ let expiredAt = blkputRet.expired_at;
514
+ // make sure the ctx at least has one day expiration
515
+ expiredAt += 3600 * 24;
516
+ if (!util.isTimestampExpired(expiredAt)) {
517
+ needUploadBlk = false;
518
+ finishedCtxList.push(blkputRet.ctx);
519
+ finishedBlkPutRets.parts.push(blkputRet);
520
+ }
521
+ }
522
+
523
+ curBlock += 1; // set current block
524
+ if (needUploadBlk) {
525
+ blkStream.pause();
526
+ mkblkReq(
527
+ upDomain,
528
+ uploadToken,
529
+ chunk,
530
+ function (
531
+ respErr,
532
+ respBody,
533
+ respInfo
534
+ ) {
535
+ const bodyCrc32 = parseInt('0x' + getCrc32(chunk));
536
+ if (respInfo.statusCode !== 200 || respBody.crc32 !== bodyCrc32) {
537
+ callbackFunc(respErr, respBody, respInfo);
538
+ destroy(rsStream);
539
+ } else {
540
+ const blkputRet = respBody;
541
+ finishedCtxList.push(blkputRet.ctx);
542
+ finishedBlkPutRets.parts.push(blkputRet);
543
+ if (putExtra.resumeRecorder && putExtra.resumeKey) {
544
+ putExtra.resumeRecorder.setSync(putExtra.resumeKey, finishedBlkPutRets);
545
+ }
546
+ if (putExtra.progressCallback) {
547
+ try {
548
+ putExtra.progressCallback(readLen, rsStreamLen);
549
+ } catch (err) {
550
+ callbackFunc(
551
+ getNoNeedRetryError(
552
+ err,
553
+ 'Some unexpect error occurred on calling progressCallback'
554
+ ),
555
+ respBody,
556
+ respInfo
557
+ );
558
+ return;
559
+ }
560
+ }
561
+ blkStream.resume();
562
+ if (finishedCtxList.length === totalBlockNum) {
563
+ mkfileReq(upDomain, uploadToken, rsStreamLen, finishedCtxList, key, putExtra, callbackFunc);
564
+ isSent = true;
565
+ }
566
+ }
567
+ });
568
+ }
569
+ });
570
+
571
+ blkStream.on('end', function () {
572
+ if (!isSent && finishedCtxList.length === totalBlockNum) {
573
+ mkfileReq(upDomain, uploadToken, rsStreamLen, finishedCtxList, key, putExtra, callbackFunc);
574
+ }
575
+ destroy(rsStream);
576
+ });
577
+ }
578
+
579
+ /**
580
+ * @param {SourceOptions} sourceOptions
581
+ * @param {UploadOptions} uploadOptions
582
+ * @param {reqCallback} callbackFunc
583
+ * @returns {void}
584
+ */
585
+ function putReqV2 (sourceOptions, uploadOptions, callbackFunc) {
586
+ const {
587
+ blkputRets,
588
+ blkStream,
589
+ totalBlockNum,
590
+ rsStreamLen,
591
+ rsStream
592
+ } = sourceOptions;
593
+ const {
594
+ upEndpoint,
595
+ preferredScheme,
596
+ uploadToken,
597
+ key,
598
+ putExtra
599
+ } = uploadOptions;
600
+
601
+ // try resume upload blocks
602
+ let finishedBlock = 0;
603
+ const finishedEtags = {
604
+ upDomains: [],
605
+ etags: [],
606
+ uploadId: '',
607
+ expiredAt: 0
608
+ };
609
+ if (blkputRets && Array.isArray(blkputRets.upDomains)) {
610
+ // check etag expired or not
611
+ const expiredAt = blkputRets.expiredAt;
612
+ const timeNow = Date.now() / 1000;
613
+ if (expiredAt > timeNow && blkputRets.uploadId) {
614
+ finishedEtags.upDomains = blkputRets.upDomains;
615
+ finishedEtags.etags = blkputRets.etags;
616
+ finishedEtags.uploadId = blkputRets.uploadId;
617
+ finishedEtags.expiredAt = blkputRets.expiredAt;
618
+ finishedBlock = finishedEtags.etags.length;
619
+ }
620
+ }
621
+ finishedEtags.upDomains.push(upEndpoint.host);
622
+
623
+ const upDomain = upEndpoint.getValue({ scheme: preferredScheme });
624
+ const bucket = util.getBucketFromUptoken(uploadToken);
625
+ const encodedObjectName = key ? util.urlsafeBase64Encode(key) : '~';
626
+ if (finishedEtags.uploadId) {
627
+ if (finishedBlock === totalBlockNum) {
628
+ completeParts(upDomain, bucket, encodedObjectName, uploadToken, finishedEtags,
629
+ putExtra, callbackFunc);
630
+ return;
631
+ }
632
+ // if it has resumeRecordFile
633
+ resumeUploadV2(uploadToken, bucket, encodedObjectName, upDomain, blkStream,
634
+ finishedEtags, finishedBlock, totalBlockNum, putExtra, rsStreamLen, rsStream, callbackFunc);
635
+ } else {
636
+ // init a new uploadId for next step
637
+ initReq(uploadToken, bucket, encodedObjectName, upDomain, blkStream,
638
+ finishedEtags, finishedBlock, totalBlockNum, putExtra, rsStreamLen, rsStream, callbackFunc);
639
+ }
640
+ }
641
+
642
+ /**
643
+ * @param {string} upDomain
644
+ * @param {string} uploadToken
645
+ * @param {Buffer | string} blkData
646
+ * @param {reqCallback} callbackFunc
647
+ */
648
+ function mkblkReq (upDomain, uploadToken, blkData, callbackFunc) {
649
+ const requestURI = upDomain + '/mkblk/' + blkData.length;
650
+ const auth = 'UpToken ' + uploadToken;
651
+ const headers = {
652
+ Authorization: auth,
653
+ 'Content-Type': 'application/octet-stream'
654
+ };
655
+ rpc.post(requestURI, blkData, headers, callbackFunc);
656
+ }
657
+
658
+ /**
659
+ * @param {string} upDomain
660
+ * @param {string} uploadToken
661
+ * @param {number} fileSize
662
+ * @param {string[]} ctxList
663
+ * @param {string | null} key
664
+ * @param putExtra
665
+ * @param callbackFunc
666
+ */
667
+ function mkfileReq (
668
+ upDomain,
669
+ uploadToken,
670
+ fileSize,
671
+ ctxList,
672
+ key,
673
+ putExtra,
674
+ callbackFunc
675
+ ) {
676
+ let requestURI = upDomain + '/mkfile/' + fileSize;
677
+ if (key) {
678
+ requestURI += '/key/' + util.urlsafeBase64Encode(key);
679
+ }
680
+ if (putExtra.mimeType) {
681
+ requestURI += '/mimeType/' + util.urlsafeBase64Encode(putExtra.mimeType);
682
+ }
683
+ if (putExtra.fname) {
684
+ requestURI += '/fname/' + util.urlsafeBase64Encode(putExtra.fname);
685
+ }
686
+ if (putExtra.params) {
687
+ // putExtra params
688
+ for (const k in putExtra.params) {
689
+ if (k.startsWith('x:') && putExtra.params[k]) {
690
+ requestURI += '/' + k + '/' + util.urlsafeBase64Encode(putExtra.params[
691
+ k].toString());
692
+ }
693
+ }
694
+ }
695
+
696
+ // putExtra metadata
697
+ if (putExtra.metadata) {
698
+ for (const metadataKey in putExtra.metadata) {
699
+ if (metadataKey.startsWith('x-qn-meta-') && putExtra.metadata[metadataKey]) {
700
+ requestURI +=
701
+ '/' + metadataKey + '/' +
702
+ util.urlsafeBase64Encode(putExtra.metadata[metadataKey].toString());
703
+ }
704
+ }
705
+ }
706
+
707
+ const auth = 'UpToken ' + uploadToken;
708
+ const headers = {
709
+ Authorization: auth,
710
+ 'Content-Type': 'application/octet-stream'
711
+ };
712
+ const postBody = ctxList.join(',');
713
+ rpc.post(requestURI, postBody, headers, callbackFunc);
714
+ }
715
+
716
+ /**
717
+ * @typedef FinishedEtags
718
+ * @property {{etag: string, partNumber: number}[]}etags
719
+ * @property {string} uploadId
720
+ * @property {number} expiredAt
721
+ */
722
+
723
+ /**
724
+ * @param {string} uploadToken
725
+ * @param {string} bucket
726
+ * @param {string} encodedObjectName
727
+ * @param {string} upDomain
728
+ * @param {BlockStream} blkStream
729
+ * @param {FinishedEtags} finishedEtags
730
+ * @param {number} finishedBlock
731
+ * @param {number} totalBlockNum
732
+ * @param {PutExtra} putExtra
733
+ * @param {number} rsStreamLen
734
+ * @param {stream.Readable} rsStream
735
+ * @param {reqCallback} callbackFunc
736
+ */
737
+ function initReq (
738
+ uploadToken,
739
+ bucket,
740
+ encodedObjectName,
741
+ upDomain,
742
+ blkStream,
743
+ finishedEtags,
744
+ finishedBlock,
745
+ totalBlockNum,
746
+ putExtra,
747
+ rsStreamLen,
748
+ rsStream,
749
+ callbackFunc
750
+ ) {
751
+ const requestUrl = upDomain + '/buckets/' + bucket + '/objects/' + encodedObjectName + '/uploads';
752
+ const headers = {
753
+ Authorization: 'UpToken ' + uploadToken,
754
+ 'Content-Type': 'application/json'
755
+ };
756
+ rpc.post(requestUrl, '', headers, function (err, ret, info) {
757
+ if (info.statusCode !== 200) {
758
+ callbackFunc(err, ret, info);
759
+ return;
760
+ }
761
+ finishedEtags.expiredAt = ret.expireAt;
762
+ finishedEtags.uploadId = ret.uploadId;
763
+ resumeUploadV2(uploadToken, bucket, encodedObjectName, upDomain, blkStream,
764
+ finishedEtags, finishedBlock, totalBlockNum, putExtra, rsStreamLen, rsStream, callbackFunc);
765
+ });
766
+ }
767
+
768
+ /**
769
+ * @param {string} uploadToken
770
+ * @param {string} bucket
771
+ * @param {string} encodedObjectName
772
+ * @param {string} upDomain
773
+ * @param {BlockStream} blkStream
774
+ * @param {FinishedEtags} finishedEtags
775
+ * @param {number} finishedBlock
776
+ * @param {number} totalBlockNum
777
+ * @param {PutExtra} putExtra
778
+ * @param {number} rsStreamLen
779
+ * @param {stream.Readable} rsStream
780
+ * @param {reqCallback} callbackFunc
781
+ */
782
+ function resumeUploadV2 (
783
+ uploadToken,
784
+ bucket,
785
+ encodedObjectName,
786
+ upDomain,
787
+ blkStream,
788
+ finishedEtags,
789
+ finishedBlock,
790
+ totalBlockNum,
791
+ putExtra,
792
+ rsStreamLen,
793
+ rsStream,
794
+ callbackFunc
795
+ ) {
796
+ let isSent = false;
797
+ let readLen = 0;
798
+ let curBlock = 0;
799
+ blkStream.on('data', function (chunk) {
800
+ let partNumber = 0;
801
+ readLen += chunk.length;
802
+ curBlock += 1; // set current block
803
+ if (curBlock > finishedBlock) {
804
+ blkStream.pause();
805
+ partNumber = finishedBlock + 1;
806
+ const bodyMd5 = util.getMd5(chunk);
807
+ uploadPart(bucket, upDomain, uploadToken, encodedObjectName, chunk, finishedEtags.uploadId, partNumber, putExtra,
808
+ function (respErr, respBody, respInfo) {
809
+ if (respInfo.statusCode !== 200 || respBody.md5 !== bodyMd5) {
810
+ callbackFunc(respErr, respBody, respInfo);
811
+ destroy(rsStream);
812
+ } else {
813
+ finishedBlock += 1;
814
+ const blockStatus = {
815
+ etag: respBody.etag,
816
+ partNumber: partNumber
817
+ };
818
+ finishedEtags.etags.push(blockStatus);
819
+ if (putExtra.resumeRecorder && putExtra.resumeKey) {
820
+ putExtra.resumeRecorder.setSync(putExtra.resumeKey, finishedEtags);
821
+ }
822
+ if (putExtra.progressCallback) {
823
+ try {
824
+ putExtra.progressCallback(readLen, rsStreamLen);
825
+ } catch (err) {
826
+ callbackFunc(
827
+ getNoNeedRetryError(
828
+ err,
829
+ 'Some unexpect error occurred on calling progressCallback'
830
+ ),
831
+ respBody,
832
+ respInfo
833
+ );
834
+ return;
835
+ }
836
+ }
837
+ blkStream.resume();
838
+ if (finishedEtags.etags.length === totalBlockNum) {
839
+ completeParts(upDomain, bucket, encodedObjectName, uploadToken, finishedEtags,
840
+ putExtra, callbackFunc);
841
+ isSent = true;
842
+ }
843
+ }
844
+ });
845
+ }
846
+ });
847
+
848
+ blkStream.on('end', function () {
849
+ if (!isSent && rsStreamLen === 0) {
850
+ completeParts(upDomain, bucket, encodedObjectName, uploadToken, finishedEtags,
851
+ putExtra, callbackFunc);
852
+ }
853
+ destroy(rsStream);
854
+ });
855
+ }
856
+
857
+ /**
858
+ * @param {string} bucket
859
+ * @param {string} upDomain
860
+ * @param {string} uploadToken
861
+ * @param {string} encodedObjectName
862
+ * @param {Buffer | string} chunk
863
+ * @param {string} uploadId
864
+ * @param {number} partNumber
865
+ * @param {PutExtra} putExtra
866
+ * @param {reqCallback} callbackFunc
867
+ */
868
+ function uploadPart (bucket, upDomain, uploadToken, encodedObjectName, chunk, uploadId, partNumber, putExtra, callbackFunc) {
869
+ const headers = {
870
+ Authorization: 'UpToken ' + uploadToken,
871
+ 'Content-Type': 'application/octet-stream',
872
+ 'Content-MD5': util.getMd5(chunk)
873
+ };
874
+ const requestUrl = upDomain + '/buckets/' + bucket + '/objects/' + encodedObjectName + '/uploads/' + uploadId +
875
+ '/' + partNumber.toString();
876
+ rpc.put(requestUrl, chunk, headers, callbackFunc);
877
+ }
878
+
879
+ /**
880
+ * @param {string} upDomain
881
+ * @param {string} bucket
882
+ * @param {string} encodedObjectName
883
+ * @param {string} uploadToken
884
+ * @param {FinishedEtags} finishedEtags
885
+ * @param {PutExtra} putExtra
886
+ * @param {reqCallback} callbackFunc
887
+ */
888
+ function completeParts (
889
+ upDomain,
890
+ bucket,
891
+ encodedObjectName,
892
+ uploadToken,
893
+ finishedEtags,
894
+ putExtra,
895
+ callbackFunc
896
+ ) {
897
+ const headers = {
898
+ Authorization: 'UpToken ' + uploadToken,
899
+ 'Content-Type': 'application/json'
900
+ };
901
+ const sortedParts = finishedEtags.etags.sort(function (a, b) {
902
+ return a.partNumber - b.partNumber;
903
+ });
904
+ const body = {
905
+ fname: putExtra.fname,
906
+ mimeType: putExtra.mimeType,
907
+ customVars: putExtra.params,
908
+ metadata: putExtra.metadata,
909
+ parts: sortedParts
910
+ };
911
+ const requestUrl = upDomain + '/buckets/' + bucket + '/objects/' + encodedObjectName + '/uploads/' + finishedEtags.uploadId;
912
+ const requestBody = JSON.stringify(body);
913
+ rpc.post(
914
+ requestUrl,
915
+ requestBody,
916
+ headers,
917
+ callbackFunc
918
+ );
919
+ }
920
+
921
+ /**
922
+ * @deprecated Use putFileV2 instead
923
+ * @param {string} uploadToken
924
+ * @param {string | null} key
925
+ * @param {string} localFile
926
+ * @param {PutExtra} putExtra
927
+ * @param {reqCallback} [callbackFunc]
928
+ * @returns {Promise<UploadResult>}
929
+ */
930
+ ResumeUploader.prototype.putFile = function (
931
+ uploadToken,
932
+ key,
933
+ localFile,
934
+ putExtra,
935
+ callbackFunc
936
+ ) {
937
+ // PutExtra
938
+ putExtra = putExtra || new PutExtra();
939
+ return this.putFileV2(uploadToken, key, localFile, putExtra, callbackFunc);
940
+ };
941
+
942
+ /**
943
+ * @param {string} uploadToken
944
+ * @param {string | null} key
945
+ * @param {string} localFile
946
+ * @param {PutExtra} putExtra
947
+ * @param {reqCallback} [callbackFunc]
948
+ * @returns {Promise<UploadResult>}
949
+ */
950
+ ResumeUploader.prototype.putFileV2 = function (
951
+ uploadToken,
952
+ key,
953
+ localFile,
954
+ putExtra,
955
+ callbackFunc
956
+ ) {
957
+ const preferredScheme = this.config.useHttpsDomain ? 'https' : 'http';
958
+
959
+ // PutExtra
960
+ putExtra = putExtra || PutExtra.create();
961
+ if (!putExtra.mimeType) {
962
+ putExtra.mimeType = mime.getType(localFile);
963
+ }
964
+
965
+ if (!putExtra.fname) {
966
+ putExtra.fname = path.basename(localFile);
967
+ }
968
+
969
+ const accessKey = util.getAKFromUptoken(uploadToken);
970
+ const bucketName = util.getBucketFromUptoken(uploadToken);
971
+
972
+ putExtra = getDefaultPutExtra(
973
+ putExtra,
974
+ {
975
+ key
976
+ }
977
+ );
978
+
979
+ const result = _getRegionsRetrier.call(this, {
980
+ accessKey,
981
+ bucketName,
982
+ key,
983
+ filePath: localFile,
984
+
985
+ putExtra
986
+ })
987
+ .then(retrier => Promise.all([
988
+ retrier,
989
+ retrier.initContext()
990
+ ]))
991
+ .then(([retrier, context]) => retrier.retry({
992
+ func: ctx => {
993
+ const rsStream = fs.createReadStream(localFile, {
994
+ highWaterMark: conf.BLOCK_SIZE
995
+ });
996
+ const rsStreamLen = fs.statSync(localFile).size;
997
+ const p = putReq(
998
+ ctx.endpoint,
999
+ preferredScheme,
1000
+ uploadToken,
1001
+ key,
1002
+ rsStream,
1003
+ rsStreamLen,
1004
+ putExtra
1005
+ );
1006
+ p
1007
+ .then(() => {
1008
+ destroy(rsStream);
1009
+ })
1010
+ .catch(() => {
1011
+ // use finally when min version of Node.js update to ≥ v10.3.0
1012
+ destroy(rsStream);
1013
+ });
1014
+ return p;
1015
+ },
1016
+ context
1017
+ }));
1018
+
1019
+ handleReqCallback(result, callbackFunc);
1020
+
1021
+ return result;
1022
+ };
1023
+
1024
+ /**
1025
+ * @deprecated use putFileWithoutKeyV2 instead
1026
+ * @param {string} uploadToken
1027
+ * @param {string} localFile
1028
+ * @param {PutExtra} putExtra
1029
+ * @param {reqCallback} [callbackFunc]
1030
+ * @returns {Promise<UploadResult>}
1031
+ */
1032
+ ResumeUploader.prototype.putFileWithoutKey = function (
1033
+ uploadToken,
1034
+ localFile,
1035
+ putExtra,
1036
+ callbackFunc
1037
+ ) {
1038
+ return this.putFile(uploadToken, null, localFile, putExtra, callbackFunc);
1039
+ };
1040
+
1041
+ /**
1042
+ * @param {string} uploadToken
1043
+ * @param {string} localFile
1044
+ * @param {PutExtra} putExtra
1045
+ * @param {reqCallback} [callbackFunc]
1046
+ * @returns {Promise<UploadResult>}
1047
+ */
1048
+ ResumeUploader.prototype.putFileWithoutKeyV2 = function (
1049
+ uploadToken,
1050
+ localFile,
1051
+ putExtra,
1052
+ callbackFunc
1053
+ ) {
1054
+ return this.putFileV2(uploadToken, null, localFile, putExtra, callbackFunc);
1055
+ };
1056
+
1057
+ /**
1058
+ * @param {PutExtra} putExtra
1059
+ * @param {Object} options
1060
+ * @param {string | null} [options.key]
1061
+ * @returns {PutExtra}
1062
+ */
1063
+ function getDefaultPutExtra (putExtra, options) {
1064
+ options = options || {};
1065
+
1066
+ // assign to a new object to make the modification later
1067
+ putExtra = Object.assign(new PutExtra(), putExtra);
1068
+ if (!putExtra.mimeType) {
1069
+ putExtra.mimeType = 'application/octet-stream';
1070
+ }
1071
+
1072
+ if (!putExtra.fname) {
1073
+ putExtra.fname = options.key || '?';
1074
+ }
1075
+
1076
+ if (!putExtra.version) {
1077
+ putExtra.version = 'v1';
1078
+ }
1079
+
1080
+ if (putExtra.resumeRecordFile) {
1081
+ const parsedPath = path.parse(path.resolve(putExtra.resumeRecordFile));
1082
+ putExtra.resumeRecorder = createResumeRecorderSync(parsedPath.dir);
1083
+ putExtra.resumeKey = parsedPath.base;
1084
+ }
1085
+
1086
+ return putExtra;
1087
+ }
1088
+
1089
+ /**
1090
+ * @param {PutExtra} putExtra
1091
+ * @param {Object} options
1092
+ * @param {string | null} [options.key]
1093
+ * @returns {PutExtra}
1094
+ */
1095
+ function getDefaultPutExtraV2 (putExtra, options) {
1096
+ putExtra = putExtra || PutExtra.create();
1097
+ return getDefaultPutExtra(putExtra, options);
1098
+ }
1099
+
1100
+ /**
1101
+ * @class
1102
+ * @param {string} baseDirPath
1103
+ * @constructor
1104
+ */
1105
+ function JsonFileRecorder (baseDirPath) {
1106
+ this.baseDirPath = baseDirPath;
1107
+ }
1108
+
1109
+ /**
1110
+ * @param {string} key
1111
+ * @param {Object.<string, any>} data
1112
+ */
1113
+ JsonFileRecorder.prototype.setSync = function (key, data) {
1114
+ const filePath = path.join(this.baseDirPath, key);
1115
+ const contents = JSON.stringify(data);
1116
+ fs.writeFileSync(
1117
+ filePath,
1118
+ contents,
1119
+ {
1120
+ encoding: 'utf-8',
1121
+ mode: 0o600
1122
+ }
1123
+ );
1124
+ };
1125
+
1126
+ /**
1127
+ * @param key
1128
+ * @returns {undefined | Object.<string, any>}
1129
+ */
1130
+ JsonFileRecorder.prototype.getSync = function (key) {
1131
+ let result;
1132
+ try {
1133
+ const filePath = path.join(this.baseDirPath, key);
1134
+ const recordContent = fs.readFileSync(
1135
+ filePath,
1136
+ {
1137
+ encoding: 'utf-8'
1138
+ }
1139
+ ).toString();
1140
+ result = JSON.parse(recordContent);
1141
+ } catch (_err) {
1142
+ // pass
1143
+ }
1144
+ return result;
1145
+ };
1146
+
1147
+ JsonFileRecorder.prototype.hasSync = function (key) {
1148
+ try {
1149
+ const filePath = path.join(this.baseDirPath, key);
1150
+ return fs.existsSync(filePath);
1151
+ } catch (_err) {
1152
+ return false;
1153
+ }
1154
+ };
1155
+
1156
+ JsonFileRecorder.prototype.deleteSync = function (key) {
1157
+ try {
1158
+ const filePath = path.join(this.baseDirPath, key);
1159
+ fs.unlinkSync(filePath);
1160
+ } catch (_err) {
1161
+ // pass
1162
+ }
1163
+ };
1164
+
1165
+ /**
1166
+ * @param {Object} options
1167
+ * @param {string[]} options.hosts
1168
+ * @param {string} options.accessKey
1169
+ * @param {string} options.bucketName
1170
+ * @param {string} options.key
1171
+ * @param {string} options.filePath
1172
+ * @param {string} options.version
1173
+ * @param {string} options.partSize
1174
+ * @returns {string | undefined}
1175
+ */
1176
+ JsonFileRecorder.prototype.generateKeySync = function (options) {
1177
+ // if some options not pass in, can't generate a valid key
1178
+ if (
1179
+ [
1180
+ Array.isArray(options.hosts),
1181
+ options.accessKey,
1182
+ options.bucketName,
1183
+ options.key,
1184
+ options.filePath,
1185
+ options.version,
1186
+ options.partSize
1187
+ ].some(v => !v)
1188
+ ) {
1189
+ return;
1190
+ }
1191
+
1192
+ let fileStats;
1193
+ try {
1194
+ fileStats = options.filePath && fs.statSync(options.filePath);
1195
+ } catch (_err) {
1196
+ return;
1197
+ }
1198
+
1199
+ const fields = [
1200
+ options.hosts.join(''),
1201
+ options.accessKey,
1202
+ options.bucketName,
1203
+ options.key || '',
1204
+ options.filePath,
1205
+ // use `stats.mtimeMs` when min version of Node.js update to ≥ v8.1.0
1206
+ fileStats ? fileStats.mtime.getTime().toString() : '',
1207
+ fileStats ? fileStats.size.toString() : '',
1208
+ options.version, // the upload version
1209
+ options.version === 'v1'
1210
+ ? conf.BLOCK_SIZE.toString()
1211
+ : options.partSize.toString(),
1212
+ 'json.v1' // the record file format version
1213
+ ];
1214
+ const h = crypto.createHash('sha1');
1215
+ fields.forEach(v => {
1216
+ h.update(v);
1217
+ });
1218
+ return `qn-resume-${h.digest('hex')}.json`;
1219
+ };
1220
+
1221
+ function createResumeRecorder (baseDirPath) {
1222
+ if (baseDirPath) {
1223
+ // make baseDirPath absolute
1224
+ baseDirPath = path.resolve(baseDirPath);
1225
+ } else {
1226
+ // set default baseDirPath to os temp
1227
+ baseDirPath = os.tmpdir();
1228
+ }
1229
+ // with mkdirp on Windows the root-level ENOENT errors can lead to infinite regress
1230
+ // remove the fs.access when instead mkdirp with the native option `recursive`
1231
+ return new Promise((resolve, reject) => {
1232
+ fs.access(
1233
+ path.parse(baseDirPath).root,
1234
+ fs.constants.R_OK | fs.constants.W_OK,
1235
+ err => {
1236
+ if (err) {
1237
+ reject(err);
1238
+ return;
1239
+ }
1240
+ resolve();
1241
+ }
1242
+ );
1243
+ })
1244
+ .then(() => new Promise((resolve, reject) => {
1245
+ mkdirp(baseDirPath, { mode: 0o700 }, err => {
1246
+ if (err) {
1247
+ reject(err);
1248
+ return;
1249
+ }
1250
+ resolve();
1251
+ });
1252
+ }))
1253
+ .then(() => new JsonFileRecorder(baseDirPath));
1254
+ }
1255
+
1256
+ function createResumeRecorderSync (baseDirPath) {
1257
+ if (baseDirPath) {
1258
+ // make baseDirPath absolute
1259
+ baseDirPath = path.resolve(baseDirPath);
1260
+ } else {
1261
+ // set default baseDirPath to os temp
1262
+ baseDirPath = os.tmpdir();
1263
+ }
1264
+ // with mkdirp on Windows the root-level ENOENT errors can lead to infinite regress
1265
+ // remove the fs.access when instead mkdirp with the native option `recursive`
1266
+ fs.accessSync(
1267
+ path.parse(baseDirPath).root,
1268
+ fs.constants.F_OK
1269
+ );
1270
+ mkdirp.sync(baseDirPath, { mode: 0o700 });
1271
+ return new JsonFileRecorder(baseDirPath);
1272
+ }