Skip to content

Commit

Permalink
Merge pull request cypht-org#1252 from jacob-js/enh-perf
Browse files Browse the repository at this point in the history
Improve messages caching to ensure a message isn't fetched more than once when viewing its content
  • Loading branch information
kroky authored Sep 30, 2024
2 parents b00223a + 63ebbfa commit 074fc11
Show file tree
Hide file tree
Showing 9 changed files with 326 additions and 109 deletions.
5 changes: 5 additions & 0 deletions modules/core/output_modules.php
Original file line number Diff line number Diff line change
Expand Up @@ -561,6 +561,11 @@ protected function output() {
$mod = str_replace(array('modules', DIRECTORY_SEPARATOR), '', $rel_name);
if (in_array($mod, $mods, true) && is_readable(sprintf("%ssite.js", $name))) {
$res .= '<script type="text/javascript" src="'.WEB_ROOT.sprintf("%ssite.js", $rel_name).'"></script>';
$res .= '<script type="module">';
foreach (glob($name.'js_modules' . DIRECTORY_SEPARATOR . '*.js') as $js) {
$res .= 'import "./'.WEB_ROOT.str_replace(APP_PATH, '', $js).'";';
}
$res .= '</script>';
}
}
if ($core) {
Expand Down
47 changes: 27 additions & 20 deletions modules/core/site.js
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,14 @@ var Hm_Ajax_Request = function() { return {
for (var name in res.folder_status) {
Hm_Folders.unread_counts[name] = res.folder_status[name]['unseen'];
Hm_Folders.update_unread_counts();
const messages = new Hm_MessagesStore(name, Hm_Utils.get_url_page_number());
messages.load().then(() => {
if (messages.count != res.folder_status[name].messages) {
messages.load(true).then(() => {
display_imap_mailbox(messages.rows, messages.links);
})
}
});
}
}
if (this.callback) {
Expand Down Expand Up @@ -959,30 +967,29 @@ function Message_List() {
}
};

this.prev_next_links = function(cache, class_name) {
var phref;
var nhref;
var target;
var subject;
var plink = false;
var nlink = false;
var list = Hm_Utils.get_from_local_storage(cache);
var current = $('<div></div>').append(list).find('.'+Hm_Utils.clean_selector(class_name));
var prev = current.prev();
var next = current.next();
target = $('.msg_headers tr').last();
if (prev.length) {
phref = prev.find('.subject').find('a').prop('href');
subject = new Option(prev.find('.subject').text()).innerHTML;
plink = '<a class="plink" href="'+phref+'"><i class="prevnext bi bi-arrow-left-square-fill"></i> '+subject+'</a>';
this.prev_next_links = function() {
let phref;
let nhref;
const target = $('.msg_headers tr').last();
const messages = new Hm_MessagesStore(hm_list_path(), Hm_Utils.get_url_page_number());
messages.load(false, true);
const next = messages.getNextRowForMessage(hm_msg_uid());
const prev = messages.getPreviousRowForMessage(hm_msg_uid());
if (prev) {
const prevSubject = $(prev['0']).find('.subject');
phref = prevSubject.find('a').prop('href');
const subject = new Option(prevSubject.text()).innerHTML;
const plink = '<a class="plink" href="'+phref+'"><i class="prevnext bi bi-arrow-left-square-fill"></i> '+subject+'</a>';
$('<tr class="prev"><th colspan="2">'+plink+'</th></tr>').insertBefore(target);
}
if (next.length) {
nhref = next.find('.subject').find('a').prop('href');
subject = new Option(next.find('.subject').text()).innerHTML;
nlink = '<a class="nlink" href="'+nhref+'"><i class="prevnext bi bi-arrow-right-square-fill"></i> '+subject+'</a>';
if (next) {
const nextSubject = $(next['0']).find('.subject');
nhref = nextSubject.find('a').prop('href');
const subject = new Option(nextSubject.text()).innerHTML;
const nlink = '<a class="nlink" href="'+nhref+'"><i class="prevnext bi bi-arrow-right-square-fill"></i> '+subject+'</a>';
$('<tr class="next"><th colspan="2">'+nlink+'</th></tr>').insertBefore(target);
}

return [phref, nhref];
};

Expand Down
1 change: 1 addition & 0 deletions modules/imap/handler_modules.php
Original file line number Diff line number Diff line change
Expand Up @@ -865,6 +865,7 @@ public function process() {
$this->out('imap_mailbox_page', $msgs);
$this->out('list_page', $list_page);
$this->out('imap_server_id', $form['imap_server_id']);
$this->out('do_not_flag_as_read_on_open', $this->user_config->get('unread_on_open_setting', false));
}
}
}
Expand Down
6 changes: 3 additions & 3 deletions modules/imap/hm-imap.php
Original file line number Diff line number Diff line change
Expand Up @@ -951,7 +951,6 @@ private function parse_bodystructure_response($result) {
* @param string $message_part the IMAP message part number
* @param int $max maximum read length to allow.
* @param mixed $struct a message part structure array for decoding and
* charset conversion. bool true for auto discovery
* @return string message content
*/
public function get_message_content($uid, $message_part, $max=false, $struct=true) {
Expand All @@ -960,13 +959,13 @@ public function get_message_content($uid, $message_part, $max=false, $struct=tru
return '';
}
if ($message_part == 0) {
$command = "UID FETCH $uid BODY[]\r\n";
$command = "UID FETCH $uid BODY" . "[]\r\n";
}
else {
if (!$this->is_clean($message_part, 'msg_part')) {
return '';
}
$command = "UID FETCH $uid BODY[$message_part]\r\n";
$command = "UID FETCH $uid BODY" . "[$message_part]\r\n";
}
$cache_command = $command.(string)$max;
if ($struct) {
Expand Down Expand Up @@ -2099,6 +2098,7 @@ public function enable_compression() {
* @param int $uid IMAP UID value for the message
* @param string $type Primary MIME type like "text"
* @param string $subtype Secondary MIME type like "plain"
* @param array $struct message structure array
* @return string formatted message content, bool false if no matching part is found
*/
public function get_first_message_part($uid, $type, $subtype=false, $struct=false) {
Expand Down
172 changes: 172 additions & 0 deletions modules/imap/js_modules/Hm_Message.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
/**
* An abstraction object of the Message_List focused on state management without UI interaction.
*/
class Hm_MessagesStore {

/**
* @typedef {Object} RowObject
* @property {String} 0 - The HTML string of the row
* @property {String} 1 - The IMAP key
*/

/**
* @typedef {Array} RowEntry
* @property {String} 0 - The IMAP key
* @property {RowObject} 1 - An object containing the row message and the IMAP key
*/

constructor(path, page, rows = {}) {
this.path = path;
this.list = path + '_' + page;
this.rows = rows;
this.links = "";
this.count = 0;
this.flagAsReadOnOpen = true;
}

/**
*
* @returns {Promise<Array<String>>}
*/
async load(reload = false, hideLoadingState = false) {
const storedMessages = this.#retrieveFromLocalStorage();
if (storedMessages && !reload) {
this.rows = storedMessages.rows;
this.links = storedMessages.links;
this.count = storedMessages.count;
this.flagAsReadOnOpen = storedMessages.flagAsReadOnOpen;
return this;
}

const { formatted_message_list: updatedMessages, page_links: pageLinks, folder_status, do_not_flag_as_read_on_open } = await this.#fetch(hideLoadingState);

this.count = Object.values(folder_status)[0].messages;
this.links = pageLinks;
this.rows = updatedMessages;
this.flagAsReadOnOpen = !do_not_flag_as_read_on_open;

this.#saveToLocalStorage();

return this;
}

/**
*
* @param {String} uid the id of the message to be marked as read
* @returns {Boolean} true if the message was marked as read, false otherwise
*/
markRowAsRead(uid) {
const rows = Object.entries(this.rows);
const row = this.#getRowByUid(uid)?.value;

if (row) {
const htmlRow = $(row[1]['0']);
const wasUnseen = htmlRow.find('.unseen').length > 0 || htmlRow.hasClass('unseen');

htmlRow.removeClass('unseen');
htmlRow.find('.unseen').removeClass('unseen');
const objectRows = Object.fromEntries(rows);
objectRows[row[0]]['0'] = htmlRow[0].outerHTML;

this.rows = objectRows;
this.#saveToLocalStorage();

return wasUnseen;
}
return false;
}

/**
*
* @param {*} uid
* @returns {RowObject|false} the next row entry if found, false otherwise
*/
getNextRowForMessage(uid) {
const rows = Object.entries(this.rows);
const row = this.#getRowByUid(uid)?.index;

if (row !== false) {
const nextRow = rows[row + 1];
if (nextRow) {
return nextRow[1];
}
}
return false;
}

/**
*
* @param {*} uid
* @returns {RowObject|false} the previous row entry if found, false otherwise
*/
getPreviousRowForMessage(uid) {
const rows = Object.entries(this.rows);
const row = this.#getRowByUid(uid)?.index;
if (row) {
const previousRow = rows[row - 1];
if (previousRow) {
return previousRow[1];
}
}
return false;
}

#fetch(hideLoadingState = false) {
const detail = Hm_Utils.parse_folder_path(this.path, 'imap');
return new Promise((resolve, reject) => {
Hm_Ajax.request(
[
{ name: "hm_ajax_hook", value: "ajax_imap_folder_display" },
{ name: "imap_server_id", value: detail.server_id },
{ name: "folder", value: detail.folder },
],
(response) => {
resolve(response);
},
[],
hideLoadingState,
undefined,
reject
);
});
}

#saveToLocalStorage() {
Hm_Utils.save_to_local_storage(this.list, JSON.stringify({ rows: this.rows, links: this.links, count: this.count }));
Hm_Utils.save_to_local_storage('flagAsReadOnOpen', this.flagAsReadOnOpen);
}

#retrieveFromLocalStorage() {
const stored = Hm_Utils.get_from_local_storage(this.list);
const flagAsReadOnOpen = Hm_Utils.get_from_local_storage('flagAsReadOnOpen');
if (stored) {
return {...JSON.parse(stored), flagAsReadOnOpen: flagAsReadOnOpen !== 'false'};
}
return false;
}

/**
* @typedef {Object} RowOutput
* @property {Number} index - The index of the row
* @property {RowEntry} value - The row entry
*
* @param {String} uid
* @returns {RowOutput|false} row - The row object if found, false otherwise
*/
#getRowByUid(uid) {
const rows = Object.entries(this.rows);
const row = rows.find(([key, value]) => $(value['0']).attr('data-uid') == uid);

if (row) {
const index = rows.indexOf(row);
return { index, value: row };
}
return false;
}
}

[
Hm_MessagesStore
].forEach((item) => {
window[item.name] = item;
});
1 change: 1 addition & 0 deletions modules/imap/output_modules.php
Original file line number Diff line number Diff line change
Expand Up @@ -1004,6 +1004,7 @@ protected function output() {
elseif (!$this->get('formatted_message_list')) {
$this->out('formatted_message_list', array());
}
$this->out('do_not_flag_as_read_on_open', $this->get('do_not_flag_as_read_on_open', false));
}
}

Expand Down
1 change: 1 addition & 0 deletions modules/imap/setup.php
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,7 @@
'show_pagination_links' => array(FILTER_VALIDATE_BOOLEAN, false),
'snoozed_messages' => array(FILTER_VALIDATE_INT, false),
'auto_advance_email_enabled' => array(FILTER_VALIDATE_BOOLEAN, false),
'do_not_flag_as_read_on_open' => array(FILTER_VALIDATE_BOOLEAN, false),
),

'allowed_get' => array(
Expand Down
Loading

0 comments on commit 074fc11

Please sign in to comment.