diffusion
Version:
Diffusion JavaScript client
190 lines (153 loc) • 5.87 kB
JavaScript
var parseSelector = require('topics/topic-selector-parser');
var canonicalise = require('topics/topic-path-utils').canonicalise;
var Services = require('services/services');
var DataTypes = require('data/datatypes');
var Emitter = require('events/emitter');
var Result = require('events/result');
var UpdateFailReason = require('../../topics/topics').UpdateFailReason;
var CloseReason = require('client/close-reason');
function dataToBytes(d) {
return d.$buffer.slice(d.$offset, d.$length);
}
function clearCache(cache, path) {
var selector = parseSelector('?' + canonicalise(path) + '//');
cache.remove(selector);
}
function Updater(cid, dispatch) {
var self = this;
this.isClosed = false;
var update = function(topic, value, getDataType) {
var emitter = new Emitter();
var result = new Result(emitter);
if (self.isClosed) {
emitter.error(new Error('Updater is closed'));
} else if (!topic) {
emitter.error(new Error('Topic can not be null or empty'));
} else if (value === undefined || value === null) {
emitter.error(new Error('Update cannot be null'));
} else {
dispatch(emitter, cid, topic, value, getDataType);
}
return result;
};
this.update = function(topic, value) {
return update(topic, value, function(content) {
return DataTypes.get(content);
});
};
this.updateValue = function(topic, value, dataType) {
return update(topic, value, function() {
return dataType;
});
};
}
module.exports = function UpdateResponseHandler(internal, valueCache, topic, handler) {
var UPDATE_SOURCE_DEREGISTRATION = internal.getServiceLocator().obtain(Services.UPDATE_SOURCE_DEREGISTRATION);
var UPDATE_SOURCE_DELTA = internal.getServiceLocator().obtain(Services.UPDATE_SOURCE_DELTA);
var UPDATE_SOURCE_SET = internal.getServiceLocator().obtain(Services.UPDATE_SOURCE_SET);
var dispatch = function (emitter, cid, path, content, getDataType) {
var callback = function (err, result) {
if (err) {
emitter.error(err);
} else if (result.error) {
emitter.error(new Error("Topic update error for topic " + path + " : " + result.error));
} else {
emitter.emit('complete');
}
};
if (internal.checkConnected(emitter)) {
var datatype = getDataType(content);
if (!datatype) {
emitter.error(UpdateFailReason.INCOMPATIBLE_UPDATE);
return;
}
var value = datatype.from(content);
var prev = valueCache.get(path);
if (prev) {
var deltaType = datatype.deltaType("binary");
var delta = deltaType.diff(prev, value);
if (delta === deltaType.noChange()) {
callback(null, {});
return;
}
UPDATE_SOURCE_DELTA.send({
id: 0,
cid: cid,
path: path,
bytes: dataToBytes(delta)
}, callback);
} else {
UPDATE_SOURCE_SET.send({
cid: cid,
path: path,
bytes: dataToBytes(value)
}, callback);
}
valueCache.put(path, value);
}
};
var state = 'init';
var close, updater;
return {
onOpen: function (cid) {
close = function close() {
var emitter = new Emitter();
var result = new Result(emitter);
UPDATE_SOURCE_DEREGISTRATION.send({cid: cid}, function (err) {
if (err) {
internal.getConversationSet().discard(cid, err);
emitter.error(err);
} else {
internal.getConversationSet().respond(cid, {
old: state,
current: 'closed'
});
emitter.emit('complete');
}
});
return result;
};
},
onResponse: function (cid, change) {
if (change.old !== state) {
internal.getConversationSet().discard(cid,
new Error("Inconsistent server/client update source state. Current: " +
state + ", expected: " + change.old));
return false;
}
if (state === 'init' && change.current !== 'closed') {
handler.onRegister(topic, close);
}
if (updater) {
updater.isClosed = true;
}
state = change.current;
switch (state) {
case 'active' :
updater = new Updater(cid, dispatch);
clearCache(valueCache, topic);
handler.onActive(topic, updater);
return false;
case 'standby' :
handler.onStandBy(topic);
return false;
default :
clearCache(valueCache, topic);
handler.onClose(topic);
return true;
}
},
onDiscard: function (cid, reason) {
state = 'closed';
if (updater) {
updater.isClosed = true;
}
clearCache(valueCache, topic);
if (reason instanceof CloseReason) {
handler.onClose(topic);
} else {
handler.onClose(topic, reason);
}
}
};
};