blob: d9ebe53bcafb199e16e2c4008cd8ce518278ff54 [file] [log] [blame]
/**
* PSLocal PSLocal.js file.
* This plugin is a Gears based local persistent storage backend.
*/
(function() {
var PSLocal = window.PSLocal = function PSLocal(editor) {
this.editor = editor;
this.config = {
capabilities: {
directory_operations: true,
file_operations: true,
image_operations: false,
upload_operations: true,
import_operations: false,
user_publish: false,
shared_publish: false,
user_storage: true
},
displayName: 'Local'
}
}
PSLocal._pluginInfo = {
name : "PSLocal",
version : "2.0",
developer : "Douglas Mayle",
developer_url : "http://xinha.org",
license : "BSD"
};
PSLocal.prototype.onGenerateOnce = function () {
// We use _prepareBackend to asynchronously load the Gears backend.
this._prepareBackend();
};
PSLocal.prototype._showGearsButton = function() {
var self = this;
var editor = this.editor;
editor.config.registerButton({
id : 'localstorage',
tooltip : Xinha._lc( 'Learn About Local Storage', 'PSLocal' ),
image : [_editor_url + editor.config.imgURL + 'ed_buttons_main.png',2,8],
textMode : true,
action : function() { self.learnDialog(); }
}
);
editor.config.addToolbarElement('localstorage', 'fullscreen', 1);
// Since this is after the editor load, we have to trigger an udate manually...
editor._rebuildToolbar();
}
PSLocal.prototype._prepareBackend = function() {
var self = this;
var editor = this.editor;
if (!this.gears_init) {
this.gears_init = true;
Xinha._loadback(Xinha.getPluginDir("PSLocal") + "/gears_init.js",
function() {
self._prepareBackend();
});
return;
}
if (!window.google || !google.gears) {
// No gears, so no need to register. We'll register a button, however, to
// enable users to learn about the support we offer.
this._showGearsButton();
return;
}
if (!google.gears.factory.hasPermission) {
if (!google.gears.factory.getPermission('Xinha', editor.imgURL('/images/xinha-small-icon.gif'), Xinha._lc( 'Enable Gears in order to use local document storage and configuration.', 'PSLocal' ))) {
// The user has denied access to Gears. We'll give them a UI to allow
// them to change their mind.
this._showGearsButton();
return;
}
}
this.workerPool = google.gears.factory.create('beta.workerpool', '1.0');
this.remoteStorageWorker = this.workerPool.createWorkerFromUrl("http://xinhadocs.org/worker.js");
this._registerBackend();
}
PSLocal.prototype.learnDialog = function(timeWaited) {
var self = this;
var editor = this.editor;
if (!this.html) {
Xinha._getback(Xinha.getPluginDir("PSLocal") + "/dialog.html",
function(getback) {
self.html = getback;
self.learnDialog();
});
return;
}
if (this.dialog) {
this.dialog.show();
return;
}
this.dialog = new Xinha.Dialog(editor, this.html, "PersistentStorage",
{width: 700,
closeOnEscape: true,
resizable: true,
centered: true,
modal: true
});
var link = this.dialog.getElementById('GearsLink');
// Tack on our URL so that Google will return here after installation.
link.href += location.href;
var button = this.dialog.getElementById("confirm");
if (window.google && google.gears) {
Xinha._addClass(this.dialog.getElementById('InstallText'), 'hidden');
// The user has gears installed, but has denied us access to it, so we'll
// give them the option to change their mind.
button.value = Xinha._lc('Enable', 'PSLocal');
button.onclick = function() {
// The user gave us permission, so we'll reload the page.
if (confirm(Xinha._lc('This will reload the page, causing you to lose any unsaved work. Press "OK" to reload.', 'PSLocal' ))) {
window.location.reload(true);
}
}
} else {
Xinha._addClass(this.dialog.getElementById('EnableText'), 'hidden');
// Gears isn't installed, so we'll build the dialog to prompt installation.
button.value = Xinha._lc('Install', 'PSLocal');
button.onclick = function() {
location.href = link.href;
}
}
var cancel = this.dialog.getElementById('cancel');
cancel.onclick = function() {
self.dialog.hide();
}
this.dialog.show();
}
PSLocal.prototype._registerBackend = function(timeWaited) {
var editor = this.editor;
var self = this;
if (!timeWaited) {
timeWaited = 0;
}
// Retry over a period of ten seconds to register. We back off exponentially
// to limit resouce usage in the case of misconfiguration.
var registerTimeout = 10000;
if (timeWaited > registerTimeout) {
// This is most likely a configuration error. We're loaded and
// PersistentStorage is not.
return;
}
if (!editor.plugins['PersistentStorage'] ||
!editor.plugins['PersistentStorage'].instance ||
!editor.plugins['PersistentStorage'].instance.ready) {
window.setTimeout(function() {self._registerBackend(timeWaited ? timeWaited*2 : 50);}, timeWaited ? timeWaited : 50);
return;
}
var PS = editor.plugins['PersistentStorage'].instance;
var self = this;
this.config.thumbURL = this.editor.imgURL('images/tango/32x32/places/folder.png');
this.loadDocument({URL:'', name:'config.js', key:'/config.js'}, function(json) {
var userconfig = json ? eval('(' + json + ')') : false;
PS.registerBackend('PSLocal', self, self.config, userconfig);
});
}
PSLocal.prototype.loadDocument = function(entry, asyncCallback) {
this.workerPool.onmessage = function(a, b, message) {
if (!message.body || !message.body.authorized) {
// Fail
asyncCallback('');
}
if (message.body.response) {
asyncCallback(message.body.response);
} else if (entry.URL) {
Xinha._getback(entry.URL,
function(documentSource) {
asyncCallback(documentSource);
});
} else {
// Oops, no data :-(
asyncCallback('');
}
}
this.workerPool.sendMessage({func: 'loadDocument', entry: entry}, this.remoteStorageWorker);
}
PSLocal.prototype.loadData = function (asyncCallback) {
this.workerPool.onmessage = function(a, b, message) {
if (!message.body || !message.body.authorized) {
// Fail
asyncCallback('');
}
asyncCallback({dirs: message.body.dirs, files: message.body.files});
}
this.workerPool.sendMessage({func: 'loadData'}, this.remoteStorageWorker);
}
PSLocal.prototype.getFilters = function(filedata) {
// Clear out the previous directory listing.
var filters = [], paths = {};
var dirList = filedata.dirs;
for (var index=0; index<dirList.length; ++index) {
filters.push({value:dirList[index],display:dirList[index]});
}
// We can't return an empty filter list.
if (!filters.length) {
filters.push({value:'/',display:'/'});
}
return filters;
}
PSLocal.prototype.getMetadata = function(filedata, filterPath) {
var editor = this.editor;
var self = this;
var metadata = [];
var fileList = filedata.files;
for (var index=0; index<fileList.length; ++index) {
// Gah perform file splitting here..
var pathpart = fileList[index].fullpath.split('/');
if (pathpart.length > 2) {
pathpart = pathpart.slice(0,pathpart.length-1).join('/');
} else {
pathpart = '/';
}
var filepart = fileList[index].fullpath.split('/').slice(-1)[0];
if (filterPath == pathpart) {
metadata.push({
URL: fileList[index].url,
thumbURL: editor.imgURL('images/tango/32x32/mimetypes/text-x-generic.png'),
name: filepart,
key: fileList[index].fullpath,
$type: fileList[index].filetype
});
}
}
var dirList = filedata.dirs;
for (var index=0; index<dirList.length; ++index) {
// We have to be careful to filter out when filterPath IS the current intry in dirList
if (1 == filterPath.length) {
// If the filter path is the root '/' (ie. length of one) then we
// select the directory if a) It is not also of length one (ie. '/')
// and b) has only one slash in it. (We check the number of slashes by
// splitting the string by '/' and subtracting one from the length.)
var matches = dirList[index].length > 1 && dirList[index].split('/').length == 2;
} else {
// Chop the last component of the directory and compare against the filter.
var matches = dirList[index].split('/').slice(0,-1).join('/') == filterPath;
}
if (matches) {
metadata.push({
name: dirList[index].split('/').slice(-1),
key: dirList[index],
$type: 'folder'
});
}
}
return metadata;
}
PSLocal.prototype.saveDocument = function(parentpath, filename, documentSource, asyncCallback) {
this.workerPool.onmessage = function(a, b, message) {
if (!message.body || !message.body.authorized) {
// Fail
asyncCallback(false);
}
if (asyncCallback) {
asyncCallback(message.body.response);
}
}
this.workerPool.sendMessage({func: 'saveDocument', parentpath: parentpath, filename: filename, content:documentSource}, this.remoteStorageWorker);
}
PSLocal.prototype.deleteEntry = function(entry, asyncCallback) {
this.workerPool.onmessage = function(a, b, message) {
if (!message.body || !message.body.authorized) {
// Fail
asyncCallback(false);
}
if (asyncCallback) {
asyncCallback(message.body.response);
}
}
this.workerPool.sendMessage({func: 'deleteEntry', entry: entry}, this.remoteStorageWorker);
}
PSLocal.prototype.makeFolder = function(currentPath, folderName, asyncCallback) {
this.workerPool.onmessage = function(a, b, message) {
if (!message.body || !message.body.authorized) {
// Fail
asyncCallback(false);
}
if (asyncCallback) {
asyncCallback(true);
}
}
this.workerPool.sendMessage({func: 'makeFolder', parentpath: currentPath, dirname: folderName}, this.remoteStorageWorker);
}
PSLocal.prototype.copyEntry = function(entry, asyncCallback) {
this.workerPool.onmessage = function(a, b, message) {
if (!message.body || !message.body.authorized) {
// Fail
asyncCallback(false);
}
if (asyncCallback) {
asyncCallback(message.body.response, message.body.entry);
}
}
this.workerPool.sendMessage({func: 'copyEntry', entry: entry}, this.remoteStorageWorker);
}
})();