reimplement autosave after dalay option in collab-friendly way

pull/483/merge
nightwing 2017-12-23 23:25:42 +04:00
rodzic 7c23e8e7ac
commit 2ca11b2d7d
3 zmienionych plików z 108 dodań i 23 usunięć

Wyświetl plik

@ -14,16 +14,20 @@ define(function(require, exports, module) {
var Plugin = imports.Plugin; var Plugin = imports.Plugin;
var settings = imports.settings; var settings = imports.settings;
var lang = require("ace/lib/lang");
/***** Initialization *****/ /***** Initialization *****/
var plugin = new Plugin("Ajax.org", main.consumes); var plugin = new Plugin("Ajax.org", main.consumes);
var CHANGE_TIMEOUT = 500; var CHANGE_TIMEOUT = options.changeTimeout || 1000;
var SLOW_CHANGE_TIMEOUT = options.slowChangeTimeout || 30000; var SLOW_CHANGE_TIMEOUT = options.slowChangeTimeout || 30000;
var SLOW_SAVE_THRESHOLD = 100 * 1024; // 100KB
var docChangeTimeout; var docChangeTimeout;
var lastSaveTime = 0;
var sessionId;
var autosave; var autosave;
var saveWhenIdle;
function load() { function load() {
prefs.add({ prefs.add({
@ -31,10 +35,16 @@ define(function(require, exports, module) {
position: 150, position: 150,
"Save": { "Save": {
position: 100, position: 100,
"Enable Auto-Save On Blur": { "Auto-Save Files": {
type: "checkbox", type: "dropdown",
position: 100, position: 100,
path: "user/general/@autosave" path: "user/general/@autosave",
width: 130,
items: [
{ caption: "Off", value: false },
{ caption: "On Focus Change", value: "onFocusChange" },
{ caption: "After Delay", value: "afterDelay" },
],
} }
} }
} }
@ -52,25 +62,69 @@ define(function(require, exports, module) {
/***** Helpers *****/ /***** Helpers *****/
function onSettingChange() { function onSettingChange() {
autosave = settings.getBool("user/general/@autosave"); autosave = settings.get("user/general/@autosave");
if (autosave == "off" || autosave == "false")
autosave = false;
disable();
if (autosave == "afterDelay")
enableDelay();
if (autosave) if (autosave)
enable(); enable();
else }
disable();
function enableDelay() {
saveWhenIdle = lang.delayedCall(function() {
var tab = tabs.focussedTab;
var ace = tab && tab.editor && tab.editor.ace;
if (ace && ace.session && sessionId == ace.session.id) {
saveTab(tab);
}
});
} }
function enable() { function enable() {
apf.on("movefocus", scheduleCheck); apf.on("movefocus", scheduleCheck);
tabs.on("tabAfterActivate", scheduleCheck, plugin); tabs.on("tabAfterActivate", scheduleCheck, plugin);
if (saveWhenIdle)
tabs.on("focusSync", attachToTab, plugin);
window.addEventListener("blur", scheduleCheck); window.addEventListener("blur", scheduleCheck);
} }
function disable() { function disable() {
sessionId = null;
if (saveWhenIdle) {
saveWhenIdle.cancel();
saveWhenIdle = null;
}
if (docChangeTimeout) {
clearTimeout(docChangeTimeout);
docChangeTimeout = null;
}
apf.off("movefocus", scheduleCheck); apf.off("movefocus", scheduleCheck);
tabs.off("tabAfterActivate", scheduleCheck); tabs.off("tabAfterActivate", scheduleCheck);
tabs.off("focusSync", attachToTab);
window.removeEventListener("blur", scheduleCheck); window.removeEventListener("blur", scheduleCheck);
} }
function attachToTab(e) {
var ace = e.tab && e.tab.editor && e.tab.editor.ace;
if (ace)
ace.on("beforeEndOperation", beforeEndOperation);
}
function beforeEndOperation(e, ace) {
if (!saveWhenIdle)
return ace.off("beforeEndOperation", beforeEndOperation);
if (!ace.isFocused() && !options.ignoreFocusForTesting)
return;
sessionId = ace.session.id;
if (sessionId && ace.curOp.docChanged && ace.curOp.command.name) {
var timeout = Math.min(Math.max(CHANGE_TIMEOUT, lastSaveTime || 0), SLOW_CHANGE_TIMEOUT);
saveWhenIdle.delay(timeout);
}
}
function scheduleCheck(e) { function scheduleCheck(e) {
if (docChangeTimeout) if (docChangeTimeout)
return; return;
@ -117,11 +171,6 @@ define(function(require, exports, module) {
function saveTab(tab, force) { function saveTab(tab, force) {
if (!autosave) return; if (!autosave) return;
if (!c9.has(c9.STORAGE)) {
save.setSavingState(tab, "offline");
return;
}
var doc; var doc;
if (!force && (!tab.path if (!force && (!tab.path
|| !(doc = tab.document).changed || !(doc = tab.document).changed
@ -133,9 +182,18 @@ define(function(require, exports, module) {
|| !doc.hasValue())) || !doc.hasValue()))
return; return;
if (!c9.has(c9.STORAGE)) {
save.setSavingState(tab, "offline");
return;
}
var t = Date.now();
save.save(tab, { save.save(tab, {
silentsave: true, silentsave: true,
}, function() {}); noUi: true,
}, function() {
lastSaveTime = t - Date.now();
});
return true; return true;
} }
@ -146,12 +204,8 @@ define(function(require, exports, module) {
load(); load();
}); });
plugin.on("unload", function() { plugin.on("unload", function() {
window.removeEventListener("blur", scheduleCheck); disable();
autosave = false; autosave = false;
if (docChangeTimeout) {
clearTimeout(docChangeTimeout);
docChangeTimeout = null;
}
}); });
/***** Register and define API *****/ /***** Register and define API *****/

Wyświetl plik

@ -46,6 +46,8 @@ require(["lib/chai/chai"], function (chai) {
"plugins/c9.ide.save/save", "plugins/c9.ide.save/save",
{ {
packagePath: "plugins/c9.ide.save/autosave", packagePath: "plugins/c9.ide.save/autosave",
ignoreFocusForTesting: true,
changeTimeout: 5,
}, },
{ {
packagePath: "plugins/c9.vfs.client/vfs_client_mock", packagePath: "plugins/c9.vfs.client/vfs_client_mock",
@ -175,6 +177,30 @@ require(["lib/chai/chai"], function (chai) {
}); });
}); });
it("should automatically save after delay", function(done) {
settings.set("user/general/@autosave", "afterDelay");
var path = "/autosave2.txt";
createAndChangeTab(path, function(tab) {
expect(tab.document.changed).to.ok;
setTimeout(function() {
expect(tab.document.changed).to.ok;
tab.editor.ace.execCommand("insertstring", "x");
}, 10);
save.once("afterSave", function() {
fs.readFile(path, function(err, data) {
if (err) throw err;
expect(data).to.equal("testx" + path);
expect(tab.document.changed).to.not.ok;
fs.unlink(path, function() {
done();
});
});
});
});
});
if (!onload.remain) { if (!onload.remain) {
describe("unload()", function() { describe("unload()", function() {
it('should destroy all ui elements when it is unloaded', function(done) { it('should destroy all ui elements when it is unloaded', function(done) {

Wyświetl plik

@ -412,7 +412,7 @@ define(function(require, exports, module) {
doc.meta.$saveBuffer = true; doc.meta.$saveBuffer = true;
} }
setSavingState(tab, "saving"); setSavingState(tab, "saving", null, options.noUi);
var bookmark = doc.undoManager.position; var bookmark = doc.undoManager.position;
var loadStartT = Date.now(); var loadStartT = Date.now();
@ -450,7 +450,7 @@ define(function(require, exports, module) {
if (options.path) if (options.path)
tab.path = options.path; tab.path = options.path;
setSavingState(tab, "saved", options.timeout); setSavingState(tab, "saved", options.timeout, options.noUi);
settings.save(); settings.save();
logger.log("Successfully saved " + path); logger.log("Successfully saved " + path);
} }
@ -541,7 +541,7 @@ define(function(require, exports, module) {
} }
var stateTimer = null, pageTimers = {}; var stateTimer = null, pageTimers = {};
function setSavingState(tab, state, timeout) { function setSavingState(tab, state, timeout, silent) {
clearTimeout(stateTimer); clearTimeout(stateTimer);
clearTimeout(pageTimers[tab.name]); clearTimeout(pageTimers[tab.name]);
@ -554,6 +554,12 @@ define(function(require, exports, module) {
else else
delete doc.meta.$saving; delete doc.meta.$saving;
if (!silent)
updateSavingUi();
emit("tabSavingState", { tab: tab });
}
function updateSavingUi(tab, state, timeout) {
if (state == "saving") { if (state == "saving") {
btnSave.show(); btnSave.show();
@ -621,7 +627,6 @@ define(function(require, exports, module) {
btnSave.setCaption("Not saved"); btnSave.setCaption("Not saved");
tab.classList.add("error"); tab.classList.add("error");
} }
emit("tabSavingState", { tab: tab });
} }
/***** Lifecycle *****/ /***** Lifecycle *****/