Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

IMAP "semi-asynchronous" backwards search in batches #7825

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 44 additions & 13 deletions program/actions/mail/search.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,21 @@ public function run($args = [])
$scope = rcube_utils::get_input_value('_scope', rcube_utils::INPUT_GET);
$interval = rcube_utils::get_input_value('_interval', rcube_utils::INPUT_GET);
$continue = rcube_utils::get_input_value('_continue', rcube_utils::INPUT_GET);
$range = rcube_utils::get_input_value('_range', rcube_utils::INPUT_GET);

$filter = trim($filter);
$search_request = md5($mbox . $scope . $interval . $filter . $str);

// Parse input
list($subject, $search) = self::search_input($str, $headers, $scope, $mbox);

if (!empty($range)) {
$range = explode("-", $range, 2);
$range[0] = (int) $range[0];
$range[1] = (int) $range[1];
} else {
$range = null;
}

// add list filter string
$search_str = $filter && $filter != 'ALL' ? $filter : '';
Expand All @@ -68,6 +77,7 @@ public function run($args = [])
}

$search_str = trim($search_str);
$sort_column = empty($range) ? self::sort_column() : 'ARRIVAL';
$sort_column = self::sort_column();
$sort_order = self::sort_order();

Expand All @@ -76,8 +86,10 @@ public function run($args = [])
$rcmail->storage->set_search_set($_SESSION['search']);
$search_str = $_SESSION['search'][0];
}



// execute IMAP search
$curr_count = 0;
if ($search_str) {
$mboxes = [];

Expand All @@ -99,15 +111,30 @@ public function run($args = [])
$rcmail->output->set_env('mailbox', $mbox);
}

$result = $rcmail->storage->search($mboxes, $search_str, RCUBE_CHARSET, $sort_column);
$result = $rcmail->storage->search($mboxes, $search_str, RCUBE_CHARSET, $sort_column, $range);
$curr_count = $rcmail->storage->count($mbox, $rcmail->storage->get_threading() ? 'THREADS' : 'ALL');

if (!empty($range) && !$result) {
$rcmail->output->set_env('last_batch', 1);
$last_batch = 1;
}
}

$batch_continue = !empty($range) && $range[0] != 0;
if ($batch_continue && isset($_SESSION['search'])) {
$rcmail->storage->prepend_search_set($_SESSION['search']);
}

// save search results in session
if (!isset($_SESSION['search']) || !is_array($_SESSION['search'])) {
$_SESSION['search'] = [];
}


if ($search_str) {
if (!empty($range)) {
$rcmail->storage->search_disable_sort();
}
$_SESSION['search'] = $rcmail->storage->get_search_set();
$_SESSION['last_text_search'] = $str;
}
Expand All @@ -116,20 +143,25 @@ public function run($args = [])
$_SESSION['search_scope'] = $scope;
$_SESSION['search_interval'] = $interval;
$_SESSION['search_filter'] = $filter;

$count = $rcmail->storage->count($mbox, $rcmail->storage->get_threading() ? 'THREADS' : 'ALL');
$all_count = $rcmail->storage->count(null, 'ALL');

// Get the headers
if (!isset($result) || empty($result->incomplete)) {
$result_h = $rcmail->storage->list_messages($mbox, 1, $sort_column, $sort_order);
$result_h = $rcmail->storage->list_messages($mbox, 1, $sort_column, $sort_order, 0, $count-$curr_count);
}

if (!empty($range) && !$count == 0 && ($count>$curr_count || $range[0] == 0)) {
// replace existing message
$rcmail->output->show_message('searchsuccessful', 'confirmation', ['nr' => $all_count], true, 0, true);
}

