Bug fix: Plugin Sync

1. Failed to create more than one realtime document.
   Pass assignIdCallback and failedCallback as function parameter for FileSearcher instead of saving it.

2. If a client with empty data use Sync plugin first, other clients will lose their data once connected with Realtime API.
   Assign '' to lastUpdateUUID on model initialization if the client do not have data.

3. Realtime API sometimes return 'CLIENT_ERROR' instead of 'NOT_FOUND' if a file is not found
   Change handling of 'CLIENT_ERROR' to the same as 'NOT_FOUND'
This commit is contained in:
Xelio 2013-08-25 22:50:59 +08:00
parent 4c57a31375
commit 8b9c6fbe0d

View File

@ -135,7 +135,8 @@ window.plugin.sync.RegisteredMap.prototype.updateMap = function(keyArray) {
} }
window.plugin.sync.RegisteredMap.prototype.isUpdatedByOthers = function() { window.plugin.sync.RegisteredMap.prototype.isUpdatedByOthers = function() {
return this.lastUpdateUUID.toString() !== this.uuid; var remoteUUID = this.lastUpdateUUID.toString();
return (remoteUUID !== '') && (remoteUUID !== this.uuid);
} }
window.plugin.sync.RegisteredMap.prototype.getFileName = function() { window.plugin.sync.RegisteredMap.prototype.getFileName = function() {
@ -159,10 +160,8 @@ window.plugin.sync.RegisteredMap.prototype.initFile = function(callback) {
} }
this.fileSearcher = new plugin.sync.FileSearcher({'fileName': this.getFileName(), this.fileSearcher = new plugin.sync.FileSearcher({'fileName': this.getFileName(),
'description': 'IITC plugin data for ' + this.getFileName(), 'description': 'IITC plugin data for ' + this.getFileName()});
'assignIdCallback': assignIdCallback, this.fileSearcher.initialize(this.forceFileSearch, assignIdCallback, failedCallback);
'failedCallback': failedCallback});
this.fileSearcher.initialize(this.forceFileSearch);
} }
window.plugin.sync.RegisteredMap.prototype.updateListener = function(e) { window.plugin.sync.RegisteredMap.prototype.updateListener = function(e) {
@ -191,14 +190,18 @@ window.plugin.sync.RegisteredMap.prototype.loadRealtimeDocument = function(callb
// this function called when the document is created first time // this function called when the document is created first time
// and the CollaborativeMap is populated with data in plugin field // and the CollaborativeMap is populated with data in plugin field
initializeModel = function(model) { initializeModel = function(model) {
var empty = true;
var map = model.createMap(); var map = model.createMap();
var lastUpdateUUID = model.createString(); var lastUpdateUUID = model.createString();
// Init the map values if this map is first created // Init the map values if this map is first created
$.each(window.plugin[_this.pluginName][_this.fieldName], function(key, val) { $.each(window.plugin[_this.pluginName][_this.fieldName], function(key, val) {
map.set(key, val); map.set(key, val);
empty = false;
}); });
lastUpdateUUID.setText(_this.uuid);
// Only set the update client if the map is not empty, avoid clearing data of other clients
lastUpdateUUID.setText(empty ? '' : _this.uuid);
model.getRoot().set('map', map); model.getRoot().set('map', map);
model.getRoot().set('last-udpate-uuid', lastUpdateUUID); model.getRoot().set('last-udpate-uuid', lastUpdateUUID);
@ -211,8 +214,8 @@ window.plugin.sync.RegisteredMap.prototype.loadRealtimeDocument = function(callb
onFileLoaded = function(doc) { onFileLoaded = function(doc) {
_this.doc = doc; _this.doc = doc;
_this.model = doc.getModel(); _this.model = doc.getModel();
_this.map = doc.getModel().getRoot().get("map"); _this.map = doc.getModel().getRoot().get('map');
_this.lastUpdateUUID = doc.getModel().getRoot().get("last-udpate-uuid"); _this.lastUpdateUUID = doc.getModel().getRoot().get('last-udpate-uuid');
_this.map.addEventListener(gapi.drive.realtime.EventType.VALUE_CHANGED, _this.updateListener); _this.map.addEventListener(gapi.drive.realtime.EventType.VALUE_CHANGED, _this.updateListener);
// Replace local value if data is changed by others // Replace local value if data is changed by others
@ -240,6 +243,10 @@ window.plugin.sync.RegisteredMap.prototype.loadRealtimeDocument = function(callb
_this.authorizer.authorize(); _this.authorizer.authorize();
} else if(e.type === gapi.drive.realtime.ErrorType.NOT_FOUND) { } else if(e.type === gapi.drive.realtime.ErrorType.NOT_FOUND) {
_this.forceFileSearch = true; _this.forceFileSearch = true;
} else if(e.type === gapi.drive.realtime.ErrorType.CLIENT_ERROR) {
// Workaround: if Realtime API open a second docuemnt and the file do not exist,
// it will rasie 'CLIENT_ERROR' instead of 'NOT_FOUND'. So we do a force file search here.
_this.forceFileSearch = true;
} else { } else {
alert('Plugin Sync error: ' + e.type + ', ' + e.message); alert('Plugin Sync error: ' + e.type + ', ' + e.message);
} }
@ -367,8 +374,6 @@ window.plugin.sync.FileSearcher = function(options) {
this.fileName = options['fileName']; this.fileName = options['fileName'];
this.description = options['description']; this.description = options['description'];
this.isFolder = options['isFolder']; this.isFolder = options['isFolder'];
this.assignIdCallback = options['assignIdCallback'];
this.failedCallback = options['failedCallback'];
this.force = false; this.force = false;
this.parent = null; this.parent = null;
@ -386,27 +391,27 @@ window.plugin.sync.FileSearcher.prototype.MIMETYPE_FOLDER = 'application/vnd.goo
window.plugin.sync.FileSearcher.prototype.parentName = 'IITC-SYNC-DATA-V2'; window.plugin.sync.FileSearcher.prototype.parentName = 'IITC-SYNC-DATA-V2';
window.plugin.sync.FileSearcher.prototype.parentDescription = 'Store IITC sync data'; window.plugin.sync.FileSearcher.prototype.parentDescription = 'Store IITC sync data';
window.plugin.sync.FileSearcher.prototype.initialize = function(force) { window.plugin.sync.FileSearcher.prototype.initialize = function(force, assignIdCallback, failedCallback) {
this.force = force; this.force = force;
// throw error if too many retry // throw error if too many retry
if(this.retryCount >= this.RETRY_LIMIT) { if(this.retryCount >= this.RETRY_LIMIT) {
plugin.sync.logger.log('Too many file operation: ' + this.fileName); plugin.sync.logger.log('Too many file operation: ' + this.fileName);
this.failedCallback(); failedCallback();
return; return;
} }
if(this.force) this.retryCount++; if(this.force) this.retryCount++;
if(this.isFolder) { if(this.isFolder) {
this.initFile(); this.initFile(assignIdCallback, failedCallback);
} else { } else {
this.initParent(); this.initParent(assignIdCallback, failedCallback);
} }
} }
window.plugin.sync.FileSearcher.prototype.initFile = function() { window.plugin.sync.FileSearcher.prototype.initFile = function(assignIdCallback, failedCallback) {
// If not force search and have cached fileId, return the fileId // If not force search and have cached fileId, return the fileId
if(!this.force && this.fileId) { if(!this.force && this.fileId) {
this.assignIdCallback(this.fileId); assignIdCallback(this.fileId);
return; return;
} }
@ -416,14 +421,14 @@ window.plugin.sync.FileSearcher.prototype.initFile = function() {
handleFileId = function(id) { handleFileId = function(id) {
_this.fileId = id; _this.fileId = id;
_this.saveFileId(); _this.saveFileId();
_this.assignIdCallback(id); assignIdCallback(id);
}; };
handleFailed = function(resp) { handleFailed = function(resp) {
_this.fileId = null; _this.fileId = null;
_this.saveFileId(); _this.saveFileId();
plugin.sync.logger.log('File operation failed: ' + (resp.error || 'unknown error')); plugin.sync.logger.log('File operation failed: ' + (resp.error || 'unknown error'));
_this.failedCallback(resp); failedCallback(resp);
} }
createCallback = function(resp) { createCallback = function(resp) {
@ -447,27 +452,25 @@ window.plugin.sync.FileSearcher.prototype.initFile = function() {
this.searchFileOrFolder(searchCallback); this.searchFileOrFolder(searchCallback);
} }
window.plugin.sync.FileSearcher.prototype.initParent = function() { window.plugin.sync.FileSearcher.prototype.initParent = function(assignIdCallback, failedCallback) {
var assignIdCallback, failedCallback, _this; var parentAssignIdCallback, parentFailedCallback, _this;
_this = this; _this = this;
assignIdCallback = function(id) { parentAssignIdCallback = function(id) {
_this.initFile(); _this.initFile(assignIdCallback, failedCallback);
} }
failedCallback = function(resp) { parentFailedCallback = function(resp) {
_this.fileId = null; _this.fileId = null;
_this.saveFileId(); _this.saveFileId();
plugin.sync.logger.log('File operation failed: ' + (resp.error || 'unknown error')); plugin.sync.logger.log('File operation failed: ' + (resp.error || 'unknown error'));
_this.failedCallback(resp); failedCallback(resp);
} }
this.parent = new plugin.sync.FileSearcher({'fileName': this.parentName, this.parent = new plugin.sync.FileSearcher({'fileName': this.parentName,
'description': this.parentDescription, 'description': this.parentDescription,
'isFolder': true, 'isFolder': true});
'assignIdCallback': assignIdCallback, this.parent.initialize(this.force, parentAssignIdCallback, parentFailedCallback);
'failedCallback': failedCallback});
this.parent.initialize(this.force);
} }
window.plugin.sync.FileSearcher.prototype.createFileOrFolder = function(callback) { window.plugin.sync.FileSearcher.prototype.createFileOrFolder = function(callback) {
@ -629,12 +632,12 @@ window.plugin.sync.generateUUID = function() {
window.crypto.getRandomValues(buf); window.crypto.getRandomValues(buf);
var S4 = function(num) { var S4 = function(num) {
var ret = num.toString(16); var ret = num.toString(16);
return "000".substring(0, 4-ret.length) + ret; return '000'.substring(0, 4-ret.length) + ret;
}; };
var yxxx = function(num) { var yxxx = function(num) {
return num&0x3fff|0x8000; return num&0x3fff|0x8000;
} }
return (S4(buf[0])+S4(buf[1])+"-"+S4(buf[2])+"-4"+S4(buf[3]).substring(1)+"-"+S4(yxxx(buf[4]))+"-"+S4(buf[5])+S4(buf[6])+S4(buf[7])); return (S4(buf[0])+S4(buf[1])+'-'+S4(buf[2])+'-4'+S4(buf[3]).substring(1)+'-'+S4(yxxx(buf[4]))+'-'+S4(buf[5])+S4(buf[6])+S4(buf[7]));
} else { } else {
var d = new Date().getTime(); var d = new Date().getTime();
var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {