ks3
Version:
本代码库为`金山云存储KS3`服务.主要提供`KS3 nodejs SDK`和`命令行工具`.
303 lines (271 loc) • 10.1 kB
JavaScript
const EventDispatcer = require('./common/event')
const async = require('./common/async')
const utils = require('./util')
const config = require('./../config');
const fs = require('fs');
var debug = require('debug')('ks3:advance');
/**
* 分块拷贝大文件
* @param {String} params.Bucket
* @param {String} params.Key
* @param {String} params.SourceBucket
* @param {String} params.SourceKey
* @param {String} params.ACL
* @param {String} params.StorageClass
* @param {String} params.onProgress
* @param {String} params.Headers
*
* @description 不支持客户端加密的数据大文件分块拷贝
*/
function sliceCopyFile (params, cb) {
const self = this
const eventBus = new EventDispatcer()
var onProgress = null
var fileSize = 0
var uploadId = ''
var finishedSize = 0 // 成功的文件大小
self.config({
dataType: 'json'
})
var copyChunkParallelLimit = params.ParallelLimit || 10
const { SourceBucket, SourceKey, Headers } = params
// 数据准备 sse-c时headObject会报错 故不支持客户端加密类型的拷贝
self.object.headObject({
Bucket: SourceBucket,
Key: SourceKey,
Endpoint: params.Endpoint
}, function (err, data, res) {
if (err) {
cb(err)
return
}
fileSize = res.caseless.get('content-length')
if (!fileSize) {
cb(new Error('[sliceCopyFile] get object content-length error'))
return
}
fileSize = Number(fileSize)
if (fileSize < config.uploadMaxSize) {
params.headers = params.Headers;
return self.object.copyObject(params, cb)
}
onProgress = utils.throttleOnProgress.call(self, fileSize, params.onProgress)
let originHeaderInfo = {}
const headerKeys = ['cache-control', 'content-disposition', 'content-encoding', 'content-type', 'expires', 'x-kss-storage-class', 'x-kss-server-side-encryption']
Object.keys(res.headers).forEach(item => {
const kssMetaPrefix = 'x-kss-meta'
let k = item.toLowerCase();
const val = res.headers[k]
if (k.indexOf(kssMetaPrefix) == 0 && k.length > kssMetaPrefix.length) {
originHeaderInfo[k] = val
}
// 提取headers里面的acl、storageClass、meta、加密
if (headerKeys.indexOf(k) > -1) {
originHeaderInfo[k] = val
}
})
if (params.ACL) originHeaderInfo['x-kss-acl'] = params.ACL
const chunks = utils.getSliceChunks(fileSize, config.chunkSize, config.chunkMaxNum)
eventBus.emit('slice-copy-prepared', {
chunks,
originHeaders: originHeaderInfo
})
})
eventBus.on('slice-copy-prepared', d => {
const { chunks, originHeaders} = d
self.object.multitpart_upload_init({
Bucket: params.Bucket,
Key: params.Key,
headers: Object.assign(originHeaders, Headers),
Endpoint: params.Endpoint
}, (err, data, res) => {
if (err) return cb(err)
uploadId = data.InitiateMultipartUploadResult.UploadId;
chunks.map(item => { item.UploadId = uploadId })
eventBus.emit('slice-copy-start', chunks)
})
})
eventBus.on('slice-copy-start', chunks => {
async.eachLimit(chunks, copyChunkParallelLimit, function (sliceItem, callback) {
const { PartNumber, Range, UploadId, ChunkStart, ChunkEnd } = sliceItem
const chunkSize = ChunkEnd - ChunkStart
self.object.uploadPartCopy({
SourceBucket, SourceKey,
Bucket: params.Bucket,
Key: params.Key,
PartNumber, Range, UploadId,
Endpoint: params.Endpoint
}, function (err, data, res) {
if (err) {
return callback(err)
}
finishedSize += chunkSize
onProgress({
loaded: finishedSize,
total: fileSize
})
const etag = data.CopyObjectResult.ETag
sliceItem.ETag = etag.replace(/"/g, '')
callback(err || null, data)
})
}, function (err) {
if (err) {
onProgress(null, true)
console.log('request error should abort upload:', uploadId)
self.object.upload_abort({
UploadId: uploadId,
Bucket: params.Bucket,
Key: params.Key,
Endpoint: params.Endpoint
}, function (err, data, res) {
if (!err) console.log('cancel upload success')
})
return cb(err)
}
if (chunks && chunks.length) eventBus.emit('slice-copy-complete', chunks)
})
})
eventBus.on('slice-copy-complete', chunks => {
const etags = chunks.map(item => { return item.ETag;})
const opt = {
Bucket: params.Bucket,
Key: params.Key,
PartNumber: etags.length,
UploadId: uploadId,
body: utils.generateCompleteXML(etags),
Endpoint: params.Endpoint,
//拷贝支持覆盖,complete时也需要传入headers[x-kss-forbid-overwrite]
header: params.Headers
}
self.object.upload_complete(opt, function(err, data, res) {
if (err) {
onProgress(null, true)
return cb(err)
}
onProgress({
loaded: finishedSize,
total: finishedSize
}, true)
cb(err, data, res)
})
})
}
/**
* 分块下载
* @param {String} params.Bucket
* @param {String} params.Key
* @param {String} params.FilePath 下载位置
* @param {String} params.ParallelLimit 并发数
* @param {*} callback
*/
function downloadFile (params, callback) {
const self = this
const eventBus = new EventDispatcer()
var onProgress = null
var fileSize = 0
var uploadId = ''
var parallelLimit = params.ParallelLimit || 5
var finishedSize = 0 // 成功的文件大小
var stopped = false;
var filePath = params.FilePath
const { Bucket, Key, Headers } = params
self.object.headObject({
Bucket,
Key,
Endpoint: params.Endpoint
}, function(err, data, res) {
if (err) {
callback(err)
return
}
fileSize = res.caseless.get('content-length')
if (!fileSize) {
cb(new Error('[downloadFile] get object content-length error'))
return
}
// 先校验文件是否可写入
try {
fs.writeFileSync(filePath, '')
} catch (error) {
callback(error)
return;
}
fileSize = Number(fileSize)
if (fileSize < config.chunkSize) {
self.object.get(params, function (err, data, resp) {
if (err) {
callback(err)
return;
} else {
fs.writeFileSync(filePath, data)
}
})
}
onProgress = utils.throttleOnProgress.call(self, fileSize, params.onProgress)
const chunks = utils.getSliceChunks(fileSize, config.chunkSize, config.chunkMaxNum)
eventBus.emit('slice-download-start', chunks)
})
eventBus.on('slice-download-start', function (chunks) {
async.eachLimit(chunks, parallelLimit, function (sliceItem, callback) {
if (stopped) return
const { Range, ChunkStart, ChunkEnd } = sliceItem
const dataSize = ChunkEnd - ChunkStart
console.log('请求的区间:', Range)
self.object.get({
Bucket,
Key,
headers: {
Range: Range
},
Endpoint: params.Endpoint
}, function (err, data, res) {
console.log('获取到的长度:', data.length)
fs.open(filePath, 'r+', function (err, fd) {
if (err) {
callback(err)
return
}
console.log('开始写入的位置:', `${ChunkStart}-${ChunkEnd}`, dataSize)
// write(fd, buffer, offset, length, position, callback)
var cb = function (err, buffer) {
if (err) {
callback(err)
return
} else {
// 更新进度
finishedSize += dataSize
onProgress({
loaded: finishedSize,
total: fileSize
})
if (finishedSize == fileSize) fs.close(fd)
callback(err || null, data)
}
}
console.log('data.length, ChunkStart: ', data.length, ChunkStart)
var len = data.length
var position = ChunkStart
var buffers = Buffer.from(data)
fs.write(fd, buffers, 0, len, position, cb)
})
})
}, function(err){
if (err) {
onProgress(null, true)
console.log('request error :', err)
return callback(err)
}
eventBus.emit('slice-download-complete', chunks)
})
})
eventBus.on('error', function () {
stopped = true;
})
eventBus.on('slice-download-complete', function () {
callback(null, '')
})
}
module.exports = {
sliceCopyFile,
downloadFile
}