ks3-js-sdk
Version:
js sdk for ks3
598 lines (565 loc) • 21.5 kB
JavaScript
var util = require('./utils')
var EventProxy = require('./event').EventProxy;
var session = require('./session');
const Async = require("async");
// 获取上传任务的 UploadId 和 分片列表
export function getUploadIdAndPartList(params, callback) {
var taskId = params.taskId;
var bucket = params.bucket;
var region = params.region;
var key = params.key;
var storageClass = params.storageClass;
var self = this;
// 计算 ETag
var ETagMap = {};
var FileSize = params.fileSize;
var SliceSize = params.sliceSize; // 这里的 sliceSize 就是 chunkSize
var SliceCount = Math.ceil(FileSize / SliceSize);
var FinishSliceCount = 0; // 结束分片的数量
var FinishSize = 0; // 结束分片的总大小
var getChunkETag = function (PartNumber, callback) {
var start = SliceSize * (PartNumber - 1);
var end = Math.min(start + SliceSize, FileSize);
var ChunkSize = end - start;
if (ETagMap[PartNumber]) {
callback(null, {
PartNumber: PartNumber,
ETag: ETagMap[PartNumber],
Size: ChunkSize,
});
} else {
util.fileSliceForUpload(params.body, start, end, false, function (chunkItem) {
util.getFileMd5(chunkItem, function (err, md5) {
if (err) return callback(util.error(err));
var ETag = String(md5);
ETagMap[PartNumber] = ETag;
FinishSliceCount += 1;
FinishSize += ChunkSize;
callback(null, {
PartNumber: PartNumber,
ETag: ETag,
Size: ChunkSize,
});
});
});
}
};
// 通过和文件的 md5 对比,判断 UploadId 是否可用
var isAvailableUploadList = function (PartList, callback) {
var PartCount = PartList.length;
// 如果没有分片,通过
if (PartCount === 0) {
return callback(null, true);
}
// 检查分片数量
if (PartCount > SliceCount) {
return callback(null, false);
}
// 检查分片大小
if (PartCount > 1) {
var PartSliceSize = Math.max(PartList[0].Size, PartList[1].Size);
if (PartSliceSize !== SliceSize) {
return callback(null, false);
}
}
// 逐个分片计算并检查 ETag 是否一致
var next = function (index) {
if (index < PartCount) {
var Part = PartList[index];
getChunkETag(Part.PartNumber, function (err, chunk) {
if (chunk && chunk.ETag === Part.ETag && chunk.Size === Part.Size) {
next(index + 1);
} else {
callback(null, false);
}
});
} else {
callback(null, true);
}
};
next(0);
};
var ep = new EventProxy();
ep.on('error', function (errData) {
if (!self._isRunningTask(taskId)) return;
return callback(errData);
});
// 存在 UploadId 构造待上传 partList 列表
ep.on('upload_id_available', function (UploadData) {
// 转换成 map
var map = {};
var list = [];
util.each(UploadData.PartList, function (item) {
map[item.PartNumber] = item;
});
for (var PartNumber = 1; PartNumber <= SliceCount; PartNumber++) {
var item = map[PartNumber];
if (item) {
item.PartNumber = PartNumber;
item.Uploaded = true;
} else {
item = {
PartNumber: PartNumber,
ETag: null,
Uploaded: false,
};
}
list.push(item);
}
UploadData.PartList = list;
callback(null, UploadData);
});
// 不存在 UploadId, 初始化生成 UploadId
ep.on('no_available_upload_id', function () {
if (!self._isRunningTask(taskId)) return;
var _params = util.extend(
{
bucket: bucket,
region: region,
key: key,
storageClass: storageClass,
body: params.body,
encryption: params.encryption,
acl: params.acl,
headers: params.headers
},
params
);
var headers = params.Headers;
_params.Headers = headers;
// 初始化分片上传
self.initiateMultipartUpload(_params).then(res => {
if (!self._isRunningTask(taskId)) return
if (res.error) return ep.emit('error', res.error)
if (!res.body) return ep.emit('error', new Error('no such upload id'))
var data = res.body.InitiateMultipartUploadResult;
var UploadId = data.UploadId;
if (!UploadId) {
return callback(util.error(new Error('no such upload id')));
}
ep.emit('upload_id_available', {UploadId: UploadId, PartList: []});
})
});
// 如果已存在 UploadId,找一个可以用的 UploadId
ep.on('has_and_check_upload_id', function (UploadIdList) {
// 串行地,找一个内容一致的 UploadId
UploadIdList = UploadIdList.reverse();
Async.eachLimit(
UploadIdList,
1,
function (UploadId, asyncCallback) {
if (!self._isRunningTask(taskId)) return;
// 如果正在上传,跳过
if (session.using[UploadId]) {
asyncCallback(); // 检查下一个 UploadId
return;
}
// 判断 UploadId 是否可用
wholeMultipartListPart.call(
self,
{
bucket: bucket,
region: region,
key: key,
UploadId: UploadId,
tracker: params.tracker,
},
function (err, PartListData) {
if (!self._isRunningTask(taskId)) return;
if (err) {
session.removeUsing(UploadId);
return ep.emit('error', err);
}
// 已上传的分片
var PartList = PartListData.PartList;
PartList.forEach(function (item) {
item.PartNumber *= 1;
item.Size *= 1;
item.ETag = item.ETag || '';
});
isAvailableUploadList(PartList, function (err, isAvailable) {
if (!self._isRunningTask(taskId)) return;
if (err) return ep.emit('error', err);
if (isAvailable) {
asyncCallback({
UploadId: UploadId,
PartList: PartList,
}); // 马上结束
} else {
asyncCallback(); // 检查下一个 UploadId
}
});
}
);
},
function (AvailableUploadData) {
if (!self._isRunningTask(taskId)) return;
if (AvailableUploadData && AvailableUploadData.UploadId) {
ep.emit('upload_id_available', AvailableUploadData);
} else {
ep.emit('no_available_upload_id');
}
}
);
});
// 在本地缓存找可用的 UploadId
ep.on('seek_local_avail_upload_id', function (RemoteUploadIdList) {
// 在本地找可用的 UploadId
var uuid = session.getFileId(params.body, params.chunkSize, bucket, key);
var LocalUploadIdList = session.getUploadIdList.call(self, uuid);
if (!uuid || !LocalUploadIdList) {
ep.emit('has_and_check_upload_id', RemoteUploadIdList);
return;
}
var next = function (index) {
// 如果本地找不到可用 UploadId,再一个个遍历校验远端
if (index >= LocalUploadIdList.length) {
ep.emit('has_and_check_upload_id', RemoteUploadIdList);
return;
}
var UploadId = LocalUploadIdList[index];
// 如果不在远端 UploadId 列表里,跳过并删除
if (!util.isInArray(RemoteUploadIdList, UploadId)) {
session.removeUploadId.call(self, UploadId);
next(index + 1);
return;
}
// 如果正在上传,跳过
if (session.using[UploadId]) {
next(index + 1);
return;
}
// 判断 UploadId 是否存在线上
wholeMultipartListPart.call(
self,
{
bucket: bucket,
region: region,
key: key,
UploadId: UploadId,
// tracker: params.tracker,
},
function (err, PartListData) {
if (!self._isRunningTask(taskId)) return;
if (err) {
// 如果 UploadId 获取会出错,跳过并删除
session.removeUploadId.call(self, UploadId);
next(index + 1);
} else {
// 找到可用 UploadId
ep.emit('upload_id_available', {
UploadId: UploadId,
PartList: PartListData.PartList,
});
}
}
);
};
next(0);
});
// 获取线上 UploadId 列表
ep.on('get_remote_upload_id_list', function () {
// 获取符合条件的 UploadId 列表,因为同一个文件可以有多个上传任务。
wholeMultipartList.call(self,
{
bucket: bucket,
region: region,
key: key
},
function (err, data) {
if (!self._isRunningTask(taskId)) return;
if (err) return ep.emit('error', err);
// 整理远端 UploadId 列表
var RemoteUploadIdList = util
.filter(data.UploadList, function (item) {
return (
item.Key === key && (!storageClass || item.StorageClass.toUpperCase() === storageClass.toUpperCase())
);
})
.reverse()
.map(function (item) {
return item.UploadId || item.UploadID;
});
if (RemoteUploadIdList.length) {
ep.emit('seek_local_avail_upload_id', RemoteUploadIdList);
} else {
// 远端没有 UploadId,清理缓存的 UploadId
var uuid = session.getFileId(params.body, params.ChunkSize, bucket, key)
var LocalUploadIdList;
if (uuid && (LocalUploadIdList = session.getUploadIdList.call(self, uuid))) {
util.each(LocalUploadIdList, function (UploadId) {
session.removeUploadId.call(self, UploadId);
});
}
ep.emit('no_available_upload_id');
}
}
);
});
// 开始找可用 UploadId
ep.emit('get_remote_upload_id_list');
}
// 获取符合条件的全部上传任务 (条件包括 bucket, Region, Prefix)
function wholeMultipartList(params, callback) {
var self = this;
var uploadList = [];
var sendParams = {
bucket: params.bucket,
region: params.region,
prefix: params.key,
};
var next = function () {
// 通过 key 寻找已存在的上传列表
self.listMultipartUploads(sendParams).then(res => {
if (res.error) {
return callback(res.error);
} else if (res.body && res.body.ListMultipartUploadsResult) {
const data = res.body.ListMultipartUploadsResult;
// 检查 data.Upload 是否存在且为数组
if (data.Upload && Array.isArray(data.Upload)) {
uploadList = data.Upload;
} else if (data.Upload && typeof data.Upload === 'object') {
uploadList.push(data.Upload);
}
// 用户空间中正在进行的分块上传任务数大于此操作设定的上限值
if (data.IsTruncated === 'true') {
// 列表不完整
sendParams['key-marker'] = data.NextKeyMarker;
sendParams['upload-id-marker'] = data.NextUploadIdMarker;
next();
} else {
callback(null, {UploadList: uploadList});
}
}
}).catch(err => {
return callback(err)
})
};
next();
}
// 获取指定上传任务的分块列表
function wholeMultipartListPart(params, callback) {
var self = this;
var PartList = [];
var sendParams = {
bucket: params.bucket,
region: params.region,
key: params.key,
uploadId: params.UploadId,
};
var next = function () {
self.listParts(sendParams).then(res => {
if (res.error) {
callback(res.error)
} else {
var data = res.body['ns2:ListPartsResult'];
PartList.push.apply(PartList, data['ns2:Part'] || []);
if (data['ns2:IsTruncated'] === 'true') {
// 列表不完整
sendParams['part-number-marker'] = data['ns2:NextPartNumberMarker'];
next();
} else {
callback(null, {PartList: PartList});
}
}
}).catch(err => {
callback(err)
})
};
next();
}
// 上传文件分块,包括
/*
UploadId (上传任务编号)
asyncLimit (并发量),
UploadData (上传的分块数组),
FilePath (本地文件的位置),
SliceSize (文件分块大小)
FileSize (文件大小)
onProgress (上传成功之后的回调函数)
*/
export function uploadSliceList(params, cb) {
var self = this;
var taskId = params.taskId;
var bucket = params.bucket;
var region = params.region;
var key = params.key;
var UploadData = params.UploadData;
var FileSize = params.FileSize;
var SliceSize = params.SliceSize;
var chunkParallel = params.asyncLimit;
var chunkRetryTimes = params.chunkRetryTimes;
var Body = params.Body;
var SliceCount = Math.ceil(FileSize / SliceSize); // 分片数量 = 文件大小 / 分片大小
var FinishSize = 0;
var encryption = params.encryption;
var Headers = params.Headers;
var needUploadSlices = util.filter(UploadData.PartList, function (SliceItem) {
if (SliceItem['Uploaded']) {
FinishSize += SliceItem['PartNumber'] >= SliceCount ? FileSize % SliceSize || SliceSize : SliceSize;
}
return !SliceItem['Uploaded'];
});
var onProgress = params.onProgress;
Async.eachLimit(
needUploadSlices,
chunkParallel,
function (SliceItem, asyncCallback) {
if (!self._isRunningTask(taskId)) return;
var PartNumber = SliceItem['PartNumber'];
var currentSize =
Math.min(FileSize, SliceItem['PartNumber'] * SliceSize) - (SliceItem['PartNumber'] - 1) * SliceSize;
var preAddSize = 0;
uploadSliceItem.call(
self,
{
taskId: taskId,
bucket: bucket,
region: region,
key: key,
SliceSize: SliceSize,
FileSize: FileSize,
PartNumber: PartNumber,
chunkRetryTimes: chunkRetryTimes,
encryption: encryption,
Body: Body,
UploadData: UploadData,
Headers: Headers,
onProgress: function (data) {
FinishSize += data.loaded - preAddSize;
preAddSize = data.loaded;
onProgress({loaded: FinishSize, total: FileSize});
},
tracker: params.tracker,
},
function (err, res) {
if (!self._isRunningTask(taskId)) return;
if (err) {
FinishSize -= preAddSize;
} else {
FinishSize += currentSize - preAddSize;
SliceItem.ETag = res.headers.etag;
}
onProgress({loaded: FinishSize, total: FileSize});
asyncCallback(err || null, res);
}
);
},
function (err) {
if (!self._isRunningTask(taskId)) return;
if (err) return cb(err);
cb(null, {
UploadId: UploadData.UploadId,
SliceList: UploadData.PartList,
});
}
);
}
// 上传指定分片
function uploadSliceItem(params, callback) {
var self = this;
var taskId = params.taskId;
var bucket = params.bucket;
var region = params.region;
var key = params.key;
var FileSize = params.FileSize;
var FileBody = params.Body;
var PartNumber = params.PartNumber * 1;
var SliceSize = params.SliceSize;
var encryption = params.encryption;
var UploadData = params.UploadData;
var Headers = params.Headers || {};
var ChunkRetryTimes = params.chunkRetryTimes;
var start = SliceSize * (PartNumber - 1);
var ContentLength = SliceSize;
var end = start + SliceSize;
if (end > FileSize) {
end = FileSize;
ContentLength = end - start;
}
var PartItem = UploadData.PartList[PartNumber - 1];
Async.retry(
ChunkRetryTimes,
function (tryCallback) {
if (!self._isRunningTask(taskId)) return;
util.fileSliceForUpload(FileBody, start, end, true, function (Body) {
var sliceParams = {
taskId: taskId,
bucket: bucket,
region: region,
key: key,
contentLength: ContentLength,
partNumber: PartNumber,
uploadId: UploadData.UploadId,
encryption: encryption,
body: Body,
uploadType: "upload",
headers: Headers,
onProgress: params.onProgress,
};
self.uploadSlicePart(sliceParams, (err, data) => {
if (!self._isRunningTask(taskId)) return;
if (err || data.error) {
return tryCallback(err);
}
PartItem.Uploaded = true;
tryCallback(null, data);
})
})
},
function (err, data) {
if (!self._isRunningTask(taskId)) return;
return callback(err, data);
}
);
}
// 完成分块上传
export function uploadSliceComplete(params, callback) {
var bucket = params.bucket;
var region = params.region;
var key = params.key;
var UploadId = params.UploadId;
var SliceList = params.SliceList;
var self = this;
var ChunkRetryTimes = this.config.ChunkRetryTimes + 1;
var Headers = params.Headers;
var Parts = SliceList.map(function (item) {
return {
PartNumber: item.PartNumber,
ETag: item.ETag,
};
});
// 转换格式
var senderParts = transformData(Parts);
// 完成上传的请求也做重试
Async.retry(
ChunkRetryTimes,
function (tryCallback) {
self.completeMultipart(
{
bucket: bucket,
region: region,
key: key,
uploadId: UploadId,
parts: senderParts,
headers: Headers,
}
).then(res => {
if (res.error) {
return tryCallback(res.error);
}
return tryCallback(null, res);
})
},
function (err, data) {
callback(err, data);
}
);
}
// 转换函数
function transformData(sourceArray) {
return sourceArray.map(item => ({
partNumber: item.PartNumber,
etag: item.ETag,
}));
}