T127626: Improved performance of folders listings (in Files)

By fetching list of folders in semi-recursive way using many parallel requests to Chwala.
This commit is contained in:
Aleksander Machniak 2019-03-08 13:12:09 +00:00
parent f66950a5d8
commit 7c87ca644c
3 changed files with 237 additions and 57 deletions

View file

@ -185,7 +185,7 @@ function kolab_files_init()
}
else if (rcmail.env.action == 'open') {
// initialize folders list (for dialogs)
file_api.folder_list();
// file_api.folder_list();
// get ongoing sessions
file_api.request('folder_info', {folder: file_api.file_path(rcmail.env.file), sessions: 1}, 'folder_info_response');
@ -259,6 +259,11 @@ function kolab_files_init()
document_editor = new document_editor_api(editor_config);
else
document_editor = new manticore_api(editor_config);
rcmail.addEventListener('responseafterreset', function(o) {
// update caps/mountpoints on reset
file_api.set_env({caps: rcmail.env.files_caps});
});
};
// returns API authorization token
@ -1858,9 +1863,14 @@ rcube_webmail.prototype.files_copy = function(folder, obj, event, files)
// create folder selector popup
rcube_webmail.prototype.files_folder_selector = function(event, callback)
{
if (this.folder_selector_reset)
this.destroy_entity_selector('folder-selector');
// The list is incomplete, reset needed before next use
this.folder_selector_reset = file_api.list_updates > 0;
this.entity_selector('folder-selector', callback, file_api.env.folders, function(folder, a, folder_fullname) {
var n = folder.depth || 0,
id = folder.id,
row = $('<li>');
if (folder.virtual || folder.readonly)
@ -2092,6 +2102,7 @@ function kolab_files_ui()
this.requests = {};
this.uploads = [];
this.workers = {};
this.list_updates = 0;
/*
// Called on "session expired" session
@ -2155,28 +2166,39 @@ function kolab_files_ui()
var root = folder.split(this.env.directory_separator)[0],
caps = this.env.caps;
if (this.env.caps.MOUNTPOINTS && this.env.caps.MOUNTPOINTS[root])
if (this.env.caps.MOUNTPOINTS[root])
caps = root != folder ? this.env.caps.MOUNTPOINTS[root] : {};
return !!caps.ACL;
};
// folders list request
this.folder_list = function(params)
this.folder_list = function(params, update)
{
if (!params)
params = {}
params.permissions = 1;
params.req = this.set_busy(true, 'loading');
this.request('folder_list', this.list_params = params, 'folder_list_response');
if (params.level === undefined)
params.level = -1;
if (update) {
this.list_updates++;
params.req = rcmail.display_message('', 'loading');
}
else {
params.req = this.set_busy(true, 'loading');
this.list_params = params;
}
this.request('folder_list', params, update ? 'folder_list_update_response' : 'folder_list_response');
};
// folder list response handler
this.folder_list_response = function(response)
this.folder_list_response = function(response, params)
{
rcmail.hide_message(this.list_params.req);
rcmail.hide_message(params.req);
if (!this.response(response))
return;
@ -2197,12 +2219,14 @@ function kolab_files_ui()
searchbox = $(search_selector, body);
}
this.list_element = list;
if (elem.data('no-collections') == true)
collections = [];
this.env.folders = this.folder_list_parse(response.result && response.result.list ? response.result.list : response.result);
rcmail.enable_command('files-create', true);
rcmail.enable_command('files-create', response.result && response.result.list && response.result.list.length > 0);
if (!elem.length)
return;
@ -2288,8 +2312,10 @@ function kolab_files_ui()
else if (this.env.collection)
rcmail.folder_list.select('folder-collection-' + this.env.collection);
else if (folder = this.env.init_folder) {
this.env.init_folder = null;
rcmail.folder_list.select(folder);
if (this.env.folders[folder]) {
this.env.init_folder = null;
rcmail.folder_list.select(folder);
}
}
else if (folder = this.env.init_collection) {
this.env.init_collection = null;
@ -2298,12 +2324,19 @@ function kolab_files_ui()
else if (first)
rcmail.folder_list.select(first);
// add tree icons
// this.folder_list_tree(this.env.folders);
// handle authentication errors on external sources
this.folder_list_auth_errors(response.result);
// Fetch 2 levels of folder hierarchy for all mount points that
// do not support fast folders list
if (rcmail.env.files_api_version > 4) {
var ref = this;
$.each(rcmail.env.files_caps.MOUNTPOINTS || [], function(k, v) {
if (!v.FAST_FOLDER_LIST)
ref.folder_list({level: 2, folder: k}, true);
});
}
// Elastic: Set notree class on the folder list
var callback = function() {
list[list.find('.treetoggle').length > 0 ? 'removeClass' : 'addClass']('notree');
@ -2313,6 +2346,34 @@ function kolab_files_ui()
callback();
};
// folder list response handler
this.folder_list_update_response = function(response, params)
{
rcmail.hide_message(params.req);
this.list_updates--;
if (!this.response(response))
return;
// handle authentication errors on external sources
this.folder_list_auth_errors(response.result);
// Update the list
this.folder_list_merge(params.folder, response.result.list, params.level);
};
this.folder_list_update_wait = function(folder)
{
var ref = this;
// do maximum 10 parallel requests
if (this.list_updates > 10)
return setTimeout(function() { ref.folder_list_update_wait(folder); }, 20);
this.folder_list({folder: folder, level: 0}, true);
};
this.folder_select = function(folder)
{
if (rcmail.busy || !folder)
@ -2404,6 +2465,7 @@ function kolab_files_ui()
if (folder.depth) {
// find parent folder
parent_name = i.replace(/\/[^/]+$/, '');
if (!parent)
parent = $(this.env.folders[parent_name].ref);
@ -2733,6 +2795,7 @@ function kolab_files_ui()
this.display_message('kolab_files.foldercreatenotice', 'confirmation');
// refresh folders list
// TODO: Don't reload the whole list
this.folder_list();
};
@ -2758,6 +2821,7 @@ function kolab_files_ui()
if (this.env.folder == data.folder)
this.env.folder = data['new'];
// TODO: Don't reload the whole list
this.folder_list();
};
@ -2769,14 +2833,22 @@ function kolab_files_ui()
};
// folder create response handler
this.folder_mount_response = function(response)
this.folder_mount_response = function(response, params)
{
if (!this.response(response))
return;
this.display_message('kolab_files.foldermountnotice', 'confirmation');
if (response.result.capabilities) {
rcmail.env.files_caps.MOUNTPOINTS[params.folder] = response.result.capabilities;
}
// Refresh capabilities stored in session
rcmail.http_post('files/reset', {});
// refresh folders list
// TODO: load only folders from the created mount point
this.folder_list();
};
@ -2788,18 +2860,24 @@ function kolab_files_ui()
};
// folder delete response handler
this.folder_delete_response = function(response, data)
this.folder_delete_response = function(response, params)
{
if (!this.response(response))
return;
this.display_message('kolab_files.folderdeletenotice', 'confirmation');
if (this.env.folder == data.folder) {
if (this.env.folder == params.folder) {
this.env.folder = null;
rcmail.enable_command('files-folder-delete', 'folder-rename', 'files-list', false);
}
// Removed mount point, refresh capabilities stored in session
if (rcmail.env.files_caps.MOUNTPOINTS[params.folder]) {
delete rcmail.env.files_caps.MOUNTPOINTS[params.folder];
rcmail.http_post('files/reset', {});
}
// refresh folders list
this.folder_list();
this.quota();
@ -4060,13 +4138,14 @@ function kolab_files_ui()
delete result.auth_errors[i];
}
});
$.extend(this.auth_errors, result.auth_errors);
}
// ask for password to the first storage on the list
var ref = this;
$.each(this.auth_errors || [], function(i, v) {
file_api.folder_list_auth_dialog(i, v);
delete ref.auth_errors[i];
return false;
});
};
@ -4082,15 +4161,15 @@ function kolab_files_ui()
$('.auth-options', dialog).before(content);
args.buttons[this.t('kolab_files.save')] = function() {
var data = {folder: label, list: 1};
var data = {folder: label, list: 1, permissions: 1, level: -2};
$('input', dialog).each(function() {
data[this.name] = this.type == 'checkbox' && !this.checked ? '' : this.value;
});
file_api.open_dialog = this;
file_api.req = file_api.set_busy(true, 'kolab_files.authenticating');
file_api.request('folder_auth', data, 'folder_auth_response');
kolab_dialog_close(this);
};
args.buttons[this.t('kolab_files.cancel')] = function() {
@ -4116,45 +4195,94 @@ function kolab_files_ui()
};
// folder_auth handler
this.folder_auth_response = function(response)
this.folder_auth_response = function(response, params)
{
if (!this.response(response))
return;
var folders, found,
folder = response.result.folder,
id = 'rcmli' + rcmail.html_identifier_encode(folder),
parent = $('#' + id);
// try parent window if the folder element does not exist
if (!parent.length && rcmail.is_framed()) {
parent = $('#' + id, window.parent.document.body);
}
delete this.auth_errors[folder];
kolab_dialog_close(this.open_dialog);
delete this.auth_errors[response.result.folder];
// go to the next one
this.folder_list_auth_errors();
// parse result
folders = this.folder_list_parse(response.result.list);
delete folders[folder]; // remove root added in folder_list_parse()
// update the list
this.folder_list_merge(response.result.folder, response.result.list, params.level);
};
// add folders from the external source to the list
// Update folders list with additional folders
this.folder_list_merge = function(folder, list, level)
{
if (!list || !list.length)
return;
var i, last, folders, result = {}, index = [], ref = this;
if (this.list_merge_lock) {
return setTimeout(function() { ref.folder_list_merge(folder, list); }, 50);
}
this.list_merge_lock = true;
// Parse result
folders = this.folder_list_parse(list);
if (level < 0)
level *= -1;
// Add folders from the external source to the list
$.each(folders, function(i, f) {
file_api.folder_list_row(i, f, parent.get(0));
found = true;
if (ref.env.folders[i]) {
last = i;
return;
}
// We don't use this id
delete f.id;
// Append row to the list
var row = file_api.folder_list_row(i, f);
if (row)
ref.list_element.append(row);
// Need env.folders update so all parents are available
// in successive folder_list_row() calls
ref.env.folders[i] = f;
index.push(i);
// Load deeper folder hierarchies
if (level && f.depth == level)
ref.folder_list_update_wait(i);
});
// reset folders list widget
if (found)
rcmail.folder_list.reset(true);
// Reset folders list widget
rcmail.folder_list.reset(true, true);
// add tree icons
// this.folder_list_tree(folders);
// Rebuild folders list with correct order
for (i in this.env.folders) {
result[i] = this.env.folders[i];
if (i === last)
break;
}
for (i in index) {
result[index[i]] = this.env.folders[index[i]];
}
for (i in this.env.folders) {
if (!result[i]) {
result[i] = this.env.folders[i];
}
}
$.extend(this.env.folders, folders);
this.env.folders = result;
if ((folder = this.env.folder) || (folder = this.env.init_folder)) {
if (this.env.folders[folder]) {
this.env.init_folder = null;
if (folder != rcmail.folder_list.get_selection())
rcmail.folder_list.select(folder);
}
}
this.list_merge_lock = false;
};
// returns content of the external storage authentication form

View file

@ -51,6 +51,7 @@ class kolab_files extends rcube_plugin
$this->register_action('open', array($this, 'actions'));
$this->register_action('edit', array($this, 'actions'));
$this->register_action('share', array($this, 'actions'));
$this->register_action('reset', array($this, 'actions'));
$this->register_action('autocomplete', array($this, 'autocomplete'));
// we use libkolab::http_request() from libkolab with its configuration

View file

@ -131,10 +131,7 @@ class kolab_files_engine
), 'taskbar');
}
if ($_SESSION['kolab_files_caps']['MANTICORE'] || $_SESSION['kolab_files_caps']['WOPI']) {
$_SESSION['kolab_files_caps']['DOCEDIT'] = true;
$_SESSION['kolab_files_caps']['DOCTYPE'] = $_SESSION['kolab_files_caps']['MANTICORE'] ? 'manticore' : 'wopi';
}
$caps = $this->capabilities();
$this->plugin->include_stylesheet($this->plugin->local_skin_path().'/style.css');
$this->plugin->include_script($this->url . '/js/files_api.js');
@ -142,11 +139,11 @@ class kolab_files_engine
$this->rc->output->set_env('files_url', $this->url . '/api/');
$this->rc->output->set_env('files_token', $this->get_api_token());
$this->rc->output->set_env('files_caps', $_SESSION['kolab_files_caps']);
$this->rc->output->set_env('files_api_version', $_SESSION['kolab_files_caps']['VERSION'] ?: 3);
$this->rc->output->set_env('files_caps', $caps);
$this->rc->output->set_env('files_api_version', $caps['VERSION'] ?: 3);
$this->rc->output->set_env('files_user', $this->rc->get_user_name());
if ($_SESSION['kolab_files_caps']['DOCEDIT']) {
if ($caps['DOCEDIT']) {
$this->plugin->add_label('declinednotice', 'invitednotice', 'acceptedownernotice',
'declinedownernotice', 'requestednotice', 'acceptednotice', 'declinednotice',
'more', 'accept', 'decline', 'join', 'status', 'when', 'file', 'comment',
@ -1030,6 +1027,46 @@ class kolab_files_engine
return $token;
}
protected function capabilities()
{
if (empty($_SESSION['kolab_files_caps'])) {
$token = $this->get_api_token();
if (empty($_SESSION['kolab_files_caps'])) {
$request = $this->get_request(array('method' => 'capabilities'), $token);
// send request to the API
try {
$response = $request->send();
$status = $response->getStatus();
$body = @json_decode($response->getBody(), true);
if ($status == 200 && $body['status'] == 'OK') {
$_SESSION['kolab_files_caps'] = $body['result'];
}
else {
throw new Exception($body['reason'] ?: "Failed to get capabilities. Status: $status");
}
}
catch (Exception $e) {
rcube::raise_error($e, true, false);
return array();
}
}
}
if ($_SESSION['kolab_files_caps']['MANTICORE'] || $_SESSION['kolab_files_caps']['WOPI']) {
$_SESSION['kolab_files_caps']['DOCEDIT'] = true;
$_SESSION['kolab_files_caps']['DOCTYPE'] = $_SESSION['kolab_files_caps']['MANTICORE'] ? 'manticore' : 'wopi';
}
if (!empty($_SESSION['kolab_files_caps']) && !isset($_SESSION['kolab_files_caps']['MOUNTPOINTS'])) {
$_SESSION['kolab_files_caps']['MOUNTPOINTS'] = array();
}
return $_SESSION['kolab_files_caps'];
}
/**
* Initialize HTTP_Request object
*/
@ -1130,16 +1167,29 @@ class kolab_files_engine
$this->rc->output->set_env('collection', rcube_utils::get_input_value('collection', rcube_utils::INPUT_GET));
}
$caps = $this->capabilities();
$this->rc->output->add_label('uploadprogress', 'GB', 'MB', 'KB', 'B');
$this->rc->output->set_pagetitle($this->plugin->gettext('files'));
$this->rc->output->set_env('file_mimetypes', $this->get_mimetypes());
$this->rc->output->set_env('files_quota', $_SESSION['kolab_files_caps']['QUOTA']);
$this->rc->output->set_env('files_max_upload', $_SESSION['kolab_files_caps']['MAX_UPLOAD']);
$this->rc->output->set_env('files_progress_name', $_SESSION['kolab_files_caps']['PROGRESS_NAME']);
$this->rc->output->set_env('files_progress_time', $_SESSION['kolab_files_caps']['PROGRESS_TIME']);
$this->rc->output->set_env('files_quota', $caps['QUOTA']);
$this->rc->output->set_env('files_max_upload', $caps['MAX_UPLOAD']);
$this->rc->output->set_env('files_progress_name', $caps['PROGRESS_NAME']);
$this->rc->output->set_env('files_progress_time', $caps['PROGRESS_TIME']);
$this->rc->output->send('kolab_files.files');
}
/**
* Handler for resetting some session/cached information
*/
protected function action_reset()
{
$this->rc->session->remove('kolab_files_caps');
if (($caps = $this->capabilities()) && !empty($caps)) {
$this->rc->output->set_env('files_caps', $caps);
}
}
/**
* Handler for preferences save action
*/
@ -1623,6 +1673,7 @@ class kolab_files_engine
$this->mimetypes = false;
$token = $this->get_api_token();
$caps = $this->capabilities();
$request = $this->get_request(array('method' => 'mimetypes'), $token);
$response = $request->send();
$status = $response->getStatus();
@ -1646,7 +1697,7 @@ class kolab_files_engine
'text/plain' => 'txt',
'text/html' => 'html',
);
if (!empty($_SESSION['kolab_files_caps']['MANTICORE'])) {
if (!empty($caps['MANTICORE'])) {
$mimetypes = array_merge(array('application/vnd.oasis.opendocument.text' => 'odt'), $mimetypes);
}