Skip to content

Commit 90cb5b6

Browse files
committed
fix(frontend,backend): optimization - batch process results from async message load when more than one ajax request finishes but the previous UI thread is still working - dramatically decreases processing time for 5+ mailboxes in combined views; also skip getting imap folder status when it is already present for combined views
1 parent e55265e commit 90cb5b6

File tree

3 files changed

+51
-24
lines changed

3 files changed

+51
-24
lines changed

modules/core/hm-mailbox.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,7 @@ public function get_special_use_mailboxes($folder = false) {
277277
return $this->connection->get_special_use_folders($folder);
278278
}
279279
}
280-
280+
281281
/**
282282
* Get messages in a folder applying filters, sorting and pagination
283283
* @return array - [total results found, results for a single page]
@@ -307,7 +307,7 @@ public function get_message_headers($folder, $msg_id) {
307307
} else {
308308
return $this->connection->get_message_headers($msg_id);
309309
}
310-
310+
311311
}
312312

313313
public function get_message_content($folder, $msg_id, $part = 0) {

modules/core/js_modules/Hm_MessagesStore.js

Lines changed: 48 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -70,30 +70,42 @@ class Hm_MessagesStore {
7070
const sourcesToRemove = Object.keys(this.sources).filter(key => !this.currentlyAvailableSources().includes(key));
7171
sourcesToRemove.forEach(key => delete this.sources[key]);
7272

73-
this.fetch(hideLoadingState).forEach(async (req) => {
74-
const { formatted_message_list: updatedMessages, pages, folder_status, do_not_flag_as_read_on_open, sourceId } = await req;
75-
// count and pages only available in non-combined pages where there is only one ajax call, so it is safe to overwrite
76-
this.count = folder_status && Object.values(folder_status)[0]?.messages;
77-
this.pages = parseInt(pages);
78-
this.newMessages = this.getNewMessages(updatedMessages);
79-
80-
if (typeof do_not_flag_as_read_on_open == 'booelan') {
81-
this.flagAsReadOnOpen = !do_not_flag_as_read_on_open;
82-
}
73+
// Batch processing for multiple requests
74+
const pendingResponses = new Map();
75+
let processingTimeout = null;
8376

84-
if (this.sources[sourceId]) {
85-
this.rows = this.rows.filter(row => !this.sources[sourceId].includes(row['1']));
86-
}
87-
this.sources[sourceId] = Object.keys(updatedMessages);
88-
for (const id in updatedMessages) {
89-
if (this.rows.map(row => row['1']).indexOf(id) === -1) {
90-
this.rows.push(updatedMessages[id]);
91-
} else {
92-
const index = this.rows.map(row => row['1']).indexOf(id);
93-
this.rows[index] = updatedMessages[id];
77+
const processPendingResponses = () => {
78+
if (pendingResponses.size === 0) return;
79+
80+
// Process all pending responses at once
81+
const responses = Array.from(pendingResponses.values());
82+
pendingResponses.clear();
83+
84+
responses.forEach(({ formatted_message_list: updatedMessages, pages, folder_status, do_not_flag_as_read_on_open, sourceId }) => {
85+
// count and pages only available in non-combined pages where there is only one ajax call, so it is safe to overwrite
86+
this.count = folder_status && Object.values(folder_status)[0]?.messages;
87+
this.pages = parseInt(pages);
88+
this.newMessages = this.getNewMessages(updatedMessages);
89+
90+
if (typeof do_not_flag_as_read_on_open == 'booelan') {
91+
this.flagAsReadOnOpen = !do_not_flag_as_read_on_open;
9492
}
95-
}
9693

94+
if (this.sources[sourceId]) {
95+
this.rows = this.rows.filter(row => !this.sources[sourceId].includes(row['1']));
96+
}
97+
this.sources[sourceId] = Object.keys(updatedMessages);
98+
for (const id in updatedMessages) {
99+
if (this.rows.map(row => row['1']).indexOf(id) === -1) {
100+
this.rows.push(updatedMessages[id]);
101+
} else {
102+
const index = this.rows.map(row => row['1']).indexOf(id);
103+
this.rows[index] = updatedMessages[id];
104+
}
105+
}
106+
});
107+
108+
// Do expensive operations only once for all responses
97109
if (this.path == 'unread') {
98110
$('.total_unread_count').html(' '+this.rows.length+' ');
99111
}
@@ -104,6 +116,21 @@ class Hm_MessagesStore {
104116
if (messagesReadyCB) {
105117
messagesReadyCB(this);
106118
}
119+
};
120+
121+
this.fetch(hideLoadingState).forEach((req) => {
122+
req.then((response) => {
123+
pendingResponses.set(response.sourceId, response);
124+
125+
if (processingTimeout) {
126+
clearTimeout(processingTimeout);
127+
}
128+
129+
// Process after a short delay to allow batching
130+
processingTimeout = setTimeout(processPendingResponses, 10);
131+
}).catch((error) => {
132+
console.error('Error loading messages from source:', error);
133+
});
107134
});
108135

109136
return this;

modules/imap/handler_modules.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1360,7 +1360,7 @@ public function process() {
13601360
$messages[] = $msg;
13611361
}
13621362

1363-
$status['imap_'.$id.'_'.$folders[$key]] = $mailbox->get_folder_status(hex2bin($folders[$key]));
1363+
$status['imap_'.$id.'_'.$folders[$key]] = $mailbox->get_folder_state(); // this is faster than get_folder_status as search call above already gets this folder's state
13641364
}
13651365

13661366
$this->out('folder_status', $status);

0 commit comments

Comments
 (0)