diff --git a/README.md b/README.md
index 3c94d5e..15eb4ab 100644
--- a/README.md
+++ b/README.md
@@ -850,7 +850,7 @@ Examples:
```javascript
// Append UTF8 text file
fs.writeFile('/myfile.txt', "More...", function (err) {
- if (err) throw err;
+ if (err) throw err;
});
fs.appendFile('/myfile.txt', "Data...", function (err) {
if (err) throw err;
@@ -861,7 +861,7 @@ fs.appendFile('/myfile.txt', "Data...", function (err) {
var more = new Uint8Array([1, 2, 3, 4]);
var data = new Uint8Array([5, 6, 7, 8]);
fs.writeFile('/myfile', more, function (err) {
- if (err) throw err;
+ if (err) throw err;
});
fs.appendFile('/myfile', buffer, function (err) {
if (err) throw err;
@@ -1324,7 +1324,7 @@ sh.tempDir(function(err, tmp) {
});
```
-#### sh.mkdirp(callback)
+#### sh.mkdirp(path, callback)
Recursively creates the directory at the provided path. If the
directory already exists, no error is returned. All parents must
@@ -1349,7 +1349,9 @@ Options object can currently have the following attributes (and their default va
recursive: true //default 'false'
size: 5 //default 750. File chunk size in Kb.
-checksum: false //default 'false'. False will skip files if their size AND modified times are the same (regardless of content difference)
+checksum: false //default 'false'. False will skip files if their size AND modified times are the same (regardless of content difference).
+time: true //default 'false'. Preserves file modified time when syncing.
+links: true //default 'false'. Copies symlinks as links instead of resolving.
Example:
diff --git a/src/hash.js b/src/hash.js
deleted file mode 100644
index c4006a3..0000000
--- a/src/hash.js
+++ /dev/null
@@ -1,53 +0,0 @@
-// RSync hashing algorithms
-// Based on node.js Anchor's hash.js
-// Used under MIT License
-// https://github.com/ttezel/anchor
-
-define(function(require) {
- require("crypto-js/rollups/md5");
-
- function md5(data) {
- return CryptoJS.MD5(String.fromCharCode(data)).toString();
- }
- function weak32(data, prev, start, end) {
- var a = 0,
- b = 0,
- sum = 0,
- M = 1 << 16;
-
- if (!prev) {
- var len = start >= 0 && end >= 0 ? end - start : data.length,
- i = 0;
-
- for (; i < len; i++) {
- a += data[i];
- b += a;
- }
-
- a %= M;
- b %= M;
- } else {
- var k = start,
- l = end - 1,
- prev_k = k - 1,
- prev_l = l - 1,
- prev_first = data[prev_k],
- prev_last = data[prev_l],
- curr_first = data[k],
- curr_last = data[l];
-
- a = (prev.a - prev_first + curr_last) % M
- b = (prev.b - (prev_l - prev_k + 1) * prev_first + a) % M
- }
- return { a: a, b: b, sum: a + b * M };
- }
- function weak16(data) {
- return 0xffff & (data >> 16 ^ data*1009);
- }
-
- return {
- md5: md5,
- weak16: weak16,
- weak32: weak32
- };
-});
diff --git a/src/rsync.js b/src/rsync.js
index 9bf7141..ff04455 100644
--- a/src/rsync.js
+++ b/src/rsync.js
@@ -1,17 +1,71 @@
// RSync Module for Filer
-// Based on the Anchor module for node.js (https://github.com/ttezel/anchor)
-// Used under MIT
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;
- var _weak32 = require('./hash').weak32;
var cache = {};
var options;
+ //MD5 hashing for RSync
+ //Used from Node.js Anchor module
+ //MIT Licensed
+ //https://github.com/ttezel/anchor
+ function _md5(data) {
+ return CryptoJS.MD5(String.fromCharCode(data)).toString();
+ }
+
+ //Weak32 hashing for RSync
+ //Used from Node.js Anchor module
+ //MIT Licensed
+ //https://github.com/ttezel/anchor
+ function _weak32(data, prev, start, end) {
+ var a = 0;
+ var b = 0;
+ var sum = 0;
+ var M = 1 << 16;
+
+ if (!prev) {
+ var len = start >= 0 && end >= 0 ? end - start : data.length;
+ var i = 0;
+
+ for (; i < len; i++) {
+ a += data[i];
+ b += a;
+ }
+
+ a %= M;
+ b %= M;
+ } else {
+ var k = start;
+ var l = end - 1;
+ var prev_k = k - 1;
+ var prev_l = l - 1;
+ var prev_first = data[prev_k];
+ var prev_last = data[prev_l];
+ var curr_first = data[k];
+ var curr_last = data[l];
+
+ a = (prev.a - prev_first + curr_last) % M;
+ b = (prev.b - (prev_l - prev_k + 1) * prev_first + a) % M;
+ }
+ return { a: a, b: b, sum: a + b * M };
+ }
+
+ //Weak16 hashing for RSync
+ //Used from Node.js Anchor module
+ //MIT Licensed
+ //https://github.com/ttezel/anchor
+ function _weak16(data) {
+ return 0xffff & (data >> 16 ^ data*1009);
+ }
+
+ /* RSync Algorithm function
+ * Copyright(c) 2011 Mihai Tomescu
+ * Copyright(c) 2011 Tolga Tezel
+ * https://github.com/ttezel/anchor
+ * MIT Licensed
+ */
function createHashtable(checksums) {
var hashtable = {};
var len = checksums.length;
@@ -28,6 +82,12 @@ define(function(require) {
return hashtable;
}
+ /* RSync Algorithm function
+ * Copyright(c) 2011 Mihai Tomescu
+ * Copyright(c) 2011 Tolga Tezel
+ * https://github.com/ttezel/anchor
+ * MIT Licensed
+ */
function roll(data, checksums, chunkSize) {
var results = [];
var hashtable = createHashtable(checksums);
@@ -96,11 +156,17 @@ define(function(require) {
options = {};
options.size = 750;
options.checksum = false;
+ options.recursive = false;
+ options.time = false;
+ options.links = false;
}
else {
options = opts || {};
options.size = options.size || 750;
options.checksum = options.checksum || false;
+ options.recursive = options.recursive || false;
+ options.time = options.time || false;
+ options.links = options.links || false;
callback = callback || function() {};
}
if(srcPath === null || srcPath === '/' || srcPath === '') {
@@ -110,7 +176,7 @@ define(function(require) {
function getSrcList(path, callback) {
var result = [];
- self.fs.stat(path, function(err, stats) {
+ self.fs.lstat(path, function(err, stats) {
if(err) {
callback(err);
return;
@@ -124,11 +190,13 @@ define(function(require) {
function getSrcContents(_name, callback) {
var name = Path.join(path, _name);
- self.fs.stat(name, function(error, stats) {
+ self.fs.lstat(name, function(error, stats) {
+
if(error) {
callback(error);
return;
}
+
var entry = {
path: Path.basename(name),
modified: stats.mtime,
@@ -145,10 +213,11 @@ define(function(require) {
result.push(entry);
callback();
});
- } else if(stats.isFile()) {
+ } else if(stats.isFile() || !options.links) {
result.push(entry);
callback();
- } else {
+ } else if (entry.type === 'SYMLINK'){
+ result.push(entry);
callback();
}
});
@@ -159,12 +228,12 @@ define(function(require) {
});
});
}
- else{
+ else {
var entry = {
path: Path.basename(path),
- modified: stats.mtime,
size: stats.size,
- type: stats.type
+ type: stats.type,
+ modified: stats.mtime
};
result.push(entry);
callback(err, result);
@@ -186,12 +255,12 @@ define(function(require) {
result.push(item);
callback();
});
- } else if(entry.type === 'FILE') {
- if(options.checksum === false) {
+ } else if(entry.type === 'FILE' || !options.links) {
+ if(!options.checksum) {
self.fs.stat(Path.join(destPath, entry.path), function(err, stat) {
if(!err && stat.mtime === entry.modified && stat.size === entry.size) {
- callback();
- }
+ callback();
+ }
else {
checksum.call(self, Path.join(destPath, entry.path), function(err, checksums) {
if(err) {
@@ -199,6 +268,7 @@ define(function(require) {
return;
}
item.checksum = checksums;
+ item.modified = entry.modified;
result.push(item);
callback();
});
@@ -212,13 +282,29 @@ define(function(require) {
return;
}
item.checksum = checksums;
+ item.modified = entry.modified;
result.push(item);
callback();
});
}
}
- else {
- callback();
+ else if(entry.type === 'SYMLINK'){
+ if(!options.checksum) {
+ self.fs.stat(Path.join(destPath, entry.path), function(err, stat){
+ if(!err && stat.mtime === entry.modified && stat.size === entry.size) {
+ callback();
+ }
+ else {
+ item.link = true;
+ result.push(item);
+ callback();
+ }
+ });
+ } else {
+ item.link = true;
+ result.push(item);
+ callback();
+ }
}
}
async.each(srcList, getDirChecksums, function(error) {
@@ -255,6 +341,13 @@ define(function(require) {
});
}
+ /* RSync Checksum Function
+ * Based on Node.js Anchor module checksum function
+ * Copyright(c) 2011 Mihai Tomescu
+ * Copyright(c) 2011 Tolga Tezel
+ * https://github.com/ttezel/anchor
+ * MIT Licensed
+ */
function checksum (path, callback) {
var self = this;
self.fs.readFile(path, function (err, data) {
@@ -293,26 +386,54 @@ define(function(require) {
});
}
+ /* RSync Checksum Function
+ * Based on Node.js Anchor module diff function
+ * Copyright(c) 2011 Mihai Tomescu
+ * Copyright(c) 2011 Tolga Tezel
+ * https://github.com/ttezel/anchor
+ * MIT Licensed
+ */
function diff(path, checksums, callback) {
var self = this;
// roll through the file
var diffs = [];
- self.fs.stat(path, function(err, stat) {
+ self.fs.lstat(path, function(err, stat) {
if(stat.isDirectory()) {
async.each(checksums, getDiff, function(err) {
callback(err, diffs);
});
}
- else {
+ else if (stat.isFile() || !options.links) {
self.fs.readFile(path, function (err, data) {
if (err) { return callback(err); }
diffs.push({
diff: roll(data, checksums[0].checksum, options.size),
+ modified: checksums[0].modified,
path: checksums[0].path
});
callback(err, diffs);
});
}
+ else if (stat.isSymbolicLink()) {
+ self.fs.readlink(path, function(err, linkContents) {
+ if(err) {
+ callback(err);
+ return;
+ }
+ self.fs.lstat(path, function(err, stats){
+ if(err) {
+ callback(err);
+ return;
+ }
+ diffs.push({
+ link: linkContents,
+ modified: stats.mtime,
+ path: path
+ });
+ callback(err, diffs);
+ });
+ });
+ }
});
function getDiff(entry, callback) {
@@ -328,11 +449,31 @@ define(function(require) {
});
callback();
});
+ } else if (entry.hasOwnProperty('link')) {
+ fs.readlink(Path.join(path, entry.path), function(err, linkContents) {
+ if(err) {
+ callback(err);
+ return;
+ }
+ fs.lstat(Path.join(path, entry.path), function(err, stats){
+ if(err) {
+ callback(err);
+ return;
+ }
+ diffs.push({
+ link: linkContents,
+ modified: stats.mtime,
+ path: entry.path
+ });
+ callback(err, diffs);
+ });
+ });
} else {
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),
+ modified: entry.modified,
path: entry.path
});
callback(err, diffs);
@@ -341,6 +482,13 @@ define(function(require) {
}
}
+ /* RSync Checksum Function
+ * Based on Node.js Anchor module sync function
+ * Copyright(c) 2011 Mihai Tomescu
+ * Copyright(c) 2011 Tolga Tezel
+ * https://github.com/ttezel/anchor
+ * MIT Licensed
+ */
function sync(path, diff, callback) {
var self = this;
@@ -362,7 +510,17 @@ define(function(require) {
callback();
});
}
- else {
+ else if (entry.hasOwnProperty('link')) {
+
+ var syncPath = Path.join(path,entry.path);
+ self.fs.symlink(entry.link, syncPath, function(err){
+ if(err) {
+ callback(err);
+ return;
+ }
+ return callback();
+ });
+ } else {
var raw = cache[Path.join(path,entry.path)];
var i = 0;
var len = entry.diff.length;
@@ -388,7 +546,18 @@ define(function(require) {
callback(err);
return;
}
- return callback(null);
+ if(options.time) {
+ self.fs.utimes(Path.join(path,entry.path), entry.modified, entry.modified, function(err) {
+ if(err) {
+ callback(err);
+ return;
+ }
+ return callback();
+ });
+ }
+ else {
+ return callback();
+ }
});
}
diff --git a/tests/spec/shell/rsync.spec.js b/tests/spec/shell/rsync.spec.js
index 09e75ed..f771416 100644
--- a/tests/spec/shell/rsync.spec.js
+++ b/tests/spec/shell/rsync.spec.js
@@ -9,7 +9,7 @@ define(["Filer", "util"], function(Filer, util) {
expect(shell.rsync).to.be.a('function');
});
- it('should fail without a source path provided (null)', function(done) {
+ it('should fail if source path is null', function(done) {
var fs = util.fs();
var shell = fs.Shell();
@@ -20,7 +20,7 @@ define(["Filer", "util"], function(Filer, util) {
});
});
- it('should fail without a source path provided (\'\')', function(done) {
+ it('should fail if source path is empty string', function(done) {
var fs = util.fs();
var shell = fs.Shell();
@@ -42,7 +42,7 @@ define(["Filer", "util"], function(Filer, util) {
});
});
- it('should fail with a non-existant path', function(done) {
+ it('should fail if source path doesn\'t exist', function(done) {
var fs = util.fs();
var shell = fs.Shell();
shell.rsync('/1.txt', '/', function(err) {
@@ -198,7 +198,7 @@ define(["Filer", "util"], function(Filer, util) {
});
});
- it('should suceed if the source file and destination file have the same mtime and size with \'checksum = true\' flag', function(done){
+ it('should succeed if the source file and destination file have the same mtime and size with \'checksum = true\' flag', function(done){
var fs = util.fs();
var shell = fs.Shell();
@@ -222,7 +222,139 @@ define(["Filer", "util"], function(Filer, util) {
});
});
- it('should succeed if the destination folder does not exist (Destination file created)', function(done) {
+ it('should succeed and update mtime with \'time = true\' flag', function(done) {
+ var fs = util.fs();
+ var shell = fs.Shell();
+ var mtime;
+
+ 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.stat('/1.txt', function(err, stats){
+ expect(err).to.not.exist;
+ expect(stats).to.exist;
+ mtime = stats.mtime;
+ shell.rsync('/1.txt', '/test', { time: true, 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.');
+ fs.stat('/test/1.txt', function(err, stats){
+ expect(err).to.not.exist;
+ expect(stats).to.exist;
+ expect(stats.mtime).to.equal(mtime);
+ done();
+ });
+ });
+ });
+ })
+ });
+ });
+ });
+
+ it('should copy a symlink as a file with \'links = false\' flag (Default)', 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 a file', function(err){
+ expect(err).to.not.exist;
+ fs.symlink('/1.txt', '/2', function(err){
+ expect(err).to.not.exist;
+ shell.rsync('/2', '/test', function(err){
+ expect(err).to.not.exist;
+ fs.unlink('/1.txt', function(err){
+ expect(err).to.not.exist;
+ fs.lstat('/test/2', function(err, stats){
+ expect(err).to.not.exist;
+ expect(stats).to.exist;
+ expect(stats.type).to.equal('FILE');
+ fs.readFile('/test/2', 'utf8', function(err, data){
+ expect(err).to.not.exist;
+ expect(data).to.exist;
+ expect(data).to.equal('This is a file');
+ done();
+ });
+ });
+ });
+ });
+ });
+ });
+ });
+ });
+
+ it('should copy a symlink as a file with \'links = true\' flag', function(done){
+ var fs = util.fs();
+ var shell = fs.Shell();
+
+ fs.mkdir('/test', function(err){
+ expect(err).to.not.exist;
+ fs.writeFile('/apple.txt', 'This is a file', function(err){
+ expect(err).to.not.exist;
+ fs.symlink('/apple.txt', '/apple', function(err){
+ expect(err).to.not.exist;
+ shell.rsync('/apple', '/test', { links:true }, function(err){
+ expect(err).to.not.exist;
+ fs.lstat('/test/apple', function(err, stats){
+ expect(err).to.not.exist;
+ expect(stats).to.exist;
+ expect(stats.type).to.equal('SYMLINK');
+ fs.readFile('/test/apple', 'utf8', function(err, data){
+ expect(err).to.not.exist;
+ expect(data).to.exist;
+ expect(data).to.equal('This is a file');
+ done();
+ });
+ });
+ });
+ });
+ });
+ });
+ });
+
+ it('should copy a symlink as a file with \'links = false\' flag and update time with \'time: true\' flag', function(done){
+ var fs = util.fs();
+ var shell = fs.Shell();
+ var mtime;
+
+ fs.mkdir('/test', function(err){
+ expect(err).to.not.exist;
+ fs.writeFile('/1.txt', 'This is a file', function(err){
+ expect(err).to.not.exist;
+ fs.symlink('/1.txt', '/2', function(err){
+ expect(err).to.not.exist;
+ fs.lstat('/2', function(err, stats){
+ expect(err).to.not.exist;
+ expect(stats).to.exist;
+ mtime = stats.mtime;
+ shell.rsync('/2', '/test', { time: true }, function(err){
+ expect(err).to.not.exist;
+ fs.unlink('/1.txt', function(err){
+ expect(err).to.not.exist;
+ fs.lstat('/test/2', function(err, stats){
+ expect(err).to.not.exist;
+ expect(stats).to.exist;
+ expect(stats.mtime).to.equal(mtime);
+ expect(stats.type).to.equal('FILE');
+ fs.readFile('/test/2', 'utf8', function(err, data){
+ expect(err).to.not.exist;
+ expect(data).to.exist;
+ expect(data).to.equal('This is a file');
+ done();
+ });
+ });
+ });
+ });
+ })
+ });
+ });
+ });
+ });
+
+ it('should succeed if the destination folder does not exist (Destination directory created)', function(done) {
var fs = util.fs();
var shell = fs.Shell();