Added teamserver provider/helper

This commit is contained in:
benweet 2014-05-04 18:31:57 +01:00
parent 61e472d7f0
commit 72f85bd793
8 changed files with 461 additions and 39 deletions

View File

@ -27,8 +27,9 @@ define([], function() {
constants.PICASA_PROXY_URL = "https://stackedit-picasa-proxy.herokuapp.com/"; constants.PICASA_PROXY_URL = "https://stackedit-picasa-proxy.herokuapp.com/";
constants.SSH_PROXY_URL = "https://stackedit-ssh-proxy.herokuapp.com/"; constants.SSH_PROXY_URL = "https://stackedit-ssh-proxy.herokuapp.com/";
constants.HTMLTOPDF_URL = "https://stackedit-htmltopdf.herokuapp.com/"; constants.HTMLTOPDF_URL = "https://stackedit-htmltopdf.herokuapp.com/";
constants.TEAM_SERVER_URL = "http://localhost:11583/";
// Site dependent // Site dependent
constants.BASE_URL = "http://localhost/"; constants.BASE_URL = "http://localhost/";
constants.GOOGLE_CLIENT_ID = '241271498917-lev37kef013q85avc91am1gccg5g8lrb.apps.googleusercontent.com'; constants.GOOGLE_CLIENT_ID = '241271498917-lev37kef013q85avc91am1gccg5g8lrb.apps.googleusercontent.com';
constants.GITHUB_CLIENT_ID = 'e47fef6055344579799d'; constants.GITHUB_CLIENT_ID = 'e47fef6055344579799d';

View File

@ -63,12 +63,10 @@ define([
task.onRun(function() { task.onRun(function() {
if(isOffline === true) { if(isOffline === true) {
connected = false; connected = false;
task.error(new Error("Operation not available in offline mode.|stopPublish")); return task.error(new Error("Operation not available in offline mode.|stopPublish"));
return;
} }
if(connected === true) { if(connected === true) {
task.chain(); return task.chain();
return;
} }
window.delayedFunction = function() { window.delayedFunction = function() {
gapi.load("client", function() { gapi.load("client", function() {
@ -148,8 +146,7 @@ define([
var immediate; var immediate;
function localAuthenticate() { function localAuthenticate() {
if(authuser > 5) { if(authuser > 5) {
task.error(new Error('Unable to authenticate user ' + authorizationMgr.getUserId() + ', please sign in with Google.')); return task.error(new Error('Unable to authenticate user ' + authorizationMgr.getUserId() + ', please sign in with Google.'));
return;
} }
if(immediate === false) { if(immediate === false) {
task.timeout = constants.ASYNC_TASK_LONG_TIMEOUT; task.timeout = constants.ASYNC_TASK_LONG_TIMEOUT;
@ -184,8 +181,7 @@ define([
} }
function oauthRedirect() { function oauthRedirect() {
if(immediate === true) { if(immediate === true) {
task.chain(localAuthenticate); return task.chain(localAuthenticate);
return;
} }
utils.redirectConfirm('You are being redirected to <strong>Google</strong> authorization page.', function() { utils.redirectConfirm('You are being redirected to <strong>Google</strong> authorization page.', function() {
task.chain(localAuthenticate); task.chain(localAuthenticate);
@ -257,7 +253,7 @@ define([
var headers = { var headers = {
'Content-Type': 'multipart/mixed; boundary="' + boundary + '"', 'Content-Type': 'multipart/mixed; boundary="' + boundary + '"',
}; };
var base64Data = utils.encodeBase64(content); var base64Data = utils.encodeBase64(content);
var multipartRequestBody = [ var multipartRequestBody = [
delimiter, delimiter,
@ -288,8 +284,7 @@ define([
// Upload success // Upload success
result = response; result = response;
result.content = content; result.content = content;
task.chain(); return task.chain();
return;
} }
var error = response.error; var error = response.error;
// Handle error // Handle error
@ -332,8 +327,7 @@ define([
if(response && response.id) { if(response && response.id) {
// Rename success // Rename success
result = response; result = response;
task.chain(); return task.chain();
return;
} }
var error = response.error; var error = response.error;
// Handle error // Handle error
@ -380,8 +374,7 @@ define([
request.execute(function(response) { request.execute(function(response) {
if(!response || !response.largestChangeId) { if(!response || !response.largestChangeId) {
// Handle error // Handle error
handleError(response.error, task); return handleError(response.error, task);
return;
} }
// Retrieve success // Retrieve success
newChangeId = response.largestChangeId; newChangeId = response.largestChangeId;
@ -419,8 +412,7 @@ define([
task.onRun(function() { task.onRun(function() {
function recursiveDownloadMetadata() { function recursiveDownloadMetadata() {
if(ids.length === 0) { if(ids.length === 0) {
task.chain(); return task.chain();
return;
} }
var id = ids[0]; var id = ids[0];
var headers = {}; var headers = {};
@ -475,8 +467,7 @@ define([
task.onRun(function() { task.onRun(function() {
function recursiveDownloadContent() { function recursiveDownloadContent() {
if(objects.length === 0) { if(objects.length === 0) {
task.chain(); return task.chain();
return;
} }
var object = objects[0]; var object = objects[0];
result.push(object); result.push(object);
@ -491,8 +482,7 @@ define([
} }
if(!file) { if(!file) {
objects.shift(); objects.shift();
task.chain(recursiveDownloadContent); return task.chain(recursiveDownloadContent);
return;
} }
var url = file.downloadUrl; var url = file.downloadUrl;
// if file is a real time document // if file is a real time document
@ -604,16 +594,14 @@ define([
errorMsg = "Google error (" + error.code + ": " + error.message + ")."; errorMsg = "Google error (" + error.code + ": " + error.message + ").";
if(error.code >= 500 && error.code < 600) { if(error.code >= 500 && error.code < 600) {
// Retry as described in Google's best practices // Retry as described in Google's best practices
task.retry(new Error(errorMsg)); return task.retry(new Error(errorMsg));
return;
} }
else if(error.code === 401 || error.code === 403 || error.code == "token_refresh_required") { else if(error.code === 401 || error.code === 403 || error.code == "token_refresh_required") {
_.each(authorizationMgrMap, function(authorizationMgr) { _.each(authorizationMgrMap, function(authorizationMgr) {
authorizationMgr.setRefreshFlag(); authorizationMgr.setRefreshFlag();
}); });
errorMsg = "Access to Google account is not authorized."; errorMsg = "Access to Google account is not authorized.";
task.retry(new Error(errorMsg), 1); return task.retry(new Error(errorMsg), 1);
return;
} }
else if(error.code === 0 || error.code === -1) { else if(error.code === 0 || error.code === -1) {
connected = false; connected = false;
@ -632,8 +620,7 @@ define([
function loadPicker(task) { function loadPicker(task) {
task.onRun(function() { task.onRun(function() {
if(pickerLoaded === true) { if(pickerLoaded === true) {
task.chain(); return task.chain();
return;
} }
$.ajax({ $.ajax({
url: "//www.google.com/jsapi", url: "//www.google.com/jsapi",

View File

@ -0,0 +1,232 @@
/*global gapi, google */
define([
"underscore",
"jquery",
"constants",
"core",
"utils",
"storage",
"logger",
"settings",
"eventMgr",
"classes/AsyncTask"
], function(_, $, constants, core, utils, storage, logger, settings, eventMgr, AsyncTask) {
var connected = false;
var authenticated = true;
var teamserverHelper = {};
// Listen to offline status changes
var isOffline = false;
eventMgr.addListener("onOfflineChanged", function(isOfflineParam) {
isOffline = isOfflineParam;
});
function connect(task) {
task.onRun(function() {
if(isOffline === true) {
connected = false;
return task.error(new Error("Operation not available in offline mode.|stopPublish"));
}
if(connected === true) {
return task.chain();
}
$.ajax({
url: settings.teamserverURL + '/ping',
timeout: constants.AJAX_TIMEOUT
}).fail(function(jqXHR) {
var error = {
code: jqXHR.status,
message: jqXHR.statusText
};
handleError(error, task);
});
});
}
function authenticate(task) {
task.onRun(function() {
if(authenticated === true) {
return task.chain();
}
});
}
teamserverHelper.upload = function(repo, id, title, content, callback) {
var result;
var task = new AsyncTask();
connect(task);
authenticate(task);
task.onRun(function() {
var url = settings.teamserverURL + '/repo/' + repo + '/document';
var type = 'POST';
if(id) {
url += '/' + id;
type = 'PUT';
}
$.ajax({
url: url,
type: type,
data: {
title: title,
content: content
},
dataType: "json",
timeout: constants.AJAX_TIMEOUT
}).done(function(data) {
result = data;
}).fail(function(jqXHR) {
var error = {
code: jqXHR.status,
message: jqXHR.statusText
};
// Handle error
if(error.code === 404) {
error = 'File ID "' + id + '" not found on the Team Server.';
}
handleError(error, task);
});
});
task.onSuccess(function() {
callback(undefined, result);
});
task.onError(function(error) {
callback(error);
});
task.enqueue();
};
teamserverHelper.checkChanges = function(repo, lastChangeId, accountId, callback) {
var changes;
var newChangeId = lastChangeId;
var task = new AsyncTask();
connect(task);
authenticate(task);
task.onRun(function() {
var url = settings.teamserverURL + '/repo/' + repo + '/changes';
var type = 'GET';
if(lastChangeId) {
url += lastChangeId;
}
$.ajax({
url: url,
type: type,
dataType: "json",
timeout: constants.AJAX_TIMEOUT
}).done(function(data) {
newChangeId = data.newChangeId;
changes = data.changes;
}).fail(function(jqXHR) {
var error = {
code: jqXHR.status,
message: jqXHR.statusText
};
handleError(error, task);
});
});
task.onSuccess(function() {
callback(undefined, changes, newChangeId);
});
task.onError(function(error) {
callback(error);
});
task.enqueue();
};
teamserverHelper.download = function(repo, ids, callback) {
var result = [];
var task = new AsyncTask();
connect(task);
authenticate(task);
task.onRun(function() {
function recursiveDownloadMetadata() {
if(ids.length === 0) {
return task.chain();
}
var id = ids[0];
var url = settings.teamserverURL + '/repo/' + repo + '/document/' + id;
$.ajax({
url: url,
dataType: "json",
timeout: constants.AJAX_TIMEOUT
}).done(function(data) {
result.push(data);
ids.shift();
task.chain(recursiveDownloadMetadata);
}).fail(function(jqXHR) {
var error = {
code: jqXHR.status,
message: jqXHR.statusText
};
if(error.code === 404) {
error = 'File ID "' + id + '" not found on the Team Server.';
}
handleError(error, task);
});
}
task.chain(recursiveDownloadMetadata);
});
task.onSuccess(function() {
callback(undefined, result);
});
task.onError(function(error) {
callback(error);
});
task.enqueue();
};
function handleError(error, task) {
var errorMsg;
if(error) {
logger.error(error);
// Try to analyze the error
if(typeof error === "string") {
errorMsg = error;
}
else {
errorMsg = "Google error (" + error.code + ": " + error.message + ").";
if(error.code >= 500 && error.code < 600) {
// Retry as described in Google's best practices
return task.retry(new Error(errorMsg));
}
else if(error.code === 401 || error.code === 403) {
authenticated = false;
return task.retry(new Error(errorMsg), 1);
}
else if(error.code === 0 || error.code === -1) {
connected = false;
errorMsg = "|stopPublish";
}
}
}
task.error(new Error(errorMsg));
}
teamserverHelper.picker = function(repo, callback) {
var docs = [];
var picker;
function hidePicker() {
if(picker !== undefined) {
picker.setVisible(false);
$(".modal-backdrop, .picker").remove();
}
}
var task = new AsyncTask();
// Add some time for user to choose his files
task.timeout = constants.ASYNC_TASK_LONG_TIMEOUT;
connect(task);
task.onSuccess(function() {
callback(undefined, docs);
});
task.onError(function(error) {
hidePicker();
callback(error);
});
task.enqueue();
};
return teamserverHelper;
});

View File

@ -91,8 +91,7 @@ define([
var syncIndex = createSyncIndex(doc.id); var syncIndex = createSyncIndex(doc.id);
var fileDesc = fileMgr.getFileFromSyncIndex(syncIndex); var fileDesc = fileMgr.getFileFromSyncIndex(syncIndex);
if(fileDesc !== undefined) { if(fileDesc !== undefined) {
eventMgr.onError('"' + fileDesc.title + '" was already imported.'); return eventMgr.onError('"' + fileDesc.title + '" was already imported.');
return;
} }
importIds.push(doc.id); importIds.push(doc.id);
}); });
@ -109,16 +108,14 @@ define([
var fileDesc = fileMgr.getFileFromSyncIndex(syncIndex); var fileDesc = fileMgr.getFileFromSyncIndex(syncIndex);
if(fileDesc !== undefined) { if(fileDesc !== undefined) {
eventMgr.onError('File ID is already synchronized with "' + fileDesc.title + '".'); eventMgr.onError('File ID is already synchronized with "' + fileDesc.title + '".');
callback(true); return callback(true);
return;
} }
} }
var parentId = utils.getInputTextValue('#input-sync-export-' + providerId + '-parentid'); var parentId = utils.getInputTextValue('#input-sync-export-' + providerId + '-parentid');
var data = gdriveProvider.serializeContent(content, discussionListJSON); var data = gdriveProvider.serializeContent(content, discussionListJSON);
googleHelper.upload(fileId, parentId, title, data, undefined, undefined, accountId, function(error, result) { googleHelper.upload(fileId, parentId, title, data, undefined, undefined, accountId, function(error, result) {
if(error) { if(error) {
callback(error); return callback(error);
return;
} }
var syncAttributes = createSyncAttributes(result.id, result.etag, content, title, discussionListJSON); var syncAttributes = createSyncAttributes(result.id, result.etag, content, title, discussionListJSON);
callback(undefined, syncAttributes); callback(undefined, syncAttributes);
@ -128,7 +125,7 @@ define([
gdriveProvider.syncUp = function(content, contentCRC, title, titleCRC, discussionList, discussionListCRC, syncAttributes, callback) { gdriveProvider.syncUp = function(content, contentCRC, title, titleCRC, discussionList, discussionListCRC, syncAttributes, callback) {
if( if(
(syncAttributes.contentCRC == contentCRC) && // Content CRC hasn't changed (syncAttributes.contentCRC == contentCRC) && // Content CRC hasn't changed
(syncAttributes.titleCRC == titleCRC) && // Content CRC hasn't changed (syncAttributes.titleCRC == titleCRC) && // Title CRC hasn't changed
(syncAttributes.discussionListCRC == discussionListCRC) // Discussion list CRC hasn't changed (syncAttributes.discussionListCRC == discussionListCRC) // Discussion list CRC hasn't changed
) { ) {
return callback(undefined, false); return callback(undefined, false);
@ -144,8 +141,7 @@ define([
var data = gdriveProvider.serializeContent(content, discussionList); var data = gdriveProvider.serializeContent(content, discussionList);
googleHelper.upload(syncAttributes.id, undefined, title, data, undefined, syncAttributes.etag, accountId, function(error, result) { googleHelper.upload(syncAttributes.id, undefined, title, data, undefined, syncAttributes.etag, accountId, function(error, result) {
if(error) { if(error) {
callback(error, true); return callback(error, true);
return;
} }
syncAttributes.etag = result.etag; syncAttributes.etag = result.etag;
// Remove this deprecated flag if any // Remove this deprecated flag if any
@ -167,8 +163,7 @@ define([
var lastChangeId = parseInt(storage[accountId + ".gdrive.lastChangeId"], 10); var lastChangeId = parseInt(storage[accountId + ".gdrive.lastChangeId"], 10);
googleHelper.checkChanges(lastChangeId, accountId, function(error, changes, newChangeId) { googleHelper.checkChanges(lastChangeId, accountId, function(error, changes, newChangeId) {
if(error) { if(error) {
callback(error); return callback(error);
return;
} }
var interestingChanges = []; var interestingChanges = [];
_.each(changes, function(change) { _.each(changes, function(change) {

View File

@ -0,0 +1,5 @@
define([
"providers/teamserverProviderBuilder"
], function(teamserverProviderBuilder) {
return teamserverProviderBuilder("teamserver", "Team Server");
});

View File

@ -0,0 +1,200 @@
define([
"jquery",
"underscore",
"constants",
"utils",
"storage",
"logger",
"classes/Provider",
"settings",
"eventMgr",
"fileMgr",
"editor",
"helpers/teamserverHelper"
], function($, _, constants, utils, storage, logger, Provider, settings, eventMgr, fileMgr, editor, teamserverHelper) {
return function(providerId, providerName) {
var repo = 'teamserver';
var teamserverProvider = new Provider(providerId, providerName);
function createSyncIndex(id) {
return "sync." + providerId + "." + id;
}
var merge = settings.conflictMode == 'merge';
function createSyncAttributes(id, sha, content, title, discussionListJSON) {
discussionListJSON = discussionListJSON || '{}';
var syncAttributes = {};
syncAttributes.provider = teamserverProvider;
syncAttributes.id = id;
syncAttributes.sha = sha;
syncAttributes.contentCRC = utils.crc32(content);
syncAttributes.titleCRC = utils.crc32(title);
syncAttributes.discussionListCRC = utils.crc32(discussionListJSON);
syncAttributes.syncIndex = createSyncIndex(id);
if(merge === true) {
// Need to store the whole content for merge
syncAttributes.content = content;
syncAttributes.title = title;
syncAttributes.discussionList = discussionListJSON;
}
return syncAttributes;
}
function importFilesFromIds(ids) {
teamserverHelper.download(repo, ids, function(error, result) {
if(error) {
return;
}
var fileDescList = [];
var fileDesc;
_.each(result, function(file) {
var parsedContent = teamserverProvider.parseContent(file.content);
var syncLocations;
var syncAttributes = createSyncAttributes(file.id, file.sha, parsedContent.content, file.title, parsedContent.discussionListJSON);
syncLocations = {};
syncLocations[syncAttributes.syncIndex] = syncAttributes;
fileDesc = fileMgr.createFile(file.title, parsedContent.content, parsedContent.discussionListJSON, syncLocations);
fileDescList.push(fileDesc);
});
if(fileDesc !== undefined) {
eventMgr.onSyncImportSuccess(fileDescList, teamserverProvider);
fileMgr.selectFile(fileDesc);
}
});
}
teamserverProvider.importFiles = function() {
teamserverHelper.picker(repo, function(error, docs) {
if(error || docs.length === 0) {
return;
}
var importIds = [];
_.each(docs, function(doc) {
var syncIndex = createSyncIndex(doc.id);
var fileDesc = fileMgr.getFileFromSyncIndex(syncIndex);
if(fileDesc !== undefined) {
eventMgr.onError('"' + fileDesc.title + '" was already imported.');
return;
}
importIds.push(doc.id);
});
importFilesFromIds(importIds);
});
};
teamserverProvider.exportFile = function(event, title, content, discussionListJSON, callback) {
var data = teamserverProvider.serializeContent(content, discussionListJSON);
teamserverHelper.upload(repo, undefined, title, data, function(error, result) {
if(error) {
return callback(error);
}
var syncAttributes = createSyncAttributes(result.id, result.sha, content, title, discussionListJSON);
callback(undefined, syncAttributes);
});
};
teamserverProvider.syncUp = function(content, contentCRC, title, titleCRC, discussionList, discussionListCRC, syncAttributes, callback) {
if(
(syncAttributes.contentCRC == contentCRC) && // Content CRC hasn't changed
(syncAttributes.titleCRC == titleCRC) && // Title CRC hasn't changed
(syncAttributes.discussionListCRC == discussionListCRC) // Discussion list CRC hasn't changed
) {
return callback(undefined, false);
}
var data = teamserverProvider.serializeContent(content, discussionList);
teamserverHelper.upload(repo, syncAttributes.id, title, data, function(error, result) {
if(error) {
callback(error, true);
return;
}
syncAttributes.etag = result.etag;
if(merge === true) {
// Need to store the whole content for merge
syncAttributes.content = content;
syncAttributes.title = title;
syncAttributes.discussionList = discussionList;
}
syncAttributes.contentCRC = contentCRC;
syncAttributes.titleCRC = titleCRC;
syncAttributes.discussionListCRC = discussionListCRC;
callback(undefined, true);
});
};
teamserverProvider.syncDown = function(callback) {
var lastChangeId = parseInt(storage["teamserver.lastChangeId"], 10);
teamserverHelper.checkChanges(repo, lastChangeId, function(error, changes, newChangeId) {
if(error) {
return callback(error);
}
var interestingChanges = [];
_.each(changes, function(change) {
var syncIndex = createSyncIndex(change.id);
var fileDesc = fileMgr.getFileFromSyncIndex(syncIndex);
var syncAttributes = fileDesc && fileDesc.syncLocations[syncIndex];
if(!syncAttributes) {
return;
}
// Store fileDesc and syncAttributes references to avoid 2 times search
change.fileDesc = fileDesc;
change.syncAttributes = syncAttributes;
// Delete
if(change.deleted === true) {
interestingChanges.push(change);
return;
}
// Modify
if(syncAttributes.sha != change.sha) {
interestingChanges.push(change);
}
});
teamserverHelper.downloadContent(repo, interestingChanges, function(error, changes) {
if(error) {
return callback(error);
}
function mergeChange() {
if(changes.length === 0) {
storage["teamserver.lastChangeId"] = newChangeId;
return callback();
}
var change = changes.pop();
var fileDesc = change.fileDesc;
var syncAttributes = change.syncAttributes;
// File deleted
if(change.deleted === true) {
eventMgr.onError('"' + fileDesc.title + '" has been removed from ' + providerName + '.');
fileDesc.removeSyncLocation(syncAttributes);
return eventMgr.onSyncRemoved(fileDesc, syncAttributes);
}
var parsedContent = teamserverProvider.parseContent(change.content);
var remoteContent = parsedContent.content;
var remoteTitle = change.title;
var remoteDiscussionListJSON = parsedContent.discussionListJSON;
var remoteDiscussionList = parsedContent.discussionList;
var remoteCRC = teamserverProvider.syncMerge(fileDesc, syncAttributes, remoteContent, remoteTitle, remoteDiscussionList, remoteDiscussionListJSON);
// Update syncAttributes
syncAttributes.sha = change.sha;
if(merge === true) {
// Need to store the whole content for merge
syncAttributes.content = remoteContent;
syncAttributes.title = remoteTitle;
syncAttributes.discussionList = remoteDiscussionListJSON;
}
syncAttributes.contentCRC = remoteCRC.contentCRC;
syncAttributes.titleCRC = remoteCRC.titleCRC;
syncAttributes.discussionListCRC = remoteCRC.discussionListCRC;
utils.storeAttributes(syncAttributes);
setTimeout(mergeChange, 5);
}
setTimeout(mergeChange, 5);
});
});
};
return teamserverProvider;
};
});

View File

@ -56,6 +56,7 @@ define([
'</html>' '</html>'
].join(""), ].join(""),
pdfPageSize: 'A4', pdfPageSize: 'A4',
teamserverURL: constants.TEAM_SERVER_URL,
sshProxy: constants.SSH_PROXY_URL, sshProxy: constants.SSH_PROXY_URL,
extensionSettings: {} extensionSettings: {}
}; };

View File

@ -7,6 +7,7 @@ define([
"fileSystem", "fileSystem",
"fileMgr", "fileMgr",
"classes/Provider", "classes/Provider",
"providers/teamserverProvider",
"providers/dropboxProvider", "providers/dropboxProvider",
"providers/gdriveProvider", "providers/gdriveProvider",
"providers/gdrivesecProvider", "providers/gdrivesecProvider",