sumeru
Version:
A Realtime Javascript RIA Framework For Mobile WebApp
655 lines (574 loc) • 24.7 kB
JavaScript
var runnable = function(fw){
var _controller = fw.controller;
var _model = fw.model;
var isServer = fw.IS_SUMERU_SERVER;
function mergeArray(array, struct){
var structGroupType,structGroupCnt;
for (var i = 0, ilen = struct.length; i < ilen; i++){
structGroupType = struct[i]['type'];
structGroupCnt = struct[i]['cnt'];
if(structGroupType=="append"){
array.splice(array.length,0,structGroupCnt);
}else if(structGroupType=="update"){
array[structGroupCnt['id']] = structGroupCnt['cnt'];
}else if(structGroupType=="splice"){
array.splice(structGroupCnt);
}
}
};
function mergeObj(obj, struct){
var structGroupType,structGroupCnt;
for (var i = 0, ilen = struct.length; i < ilen; i++){
structGroupType = struct[i].type;
structGroupCnt = struct[i].cnt;
if(structGroupType=="insert"){
for(var p in structGroupCnt){
obj[p] = structGroupCnt[p];
}
}else if(structGroupType=="update"){
for(var p in structGroupCnt){
obj[p] = structGroupCnt[p];
}
}else if(structGroupType=="delete"){
for(var p,plen=structGroupCnt.length; p<plen; p++){
obj[p] = undefined;
}
}
}
};
function mergeModel(model, struct){
var structGroupType,structGroupCnt;
var modelName = model._modelName;
var modelFieldsMap = fw.model._getModelTemp(modelName)._fieldsMap,
fieldType,subModelName,subModelRelation;
for (var i = 0, ilen = struct.length; i < ilen; i++){
structGroupType = struct[i].type;
structGroupCnt = struct[i].cnt;
if(structGroupType=="insert"){
for(var p in structGroupCnt){
fieldType = modelFieldsMap[p]['type'];
if(fieldType=="model"){
subModelName = modelFieldsMap[p]['model'];
subModelRelation = modelFieldsMap[p]['relation'];
if(subModelRelation=="many"){
var subCollection = fw.collection.create({modelName : subModelName},structGroupCnt[p]);
model._baseSet(p, subCollection);
}else{
subModel = fw.model.create(subModelName, structGroupCnt[p]);
model._baseSet(p, subModel);
}
}else{
model._baseSet(p, structGroupCnt[p]);
}
}
}else if(structGroupType=="update"){
for(var p in structGroupCnt){
fieldType = modelFieldsMap[p]['type'];
if(fieldType=="model"){
subModelName = modelFieldsMap[p]['model'];
subModelRelation = modelFieldsMap[p]['relation'];
if(subModelRelation=="many"){
mergeCollection(model[p],structGroupCnt[p]);
}else{
mergeModel(model[p],structGroupCnt[p]);
}
}else if(fieldType=="array"){
model._baseSet(p, structGroupCnt[p]);
/*不好判断,暂时直接赋值。mergeArray(model[p],structGroupCnt[p]);*/
}else if(fieldType=="object"){
mergeObj(model[p],structGroupCnt[p]);
}else{
model._baseSet(p, structGroupCnt[p]);
}
}
}else if(structGroupType=="delete"){
for(var j=0,jlen = structGroupCnt.length;j<jlen;j++){
model._delete(structGroupCnt[j]);
}
}
}
}
/**
* Merge服务器下发的增量数据
* 由syncCollection调用
*/
function mergeCollection(collection, delta, serverVersion){
//增量
var doReactiveProcess = false;
for (var i = 0, ilen = delta.length; i < ilen; i++){
var struct = delta[i];
//此处顺序为,先insert,delete,然后update
if(struct.type == 'insert'){
var structData = struct['cnt'];
var is_exist = collection.find({smr_id : structData.smr_id});
if (is_exist.length) {
/*当本次存在id相同的项目时,认为是latency compensation的server回发请求,
但由于服务器上可能通过beforeInsert来修改数据,因此在这里先移除本地数据,再使用服务器的数据*/
/*FIXME 这里应该有一个标记,判断数据是否被服务器修改过,否则每次都reactive性能太差*/
/*collection.remove({
'smr_id' : structData.smr_id
});
/*collection.update({
_id : structData._id
}, {
__clientId : structData.__clientId
})*/
doReactiveProcess = false;
} else {
var externalInfo = fw.pubsub._subscribeMgr[collection.pubName].externalInfo;
if(externalInfo){
var uc = externalInfo.uniqueColumn;
var criteria = {};
criteria[uc] = structData[uc];
var is_uc_exist = collection.find(criteria);
if(is_uc_exist.length){
is_uc_exist[0].set("smr_id", structData.smr_id);
}else{
collection.add(structData);
}
}else{
collection.add(structData);
}
doReactiveProcess = true;
}
} else if (struct.type == 'delete'){
var structData = struct['cnt'];
//属于server下发的删除,只做remove即可,因为不知道到底是数据被删除,还是仅因为不符合pubfunc规则而被移除了
collection.remove({
'smr_id IN' : structData //这里已经是经过server变换的smr_id数组了
});
doReactiveProcess = true;
} else if (struct.type == 'update'){
var model = collection.find({
smr_id : struct.id
});
if(model.length>0){
model = model[0];
}else{
continue;
}
var structData = mergeModel(model,struct['cnt']);
/*var updateMap = {};
updateMap = structData;
//FIXME 这里是setdirty唯一的问题,服务器下发的update会把dirty位搞脏。但不造成实际bug影响,因会触发diff,发现并无实质更新
collection.update(updateMap, {
smr_id : struct.id
});*/
doReactiveProcess = true;
}
//更新client collection version
collection.setVersion(serverVersion);
}
return doReactiveProcess;
}
function deltaProcess (collection, delta) {
var i = []; //inserted item
var d = []; //deleted item
var u = []; //updated item
delta.forEach(function(item){
if (item.type === 'insert') {
i.push(item.cnt);
} else if (item.type === 'delete') {
item.cnt.forEach(function(smr_id){
d.push(smr_id);
});
} else if (item.type === 'update') {
var cnt = item.cnt;
var smr_id = item.id;
item.cnt.forEach(function(upd){
u.push({
cnt : upd.cnt,
smr_id : smr_id
});
});
}
});
if(i.length){ collection.onInsert && collection.onInsert(i); }
if(d.length){ collection.onDelete && collection.onDelete(d); }
if(u.length){ collection.onUpdate && collection.onUpdate(u); }
}
function syncCollection(type, pubname, val, item, isPlainStruct, serverVersion){
var collection = item.collection,
doReactiveProcess = false,
delta = [];
if(isPlainStruct){//publish callback是传过来的data是个简单对象。不是一个collection
collection = val;
doReactiveProcess = true;
}else{
if(type == 'data_write_from_server_delta'){
delta = val;
doReactiveProcess = mergeCollection(collection, delta, serverVersion);
} else {
//全量
if(!collection._isSynced() || collection.stringify() !== JSON.stringify(val)){
collection.setData(val);
collection.setVersion(serverVersion);
doReactiveProcess = true;
}
}
}
if(!isPlainStruct)collection._takeSnapshot();
//onInser, onUpdate, onDelete callback
deltaProcess(collection, delta);
if(doReactiveProcess === true){
//因为JS的单线程执行,只要callback中没有setTimeout等异步调用,全局变量tapped_blocks就不会产生类似多进程同时写的冲突。
//FIX BY SUNDONG,tapped_blocks在callback不要清空已经bind的东西
//添加修正,由于前端渲染是实时进行,所以在前端的回调中,要清空已渲染的
var tapped_blocks = [];
if (_controller._tapped_blocks && isServer) {
tapped_blocks = _controller._tapped_blocks;
}else{
_controller.__reg('_tapped_blocks', tapped_blocks, true);
}
var byPageSegment = new RegExp('@@_sumeru_@@_page_([\\d]+)'),
ifPageMatch = pubname.match(byPageSegment);
if (ifPageMatch) {
item.callback(collection, {
//delta : delta, FIXME 待端=>云的协议与云=>端的协议统一后,统一提供增量的delta给subscribe
page : ifPageMatch[1]
}); //额外传递一个页码page
} else {
item.callback(collection, {
delta : delta //FIXME 待端=>云的协议与云=>端的协议统一后,统一提供增量的delta给subscribe
});
}
//每个Controller的render方法会保证局部渲染一定等待主渲染流程完成才开始。
_controller && _controller.reactiveRender(tapped_blocks);
}
//a flag tells if data have been stored remotely
if(!isPlainStruct){
collection._setSynced(true);
fw.cache.setPubData(pubname,item.args,JSON.stringify(collection.getData()));
}
}
/*
* 因为collection执行hold方法而延迟执行的数据同步队列
* 结构:
* [{
* collection :
* runner :
* },{},...]
*/
var postponeQueue = fw.pubsub._postponeQueue;
// ========== 消息处理 ============
/**
* 接收到全局消息
*/
var onGlobalMessage = function(data) {
if (fw.onGlobalMessage) {
fw.onGlobalMessage(data);
} else {
fw.dev("GLOBAL MESSAGE : " + data);
}
};
/**
* 接收到全局错误消息
*/
var onGlobalError = function(data,target){
var msg = target == 'data_auth_from_server' ? 'auth faild' : data;
if(fw.onGlobalError ){
fw.onGlobalError(msg);
}else{
fw.log("GLOBAL ERROR : " + msg);
}
};
/**
* 认证消息的数据
* 当接收到服务端产生的认证失败消息时被触发,用于通知对应的controller认证失败,未获到有效数据
*/
var onError_data_auth_from_server = function(data){
if(!data.needAuth){
var pubName = data.pubname;
//candidates is array of collections
var candidates = fw.pubsub._subscribeMgr[pubName].stub;
//each stub
//[{collection:@, onComplete : @}]
var isSend = false;
candidates.forEach(function(item, i){
if(item.env.onerror){
item.env.onerror("auth failed");
isSend = true || isSend;
}else{
/*
* FIXME , 当找到任何controll的onError,都认为错误消息派送成功
* 唯一派送失败的可能是,数据中没有pubname,或没有任何controller订阅当前pubname的数据.
*/
isSend = false || isSend;
}
});
// 如果当前消息没有成功的接收者,将转往globalError
return isSend;
}
};
var onError_data_write_from_server_dberror = function(data){
var pubName = data.pubname;
//candidates is array of collections
var candidates = fw.pubsub._subscribeMgr[pubName].stub;
//each stub
//[{collection:@, onComplete : @}]
var isSend = false;
candidates.forEach(function(item, i){
if(item.env.onerror){
item.env.onerror("dberror:"+data.data.stringify());
isSend = true || isSend;
}else{
/*
* FIXME , 当找到任何controll的onError,都认为错误消息派送成功
* 唯一派送失败的可能是,数据中没有pubname,或没有任何controller订阅当前pubname的数据.
*/
isSend = false || isSend;
}
});
// 如果当前消息没有成功的接收者,将转往globalError
return isSend;
};
var onError_data_write_from_server_validation = function(data){
var pubName = data.pubname;
var pilotId = data['pilotid'];
if(pubName){
//candidates is array of collections
var candidates = fw.pubsub._subscribeMgr[pubName].stub;
//each stub
//[{collection:@, onComplete : @}]
var isSend = false;
candidates.forEach(function(item, i){
if(item.collection.onValidation){
var resultObjs = data.data;
if(data.modelchain!=''){
for(var i=0,ilen=resultObjs.length;i<ilen;i++){
resultObjs[i]['key'] = data.modelchain+'.'+resultObjs[i]['key'];
}
}
item.collection.onValidation.call(item.collection,resultObjs.length>0?false:true, 'server', resultObjs);
isSend = true || isSend;
}else{
/*
* FIXME , 当找到任何controll的onError,都认为错误消息派送成功
* 唯一派送失败的可能是,数据中没有pubname,或没有任何controller订阅当前pubname的数据.
*/
isSend = false || isSend;
}
});
// 如果当前消息没有成功的接收者,将转往globalError
return isSend;
}else{
var _pilot = fw.msgpilot.getPilot(pilotId);
var isSend = false;
if(_pilot.stub.onValidation){
var resultObjs = data.data;
_pilot.stub.onValidation.call(_pilot.stub,resultObjs.length>0?false:true, 'server', resultObjs);
isSend = true || isSend;
}else{
isSend = false || isSend;
}
return isSend;
}
};
/**
* 接收到服务端返回的echo信息
* 1.同步时间
* 2.添加了从服务器接收公钥
*/
var onMessage_echo_from_server = function(data){
fw.reachability.setStatus_(fw.reachability.STATUS_CONNECTED);
var localTimeStamp = (new Date()).valueOf();
serverTimeStamp = data.timestamp;
if (typeof data.swappk !="undefined"){
fw.myrsa.setPk2(data.swappk);
//fw.log('从server获得新pk2',data.swappk);
}
var getTimeStamp = function(){
var now = (new Date()).valueOf(),
delta = now - localTimeStamp,
serverTimeNow = delta + serverTimeStamp;
return serverTimeNow;
};
fw.utils.__reg('getTimeStamp', getTimeStamp);
Library.objUtils.extend(sumeru.pubsub._publishModelMap, data.pubmap);
fw.cache.set('publishModelMap',JSON.stringify(data.pubmap));
fw.netMessage.sendLocalMessage({}, 'after_echo_from_server');
};
/**
* 接收到从服务端写过来的数据订阅消息
*/
var onMessage_data_write_from_server = function(data,type){
/**
* format :
* name : pub name
* val : value object
*/
var pubName = data['pubname'];
var pilotId = data['pilotid'],
uk = data['uk']||"",
val = data['data'],
serverVersion = data['version'],
externalInfo = data['external'] || "";
if(pubName){
if (!(pubName in fw.pubsub._subscribeMgr)) {
return;
};
if (type == 'data_write_from_server'
&& fw.pubsub._subscribeMgr[pubName].topPriority == true
&& typeof fw.pubsub._priorityAsyncHandler != 'undefined') {
//如果是prioritySubscribe的全量写回(即第一次的返回),又存在fw.pubsub._priorityAsyncHandler(是redo)
fw.pubsub._priorityAsyncHandler.decrease();
};
if(externalInfo){
fw.pubsub._subscribeMgr[pubName].externalInfo = externalInfo;
}
//candidates is array of collections
var candidates = fw.pubsub._subscribeMgr[pubName].stub;
var isPlainStruct = sumeru.pubsub._publishModelMap[pubName.replace(/@@/, '')]['plainstruct'];
//each stub
//[{collection:@, onComplete : @}]
for(var i=0,len=candidates.length,item;i<len;i++){
item = candidates[i];
if (uk && uk!=item.id) {
continue;
}
var collection = item.collection;
if(isPlainStruct){
syncCollection(type, pubName, val, item, isPlainStruct, serverVersion);
}else{
if (collection.__smr__.isHolding) {
postponeQueue.push({
collection : collection,
runner : function(){syncCollection(type, pubName, val, item, false, serverVersion);}
});
} else {
syncCollection(type, pubName, val, item, false, serverVersion);
}
}
};
}else{
/*暂无此类调用*/
var _pilot = sumeru.msgpilot.getPilot(pilotId);
if(_pilot.type==='model'){
}else{
syncCollection(type, pilotId, val, item, false, serverVersion);
}
}
};
/**
* 服务端发来的错误消息
*/
var onError = function(data,type){
var pubName = data['pubname'];
//candidates is array of collections
var candidates = fw.pubsub._subscribeMgr[pubName].stub;
//each stub
//[{collection:@, onComplete : @}]
var isSend = false;
candidates.forEach(function(item, i){
if(item.env.onerror){
item.env.onerror("auth failed");
isSend = true || isSend;
}else{
/*
* FIXME , 当找到任何controll的onError,都认为错误消息派送成功
* 唯一派送失败的可能是,数据中没有pubname,或没有任何controller订阅当前pubname的数据.
*/
isSend = false || isSend;
}
});
// 如果当前消息没有成功的接收者,将转往globalError
return isSend;
};
/**
* 从本地发来的消息
*/
var onLocalMessage_data_write_latency = function(data,type){
var pubName = data['pubname'];
//candidates is array of collections
var candidates = fw.pubsub._subscribeMgr[pubName].stub;
//each stub
//[{collection:@, onComplete : @}]
candidates.forEach(function(item, i){
var collection = item.collection;
//因为JS的单线程执行,只要callback中没有setTimeout等异步调用,全局变量tapped_blocks就不会产生类似多进程同时写的冲突。
var tapped_blocks = [];
_controller.__reg('_tapped_blocks', tapped_blocks, true);
item.callback(collection, { delta : [] });
//每个Controller的render方法会保证局部渲染一定等待主渲染流程完成才开始。
_controller.reactiveRender(tapped_blocks);
});
};
var onMessage_config_write_from_server = function(data, type){
if(data && typeof data === "object"){
for(var ob in data){
fw.config.set(ob,data[ob]);
}
}
fw.config.commit();
};
/**
* 绑定服务下发消息处理
*/
fw.netMessage.setReceiver({
onMessage : {
overwrite : true,
target : 'config_write_from_server',
handle : onMessage_config_write_from_server
},
onLocalMessage : {
target : 'config_write_from_server',
handle : onMessage_config_write_from_server
}
});
fw.netMessage.setReceiver({
onMessage : {
overwrite : true,
target : 'echo_from_server',
handle : onMessage_echo_from_server
},
onLocalMessage:{
overwrite : true,
target : ['data_write_latency'],
handle : onLocalMessage_data_write_latency
}
});
fw.netMessage.setReceiver({
onMessage:{
overwrite : true,
target:['data_write_from_server','data_write_from_server_delta'],
handle : onMessage_data_write_from_server
},
onLocalMessage:{
overwrite : true,
target:['data_write_from_server','data_write_from_server_delta'],
handle : onMessage_data_write_from_server
},
onError:onError,
onGlobalError:onGlobalError,
onGlobalMessage:onGlobalMessage
});
fw.netMessage.setReceiver({
onError:{
//该标记只有服务端认证失败时才返回
overwrite : true,
target:['data_auth_from_server'],
handle : onError_data_auth_from_server
},
});
fw.netMessage.setReceiver({
onError:{
//该标记只有服务端DB操作失败时才返回
overwrite : true,
target:['data_write_from_server_dberror'],
handle : onError_data_write_from_server_dberror
},
});
fw.netMessage.setReceiver({
onError:{
//该标记只有服务端model验证失败时才返回
overwrite : true,
target:['data_write_from_server_validation'],
handle : onError_data_write_from_server_validation
},
});
};
if(typeof module !='undefined' && module.exports){
module.exports = runnable;
}else{//这里是前端
runnable(sumeru);
}