@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.
- package/README.md +82 -0
- package/bin/opencode-remote.js +70 -0
- package/bin/opencode-weixin.js +10 -0
- package/dist/AGENTS.md +20 -0
- package/dist/MEMORY.md +21 -0
- package/dist/bot-runner.js +180 -0
- package/dist/cli.js +256 -0
- package/dist/core/approval.js +95 -0
- package/dist/core/auth.js +119 -0
- package/dist/core/config.js +61 -0
- package/dist/core/notifications.js +134 -0
- package/dist/core/qiniu.js +267 -0
- package/dist/core/registry.js +86 -0
- package/dist/core/router.js +344 -0
- package/dist/core/session.js +403 -0
- package/dist/core/setup.js +418 -0
- package/dist/core/types.js +16 -0
- package/dist/feishu/adapter.js +72 -0
- package/dist/feishu/bot.js +168 -0
- package/dist/feishu/commands.js +601 -0
- package/dist/feishu/handler.js +380 -0
- package/dist/index.js +60 -0
- package/dist/opencode/client.js +823 -0
- package/dist/package-lock.json +762 -0
- package/dist/patch_spawn.js +28 -0
- package/dist/plugins/agents/acp/acp-adapter.js +42 -0
- package/dist/plugins/agents/claude-code/index.js +69 -0
- package/dist/plugins/agents/codex/index.js +44 -0
- package/dist/plugins/agents/copilot/index.js +44 -0
- package/dist/plugins/agents/opencode/index.js +66 -0
- package/dist/telegram/bot.js +288 -0
- package/dist/utils/message-split.js +38 -0
- package/dist/web/code-viewer.js +266 -0
- package/dist/weixin/adapter.js +135 -0
- package/dist/weixin/api.js +179 -0
- package/dist/weixin/bot.js +183 -0
- package/dist/weixin/commands.js +758 -0
- package/dist/weixin/handler.js +577 -0
- package/dist/weixin/node_modules/encodeurl/LICENSE +22 -0
- package/dist/weixin/node_modules/encodeurl/README.md +109 -0
- package/dist/weixin/node_modules/encodeurl/index.js +60 -0
- package/dist/weixin/node_modules/encodeurl/package.json +40 -0
- package/dist/weixin/node_modules/qiniu/.claude/settings.local.json +7 -0
- package/dist/weixin/node_modules/qiniu/.github/workflows/ci-test.yml +36 -0
- package/dist/weixin/node_modules/qiniu/.github/workflows/npm-publish.yml +20 -0
- package/dist/weixin/node_modules/qiniu/.github/workflows/version-check.yml +19 -0
- package/dist/weixin/node_modules/qiniu/.idea/MarsCodeWorkspaceAppSettings.xml +7 -0
- package/dist/weixin/node_modules/qiniu/.idea/codeStyles/Project.xml +44 -0
- package/dist/weixin/node_modules/qiniu/.idea/codeStyles/codeStyleConfig.xml +5 -0
- package/dist/weixin/node_modules/qiniu/.idea/git_toolbox_blame.xml +6 -0
- package/dist/weixin/node_modules/qiniu/.idea/inspectionProfiles/Project_Default.xml +6 -0
- package/dist/weixin/node_modules/qiniu/.idea/jsLibraryMappings.xml +6 -0
- package/dist/weixin/node_modules/qiniu/.idea/modules.xml +8 -0
- package/dist/weixin/node_modules/qiniu/.idea/nodejs-sdk.iml +12 -0
- package/dist/weixin/node_modules/qiniu/.idea/vcs.xml +6 -0
- package/dist/weixin/node_modules/qiniu/CHANGELOG.md +292 -0
- package/dist/weixin/node_modules/qiniu/README.md +56 -0
- package/dist/weixin/node_modules/qiniu/StorageResponseInterface.d.ts +239 -0
- package/dist/weixin/node_modules/qiniu/codecov.yml +28 -0
- package/dist/weixin/node_modules/qiniu/index.d.ts +1995 -0
- package/dist/weixin/node_modules/qiniu/index.js +32 -0
- package/dist/weixin/node_modules/qiniu/node_modules/encodeurl/HISTORY.md +14 -0
- package/dist/weixin/node_modules/qiniu/node_modules/encodeurl/LICENSE +22 -0
- package/dist/weixin/node_modules/qiniu/node_modules/encodeurl/README.md +128 -0
- package/dist/weixin/node_modules/qiniu/node_modules/encodeurl/index.js +60 -0
- package/dist/weixin/node_modules/qiniu/node_modules/encodeurl/package.json +40 -0
- package/dist/weixin/node_modules/qiniu/package.json +80 -0
- package/dist/weixin/node_modules/qiniu/qiniu/auth/digest.js +13 -0
- package/dist/weixin/node_modules/qiniu/qiniu/cdn.js +149 -0
- package/dist/weixin/node_modules/qiniu/qiniu/conf.js +254 -0
- package/dist/weixin/node_modules/qiniu/qiniu/fop.js +112 -0
- package/dist/weixin/node_modules/qiniu/qiniu/httpc/client.js +253 -0
- package/dist/weixin/node_modules/qiniu/qiniu/httpc/endpoint.js +66 -0
- package/dist/weixin/node_modules/qiniu/qiniu/httpc/endpointsProvider.js +27 -0
- package/dist/weixin/node_modules/qiniu/qiniu/httpc/endpointsRetryPolicy.js +76 -0
- package/dist/weixin/node_modules/qiniu/qiniu/httpc/middleware/base.js +31 -0
- package/dist/weixin/node_modules/qiniu/qiniu/httpc/middleware/index.js +9 -0
- package/dist/weixin/node_modules/qiniu/qiniu/httpc/middleware/qiniuAuth.js +53 -0
- package/dist/weixin/node_modules/qiniu/qiniu/httpc/middleware/retryDomains.js +101 -0
- package/dist/weixin/node_modules/qiniu/qiniu/httpc/middleware/ua.js +36 -0
- package/dist/weixin/node_modules/qiniu/qiniu/httpc/region.js +349 -0
- package/dist/weixin/node_modules/qiniu/qiniu/httpc/regionsProvider.js +788 -0
- package/dist/weixin/node_modules/qiniu/qiniu/httpc/regionsRetryPolicy.js +242 -0
- package/dist/weixin/node_modules/qiniu/qiniu/httpc/responseWrapper.js +40 -0
- package/dist/weixin/node_modules/qiniu/qiniu/retry/index.js +4 -0
- package/dist/weixin/node_modules/qiniu/qiniu/retry/retrier.js +99 -0
- package/dist/weixin/node_modules/qiniu/qiniu/retry/retryPolicy.js +55 -0
- package/dist/weixin/node_modules/qiniu/qiniu/rpc.js +237 -0
- package/dist/weixin/node_modules/qiniu/qiniu/rtc/app.js +123 -0
- package/dist/weixin/node_modules/qiniu/qiniu/rtc/credentials.js +57 -0
- package/dist/weixin/node_modules/qiniu/qiniu/rtc/room.js +118 -0
- package/dist/weixin/node_modules/qiniu/qiniu/rtc/util.js +16 -0
- package/dist/weixin/node_modules/qiniu/qiniu/sms/message.js +58 -0
- package/dist/weixin/node_modules/qiniu/qiniu/storage/form.js +442 -0
- package/dist/weixin/node_modules/qiniu/qiniu/storage/internal.js +214 -0
- package/dist/weixin/node_modules/qiniu/qiniu/storage/resume.js +1272 -0
- package/dist/weixin/node_modules/qiniu/qiniu/storage/rs.js +1764 -0
- package/dist/weixin/node_modules/qiniu/qiniu/util.js +382 -0
- package/dist/weixin/node_modules/qiniu/qiniu/zone.js +230 -0
- package/dist/weixin/node_modules/qiniu/tsconfig.json +112 -0
- package/dist/weixin/types.js +25 -0
- 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
|
+
}
|