watch-xdelta
Version:
smart-watch xdelta script
168 lines (142 loc) • 5.03 kB
JavaScript
/**
* 生成差分文件
*/
;
import child_process from 'child_process';
import fs from 'fs';
import babel from 'babel';
import bfs from 'babel-fs';
import co from 'co';
import ss from 'stream-stream';
import {generatorHeader, DIFF_INDEX} from './header';
import {generatorFooter} from './footer';
import {truncateLast32Bits} from './util';
const {min, ceil} = Math;
const K = 1024;
const FOOTER_LENGTH = 32;
/**
* 切分文件的
* @param {String} oldPath 旧版本的路径
* @param {String} truncatedNewPath 新版本的路径
* @param {String} diffPath 差分文件的路径
* @param [Number] size, default=64k 分块的大小
* @param [Number] softwareVersion, default=0, 软件的版本, 8位16进制, 比如12345678
* @param [Number] hardtwareVersion, default=0, 硬件的版本, 8位16进制
* @return {Promise} 差分文件的总大小
*
*/
const xdelta = co.wrap(function *({oldPath, newPath, diffPath, size=64, softwareVersion=0, hardwareVersion=0}) {
const BLKSIZE = parseInt(size)*K;
let oldStat, newStat, oldFd, newFd, truncatedOldPath, truncatedNewPath, v2Tail;
try{
let oldFile = yield truncateLast32Bits(oldPath);
let newFile = yield truncateLast32Bits(newPath);
truncatedOldPath = oldFile.tempFile;
truncatedNewPath = newFile.tempFile;
v2Tail = newFile.tail;
oldStat = yield bfs.stat(truncatedOldPath);
newStat = yield bfs.stat(truncatedNewPath);
oldFd = yield bfs.open(truncatedOldPath, 'r');
newFd = yield bfs.open(truncatedNewPath, 'r');
}catch(e){
console.error(`${truncatedOldPath} or ${truncatedNewPath} 不存在`);
}
let largerFile = oldStat.size>newStat.size ? truncatedOldPath : truncatedNewPath;
let blk = ceil(min(oldStat.size, newStat.size)/BLKSIZE);
let removeFiles = '';
let xdeltaStream = ss();
let xdeltaFiles = [];
let diffLen = 0;
for(let i=0, len=blk; i<len; i++){
let buf,
maxSize = BLKSIZE,
oldFile = `v1_${i+1}.bin`,
newFile = `v2_${i+1}.bin`,
dFile = `d${i+1}.bin`;
if(i == len-1){
if(largerFile == oldFile){
maxSize = oldStat.size-BLKSIZE*(blk-1);
}else{
maxSize = newStat.size-BLKSIZE*(blk-1);
}
}
buf = new Buffer(maxSize);
try {
yield sliceFile({fd: oldFd, buffer: buf, filename: oldFile});
yield sliceFile({fd: newFd, buffer: buf, filename: newFile});
yield exec(`xdelta3 -9 -A -f -e -s ${oldFile} ${newFile} ${dFile}`);
}catch(e) {
console.error(`生成${dFile}出错`);
}
diffLen += 4;
removeFiles += `${oldFile} ${newFile} ${dFile} `;
xdeltaFiles.push(dFile);
}
let header = yield generatorHeader({blk: blk, blksize: BLKSIZE, oldFromLenB: oldStat.size, newFromLenB: newStat.size, xdeltaFiles: xdeltaFiles});
// xdeltaStream是包含了所有差分文件内容的Transform Stream
xdeltaFiles.forEach((file) => {
xdeltaStream.write(fs.createReadStream(file));
});
xdeltaStream.end();
let ws = fs.createWriteStream(diffPath);
let totalLen = yield streamToPromise(xdeltaStream, ws, header, v2Tail);
removeFiles += `${truncatedOldPath} ${truncatedNewPath}`;
yield exec(`rm -rf ${removeFiles}`); //删除中间文件
return totalLen;
});
/**
* 按指定缓存大小切分文件
* @param {Number} fd 待切分的文件句柄
* @param {Buffer} buffer 读写文件时用的缓存
* @param {String} filename 生成的切分文件的文件名
*
*/
const sliceFile = co.wrap(function *({fd, buffer, filename}) {
let filesize = buffer.length;
let nBytes = yield bfs.read(fd, buffer, 0, filesize, null);
yield bfs.writeFile(filename, buffer.slice(0, nBytes));
});
const exec = function(command){
return new Promise((resolve, reject) => {
child_process.exec.call(null, command, (err, stdout, stderr) => {
if(err){
reject(stderr);
}else{
resolve(stdout);
}
});
});
};
/**
* 流操作转化成promise形式,将差分文件块的内容最终合成到一个文件中
* @param {ReadStream} readStream xdeltaStream
* @param {WriteStream} writeStream 生成的差分文件的可写流
* @param {Number} header 文件头
* @param {Buffer} tail 尾部32字节
* @return {Promise.<Number>} 返回文件的总大小
*
*/
const streamToPromise = function(readStream, writeStream, header, tail) {
let headerLen = header.length;
let totalLen = headerLen;
let totalBufArr = [header];
return new Promise(function(resolve) {
readStream.on('data', (chunk) => {
totalLen += chunk.length;
totalBufArr.push(chunk);
});
readStream.on('end', function() {
let totalBuf = Buffer.concat(totalBufArr);
let checksum = 0; //校验和
for(let i=0, len=totalBuf.length; i<len; i++) {
checksum += totalBuf[i];
}
writeStream.write(totalBuf);
let footerBuf = generatorFooter(tail, checksum);
writeStream.write(footerBuf);
totalLen += FOOTER_LENGTH;
resolve(totalLen);
});
});
};
export default xdelta;