// Make sure we got the headers
if (!empty($result_h)) {
$count = $rcmail->storage->count($mbox, $rcmail->storage->get_threading() ? 'THREADS' : 'ALL');

self::js_message_list($result_h, false);

if ($search_str) {
$all_count = $rcmail->storage->count(null, 'ALL');
if ($search_str && empty($range)) {
$rcmail->output->show_message('searchsuccessful', 'confirmation', ['nr' => $all_count]);
}

Expand All @@ -144,25 +176,24 @@ public function run($args = [])
}
// handle IMAP errors (e.g. #1486905)
else if ($err_code = $rcmail->storage->get_error_code()) {
$count = 0;
self::display_server_error();
}
// advice the client to re-send the (cross-folder) search request
else if (!empty($result) && !empty($result->incomplete)) {
$count = 0; // keep UI locked
$rcmail->output->command('continue_search', $search_request);
}
else {
$count = 0;

else if (empty($range) || isset($last_batch) && ($count == 0 || $range[0] == 0)) {
$rcmail->output->show_message('searchnomatch', 'notice');
$rcmail->output->set_env('multifolder_listing', isset($result) ? !empty($result->multi) : false);

if (isset($result) && !empty($result->multi) && $scope == 'all') {
$rcmail->output->command('select_folder', '');
}
} else if (isset($last_batch)) {
$rcmail->output->show_message('searchfinished', 'notice');
}


// update message count display
$rcmail->output->set_env('search_request', $search_str ? $search_request : '');
$rcmail->output->set_env('search_filter', $_SESSION['search_filter']);
Expand Down
2 changes: 1 addition & 1 deletion program/include/rcmail_output_cli.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ function add_label()
*
* @see rcube_output::show_message()
*/
function show_message($message, $type = 'notice', $vars = null, $override = true, $timeout = 0)
function show_message($message, $type = 'notice', $vars = null, $override = true, $timeout = 0, $update = false)
{
if ($this->app->text_exists($message)) {
$message = $this->app->gettext(['name' => $message, 'vars' => $vars]);
Expand Down
3 changes: 2 additions & 1 deletion program/include/rcmail_output_html.php
Original file line number Diff line number Diff line change
Expand Up @@ -523,10 +523,11 @@ public function add_label()
* @param array $vars Key-value pairs to be replaced in localized text
* @param bool $override Override last set message
* @param int $timeout Message display time in seconds
* @param bool $update Replace an existing message of the same type
*
* @uses self::command()
*/
public function show_message($message, $type = 'notice', $vars = null, $override = true, $timeout = 0)
public function show_message($message, $type = 'notice', $vars = null, $override = true, $timeout = 0, $update = false)
{
if ($override || !$this->message) {
if ($this->app->text_exists($message)) {
Expand Down
8 changes: 6 additions & 2 deletions program/include/rcmail_output_json.php
Original file line number Diff line number Diff line change
Expand Up @@ -132,10 +132,11 @@ public function add_label()
* @param array $vars Key-value pairs to be replaced in localized text
* @param bool $override Override last set message
* @param int $timeout Message displaying time in seconds
* @param bool $update Replace an existing message of the same type
*
* @uses self::command()
*/
public function show_message($message, $type = 'notice', $vars = null, $override = true, $timeout = 0)
public function show_message($message, $type = 'notice', $vars = null, $override = true, $timeout = 0, $update = false)
{
if ($override || !$this->message) {
if ($this->app->text_exists($message)) {
Expand All @@ -149,7 +150,10 @@ public function show_message($message, $type = 'notice', $vars = null, $override
}

$this->message = $message;
$this->command('display_message', $msgtext, $type, $timeout * 1000);
if ($update)
$this->command('replace_message', $msgtext, $type, $timeout * 1000);
else
$this->command('display_message', $msgtext, $type, $timeout * 1000);
}
}

Expand Down
83 changes: 56 additions & 27 deletions program/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,8 @@ function rcube_webmail()
recipients_delimiter: ', ', // @deprecated
popup_width: 1150,
popup_width_small: 900,
thread_padding: '15px'
thread_padding: '15px',
qsearch: null
};

// create protected reference to myself
Expand Down Expand Up @@ -1346,28 +1347,43 @@ function rcube_webmail()

// quicksearch
case 'search':
return this.qsearch(props);
var batch_number = null;
if ($("[name='s_backwards']").is(":checked"))
batch_number = 1 ;

if (this.message_list)
this.clear_message_list();
else if (this.contact_list)
this.list_contacts_clear();

return this.qsearch(props, batch_number);

// reset quicksearch
case 'reset-search':
var n, s = this.env.search_request || this.env.qsearch;

this.reset_qsearch(true);

if (s && this.env.action == 'compose') {
if (this.contact_list)
this.list_contacts_clear();
}
else if (s && this.env.mailbox) {
this.list_mailbox(this.env.mailbox, 1);
}
else if (s && this.task == 'addressbook') {
if (this.env.source == '') {
for (n in this.env.address_sources) break;
this.env.source = n;
this.env.group = '';
// only clear screen in case of non-batched search
if (!this.env.qsearch.batch_number) {
this.reset_qsearch(true);

if (s && this.env.action == 'compose') {
if (this.contact_list)
this.list_contacts_clear();
}
else if (s && this.env.mailbox) {
this.list_mailbox(this.env.mailbox, 1);
}
else if (s && this.task == 'addressbook') {
if (this.env.source == '') {
for (n in this.env.address_sources) break;
this.env.source = n;
this.env.group = '';
}
this.list_contacts(this.env.source, this.env.group, 1);
}
this.list_contacts(this.env.source, this.env.group, 1);
} else {
this.abort_request(this.env.qsearch);
this.env.qsearch = null;
}
break;

Expand Down Expand Up @@ -5601,20 +5617,17 @@ function rcube_webmail()
};

// send remote request to search mail or contacts
this.qsearch = function(value)
// if batch_number>0, this is #batch_number in a sequence of search requests
this.qsearch = function(value, batch_number = null)
{
// Note: Some plugins would like to do search without value,
// so we keep value != '' check to allow that use-case. Which means
// e.g. that qsearch() with no argument will execute the search.
if (value != '' || $(this.gui_objects.qsearchbox).val() || $(this.gui_objects.search_interval).val()) {
var r, lock = this.set_busy(true, 'searching'),
url = this.search_params(value),
url = this.search_params(value, null, batch_number),
action = this.env.action == 'compose' && this.contact_list ? 'search-contacts' : 'search';

if (this.message_list)
this.clear_message_list();
else if (this.contact_list)
this.list_contacts_clear();

if (this.env.source)
url._source = this.env.source;
Expand All @@ -5624,9 +5637,9 @@ function rcube_webmail()
// reset vars
this.env.current_page = 1;

r = this.http_request(action, url, lock);
//r = this.http_request(action, url, lock);

this.env.qsearch = {lock: lock, request: r};
this.env.qsearch = {lock: lock, request: r, props: value, batch_number: batch_number};
this.enable_command('set-listmode', this.env.threads && (this.env.search_scope || 'base') == 'base');

return true;
Expand All @@ -5647,7 +5660,7 @@ function rcube_webmail()
};

// build URL params for search
this.search_params = function(search, filter)
this.search_params = function(search, filter, batch_number = null)
{
var n, url = {}, mods_arr = [],
mods = this.env.search_mods,
Expand Down Expand Up @@ -5675,6 +5688,15 @@ function rcube_webmail()
url._headers = mods_arr.join(',');
}
}

if (batch_number) {
var amount = 5000;
if (mods['text'] || mods['body'])
amount = 500;
first = (batch_number-1) * amount;
last = batch_number * amount - 1;
url._range = first + '-' + last;
}

url._layout = this.env.layout;
url._filter = filter;
Expand Down Expand Up @@ -9297,7 +9319,14 @@ function rcube_webmail()

case 'getunread':
case 'search':
this.env.qsearch = null;
if (this.env.qsearch) {
if (!this.env.qsearch.batch_number || response.env.last_batch == 1) {
this.env.qsearch = null;
} else {
batch_number = this.env.qsearch.batch_number + 1;
this.qsearch(this.env.qsearch.props, batch_number);
}
}
case 'list':
if (this.task == 'mail') {
var is_multifolder = this.is_multifolder_listing(),
Expand Down
Loading