Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
node_modules/
.idea/
npm-debug.log
81 changes: 33 additions & 48 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ var util = require('util')
var crypto = require('crypto')
var minimatch = require('minimatch')
var uploadedFiles = 0;
var getEtag = require('./qetag');

module.exports = function (qiniu, option) {
option = option || {};
Expand All @@ -32,41 +33,42 @@ module.exports = function (qiniu, option) {
if (isIgnore) return next();

var fileKey = option.dir + ((!option.dir || option.dir[option.dir.length - 1]) === '/' ? '' : '/') + (option.versioning ? version + '/' : '') + filePath;
var fileHash = calcHash(file);
getEtag(file.path, function (fileHash) {
qs.push(Q.nbind(qn.stat, qn)(fileKey)
.spread(function (stat) {
// Skip when hash equal
if (stat.hash === fileHash) return false;

qs.push(Q.nbind(qn.stat, qn)(fileKey)
.spread(function (stat) {
// Skip when hash equal
if (stat.hash === fileHash) return false;
// Then delete
return Q.nbind(qn.delete, qn)(fileKey)
}, function () {
// Upload when not exists
return true;
})
.then(function (isUpload) {
if (isUpload === false) return false;
return Q.nbind(qn.upload, qn)(file._contents, {key: fileKey})
})
.then(function (stat) {
// No upload
if (stat === false) {
log('Skip:', colors.grey(filePath));
return;
}

// Then delete
return Q.nbind(qn.delete, qn)(fileKey)
}, function () {
// Upload when not exists
return true;
})
.then(function (isUpload) {
if (isUpload === false) return false;
return Q.nbind(qn.upload, qn)(file._contents, {key: fileKey})
})
.then(function (stat) {
// No upload
if (stat === false) {
log('Skip:', colors.grey(filePath));
return;
}

// Record hash
uploadedFiles++;
// Record hash
uploadedFiles++;

log('Upload:', colors.green(filePath), '→', colors.green(fileKey));
}, function (err) {
log('Error', colors.red(filePath), new PluginError('gulp-qiniu', err).message);
that.emit('Error', colors.red(filePath), new PluginError('gulp-qiniu', err));
})
)
log('Upload:', colors.green(filePath), '→', colors.green(fileKey));
}, function (err) {
log('Error', colors.red(filePath), new PluginError('gulp-qiniu', err).message);
that.emit('Error', colors.red(filePath), new PluginError('gulp-qiniu', err));
})
)

next();
next();
});

}, function () {
Q.all(qs)
.then(function (rets) {
Expand Down Expand Up @@ -96,21 +98,4 @@ module.exports = function (qiniu, option) {
}
return target;
}

/**
* Calc qiniu etag
*
* @param file
* @returns {*}
*/
function calcHash(file) {
if (file.size > 1 << 22) return false;
var shasum = crypto.createHash('sha1');
shasum.update(file._contents);
var sha1 = shasum.digest();
var hash = new Buffer(1 + sha1.length);
hash[0] = 0x16;
sha1.copy(hash, 1);
return hash.toString('base64').replace('+', '-').replace('/', '_');
}
};
79 changes: 79 additions & 0 deletions qetag.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// 计算文件的eTag,参数为buffer或者readableStream或者文件路径
function getEtag(buffer,callback){

// 判断传入的参数是buffer还是stream还是filepath
var mode = 'buffer';

if(typeof buffer === 'string'){
buffer = require('fs').createReadStream(buffer);
mode='stream';
}else if(buffer instanceof require('stream')){
mode='stream';
}

// sha1算法
var sha1 = function(content){
var crypto = require('crypto');
var sha1 = crypto.createHash('sha1');
sha1.update(content);
return sha1.digest();
};

// 以4M为单位分割
var blockSize = 4*1024*1024;
var sha1String = [];
var prefix = 0x16;
var blockCount = 0;

switch(mode){
case 'buffer':
var bufferSize = buffer.length;
blockCount = Math.ceil(bufferSize / blockSize);

for(var i=0;i<blockCount;i++){
sha1String.push(sha1(buffer.slice(i*blockSize,(i+1)*blockSize)));
}
process.nextTick(function(){
callback(calcEtag());
});
break;
case 'stream':
var stream = buffer;
stream.on('readable', function() {
var chunk;
while (chunk = stream.read(blockSize)) {
sha1String.push(sha1(chunk));
blockCount++;
}
});
stream.on('end',function(){
callback(calcEtag());
});
break;
}

function calcEtag(){
if(!sha1String.length){
return 'Fto5o-5ea0sNMlW_75VgGJCv2AcJ';
}
var sha1Buffer = Buffer.concat(sha1String,blockCount * 20);

// 如果大于4M,则对各个块的sha1结果再次sha1
if(blockCount > 1){
prefix = 0x96;
sha1Buffer = sha1(sha1Buffer);
}

sha1Buffer = Buffer.concat(
[new Buffer([prefix]),sha1Buffer],
sha1Buffer.length + 1
);

return sha1Buffer.toString('base64')
.replace(/\//g,'_').replace(/\+/g,'-');

}

}

module.exports = getEtag;