зеркало из https://github.com/mozilla/makedrive.git
Fix #372 - Do not create conflicted copies if the client has a newer version of a file, instead upstream that file
This commit is contained in:
Родитель
2e7fecbc27
Коммит
f64388e0d1
|
@ -81,6 +81,7 @@ var SyncManager = require('./sync-manager.js');
|
|||
var SyncFileSystem = require('./sync-filesystem.js');
|
||||
var Filer = require('../../lib/filer.js');
|
||||
var EventEmitter = require('events').EventEmitter;
|
||||
var resolvePath = require('../../lib/sync-path-resolver.js').resolveFromArray;
|
||||
|
||||
var MakeDrive = {};
|
||||
module.exports = MakeDrive;
|
||||
|
@ -117,6 +118,11 @@ function createFS(options) {
|
|||
var autoSync;
|
||||
var pathCache;
|
||||
|
||||
// Path that needs to be used for an upstream sync
|
||||
// to sync files that were determined to be more
|
||||
// up-to-date on the client during a downstream sync
|
||||
var upstreamPath;
|
||||
|
||||
// State of the sync connection
|
||||
sync.SYNC_DISCONNECTED = "SYNC DISCONNECTED";
|
||||
sync.SYNC_CONNECTING = "SYNC CONNECTING";
|
||||
|
@ -151,6 +157,30 @@ function createFS(options) {
|
|||
manager = null;
|
||||
}
|
||||
|
||||
function requestSync(path) {
|
||||
// If we're not connected (or are already syncing), ignore this request
|
||||
if(sync.state === sync.SYNC_DISCONNECTED || sync.state === sync.SYNC_ERROR) {
|
||||
sync.emit('error', new Error('Invalid state. Expected ' + sync.SYNC_CONNECTED + ', got ' + sync.state));
|
||||
return;
|
||||
}
|
||||
|
||||
// If there were no changes to the filesystem and
|
||||
// no path was passed to sync, ignore this request
|
||||
if(!fs.pathToSync && !path) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If a path was passed sync using it
|
||||
if(path) {
|
||||
return manager.syncPath(path);
|
||||
}
|
||||
|
||||
// Cache the path that needs to be synced for error recovery
|
||||
pathCache = fs.pathToSync;
|
||||
fs.pathToSync = null;
|
||||
manager.syncPath(pathCache);
|
||||
}
|
||||
|
||||
// Turn on auto-syncing if its not already on
|
||||
sync.auto = function(interval) {
|
||||
var syncInterval = interval|0 > 0 ? interval|0 : 15 * 1000;
|
||||
|
@ -180,7 +210,7 @@ function createFS(options) {
|
|||
sync.onError = function(err) {
|
||||
// Regress to the path that needed to be synced but failed
|
||||
// (likely because of a sync LOCK)
|
||||
fs.pathToSync = pathCache;
|
||||
fs.pathToSync = upstreamPath || pathCache;
|
||||
sync.state = sync.SYNC_ERROR;
|
||||
sync.emit('error', err);
|
||||
};
|
||||
|
@ -200,21 +230,12 @@ function createFS(options) {
|
|||
|
||||
// Request that a sync begin.
|
||||
sync.request = function() {
|
||||
// If we're not connected (or are already syncing), ignore this request
|
||||
if(sync.state === sync.SYNC_DISCONNECTED || sync.state === sync.SYNC_ERROR) {
|
||||
sync.emit('error', new Error('Invalid state. Expected ' + sync.SYNC_CONNECTED + ', got ' + sync.state));
|
||||
return;
|
||||
}
|
||||
|
||||
// If there were no changes to the filesystem, ignore this request
|
||||
if(!fs.pathToSync) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Cache the path that needs to be synced for error recovery
|
||||
pathCache = fs.pathToSync;
|
||||
fs.pathToSync = null;
|
||||
manager.syncPath(pathCache);
|
||||
// sync.request does not take any parameters
|
||||
// as the path to sync is determined internally
|
||||
// requestSync on the other hand optionally takes
|
||||
// a path to sync which can be specified for
|
||||
// internal use
|
||||
requestSync();
|
||||
};
|
||||
|
||||
// Try to connect to the server.
|
||||
|
@ -234,7 +255,7 @@ function createFS(options) {
|
|||
// Upgrade connection state to `connecting`
|
||||
sync.state = sync.SYNC_CONNECTING;
|
||||
|
||||
function downstreamSyncCompleted() {
|
||||
function downstreamSyncCompleted(paths, needUpstream) {
|
||||
// Re-wire message handler functions for regular syncing
|
||||
// now that initial downstream sync is completed.
|
||||
sync.onSyncing = function() {
|
||||
|
@ -242,26 +263,36 @@ function createFS(options) {
|
|||
sync.emit('syncing');
|
||||
};
|
||||
|
||||
sync.onCompleted = function(paths) {
|
||||
sync.onCompleted = function(paths, needUpstream) {
|
||||
// If changes happened to the files that needed to be synced
|
||||
// during the sync itself, they will be overwritten
|
||||
// https://github.com/mozilla/makedrive/issues/129 and
|
||||
// https://github.com/mozilla/makedrive/issues/3
|
||||
// https://github.com/mozilla/makedrive/issues/129
|
||||
|
||||
function complete() {
|
||||
sync.state = sync.SYNC_CONNECTED;
|
||||
sync.emit('completed');
|
||||
}
|
||||
|
||||
if(!paths) {
|
||||
if(!paths && !needUpstream) {
|
||||
return complete();
|
||||
}
|
||||
|
||||
// Changes in the client are newer (determined during
|
||||
// the sync) and need to be upstreamed
|
||||
if(needUpstream) {
|
||||
upstreamPath = resolvePath(needUpstream);
|
||||
complete();
|
||||
return requestSync(upstreamPath);
|
||||
}
|
||||
|
||||
// If changes happened during a downstream sync
|
||||
// Change the path that needs to be synced
|
||||
manager.resetUnsynced(paths, function(err) {
|
||||
if(err) {
|
||||
return sync.onError(err);
|
||||
}
|
||||
|
||||
upstreamPath = null;
|
||||
complete();
|
||||
});
|
||||
};
|
||||
|
@ -285,6 +316,16 @@ function createFS(options) {
|
|||
}
|
||||
|
||||
sync.emit('connected');
|
||||
|
||||
// If the downstream was completed and some
|
||||
// versions of files were not synced as they were
|
||||
// newer on the client, upstream them
|
||||
if(needUpstream) {
|
||||
upstreamPath = resolvePath(needUpstream);
|
||||
requestSync(upstreamPath);
|
||||
} else {
|
||||
upstreamPath = null;
|
||||
}
|
||||
}
|
||||
|
||||
function connect(token) {
|
||||
|
@ -302,9 +343,9 @@ function createFS(options) {
|
|||
sync.onSyncing = function() {
|
||||
// do nothing, wait for onCompleted()
|
||||
};
|
||||
sync.onCompleted = function() {
|
||||
sync.onCompleted = function(paths, needUpstream) {
|
||||
// Downstream sync is done, finish connect() setup
|
||||
downstreamSyncCompleted();
|
||||
downstreamSyncCompleted(paths, needUpstream);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
|
|
@ -7,6 +7,8 @@ var deserializeDiff = require('../../lib/diff').deserialize;
|
|||
var states = require('./sync-states');
|
||||
var steps = require('./sync-steps');
|
||||
var dirname = require('../../lib/filer').Path.dirname;
|
||||
var async = require('../../lib/async-lite');
|
||||
var fsUtils = require('../../lib/fs-utils');
|
||||
|
||||
function onError(syncManager, err) {
|
||||
syncManager.session.step = steps.FAILED;
|
||||
|
@ -124,9 +126,47 @@ function handleResponse(syncManager, data) {
|
|||
}
|
||||
|
||||
function handlePatchAckResponse() {
|
||||
var syncedPaths = data.content.syncedPaths;
|
||||
session.state = states.READY;
|
||||
session.step = steps.SYNCED;
|
||||
sync.onCompleted(data.content.syncedPaths);
|
||||
|
||||
function stampChecksum(path, callback) {
|
||||
fs.lstat(path, function(err, stats) {
|
||||
if(err) {
|
||||
if(err.code !== 'ENOENT') {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
// Non-existent paths (usually due to renames or
|
||||
// deletes that are included in the syncedPaths)
|
||||
// cannot be stamped with a checksum
|
||||
return callback();
|
||||
}
|
||||
|
||||
if(!stats.isFile()) {
|
||||
return callback();
|
||||
}
|
||||
|
||||
rsyncUtils.getChecksum(fs, path, function(err, checksum) {
|
||||
if(err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
fsUtils.setChecksum(fs, path, checksum, callback);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// As soon as an upstream sync happens, the files synced
|
||||
// become the last synced versions and must be stamped
|
||||
// with their checksums to version them
|
||||
async.eachSeries(syncedPaths, stampChecksum, function(err) {
|
||||
if(err) {
|
||||
return onError(syncManager, err);
|
||||
}
|
||||
|
||||
sync.onCompleted(data.content.syncedPaths);
|
||||
});
|
||||
}
|
||||
|
||||
function handlePatchResponse() {
|
||||
|
@ -150,9 +190,11 @@ function handleResponse(syncManager, data) {
|
|||
return onError(syncManager, err);
|
||||
}
|
||||
|
||||
var size = rsyncOptions.size || 5;
|
||||
if(paths.needsUpstream.length) {
|
||||
session.needsUpstream = paths.needsUpstream;
|
||||
}
|
||||
|
||||
rsyncUtils.generateChecksums(fs, paths.synced, size, function(err, checksums) {
|
||||
rsyncUtils.generateChecksums(fs, paths.synced, true, function(err, checksums) {
|
||||
if(err) {
|
||||
var message = SyncMessage.response.reset;
|
||||
syncManager.send(message.stringify());
|
||||
|
@ -160,7 +202,7 @@ function handleResponse(syncManager, data) {
|
|||
}
|
||||
|
||||
var message = SyncMessage.response.patch;
|
||||
message.content = {checksums: checksums, size: size};
|
||||
message.content = {checksums: checksums};
|
||||
syncManager.send(message.stringify());
|
||||
});
|
||||
});
|
||||
|
@ -169,7 +211,9 @@ function handleResponse(syncManager, data) {
|
|||
function handleVerificationResponse() {
|
||||
session.srcList = null;
|
||||
session.step = steps.SYNCED;
|
||||
sync.onCompleted();
|
||||
var needsUpstream = session.needsUpstream;
|
||||
delete session.needsUpstream;
|
||||
sync.onCompleted(null, needsUpstream);
|
||||
}
|
||||
|
||||
function handleUpstreamResetResponse() {
|
||||
|
|
|
@ -7,7 +7,8 @@ module.exports = {
|
|||
|
||||
attributes: {
|
||||
unsynced: 'makedrive-unsynced',
|
||||
conflict: 'makedrive-conflict'
|
||||
conflict: 'makedrive-conflict',
|
||||
checksum: 'makedrive-checksum'
|
||||
},
|
||||
|
||||
server: {
|
||||
|
|
|
@ -59,22 +59,10 @@ function fremoveUnsynced(fs, fd, callback) {
|
|||
|
||||
// Set the unsynced metadata for a path
|
||||
function setUnsynced(fs, path, callback) {
|
||||
fs.setxattr(path, constants.attributes.unsynced, Date.now(), function(err) {
|
||||
if(err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
callback();
|
||||
});
|
||||
fs.setxattr(path, constants.attributes.unsynced, Date.now(), callback);
|
||||
}
|
||||
function fsetUnsynced(fs, fd, callback) {
|
||||
fs.fsetxattr(fd, constants.attributes.unsynced, Date.now(), function(err) {
|
||||
if(err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
callback();
|
||||
});
|
||||
fs.fsetxattr(fd, constants.attributes.unsynced, Date.now(), callback);
|
||||
}
|
||||
|
||||
// Get the unsynced metadata for a path
|
||||
|
@ -97,13 +85,71 @@ function fgetUnsynced(fs, fd, callback) {
|
|||
});
|
||||
}
|
||||
|
||||
// Remove the Checksum metadata from a path
|
||||
function removeChecksum(fs, path, callback) {
|
||||
fs.removexattr(path, constants.attributes.checksum, function(err) {
|
||||
if(err && err.code !== 'ENOATTR') {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
callback();
|
||||
});
|
||||
}
|
||||
function fremoveChecksum(fs, fd, callback) {
|
||||
fs.fremovexattr(fd, constants.attributes.checksum, function(err) {
|
||||
if(err && err.code !== 'ENOATTR') {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
callback();
|
||||
});
|
||||
}
|
||||
|
||||
// Set the Checksum metadata for a path
|
||||
function setChecksum(fs, path, checksum, callback) {
|
||||
fs.setxattr(path, constants.attributes.checksum, checksum, callback);
|
||||
}
|
||||
function fsetChecksum(fs, fd, checksum, callback) {
|
||||
fs.fsetxattr(fd, constants.attributes.checksum, checksum, callback);
|
||||
}
|
||||
|
||||
// Get the Checksum metadata for a path
|
||||
function getChecksum(fs, path, callback) {
|
||||
fs.getxattr(path, constants.attributes.checksum, function(err, value) {
|
||||
if(err && err.code !== 'ENOATTR') {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
callback(null, value);
|
||||
});
|
||||
}
|
||||
function fgetChecksum(fs, fd, callback) {
|
||||
fs.fgetxattr(fd, constants.attributes.checksum, function(err, value) {
|
||||
if(err && err.code !== 'ENOATTR') {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
callback(null, value);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
forceCopy: forceCopy,
|
||||
|
||||
// Unsynced attr utils
|
||||
isPathUnsynced: isPathUnsynced,
|
||||
removeUnsynced: removeUnsynced,
|
||||
fremoveUnsynced: fremoveUnsynced,
|
||||
setUnsynced: setUnsynced,
|
||||
fsetUnsynced: fsetUnsynced,
|
||||
getUnsynced: getUnsynced,
|
||||
fgetUnsynced: fgetUnsynced
|
||||
fgetUnsynced: fgetUnsynced,
|
||||
|
||||
// Checksum attr utils
|
||||
removeChecksum: removeChecksum,
|
||||
fremoveChecksum: fremoveChecksum,
|
||||
setChecksum: setChecksum,
|
||||
fsetChecksum: fsetChecksum,
|
||||
getChecksum: getChecksum,
|
||||
fgetChecksum: fgetChecksum
|
||||
};
|
||||
|
|
|
@ -22,7 +22,7 @@ module.exports = function checksums(fs, path, srcList, options, callback) {
|
|||
function checksumsForFile(checksumNode, sourceNode, callback) {
|
||||
|
||||
function generateChecksumsForFile() {
|
||||
rsyncUtils.checksum(fs, sourceNode.path, options.size, function(err, checksums) {
|
||||
rsyncUtils.blockChecksums(fs, sourceNode.path, options.size, function(err, checksums) {
|
||||
if(err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
@ -72,7 +72,7 @@ module.exports = function checksums(fs, path, srcList, options, callback) {
|
|||
return callback(err);
|
||||
}
|
||||
|
||||
rsyncUtils.checksum(fs, linkContents, options.size, function(err, checksums) {
|
||||
rsyncUtils.blockChecksums(fs, linkContents, options.size, function(err, checksums) {
|
||||
if(err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
|
|
@ -25,6 +25,21 @@ module.exports = function diff(fs, path, checksumList, options, callback) {
|
|||
this.modified = modifiedTime;
|
||||
}
|
||||
|
||||
// Compute the checksum for the file/link and
|
||||
// append it to the diffNode.
|
||||
function appendChecksum(diffNode, diffPath, callback) {
|
||||
rsyncUtils.getChecksum(fs, diffPath, function(err, checksum) {
|
||||
if(err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
diffNode.checksum = checksum;
|
||||
diffList.push(diffNode);
|
||||
|
||||
callback(null, diffList);
|
||||
});
|
||||
}
|
||||
|
||||
function diffsForLink(checksumNode, callback) {
|
||||
var checksumNodePath = checksumNode.path;
|
||||
var diffNode = new DiffNode(checksumNodePath, checksumNode.type, checksumNode.modified);
|
||||
|
@ -52,6 +67,13 @@ module.exports = function diff(fs, path, checksumList, options, callback) {
|
|||
}
|
||||
|
||||
diffNode.diffs = rsyncUtils.rollData(data, checksumNode.checksums, options.size);
|
||||
|
||||
// If versions are enabled, add the checksum
|
||||
// field to the diffNode for version comparison
|
||||
if(options.versions) {
|
||||
return appendChecksum(diffNode, linkContents, callback);
|
||||
}
|
||||
|
||||
diffList.push(diffNode);
|
||||
|
||||
callback(null, diffList);
|
||||
|
@ -77,6 +99,13 @@ module.exports = function diff(fs, path, checksumList, options, callback) {
|
|||
}
|
||||
|
||||
diffNode.diffs = rsyncUtils.rollData(data, checksumNode.checksums, options.size);
|
||||
|
||||
// If versions are enabled, add the checksum
|
||||
// field to the diffNode for version comparison
|
||||
if(options.versions) {
|
||||
return appendChecksum(diffNode, checksumNodePath, callback);
|
||||
}
|
||||
|
||||
diffList.push(diffNode);
|
||||
|
||||
callback(null, diffList);
|
||||
|
|
|
@ -29,7 +29,8 @@ module.exports = function patch(fs, path, diffList, options, callback) {
|
|||
|
||||
var paths = {
|
||||
synced: [],
|
||||
failed: []
|
||||
failed: [],
|
||||
needsUpstream: []
|
||||
};
|
||||
var pathsToSync = extractPathsFromDiffs(diffList);
|
||||
|
||||
|
@ -147,12 +148,14 @@ module.exports = function patch(fs, path, diffList, options, callback) {
|
|||
}
|
||||
|
||||
function maybeGenerateConflicted(nodePath, callback) {
|
||||
// If the file has not been synced upstream
|
||||
// and needs to be patched, create a conflicted copy
|
||||
fsUtils.isPathUnsynced(fs, nodePath, function(err, unsynced) {
|
||||
if(err) {
|
||||
return handleError(err, callback);
|
||||
}
|
||||
|
||||
// Do not generate a conflicted copy for an unsynced file
|
||||
// Generate a conflicted copy only for an unsynced file
|
||||
if(!unsynced) {
|
||||
return callback();
|
||||
}
|
||||
|
@ -180,6 +183,43 @@ module.exports = function patch(fs, path, diffList, options, callback) {
|
|||
var diffLength = diffNode.diffs ? diffNode.diffs.length : 0;
|
||||
var filePath = diffNode.path;
|
||||
|
||||
// Compare the version of the file when it was last
|
||||
// synced with the version of the diffNode by comparing
|
||||
// checksums and modified times.
|
||||
// If they match, the file is not patched and needs to
|
||||
// be upstreamed
|
||||
function compareVersions(data) {
|
||||
fs.lstat(filePath, function(err, stats) {
|
||||
if(err) {
|
||||
return handleError(err, callback);
|
||||
}
|
||||
|
||||
// If the file was modified before the
|
||||
// diffNode's modified time, the file is outdated
|
||||
// and needs to be patched
|
||||
if(stats.mtime <= diffNode.modified) {
|
||||
return applyPatch(getPatchedData(data));
|
||||
}
|
||||
|
||||
fsUtils.getChecksum(fs, filePath, function(err, checksum) {
|
||||
if(err) {
|
||||
return handleError(err, callback);
|
||||
}
|
||||
|
||||
// If the last synced checksum matches the
|
||||
// diffNode's checksum, ignore the patch
|
||||
// because it is a newer version than whats on
|
||||
// the server
|
||||
if(checksum === diffNode.checksum) {
|
||||
paths.needsUpstream.push(filePath);
|
||||
return callback(null, paths);
|
||||
}
|
||||
|
||||
applyPatch(getPatchedData(data));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function updateModifiedTime() {
|
||||
fs.utimes(filePath, diffNode.modified, diffNode.modified, function(err) {
|
||||
if(err) {
|
||||
|
@ -251,11 +291,25 @@ module.exports = function patch(fs, path, diffList, options, callback) {
|
|||
}
|
||||
|
||||
fs.readFile(filePath, function(err, data) {
|
||||
if(err && err.code !== 'ENOENT') {
|
||||
return handleError(err, callback);
|
||||
if(err) {
|
||||
if(err.code !== 'ENOENT') {
|
||||
return handleError(err, callback);
|
||||
}
|
||||
|
||||
// Patch a non-existent file i.e. create it
|
||||
return applyPatch(getPatchedData(new Buffer(0)));
|
||||
}
|
||||
|
||||
applyPatch(getPatchedData(data || new Buffer(0)));
|
||||
// If version comparing is not enabled, apply
|
||||
// the patch directly
|
||||
if(!options.versions) {
|
||||
return applyPatch(getPatchedData(data));
|
||||
}
|
||||
|
||||
// Check the last synced checksum with
|
||||
// the given checksum and don't patch if they
|
||||
// match
|
||||
compareVersions(data);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -27,6 +27,8 @@ var fsUtils = require('../fs-utils');
|
|||
// false: do not change modified times of destination files [default]
|
||||
// links - true: sync symbolic links as links in destination
|
||||
// false: sync symbolic links as the files they link to in destination [default]
|
||||
// versions - true: do not sync a node if the last synced version matches the version it needs to be synced to [default]
|
||||
// false: sync nodes irrespective of the last synced version
|
||||
function configureOptions(options) {
|
||||
if(!options || typeof options === 'function') {
|
||||
options = {};
|
||||
|
@ -37,6 +39,7 @@ function configureOptions(options) {
|
|||
options.recursive = options.recursive || false;
|
||||
options.time = options.time || false;
|
||||
options.links = options.links || false;
|
||||
options.versions = options.versions !== false;
|
||||
|
||||
return options;
|
||||
}
|
||||
|
@ -63,17 +66,6 @@ function validateParams(fs, param2) {
|
|||
return err;
|
||||
}
|
||||
|
||||
// This function has been taken from lodash
|
||||
// Licensed under the MIT license
|
||||
// https://github.com/lodash/lodash
|
||||
function sortObjects(list, prop) {
|
||||
return list.sort(function(a,b) {
|
||||
a = a[prop];
|
||||
b = b[prop];
|
||||
return (a === b) ? 0 : (a < b) ? -1 : 1;
|
||||
});
|
||||
}
|
||||
|
||||
// MD5 hashing for RSync
|
||||
function md5sum(data) {
|
||||
return MD5(data).toString();
|
||||
|
@ -211,8 +203,11 @@ function roll(data, checksums, blockSize) {
|
|||
return results;
|
||||
}
|
||||
|
||||
// RSync function to calculate checksums
|
||||
function checksum (fs, path, size, callback) {
|
||||
// Rsync function to calculate checksums for
|
||||
// a file by dividing it into blocks of data
|
||||
// whose size is passed in and checksuming each
|
||||
// block of data
|
||||
function blockChecksums(fs, path, size, callback) {
|
||||
var cache = {};
|
||||
|
||||
fs.readFile(path, function (err, data) {
|
||||
|
@ -256,10 +251,29 @@ function checksum (fs, path, size, callback) {
|
|||
});
|
||||
}
|
||||
|
||||
// Generate the MD5 hash for the data of a file
|
||||
// in its entirety
|
||||
function getChecksum(fs, path, callback) {
|
||||
fs.readFile(path, function(err, data) {
|
||||
if(!err) {
|
||||
callback(null, md5sum(data));
|
||||
} else if(err.code === 'ENOENT') {
|
||||
// File does not exist so the checksum is an empty string
|
||||
callback(null, "");
|
||||
} else {
|
||||
callback(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Generate checksums for an array of paths to be used for comparison
|
||||
function generateChecksums(fs, paths, blockSize, callback) {
|
||||
if(!blockSize || typeof callback !== 'function') {
|
||||
return callback(new Errors.EINVAL('Insufficient data provided'));
|
||||
// It also takes an optional parameter called stampNode, a boolean which
|
||||
// indicates whether the checksum should be stamped as an xattr on the node.
|
||||
function generateChecksums(fs, paths, stampNode, callback) {
|
||||
// Maybe stampNode was not passed in
|
||||
if(typeof callback !== 'function') {
|
||||
callback = findCallback(callback, stampNode);
|
||||
stampNode = false;
|
||||
}
|
||||
|
||||
var paramError = validateParams(fs, paths);
|
||||
|
@ -267,56 +281,81 @@ function generateChecksums(fs, paths, blockSize, callback) {
|
|||
return callback(paramError);
|
||||
}
|
||||
|
||||
var checksums = [];
|
||||
var checksumList = [];
|
||||
|
||||
function ChecksumNode(path, checksum) {
|
||||
function ChecksumNode(path, type, checksum) {
|
||||
this.path = path;
|
||||
this.checksum = checksum || [];
|
||||
this.type = type;
|
||||
this.checksum = checksum;
|
||||
}
|
||||
|
||||
function addChecksumNode(path, nodeType, checksum, callback) {
|
||||
var checksumNode;
|
||||
|
||||
// If no checksum was passed in
|
||||
if(typeof checksum === 'function') {
|
||||
callback = checksum;
|
||||
checksumNode = new ChecksumNode(path, nodeType);
|
||||
} else {
|
||||
checksumNode = new ChecksumNode(path, nodeType, checksum);
|
||||
}
|
||||
|
||||
checksumList.push(checksumNode);
|
||||
callback();
|
||||
}
|
||||
|
||||
// Only calculate the checksums for synced paths
|
||||
function maybeAddChecksumNode(path, nodeType, callback) {
|
||||
fsUtils.isPathUnsynced(fs, path, function(err, unsynced) {
|
||||
if(err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
if(unsynced) {
|
||||
return callback();
|
||||
}
|
||||
|
||||
getChecksum(fs, path, function(err, checksum) {
|
||||
if(err) {
|
||||
return callback(err);
|
||||
}
|
||||
// If we shouldn't add the checksum stamp or
|
||||
// the node does not exist (cannot add a stamp)
|
||||
// immediately add the checksum
|
||||
if(!stampNode || checksum === "") {
|
||||
return addChecksumNode(path, nodeType, checksum, callback);
|
||||
}
|
||||
|
||||
// Stamp the node with the checksum
|
||||
fsUtils.setChecksum(fs, path, checksum, function(err) {
|
||||
if(err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
addChecksumNode(path, nodeType, checksum, callback);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function calcChecksum(path, callback) {
|
||||
var checksumNode;
|
||||
|
||||
fs.lstat(path, function(err, stat) {
|
||||
var nodeType = stat && stat.type;
|
||||
|
||||
if(err) {
|
||||
if(err.code !== 'ENOENT') {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
checksumNode = new ChecksumNode(path);
|
||||
checksums.push(checksumNode);
|
||||
|
||||
return callback();
|
||||
// Checksums for non-existent files
|
||||
maybeAddChecksumNode(path, nodeType, callback);
|
||||
} else if(stat.isDirectory()) {
|
||||
// Directory checksums are not calculated i.e. are undefined
|
||||
addChecksumNode(path, nodeType, callback);
|
||||
} else {
|
||||
// Checksums for synced files/links
|
||||
maybeAddChecksumNode(path, nodeType, callback);
|
||||
}
|
||||
|
||||
// Use contents of directory instead of checksums
|
||||
if(stat.isDirectory()) {
|
||||
checksumNode = new ChecksumNode(path);
|
||||
checksums.push(checksumNode);
|
||||
return callback();
|
||||
}
|
||||
|
||||
fsUtils.isPathUnsynced(fs, path, function(err, unsynced) {
|
||||
if(err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
if(unsynced) {
|
||||
return callback();
|
||||
}
|
||||
|
||||
// Calculate checksums for file or symbolic links
|
||||
checksum(fs, path, blockSize, function(err, chksum) {
|
||||
if(err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
checksumNode = new ChecksumNode(path, chksum);
|
||||
checksums.push(checksumNode);
|
||||
|
||||
callback();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -325,7 +364,7 @@ function generateChecksums(fs, paths, blockSize, callback) {
|
|||
return callback(err);
|
||||
}
|
||||
|
||||
callback(null, checksums);
|
||||
callback(null, checksumList);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -333,79 +372,45 @@ function generateChecksums(fs, paths, blockSize, callback) {
|
|||
// checksums for a collection of paths in one file system
|
||||
// against the checksums for the same those paths in
|
||||
// another file system
|
||||
function compareContents(fs, checksums, blockSize, callback) {
|
||||
var EDIFF = 'DIFF';
|
||||
function compareContents(fs, checksumList, callback) {
|
||||
var ECHKSUM = "Checksums do not match";
|
||||
|
||||
if(!blockSize || typeof callback !== 'function') {
|
||||
return callback(new Errors.EINVAL('Insufficient data provided'));
|
||||
}
|
||||
|
||||
var paramError = validateParams(fs, checksums);
|
||||
var paramError = validateParams(fs, checksumList);
|
||||
if(paramError) {
|
||||
return callback(paramError);
|
||||
}
|
||||
|
||||
// Check if two checksum arrays are equal
|
||||
function isEqual(checksumNode1, checksumNode2) {
|
||||
var comparisonLength = checksumNode2.length;
|
||||
var checksum1, checksum2;
|
||||
|
||||
if(checksumNode1.length !== comparisonLength) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Sort the checksum objects in each array by the 'index' property
|
||||
checksumNode1 = sortObjects(checksumNode1, 'index');
|
||||
checksumNode2 = sortObjects(checksumNode2, 'index');
|
||||
|
||||
// Compare each object's checksums
|
||||
for(var i = 0; i < comparisonLength; i++) {
|
||||
checksum1 = checksumNode1[i];
|
||||
checksum2 = checksumNode2[i];
|
||||
|
||||
if(checksum1.weak !== checksum2.weak ||
|
||||
checksum1.strong !== checksum2.strong) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function compare(checksumNode, callback) {
|
||||
var path = checksumNode.path;
|
||||
|
||||
fs.lstat(path, function(err, stat) {
|
||||
if(err) {
|
||||
if(err.code !== 'ENOENT') {
|
||||
return callback(err);
|
||||
}
|
||||
if(err && err.code !== 'ENOENT') {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
// Checksums for a non-existent path are empty
|
||||
if(checksumNode.checksum && !checksumNode.checksum.length) {
|
||||
// If the types of the nodes on each fs do not match
|
||||
// i.e. /a is a file on fs1 and /a is a directory on fs2
|
||||
if(!err && checksumNode.type !== stat.type) {
|
||||
return callback(ECHKSUM);
|
||||
}
|
||||
|
||||
// If the node type is a directory, checksum should not exist
|
||||
if(!err && stat.isDirectory()) {
|
||||
if(!checksumNode.checksum) {
|
||||
return callback();
|
||||
}
|
||||
|
||||
return callback(EDIFF);
|
||||
callback(ECHKSUM);
|
||||
}
|
||||
|
||||
// Directory comparison of contents
|
||||
if(stat.isDirectory()) {
|
||||
return callback();
|
||||
}
|
||||
|
||||
if(!checksumNode.checksum) {
|
||||
return callback(EDIFF);
|
||||
}
|
||||
|
||||
// Compare checksums for two files/symbolic links
|
||||
checksum(fs, path, blockSize, function(err, checksum) {
|
||||
// Checksum comparison for a non-existent path or file/link
|
||||
getChecksum(fs, path, function(err, checksum) {
|
||||
if(err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
if(!isEqual(checksum, checksumNode.checksum)) {
|
||||
return callback(EDIFF);
|
||||
if(checksum !== checksumNode.checksum) {
|
||||
return callback(ECHKSUM);
|
||||
}
|
||||
|
||||
callback();
|
||||
|
@ -413,21 +418,18 @@ function compareContents(fs, checksums, blockSize, callback) {
|
|||
});
|
||||
}
|
||||
|
||||
async.eachSeries(checksums, compare, function(err) {
|
||||
if(err && err !== EDIFF) {
|
||||
return callback(err, false);
|
||||
async.eachSeries(checksumList, compare, function(err) {
|
||||
if(err && err !== ECHKSUM) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
if(err === EDIFF) {
|
||||
return callback(null, false);
|
||||
}
|
||||
|
||||
callback(null, true);
|
||||
callback(null, err !== ECHKSUM);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
checksum: checksum,
|
||||
blockChecksums: blockChecksums,
|
||||
getChecksum: getChecksum,
|
||||
rollData: roll,
|
||||
generateChecksums: generateChecksums,
|
||||
compareContents: compareContents,
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
/**
|
||||
* Sync path resolver is a library that provides
|
||||
* functionality to determine 'syncable' paths
|
||||
* It exposes the following method:
|
||||
* It exposes the following methods:
|
||||
*
|
||||
* resolve - This method takes two paths as arguments.
|
||||
* The goal is to find the most common ancestor
|
||||
* between them. For e.g. the most common ancestor
|
||||
* between '/dir' and '/dir/file.txt' is '/dir' while
|
||||
* between '/dir' and '/file.txt' would be '/'.
|
||||
* resolve - This method takes two paths as arguments.
|
||||
* The goal is to find the most common ancestor
|
||||
* between them. For e.g. the most common ancestor
|
||||
* between '/dir' and '/dir/file.txt' is '/dir' while
|
||||
* between '/dir' and '/file.txt' would be '/'.
|
||||
*
|
||||
* resolveFromArray - This method works exactly like resolve but works for arrays of paths instead
|
||||
*/
|
||||
|
||||
var pathResolver = {};
|
||||
|
@ -59,4 +60,18 @@ pathResolver.resolve = function(path1, path2) {
|
|||
return commonAncestor(path1, path1Depth, path2, path2Depth);
|
||||
};
|
||||
|
||||
pathResolver.resolveFromArray = function(paths) {
|
||||
if(!paths) {
|
||||
return '/';
|
||||
}
|
||||
|
||||
var resolvedPath, i;
|
||||
|
||||
for(i = 1, resolvedPath = paths[0]; i < paths.length; i++) {
|
||||
resolvedPath = pathResolver.resolve(resolvedPath, paths[i]);
|
||||
}
|
||||
|
||||
return resolvedPath;
|
||||
};
|
||||
|
||||
module.exports = pathResolver;
|
||||
|
|
|
@ -631,9 +631,8 @@ SyncProtocolHandler.prototype.handlePatchResponse = function(message) {
|
|||
}
|
||||
|
||||
var checksums = message.content.checksums;
|
||||
var size = message.content.size || 5;
|
||||
|
||||
rsync.utils.compareContents(client.fs, checksums, size, function(err, equal) {
|
||||
rsync.utils.compareContents(client.fs, checksums, function(err, equal) {
|
||||
var response;
|
||||
|
||||
// We need to check if equal is true because equal can have three possible
|
||||
|
|
|
@ -439,14 +439,12 @@ var downstreamSyncSteps = {
|
|||
rsync.patch(fs, data.path, data.diffs, rsyncOptions, function(err, paths) {
|
||||
expect(err, "[Rsync patch error: \"" + err + "\"]").not.to.exist;
|
||||
|
||||
var size = rsyncOptions.size || 5;
|
||||
|
||||
rsyncUtils.generateChecksums(fs, paths.synced, size, function(err, checksums) {
|
||||
rsyncUtils.generateChecksums(fs, paths.synced, function(err, checksums) {
|
||||
expect(err, "[Rsync path checksum error: \"" + err + "\"]").not.to.exist;
|
||||
expect(checksums).to.exist;
|
||||
|
||||
var patchResponse = SyncMessage.response.patch;
|
||||
patchResponse.content = {checksums: checksums, size: size};
|
||||
patchResponse.content = {checksums: checksums};
|
||||
|
||||
socketPackage.socket.send(patchResponse.stringify());
|
||||
});
|
||||
|
@ -936,6 +934,7 @@ function ensureFilesystem(fs, layout, callback) {
|
|||
if(err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
ensureFilesystemContents(fs, layout, callback);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -4,7 +4,6 @@ var SyncMessage = require('../../lib/syncmessage');
|
|||
var MakeDrive = require('../../client/src');
|
||||
var Filer = require('../../lib/filer.js');
|
||||
var fsUtils = require('../../lib/fs-utils.js');
|
||||
var conflict = require('../../lib/conflict.js');
|
||||
|
||||
describe("Server bugs", function() {
|
||||
describe("[Issue 169]", function() {
|
||||
|
@ -77,24 +76,22 @@ describe('Client bugs', function() {
|
|||
});
|
||||
|
||||
describe('[Issue 372]', function(){
|
||||
|
||||
function findConflictedFilename(entries) {
|
||||
entries.splice(entries.indexOf('hello'), 1);
|
||||
return Filer.Path.join('/', entries[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* This test creates a file and sync then disconenct
|
||||
* and change the file's content then try to connect and sync again.
|
||||
*/
|
||||
it('should sync and create conflicted copy', function(done) {
|
||||
it('should upstream newer changes made when disconnected and not create a conflicted copy', function(done) {
|
||||
var fs = MakeDrive.fs({provider: provider, manual: true, forceCreate: true});
|
||||
var sync = fs.sync;
|
||||
var jar;
|
||||
|
||||
util.authenticatedConnection(function( err, result ) {
|
||||
util.authenticatedConnection(function(err, result) {
|
||||
if(err) throw err;
|
||||
|
||||
var layout = {'/hello': 'hello'};
|
||||
var layout = {'/hello': 'hello',
|
||||
'/dir/world': 'world'
|
||||
};
|
||||
jar = result.jar;
|
||||
|
||||
sync.once('connected', function onConnected() {
|
||||
util.createFilesystemLayout(fs, layout, function(err) {
|
||||
|
@ -111,33 +108,21 @@ describe('Client bugs', function() {
|
|||
sync.once('disconnected', function onDisconnected() {
|
||||
// Re-sync with server and make sure we get our empty dir back
|
||||
sync.once('connected', function onSecondDownstreamSync() {
|
||||
fs.readdir('/', function(err, entries) {
|
||||
if(err) throw err;
|
||||
layout['/hello'] = 'hello world';
|
||||
|
||||
expect(entries).to.have.length(2);
|
||||
expect(entries).to.include('hello');
|
||||
|
||||
// Make sure this is a real conflicted copy, both in name
|
||||
// and also in terms of attributes on the file.
|
||||
var conflictedCopyFilename = findConflictedFilename(entries);
|
||||
|
||||
conflict.isConflictedCopy(fs, conflictedCopyFilename, function(err, conflicted) {
|
||||
expect(err).not.to.exist;
|
||||
expect(conflicted).to.be.true;
|
||||
|
||||
// Make sure the conflicted copy has the changes we expect
|
||||
fs.readFile(conflictedCopyFilename, 'utf8', function(err, data) {
|
||||
if(err) throw err;
|
||||
|
||||
// Should have the modified content
|
||||
expect(data).to.equal('hello world');
|
||||
done();
|
||||
});
|
||||
});
|
||||
util.ensureFilesystem(fs, layout, function(err) {
|
||||
expect(err).not.to.exist;
|
||||
});
|
||||
});
|
||||
|
||||
util.ensureRemoteFilesystem(layout, result.jar, function(err) {
|
||||
sync.once('completed', function reconnectedUpstream() {
|
||||
util.ensureRemoteFilesystem(layout, jar, function(err) {
|
||||
expect(err).not.to.exist;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
util.ensureRemoteFilesystem(layout, jar, function(err) {
|
||||
if(err) throw err;
|
||||
|
||||
fs.writeFile('/hello', 'hello world', function (err) {
|
||||
|
@ -152,6 +137,7 @@ describe('Client bugs', function() {
|
|||
util.getWebsocketToken(result, function(err, result) {
|
||||
if(err) throw err;
|
||||
|
||||
jar = result.jar;
|
||||
sync.connect(util.socketURL, result.token);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -3,6 +3,7 @@ var util = require('../lib/util.js');
|
|||
var Filer = require('../../lib/filer.js');
|
||||
var FileSystem = Filer.FileSystem;
|
||||
var fsUtils = require('../../lib/fs-utils.js');
|
||||
var CHECKSUM = require('MD5')('This is data').toString();
|
||||
|
||||
describe('MakeDrive fs-utils.js', function(){
|
||||
var fs;
|
||||
|
@ -34,6 +35,12 @@ describe('MakeDrive fs-utils.js', function(){
|
|||
expect(fsUtils.fsetUnsynced).to.be.a.function;
|
||||
expect(fsUtils.getUnsynced).to.be.a.function;
|
||||
expect(fsUtils.fgetUnsynced).to.be.a.function;
|
||||
expect(fsUtils.removeChecksum).to.be.a.function;
|
||||
expect(fsUtils.fremoveChecksum).to.be.a.function;
|
||||
expect(fsUtils.setChecksum).to.be.a.function;
|
||||
expect(fsUtils.fsetChecksum).to.be.a.function;
|
||||
expect(fsUtils.getChecksum).to.be.a.function;
|
||||
expect(fsUtils.fgetChecksum).to.be.a.function;
|
||||
});
|
||||
|
||||
it('should copy an existing file on forceCopy()', function(done) {
|
||||
|
@ -127,7 +134,7 @@ describe('MakeDrive fs-utils.js', function(){
|
|||
});
|
||||
});
|
||||
|
||||
it('should work with fd vs. path', function(done) {
|
||||
it('should work with fd vs. path for unsynced metadata', function(done) {
|
||||
fs.open('/dir/file', 'w', function(err, fd) {
|
||||
if(err) throw err;
|
||||
|
||||
|
@ -154,4 +161,63 @@ describe('MakeDrive fs-utils.js', function(){
|
|||
});
|
||||
});
|
||||
|
||||
it('should give checksum for getChecksum() if path has checksum metadata', function(done) {
|
||||
fsUtils.setChecksum(fs, '/dir/file', CHECKSUM, function(err) {
|
||||
expect(err).not.to.exist;
|
||||
|
||||
fsUtils.getChecksum(fs, '/dir/file', function(err, checksum) {
|
||||
expect(err).not.to.exist;
|
||||
expect(checksum).to.equal(CHECKSUM);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should remove checksum metadata when calling removeChecksum()', function(done) {
|
||||
fsUtils.setChecksum(fs, '/dir/file', CHECKSUM, function(err) {
|
||||
expect(err).not.to.exist;
|
||||
|
||||
fsUtils.getChecksum(fs, '/dir/file', function(err, checksum) {
|
||||
expect(err).not.to.exist;
|
||||
expect(checksum).to.equal(CHECKSUM);
|
||||
|
||||
fsUtils.removeChecksum(fs, '/dir/file', function(err) {
|
||||
expect(err).not.to.exist;
|
||||
|
||||
fsUtils.getChecksum(fs, '/dir/file', function(err, checksum) {
|
||||
expect(err).not.to.exist;
|
||||
expect(checksum).not.to.exist;
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should work with fd vs. path for checksum metadata', function(done) {
|
||||
fs.open('/dir/file', 'w', function(err, fd) {
|
||||
if(err) throw err;
|
||||
|
||||
fsUtils.fsetChecksum(fs, fd, CHECKSUM, function(err) {
|
||||
expect(err).not.to.exist;
|
||||
|
||||
fsUtils.fgetChecksum(fs, fd, function(err, checksum) {
|
||||
expect(err).not.to.exist;
|
||||
expect(checksum).to.equal(CHECKSUM);
|
||||
|
||||
fsUtils.fremoveChecksum(fs, fd, function(err) {
|
||||
expect(err).not.to.exist;
|
||||
|
||||
fsUtils.fgetChecksum(fs, fd, function(err, checksum) {
|
||||
expect(err).not.to.exist;
|
||||
expect(checksum).not.to.exist;
|
||||
|
||||
fs.close(fd);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -3,9 +3,9 @@ var expect = require('chai').expect;
|
|||
var fs;
|
||||
var fs2;
|
||||
var provider;
|
||||
var CHUNK_SIZE = 5;
|
||||
var rsyncUtils = require('../../lib/rsync').utils;
|
||||
var testUtils = require('../lib/util.js');
|
||||
var fsUtils = require('../../lib/fs-utils');
|
||||
|
||||
function fsInit() {
|
||||
provider = new Filer.FileSystem.providers.Memory("rsync1");
|
||||
|
@ -37,7 +37,7 @@ describe('[Rsync Util Tests]', function() {
|
|||
it('should return an EINVAL error if a filesystem is not provided', function (done) {
|
||||
var filesystem;
|
||||
|
||||
rsyncUtils.generateChecksums(filesystem, [], CHUNK_SIZE, function (err, checksums) {
|
||||
rsyncUtils.generateChecksums(filesystem, [], function (err, checksums) {
|
||||
expect(err).to.exist;
|
||||
expect(err.code).to.equal('EINVAL');
|
||||
expect(checksums).to.not.exist;
|
||||
|
@ -46,16 +46,7 @@ describe('[Rsync Util Tests]', function() {
|
|||
});
|
||||
|
||||
it('should return an EINVAL error if no paths are provided', function (done) {
|
||||
rsyncUtils.generateChecksums(fs, null, CHUNK_SIZE, function (err, checksums) {
|
||||
expect(err).to.exist;
|
||||
expect(err.code).to.equal('EINVAL');
|
||||
expect(checksums).to.not.exist;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should return an error if chunk size is not provided', function (done) {
|
||||
rsyncUtils.generateChecksums(fs, [], null, function (err, checksums) {
|
||||
rsyncUtils.generateChecksums(fs, null, function (err, checksums) {
|
||||
expect(err).to.exist;
|
||||
expect(err.code).to.equal('EINVAL');
|
||||
expect(checksums).to.not.exist;
|
||||
|
@ -64,7 +55,7 @@ describe('[Rsync Util Tests]', function() {
|
|||
});
|
||||
|
||||
it('should return empty checksums if empty paths are provided', function (done) {
|
||||
rsyncUtils.generateChecksums(fs, [], CHUNK_SIZE, function (err, checksums) {
|
||||
rsyncUtils.generateChecksums(fs, [], function (err, checksums) {
|
||||
expect(err).to.not.exist;
|
||||
expect(checksums).to.exist;
|
||||
expect(checksums).to.have.length(0);
|
||||
|
@ -72,13 +63,13 @@ describe('[Rsync Util Tests]', function() {
|
|||
});
|
||||
});
|
||||
|
||||
it('should return an empty checksum if the path to the node provided does not exist', function (done) {
|
||||
rsyncUtils.generateChecksums(fs, ['/myfile.txt'], CHUNK_SIZE, function (err, checksums) {
|
||||
it('should return an empty hash checksum if the path to the node provided does not exist', function (done) {
|
||||
rsyncUtils.generateChecksums(fs, ['/myfile.txt'], function (err, checksums) {
|
||||
expect(err).to.not.exist;
|
||||
expect(checksums).to.exist;
|
||||
expect(checksums).to.have.length(1);
|
||||
expect(checksums[0]).to.include.keys('checksum');
|
||||
expect(checksums[0].checksum).to.have.length(0);
|
||||
expect(checksums[0].checksum).to.equal('');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
@ -86,12 +77,12 @@ describe('[Rsync Util Tests]', function() {
|
|||
it('should return empty checksums for a directory path', function (done) {
|
||||
fs.mkdir('/dir', function (err) {
|
||||
if(err) throw err;
|
||||
rsyncUtils.generateChecksums(fs, ['/dir'], CHUNK_SIZE, function (err, checksums) {
|
||||
rsyncUtils.generateChecksums(fs, ['/dir'], function (err, checksums) {
|
||||
expect(err).to.not.exist;
|
||||
expect(checksums).to.exist;
|
||||
expect(checksums).to.have.length(1);
|
||||
expect(checksums[0]).to.include.keys('checksum');
|
||||
expect(checksums[0].checksum).to.have.length(0);
|
||||
expect(checksums[0].checksum).to.be.undefined;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
@ -109,26 +100,58 @@ describe('[Rsync Util Tests]', function() {
|
|||
testUtils.createFilesystemLayout(fs, layout, function (err) {
|
||||
if(err) throw err;
|
||||
|
||||
rsyncUtils.generateChecksums(fs, paths, CHUNK_SIZE, function (err, checksums) {
|
||||
rsyncUtils.generateChecksums(fs, paths, function (err, checksums) {
|
||||
expect(err).to.not.exist;
|
||||
expect(checksums).to.exist;
|
||||
expect(checksums).to.have.length(paths.length);
|
||||
expect(checksums[0]).to.include.keys('checksum');
|
||||
expect(checksums[0].checksum).to.have.length(0);
|
||||
expect(checksums[0].checksum).to.be.undefined;
|
||||
expect(checksums[1]).to.include.keys('checksum');
|
||||
expect(checksums[1].checksum).to.have.length.above(0);
|
||||
expect(checksums[2]).to.include.keys('checksum');
|
||||
expect(checksums[2].checksum).to.have.length(0);
|
||||
expect(checksums[2].checksum).to.be.undefined;
|
||||
expect(checksums[3]).to.include.keys('checksum');
|
||||
expect(checksums[3].checksum).to.have.length(0);
|
||||
expect(checksums[3].checksum).to.be.undefined;
|
||||
expect(checksums[4]).to.include.keys('checksum');
|
||||
expect(checksums[4].checksum).to.have.length(0);
|
||||
expect(checksums[4].checksum).to.be.undefined;
|
||||
expect(checksums[5]).to.include.keys('checksum');
|
||||
expect(checksums[5].checksum).to.have.length.above(0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should stamp checksums onto files if stampNode parameter is passed', function (done) {
|
||||
var layout = {'/dir1/file1': 'This is a file',
|
||||
'/dir2': null,
|
||||
'/file2': 'This is another file'};
|
||||
var paths = Object.keys(layout);
|
||||
|
||||
testUtils.createFilesystemLayout(fs, layout, function (err) {
|
||||
if(err) throw err;
|
||||
|
||||
rsyncUtils.generateChecksums(fs, paths, true, function (err, checksums) {
|
||||
expect(err).not.to.exist;
|
||||
expect(checksums).to.exist;
|
||||
|
||||
fsUtils.getChecksum(fs, '/dir1/file1', function (err, checksum) {
|
||||
expect(err).not.to.exist;
|
||||
expect(checksum).to.be.a('string');
|
||||
expect(checksum).to.have.length.above(0);
|
||||
fsUtils.getChecksum(fs, '/dir2', function (err, checksum) {
|
||||
expect(err).not.to.exist;
|
||||
expect(checksum).to.be.undefined;
|
||||
fsUtils.getChecksum(fs, '/file2', function (err, checksum) {
|
||||
expect(err).not.to.exist;
|
||||
expect(checksum).to.be.a('string');
|
||||
expect(checksum).to.have.length.above(0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Rsync CompareContents', function() {
|
||||
|
@ -143,7 +166,7 @@ describe('[Rsync Util Tests]', function() {
|
|||
it('should return an EINVAL error if a filesystem is not provided', function (done) {
|
||||
var filesystem;
|
||||
|
||||
rsyncUtils.compareContents(filesystem, [], CHUNK_SIZE, function (err, equal) {
|
||||
rsyncUtils.compareContents(filesystem, [], function (err, equal) {
|
||||
expect(err).to.exist;
|
||||
expect(err.code).to.equal('EINVAL');
|
||||
expect(equal).to.not.exist;
|
||||
|
@ -152,16 +175,7 @@ describe('[Rsync Util Tests]', function() {
|
|||
});
|
||||
|
||||
it('should return an EINVAL error if no checksums are provided', function (done) {
|
||||
rsyncUtils.compareContents(fs, null, CHUNK_SIZE, function (err, equal) {
|
||||
expect(err).to.exist;
|
||||
expect(err.code).to.equal('EINVAL');
|
||||
expect(equal).to.not.exist;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should return an error if chunk size is not provided', function (done) {
|
||||
rsyncUtils.compareContents(fs, [], null, function (err, equal) {
|
||||
rsyncUtils.compareContents(fs, null, function (err, equal) {
|
||||
expect(err).to.exist;
|
||||
expect(err.code).to.equal('EINVAL');
|
||||
expect(equal).to.not.exist;
|
||||
|
@ -170,7 +184,7 @@ describe('[Rsync Util Tests]', function() {
|
|||
});
|
||||
|
||||
it('should return true if a checksum is provided for a path that does not exist', function (done) {
|
||||
rsyncUtils.compareContents(fs, [{path: '/non-existent-file.txt', checksum: []}], CHUNK_SIZE, function (err, equal) {
|
||||
rsyncUtils.compareContents(fs, [{path: '/non-existent-file.txt', checksum: ''}], function (err, equal) {
|
||||
expect(err).to.not.exist;
|
||||
expect(equal).to.equal(true);
|
||||
done();
|
||||
|
@ -189,10 +203,10 @@ describe('[Rsync Util Tests]', function() {
|
|||
testUtils.createFilesystemLayout(fs, layout, function (err) {
|
||||
if(err) throw err;
|
||||
|
||||
rsyncUtils.generateChecksums(fs, paths, CHUNK_SIZE, function (err, checksums) {
|
||||
rsyncUtils.generateChecksums(fs, paths, function (err, checksums) {
|
||||
expect(err).to.not.exist;
|
||||
expect(checksums).to.exist;
|
||||
rsyncUtils.compareContents(fs, checksums, CHUNK_SIZE, function (err, equal) {
|
||||
rsyncUtils.compareContents(fs, checksums, function (err, equal) {
|
||||
expect(err).to.not.exist;
|
||||
expect(equal).to.equal(true);
|
||||
done();
|
||||
|
@ -222,12 +236,12 @@ describe('[Rsync Util Tests]', function() {
|
|||
testUtils.createFilesystemLayout(fs2, layout2, function (err){
|
||||
if(err) throw err;
|
||||
|
||||
rsyncUtils.generateChecksums(fs, paths, CHUNK_SIZE, function (err, checksums) {
|
||||
rsyncUtils.generateChecksums(fs, paths, function (err, checksums) {
|
||||
expect(err).to.not.exist;
|
||||
expect(checksums).to.exist;
|
||||
rsyncUtils.generateChecksums(fs2, paths, CHUNK_SIZE, function (err) {
|
||||
rsyncUtils.generateChecksums(fs2, paths, function (err, checksums2) {
|
||||
expect(err).to.not.exist;
|
||||
rsyncUtils.compareContents(fs2, checksums, CHUNK_SIZE, function (err, equal) {
|
||||
rsyncUtils.compareContents(fs2, checksums, function (err, equal) {
|
||||
expect(err).to.not.exist;
|
||||
expect(equal).to.equal(false);
|
||||
done();
|
||||
|
@ -238,4 +252,55 @@ describe('[Rsync Util Tests]', function() {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Rsync GetChecksum', function () {
|
||||
beforeEach(fsInit);
|
||||
afterEach(fsCleanup);
|
||||
|
||||
it('should be a function', function () {
|
||||
expect(rsyncUtils.getChecksum).to.be.a('function');
|
||||
});
|
||||
|
||||
it('should return an error for a directory', function (done) {
|
||||
rsyncUtils.getChecksum(fs, '/', function (err, checksum) {
|
||||
expect(err).to.exist;
|
||||
expect(checksum).not.to.exist;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should return an empty checksum for a non-existent file', function (done) {
|
||||
rsyncUtils.getChecksum(fs, '/file.txt', function (err, checksum) {
|
||||
expect(err).not.to.exist;
|
||||
expect(checksum).to.equal('');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should return a non-empty checksum for a file without content', function (done) {
|
||||
fs.writeFile('/file.txt', '', function (err) {
|
||||
if(err) throw err;
|
||||
|
||||
rsyncUtils.getChecksum(fs, '/file.txt', function (err, checksum) {
|
||||
expect(err).not.to.exist;
|
||||
expect(checksum).to.be.a('string');
|
||||
expect(checksum).to.have.length.above(0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should return the checksum of a file', function (done) {
|
||||
fs.writeFile('/file.txt', 'This is a file', function (err) {
|
||||
if(err) throw err;
|
||||
|
||||
rsyncUtils.getChecksum(fs, '/file.txt', function (err, checksum) {
|
||||
expect(err).not.to.exist;
|
||||
expect(checksum).to.be.a('string');
|
||||
expect(checksum).to.have.length.above(0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -2,43 +2,55 @@ var expect = require('chai').expect;
|
|||
var pathResolver = require('../../lib/sync-path-resolver.js');
|
||||
|
||||
describe('Resolution path tests', function () {
|
||||
it('should have resolve as a function', function (done) {
|
||||
expect(pathResolver.resolve).to.be.a.function;
|
||||
done();
|
||||
it('should have resolve as a function', function () {
|
||||
expect(pathResolver.resolve).to.be.a('function');
|
||||
});
|
||||
|
||||
it('should return / as the common path', function (done) {
|
||||
it('should have resolveFromArray as a function', function() {
|
||||
expect(pathResolver.resolveFromArray).to.be.a('function');
|
||||
});
|
||||
|
||||
it('should return / as the common path', function () {
|
||||
expect(pathResolver.resolve(null, null)).to.equal('/');
|
||||
done();
|
||||
});
|
||||
|
||||
it('should return /dir as the common path', function (done) {
|
||||
it('should return /dir as the common path', function () {
|
||||
expect(pathResolver.resolve('/dir')).to.equal('/dir');
|
||||
done();
|
||||
});
|
||||
|
||||
it('should return /dir as the common path', function (done) {
|
||||
it('should return /dir as the common path', function () {
|
||||
expect(pathResolver.resolve(null, '/dir')).to.equal('/dir');
|
||||
done();
|
||||
});
|
||||
|
||||
it('should return /dir as the common path', function (done) {
|
||||
it('should return /dir as the common path', function () {
|
||||
expect(pathResolver.resolve('/dir/myfile.txt', '/dir/myfile2.txt')).to.equal('/dir');
|
||||
done();
|
||||
});
|
||||
|
||||
it('should return /dir as the common path', function (done) {
|
||||
it('should return /dir as the common path', function () {
|
||||
expect(pathResolver.resolve('/dir/myfile.txt', '/dir')).to.equal('/dir');
|
||||
done();
|
||||
});
|
||||
|
||||
it('should return / as the common path', function (done) {
|
||||
it('should return / as the common path', function () {
|
||||
expect(pathResolver.resolve('/dir/myfile.txt', '/dir2/myfile.txt')).to.equal('/');
|
||||
done();
|
||||
});
|
||||
|
||||
it('should return / as the common path', function (done) {
|
||||
it('should return / as the common path', function () {
|
||||
expect(pathResolver.resolve('/', '/dir/subdir/subsubdir')).to.equal('/');
|
||||
done();
|
||||
});
|
||||
|
||||
it('should return / as the common path', function () {
|
||||
expect(pathResolver.resolveFromArray([null, null])).to.equal('/');
|
||||
});
|
||||
|
||||
it('should return /dir as the common path', function () {
|
||||
expect(pathResolver.resolveFromArray(['/dir'])).to.equal('/dir');
|
||||
});
|
||||
|
||||
it('should return /dir as the common path', function () {
|
||||
expect(pathResolver.resolveFromArray([null, '/dir', null, '/dir/file'])).to.equal('/dir');
|
||||
});
|
||||
|
||||
it('should return /dir as the common path', function () {
|
||||
expect(pathResolver.resolveFromArray(['/dir/myfile1', '/dir', '/dir/myfile2', '/dir/dir2/myfile3'])).to.equal('/dir');
|
||||
});
|
||||
});
|
||||
|
|
Загрузка…
Ссылка в новой задаче