README started. Tests added
This commit is contained in:
parent
8b2f2914de
commit
8d92d421e9
19
README.md
19
README.md
|
@ -1133,6 +1133,7 @@ var sh = fs.Shell();
|
|||
* [sh.rm(path, [options], callback)](#rm)
|
||||
* [sh.tempDir(callback)](#tempDir)
|
||||
* [sh.mkdirp(path, callback)](#mkdirp)
|
||||
* [sh.rsync(srcPath, destPath, [options], callback)](#rsync)
|
||||
|
||||
#### sh.cd(path, callback)<a name="cd"></a>
|
||||
|
||||
|
@ -1338,3 +1339,21 @@ sh.mkdirp('/test/mkdirp', function(err) {
|
|||
// the root '/' now contains a directory 'test' containing the directory 'mkdirp'
|
||||
});
|
||||
```
|
||||
|
||||
#### sh.rsync(srcPath, destPath, [options], callback)<a name="rsync"></a>
|
||||
|
||||
Rsync copies files locally on the current host (currently no remote functionality).
|
||||
The srcPath can be either a file or a directory, with the destPath being the
|
||||
destination directory. If the destination directory does not exist, it will be created.
|
||||
Options object can currently have the following attributes:
|
||||
|
||||
recursive: true //default 'false'
|
||||
size: 5 //default 750. File chunk size in Kb.
|
||||
|
||||
Example:
|
||||
|
||||
```javascript
|
||||
sh.rsync('/test', '/test2', { recursive: true }, function(err) {
|
||||
if(err) throw err;
|
||||
});
|
||||
```
|
|
@ -7,7 +7,7 @@ define(function(require) {
|
|||
require("crypto-js/rollups/md5");
|
||||
|
||||
function md5(data) {
|
||||
return CryptoJS.MD5(String.fromCharCode.apply(null, data)).toString();
|
||||
return CryptoJS.MD5(String.fromCharCode(data)).toString();
|
||||
}
|
||||
function weak32(data, prev, start, end) {
|
||||
var a = 0,
|
||||
|
|
94
src/rsync.js
94
src/rsync.js
|
@ -4,6 +4,7 @@
|
|||
|
||||
define(function(require) {
|
||||
var Path = require('src/path');
|
||||
var Errors = require('src/errors');
|
||||
var async = require('async');
|
||||
var _md5 = require('./hash').md5;
|
||||
var _weak16 = require('./hash').weak16;
|
||||
|
@ -41,6 +42,7 @@ define(function(require) {
|
|||
var weak = _weak32(data, prevRollingWeak, start, end);
|
||||
var weak16 = _weak16(weak.sum);
|
||||
var match = false;
|
||||
var d;
|
||||
prevRollingWeak = weak;
|
||||
if (hashtable[weak16]) {
|
||||
var len = hashtable[weak16].length;
|
||||
|
@ -59,13 +61,13 @@ define(function(require) {
|
|||
}
|
||||
if (match) {
|
||||
if(start < lastMatchedEnd) {
|
||||
var d = data.subarray(lastMatchedEnd - 1, end);
|
||||
d = data.subarray(lastMatchedEnd - 1, end);
|
||||
results.push({
|
||||
data: d,
|
||||
index: match.index
|
||||
});
|
||||
} else if (start - lastMatchedEnd > 0) {
|
||||
var d = data.subarray(lastMatchedEnd, start);
|
||||
d = data.subarray(lastMatchedEnd, start);
|
||||
results.push({
|
||||
data: d,
|
||||
index: match.index
|
||||
|
@ -78,7 +80,7 @@ define(function(require) {
|
|||
lastMatchedEnd = end;
|
||||
} else if (end === length) {
|
||||
// No match and last block
|
||||
var d = data.subarray(lastMatchedEnd);
|
||||
d = data.subarray(lastMatchedEnd);
|
||||
results.push({
|
||||
data: d
|
||||
});
|
||||
|
@ -89,29 +91,36 @@ define(function(require) {
|
|||
|
||||
function rsync (srcPath, destPath, opts, callback) {
|
||||
var self = this;
|
||||
if(typeof options === 'function') {
|
||||
callback = options;
|
||||
if(typeof opts === 'function') {
|
||||
callback = opts;
|
||||
options = {};
|
||||
options.size = 750;
|
||||
}
|
||||
opts = opts || {};
|
||||
opts.size = opts.size || 750;
|
||||
options = opts;
|
||||
options = opts || {};
|
||||
options.size = options.size || 750;
|
||||
callback = callback || function() {};
|
||||
if(srcPath === null || srcPath === '/' || srcPath === '') {
|
||||
callback (new Errors.EINVAL('invalid source path'));
|
||||
return;
|
||||
}
|
||||
|
||||
function getSrcList(path, callback) {
|
||||
var result = [];
|
||||
fs.stat(path, function(err, stats) {
|
||||
if(err) callback(err);
|
||||
if(stats.type === 'DIRECTORY') {
|
||||
fs.readdir(path, function(err, entries) {
|
||||
self.fs.stat(path, function(err, stats) {
|
||||
if(err) {
|
||||
callback(err);
|
||||
return;
|
||||
};
|
||||
}
|
||||
if(stats.type === 'DIRECTORY') {
|
||||
self.fs.readdir(path, function(err, entries) {
|
||||
if(err) {
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
|
||||
function getSrcContents(name, callback) {
|
||||
var name = Path.join(path, name);
|
||||
fs.stat(name, function(error, stats) {
|
||||
function getSrcContents(_name, callback) {
|
||||
var name = Path.join(path, _name);
|
||||
self.fs.stat(name, function(error, stats) {
|
||||
if(error) {
|
||||
callback(error);
|
||||
return;
|
||||
|
@ -192,26 +201,33 @@ define(function(require) {
|
|||
}
|
||||
|
||||
getSrcList(srcPath, function(err, result){
|
||||
console.log(result);
|
||||
fs.exists(destPath, function(res){
|
||||
if(err) {
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
self.mkdirp(destPath, function(err){
|
||||
getChecksums(destPath, result, function(err, result){
|
||||
console.log(result);
|
||||
if(err){
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
diff.call(self, srcPath, result, function(err, diffs) {
|
||||
console.log(diffs);
|
||||
if(err){
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
sync.call(self, destPath, diffs, function(err){
|
||||
callback(err);
|
||||
})
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
function checksum (path, callback) {
|
||||
var self = this;
|
||||
fs.readFile(path, function (err, data) {
|
||||
self.fs.readFile(path, function (err, data) {
|
||||
if (!err){
|
||||
// cache file
|
||||
cache[path] = data;
|
||||
|
@ -253,14 +269,14 @@ define(function(require) {
|
|||
return callback(new Error('attribute does not exist'), null);
|
||||
// roll through the file
|
||||
var diffs = [];
|
||||
fs.stat(path, function(err, stat){
|
||||
self.fs.stat(path, function(err, stat){
|
||||
if(stat.type ==='DIRECTORY') {
|
||||
async.each(checksums, getDiff, function(err) {
|
||||
callback(err, diffs);
|
||||
});
|
||||
}
|
||||
else {
|
||||
fs.readFile(path, function (err, data) {
|
||||
self.fs.readFile(path, function (err, data) {
|
||||
if (err) { return callback(err); }
|
||||
diffs.push({
|
||||
diff: roll(data, checksums[0].checksum, options.size),
|
||||
|
@ -285,7 +301,7 @@ define(function(require) {
|
|||
callback();
|
||||
});
|
||||
} else {
|
||||
fs.readFile(Path.join(path,entry.path), function (err, data) {
|
||||
self.fs.readFile(Path.join(path,entry.path), function (err, data) {
|
||||
if (err) { return callback(err); }
|
||||
diffs.push({
|
||||
diff: roll(data, entry.checksum, options.size),
|
||||
|
@ -301,6 +317,14 @@ define(function(require) {
|
|||
var self = this;
|
||||
|
||||
function syncEach(entry, callback){
|
||||
|
||||
//get slice of raw file from block's index
|
||||
function rawslice(index) {
|
||||
var start = index*options.size;
|
||||
var end = start + options.size > raw.length ? raw.length : start + options.size;
|
||||
return raw.subarray(start, end);
|
||||
}
|
||||
|
||||
if(entry.hasOwnProperty('contents')) {
|
||||
sync.call(self, Path.join(path, entry.path), entry.contents, function(err){
|
||||
if(err) {
|
||||
|
@ -314,42 +338,30 @@ define(function(require) {
|
|||
var raw = cache[Path.join(path,entry.path)];
|
||||
var i = 0;
|
||||
var len = entry.diff.length;
|
||||
if(typeof raw === 'undefined')
|
||||
if(typeof raw === 'undefined'){
|
||||
return callback(new Error('must do checksum() first'), null);
|
||||
|
||||
//get slice of raw file from block's index
|
||||
function rawslice(index) {
|
||||
var start = index*options.size;
|
||||
var end = start + options.size > raw.length
|
||||
? raw.length
|
||||
: start + options.size;
|
||||
return raw.subarray(start, end);
|
||||
}
|
||||
|
||||
var synced = '';
|
||||
var buf = new Uint8Array();
|
||||
for(; i < len; i++) {
|
||||
var chunk = entry.diff[i];
|
||||
if(typeof chunk.data === 'undefined') { //use slice of original file
|
||||
buf = appendBuffer(buf, rawslice(chunk.index));
|
||||
synced += String.fromCharCode.apply(null, rawslice(chunk.index));
|
||||
} else {
|
||||
buf = appendBuffer(buf, chunk.data);
|
||||
synced += String.fromCharCode.apply(null, chunk.data);
|
||||
if(typeof chunk.index !== 'undefined') {
|
||||
buf = appendBuffer(buf, rawslice(chunk.index));
|
||||
synced += String.fromCharCode.apply(null, rawslice(chunk.index));
|
||||
}
|
||||
}
|
||||
}
|
||||
delete cache[Path.join(path,entry.path)];
|
||||
fs.writeFile(Path.join(path,entry.path), buf, function(err){
|
||||
self.fs.writeFile(Path.join(path,entry.path), buf, function(err){
|
||||
if(err){
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
return callback(null);
|
||||
})
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,303 @@
|
|||
define(["Filer", "util"], function(Filer, util) {
|
||||
|
||||
describe('FileSystemShell.rsync', function() {
|
||||
beforeEach(util.setup);
|
||||
afterEach(util.cleanup);
|
||||
|
||||
it('should be a function', function() {
|
||||
var shell = util.shell();
|
||||
expect(shell.rsync).to.be.a('function');
|
||||
});
|
||||
|
||||
it('should fail without a source path provided (null)', function(done) {
|
||||
var fs = util.fs();
|
||||
var shell = fs.Shell();
|
||||
|
||||
shell.rsync(null, '/', function(err) {
|
||||
expect(err).to.exist;
|
||||
expect(err.code).to.equal('EINVAL');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail without a source path provided (\'\')', function(done) {
|
||||
var fs = util.fs();
|
||||
var shell = fs.Shell();
|
||||
|
||||
shell.rsync('', '/', function(err) {
|
||||
expect(err).to.exist;
|
||||
expect(err.code).to.equal('EINVAL');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail with root provided (\'/\')', function(done) {
|
||||
var fs = util.fs();
|
||||
var shell = fs.Shell();
|
||||
|
||||
shell.rsync('/', '/', function(err) {
|
||||
expect(err).to.exist;
|
||||
expect(err.code).to.equal('EINVAL');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail with a non-existant path', function(done) {
|
||||
var fs = util.fs();
|
||||
var shell = fs.Shell();
|
||||
shell.rsync('/1.txt', '/', function(err) {
|
||||
expect(err).to.exist;
|
||||
expect(err.code).to.equal('ENOENT');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should succeed if the source file is altered in content but not length from the destination file. (Destination edited)', function(done) {
|
||||
var fs = util.fs();
|
||||
var shell = fs.Shell();
|
||||
|
||||
fs.mkdir('/test', function(err) {
|
||||
expect(err).to.not.exist;
|
||||
fs.writeFile('/1.txt','This is my file. It does not have any typos.','utf8',function(err) {
|
||||
expect(err).to.not.exist;
|
||||
fs.writeFile('/test/1.txt','This is my fivth file. It doez not have any topos,', 'utf8', function(err) {
|
||||
expect(err).to.not.exist;
|
||||
shell.rsync('/1.txt', '/test', { size: 5 }, function(err) {
|
||||
expect(err).to.not.exist;
|
||||
fs.readFile('/test/1.txt', 'utf8', function(err, data){
|
||||
expect(err).to.not.exist;
|
||||
expect(data).to.exist;
|
||||
expect(data).to.equal('This is my file. It does not have any typos.');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should succeed if the source file is longer than the destination file. (Destination appended)', function(done) {
|
||||
var fs = util.fs();
|
||||
var shell = fs.Shell();
|
||||
|
||||
fs.mkdir('/test', function(err) {
|
||||
expect(err).to.not.exist;
|
||||
fs.writeFile('/1.txt','This is my file. It is longer than the destination file.', 'utf8', function(err) {
|
||||
expect(err).to.not.exist;
|
||||
fs.writeFile('/test/1.txt','This is my file.','utf8',function(err) {
|
||||
expect(err).to.not.exist;
|
||||
shell.rsync('/1.txt', '/test', { size: 5 }, function(err) {
|
||||
expect(err).to.not.exist;
|
||||
fs.readFile('/test/1.txt', 'utf8', function(err, data){
|
||||
expect(err).to.not.exist;
|
||||
expect(data).to.exist;
|
||||
expect(data).to.equal('This is my file. It is longer than the destination file.');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should succeed if the source file shorter than the destination file. (Destination truncated)', function(done) {
|
||||
var fs = util.fs();
|
||||
var shell = fs.Shell();
|
||||
|
||||
fs.mkdir('/test', function(err) {
|
||||
expect(err).to.not.exist;
|
||||
fs.writeFile('/1.txt','This is my file.','utf8',function(err) {
|
||||
expect(err).to.not.exist;
|
||||
fs.writeFile('/test/1.txt','This is my file. It is longer than the source version.', 'utf8', function(err) {
|
||||
expect(err).to.not.exist;
|
||||
shell.rsync('/1.txt', '/test', { size: 5 }, function(err) {
|
||||
expect(err).to.not.exist;
|
||||
fs.readFile('/test/1.txt', 'utf8', function(err, data){
|
||||
expect(err).to.not.exist;
|
||||
expect(data).to.exist;
|
||||
expect(data).to.equal('This is my file.');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should succeed if the source file does not exist in the destination folder (Destination file created)', function(done) {
|
||||
var fs = util.fs();
|
||||
var shell = fs.Shell();
|
||||
|
||||
fs.mkdir('/test', function(err) {
|
||||
expect(err).to.not.exist;
|
||||
fs.writeFile('/1.txt','This is my file. It does not exist in the destination folder.', 'utf8', function(err) {
|
||||
expect(err).to.not.exist;
|
||||
shell.rsync('/1.txt', '/test', { size: 5 }, function(err) {
|
||||
expect(err).to.not.exist;
|
||||
fs.readFile('/test/1.txt', 'utf8', function(err, data){
|
||||
expect(err).to.not.exist;
|
||||
expect(data).to.exist;
|
||||
expect(data).to.equal('This is my file. It does not exist in the destination folder.');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should succeed if the destination folder does not exist (Destination file created)', function(done) {
|
||||
var fs = util.fs();
|
||||
var shell = fs.Shell();
|
||||
|
||||
fs.writeFile('/1.txt','This is my file. It does not exist in the destination folder.', 'utf8', function(err) {
|
||||
expect(err).to.not.exist;
|
||||
shell.rsync('/1.txt', '/test', { size: 5 }, function(err) {
|
||||
expect(err).to.not.exist;
|
||||
fs.readFile('/test/1.txt', 'utf8', function(err, data){
|
||||
expect(err).to.not.exist;
|
||||
expect(data).to.exist;
|
||||
expect(data).to.equal('This is my file. It does not exist in the destination folder.');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should succeed syncing a directory if the destination directory is empty', function(done) {
|
||||
var fs = util.fs();
|
||||
var shell = fs.Shell();
|
||||
|
||||
fs.mkdir('/test', function(err) {
|
||||
expect(err).to.not.exist;
|
||||
fs.mkdir('/test2', function(err) {
|
||||
expect(err).to.not.exist;
|
||||
fs.writeFile('/test/1.txt','This is my 1st file. It does not have any typos.', 'utf8', function(err) {
|
||||
expect(err).to.not.exist;
|
||||
fs.writeFile('/test/2.txt','This is my 2nd file. It is longer than the destination file.', 'utf8', function(err) {
|
||||
expect(err).to.not.exist;
|
||||
shell.rsync('/test', '/test2', { size: 5 }, function(err) {
|
||||
expect(err).to.not.exist;
|
||||
fs.readFile('/test2/1.txt', 'utf8', function(err, data){
|
||||
expect(err).to.not.exist;
|
||||
expect(data).to.exist;
|
||||
expect(data).to.equal('This is my 1st file. It does not have any typos.');
|
||||
fs.readFile('/test2/2.txt', 'utf8', function(err, data){
|
||||
expect(err).to.not.exist;
|
||||
expect(data).to.exist;
|
||||
expect(data).to.equal('This is my 2nd file. It is longer than the destination file.');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should succeed syncing a directory if the destination directory doesn\'t exist', function(done) {
|
||||
var fs = util.fs();
|
||||
var shell = fs.Shell();
|
||||
|
||||
fs.mkdir('/test', function(err) {
|
||||
expect(err).to.not.exist;
|
||||
fs.writeFile('/test/1.txt','This is my 1st file. It does not have any typos.', 'utf8', function(err) {
|
||||
expect(err).to.not.exist;
|
||||
fs.writeFile('/test/2.txt','This is my 2nd file. It is longer than the destination file.', 'utf8', function(err) {
|
||||
expect(err).to.not.exist;
|
||||
shell.rsync('/test', '/test2', { size: 5 }, function(err) {
|
||||
expect(err).to.not.exist;
|
||||
fs.readFile('/test2/1.txt', 'utf8', function(err, data){
|
||||
expect(err).to.not.exist;
|
||||
expect(data).to.exist;
|
||||
expect(data).to.equal('This is my 1st file. It does not have any typos.');
|
||||
fs.readFile('/test2/2.txt', 'utf8', function(err, data){
|
||||
expect(err).to.not.exist;
|
||||
expect(data).to.exist;
|
||||
expect(data).to.equal('This is my 2nd file. It is longer than the destination file.');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should succeed syncing a directory recursively (recursive: true)', function(done) {
|
||||
var fs = util.fs();
|
||||
var shell = fs.Shell();
|
||||
fs.mkdir('/test', function(err) {
|
||||
expect(err).to.not.exist;
|
||||
fs.mkdir('/test2', function(err) {
|
||||
expect(err).to.not.exist;
|
||||
fs.mkdir('/test/sync', function(err) {
|
||||
expect(err).to.not.exist;
|
||||
fs.mkdir('/test2/sync', function(err) {
|
||||
expect(err).to.not.exist;
|
||||
|
||||
fs.writeFile('/test/1.txt','This is my 1st file. It does not have any typos.', 'utf8', function(err) {
|
||||
expect(err).to.not.exist;
|
||||
fs.writeFile('/test/2.txt','This is my 2nd file. It is longer than the destination file.', 'utf8', function(err) {
|
||||
expect(err).to.not.exist;
|
||||
fs.writeFile('/test/sync/3.txt','This is my 3rd file.', 'utf8', function(err) {
|
||||
expect(err).to.not.exist;
|
||||
fs.writeFile('/test/sync/5.txt','This is my 5th file. It does not exist in the destination folder.', 'utf8', function(err) {
|
||||
expect(err).to.not.exist;
|
||||
fs.writeFile('/test2/1.txt','This is my 1st file. It doez not have any topos,', 'utf8', function(err) {
|
||||
expect(err).to.not.exist;
|
||||
fs.writeFile('/test2/2.txt','This is my 2nd file.', 'utf8', function(err) {
|
||||
expect(err).to.not.exist;
|
||||
fs.writeFile('/test2/sync/3.txt','This is my 3rd file. It is longer than the source version.', 'utf8', function(err) {
|
||||
expect(err).to.not.exist;
|
||||
fs.writeFile('/test2/sync/4.txt','This is my 4th file. It does not exist in the source folder.', 'utf8', function(err) {
|
||||
expect(err).to.not.exist;
|
||||
|
||||
shell.rsync('/test', '/test2', { recursive: true, size: 5 }, function(err) {
|
||||
expect(err).to.not.exist;
|
||||
fs.readFile('/test2/1.txt', 'utf8', function(err, data){
|
||||
expect(err).to.not.exist;
|
||||
expect(data).to.exist;
|
||||
expect(data).to.equal('This is my 1st file. It does not have any typos.');
|
||||
fs.readFile('/test2/2.txt', 'utf8', function(err, data){
|
||||
expect(err).to.not.exist;
|
||||
expect(data).to.exist;
|
||||
expect(data).to.equal('This is my 2nd file. It is longer than the destination file.');
|
||||
fs.readFile('/test2/sync/3.txt', 'utf8', function(err, data){
|
||||
expect(err).to.not.exist;
|
||||
expect(data).to.exist;
|
||||
expect(data).to.equal('This is my 3rd file.')
|
||||
fs.readFile('/test2/sync/4.txt', 'utf8', function(err, data){
|
||||
expect(err).to.not.exist;
|
||||
expect(data).to.exist;
|
||||
expect(data).to.equal('This is my 4th file. It does not exist in the source folder.')
|
||||
fs.readFile('/test2/sync/5.txt', 'utf8', function(err, data){
|
||||
expect(err).to.not.exist;
|
||||
expect(data).to.exist;
|
||||
expect(data).to.equal('This is my 5th file. It does not exist in the destination folder.')
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
});
|
|
@ -57,6 +57,7 @@ define([
|
|||
"spec/shell/rm.spec",
|
||||
"spec/shell/env.spec",
|
||||
"spec/shell/mkdirp.spec",
|
||||
"spec/shell/rsync.spec",
|
||||
|
||||
// Ported node.js tests (filenames match names in https://github.com/joyent/node/tree/master/test)
|
||||
"spec/node-js/simple/test-fs-mkdir",
|
||||
|
|
Loading…
Reference in New Issue