makedrive
Version:
Webmaker Filesystem
302 lines (255 loc) • 9.75 kB
JavaScript
var SyncMessage = require('../../lib/syncmessage');
var rsync = require('../../lib/rsync');
var rsyncUtils = rsync.utils;
var rsyncOptions = require('../../lib/constants').rsyncDefaults;
var serializeDiff = require('../../lib/diff').serialize;
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;
syncManager.sync.onError(err);
}
// Checks if path is in masterPath
function hasCommonPath(masterPath, path) {
if(masterPath === path) {
return true;
}
if(path === '/') {
return false;
}
return hasCommonPath(masterPath, dirname(path));
}
function handleRequest(syncManager, data) {
var fs = syncManager.fs;
var sync = syncManager.sync;
var session = syncManager.session;
function handleChecksumRequest() {
var srcList = session.srcList = data.content.srcList;
session.path = data.content.path;
fs.modifiedPath = null;
sync.onSyncing();
rsync.checksums(fs, session.path, srcList, rsyncOptions, function(err, checksums) {
if (err) {
return onError(syncManager, err);
}
session.step = steps.PATCH;
var message = SyncMessage.request.diffs;
message.content = {checksums: checksums};
syncManager.send(message.stringify());
});
}
function handleDiffRequest() {
rsync.diff(fs, session.path, data.content.checksums, rsyncOptions, function(err, diffs) {
if(err){
return onError(syncManager, err);
}
session.step = steps.PATCH;
var message = SyncMessage.response.diffs;
message.content = {diffs: serializeDiff(diffs)};
syncManager.send(message.stringify());
});
}
if(data.is.chksum && session.is.ready &&
(session.is.synced || session.is.failed)) {
// DOWNSTREAM - CHKSUM
handleChecksumRequest();
} else if(data.is.diffs && session.is.syncing && session.is.diffs) {
// UPSTREAM - DIFFS
handleDiffRequest();
} else {
onError(syncManager, new Error('Failed to sync with the server. Current step is: ' +
session.step + '. Current state is: ' + session.state)); }
}
function handleResponse(syncManager, data) {
var fs = syncManager.fs;
var sync = syncManager.sync;
var session = syncManager.session;
function resendChecksums() {
if(!session.srcList) {
// Sourcelist was somehow reset, the entire downstream sync
// needs to be restarted
session.step = steps.FAILED;
syncManager.send(SyncMessage.response.reset.stringify());
return onError(syncManager, new Error('Fatal Error: Could not sync filesystem from server...trying again!'));
}
rsync.checksums(fs, session.path, session.srcList, rsyncOptions, function(err, checksums) {
if(err) {
syncManager.send(SyncMessage.response.reset.stringify());
return onError(syncManager, err);
}
var message = SyncMessage.request.diffs;
message.content = {checksums: checksums};
syncManager.send(message.stringify());
});
}
function handleSrcListResponse() {
session.state = states.SYNCING;
session.step = steps.INIT;
session.path = data.content.path;
sync.onSyncing();
rsync.sourceList(fs, session.path, rsyncOptions, function(err, srcList) {
if(err){
syncManager.send(SyncMessage.request.reset.stringify());
return onError(syncManager, err);
}
session.step = steps.DIFFS;
var message = SyncMessage.request.chksum;
message.content = {srcList: srcList};
syncManager.send(message.stringify());
});
}
function handlePatchAckResponse() {
var syncedPaths = data.content.syncedPaths;
session.state = states.READY;
session.step = steps.SYNCED;
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() {
var modifiedPath = fs.modifiedPath;
fs.modifiedPath = null;
// If there was a change to the filesystem that shares a common path with
// the path being synced, regenerate the checksums and send them
// (even if it is the initial one)
if(modifiedPath && hasCommonPath(session.path, modifiedPath)) {
return resendChecksums();
}
var diffs = data.content.diffs;
diffs = deserializeDiff(diffs);
rsync.patch(fs, session.path, diffs, rsyncOptions, function(err, paths) {
if (err) {
var message = SyncMessage.response.reset;
syncManager.send(message.stringify());
return onError(syncManager, err);
}
if(paths.needsUpstream.length) {
session.needsUpstream = paths.needsUpstream;
}
rsyncUtils.generateChecksums(fs, paths.synced, true, function(err, checksums) {
if(err) {
var message = SyncMessage.response.reset;
syncManager.send(message.stringify());
return onError(syncManager, err);
}
var message = SyncMessage.response.patch;
message.content = {checksums: checksums};
syncManager.send(message.stringify());
});
});
}
function handleVerificationResponse() {
session.srcList = null;
session.step = steps.SYNCED;
var needsUpstream = session.needsUpstream;
delete session.needsUpstream;
sync.onCompleted(null, needsUpstream);
}
function handleUpstreamResetResponse() {
var message = SyncMessage.request.sync;
message.content = {path: session.path};
syncManager.send(message.stringify());
}
if(data.is.sync) {
// UPSTREAM - INIT
handleSrcListResponse();
} else if(data.is.patch && session.is.syncing && session.is.patch) {
// UPSTREAM - PATCH
handlePatchAckResponse();
} else if(data.is.diffs && session.is.ready && session.is.patch) {
// DOWNSTREAM - PATCH
handlePatchResponse();
} else if(data.is.verification && session.is.ready && session.is.patch) {
// DOWNSTREAM - PATCH VERIFICATION
handleVerificationResponse();
} else if (data.is.reset && session.is.failed) {
handleUpstreamResetResponse();
} else {
onError(syncManager, new Error('Failed to sync with the server. Current step is: ' +
session.step + '. Current state is: ' + session.state)); }
}
function handleError(syncManager, data) {
var sync = syncManager.sync;
var session = syncManager.session;
var message = SyncMessage.response.reset;
// DOWNSTREAM - ERROR
if((((data.is.srclist && session.is.synced)) ||
(data.is.diffs && session.is.patch) && (session.is.ready || session.is.syncing))) {
session.state = states.READY;
session.step = steps.SYNCED;
syncManager.send(message.stringify());
onError(syncManager, new Error('Could not sync filesystem from server... trying again'));
} else if(data.is.verification && session.is.patch && session.is.ready) {
syncManager.send(message.stringify());
onError(syncManager, new Error('Could not sync filesystem from server... trying again'));
} else if(data.is.locked && session.is.ready && session.is.synced) {
// UPSTREAM - LOCK
onError(syncManager, new Error('Current sync in progress! Try again later!'));
} else if(((data.is.chksum && session.is.diffs) ||
(data.is.patch && session.is.patch)) &&
session.is.syncing) {
// UPSTREAM - ERROR
var message = SyncMessage.request.reset;
syncManager.send(message.stringify());
onError(syncManager, new Error('Could not sync filesystem from server... trying again'));
} else if(data.is.maxsizeExceeded) {
// We are only emitting the error since this is can be sync again from the client
syncManager.sync.emit('error', new Error('Maximum file size exceeded'));
} else if(data.is.interrupted && session.is.syncing) {
// SERVER INTERRUPTED SYNC (LOCK RELEASED EARLY)
sync.onInterrupted();
} else {
onError(syncManager, new Error('Failed to sync with the server. Current step is: ' +
session.step + '. Current state is: ' + session.state));
}
}
function handleMessage(syncManager, data) {
try {
data = JSON.parse(data);
data = SyncMessage.parse(data);
} catch(e) {
return onError(syncManager, e);
}
if (data.is.request) {
handleRequest(syncManager, data);
} else if(data.is.response){
handleResponse(syncManager, data);
} else if(data.is.error){
handleError(syncManager, data);
} else {
onError(syncManager, new Error('Cannot handle message'));
}
}
module.exports = handleMessage;