From 2180b2370a835e13968a2b71b8241de6096c25a7 Mon Sep 17 00:00:00 2001 From: Andris Reinman Date: Sun, 13 Oct 2024 16:46:39 +0300 Subject: [PATCH] Only allow changing IMAP indexer by account flush --- lib/account.js | 7 +++++++ lib/email-client/imap/mailbox.js | 28 +++++++++++----------------- lib/schemas.js | 12 +++++++++++- workers/api.js | 4 ++++ workers/imap.js | 2 +- 5 files changed, 34 insertions(+), 19 deletions(-) diff --git a/lib/account.js b/lib/account.js index 0c5051330..a28071ffc 100644 --- a/lib/account.js +++ b/lib/account.js @@ -742,6 +742,8 @@ class Account { let error = Boom.boomify(new Error(message), { statusCode: 400 }); throw error; } + } else if (accountData.imap && !accountData.imapIndexer) { + accountData.imapIndexer = (await settings.get('imapIndexer')) || 'full'; } const runIndex = await this.call({ @@ -2062,6 +2064,7 @@ class Account { }); let notifyFrom = data.notifyFrom && data.notifyFrom !== 'now' ? data.notifyFrom : new Date(); + let imapIndexer = data.imapIndexer; const dateKeyTdy = new Date().toISOString().substring(0, 10).replace(/-/g, ''); const dateKeyYdy = new Date(Date.now() - 24 * 3600 * 1000).toISOString().substring(0, 10).replace(/-/g, ''); @@ -2083,6 +2086,10 @@ class Account { .del(tombstoneTdy) .del(tombstoneYdy); + if (imapIndexer) { + pipeline = pipeline.hset(this.getAccountKey(), 'imapIndexer', imapIndexer); + } + if (data.syncFrom || data.syncFrom === null) { pipeline = pipeline.hset(this.getAccountKey(), 'syncFrom', data.syncFrom ? data.syncFrom.toISOString() : 'null'); } diff --git a/lib/email-client/imap/mailbox.js b/lib/email-client/imap/mailbox.js index 3546afccb..96f4e5670 100644 --- a/lib/email-client/imap/mailbox.js +++ b/lib/email-client/imap/mailbox.js @@ -43,6 +43,7 @@ const { DEFAULT_FETCH_BATCH_SIZE, MAILBOX_HASH } = require('../../consts'); +const { should } = require('chai'); const FETCH_BATCH_SIZE = Number(readEnvValue('EENGINE_FETCH_BATCH_SIZE') || config.service.fetchBatchSize) || DEFAULT_FETCH_BATCH_SIZE; @@ -174,16 +175,6 @@ class Mailbox { await op.hmset(this.getMailboxKey(), Object.fromEntries(list)).exec(); } - async getImapIndexer(skipCache) { - const imapIndexer = this.imapIndexer || (!skipCache && this._cachedImapIndexer) || (await settings.get('imapIndexer')) || 'full'; - - if (!this.imapIndexer && (skipCache || !this._cachedImapIndexer)) { - this._cachedImapIndexer = imapIndexer; - } - - return imapIndexer; - } - /** * Sets message entry object. Entries are ordered by `uid` property * @param {Object} data @@ -436,7 +427,6 @@ class Mailbox { this.shouldRunPartialSyncAfterExists() .then(shouldRun => { if (shouldRun) { - this.logger.trace({ msg: 'Running partial sync' }); return this.partialSync(); } return false; @@ -447,10 +437,10 @@ class Mailbox { } async onExpunge(event) { + const imapIndexer = this.imapIndexer; + event.imapIndexer = imapIndexer; this.logEvent('Untagged EXPUNGE', event); - const imapIndexer = await this.getImapIndexer(); - if (imapIndexer !== 'full') { // ignore as we can not compare this value against the index return null; @@ -473,10 +463,10 @@ class Mailbox { } async onFlags(event) { + const imapIndexer = this.imapIndexer; + event.imapIndexer = imapIndexer; this.logEvent('Untagged FETCH', event); - const imapIndexer = await this.getImapIndexer(); - if (imapIndexer !== 'full') { // ignore as we can not compare this value against the index return null; @@ -1513,7 +1503,9 @@ class Mailbox { } async fullSync() { - const imapIndexer = await this.getImapIndexer(true); + const imapIndexer = this.imapIndexer; + + this.logger.trace({ msg: 'Running full sync', imapIndexer }); switch (imapIndexer) { case 'fast': @@ -1525,7 +1517,9 @@ class Mailbox { } async partialSync(storedStatus) { - const imapIndexer = await this.getImapIndexer(true); + const imapIndexer = this.imapIndexer; + + this.logger.trace({ msg: 'Running partial sync', imapIndexer }); switch (imapIndexer) { case 'fast': diff --git a/lib/schemas.js b/lib/schemas.js index 4e4eb5a10..eaaee0e7f 100644 --- a/lib/schemas.js +++ b/lib/schemas.js @@ -1190,7 +1190,17 @@ const accountSchemas = { .description( 'An array of mailbox paths. If set, then EmailEngine opens additional IMAP connections against these paths to detect changes faster. NB! connection counts are usually highly limited.' ) - .label('SubconnectionPaths') + .label('SubconnectionPaths'), + + imapIndexer: Joi.string() + .empty('') + .trim() + .allow(null) + .valid('full', 'fast') + .example('full') + .description( + 'Sets the account-specific IMAP indexing method. Choose "full" to build a complete index that tracks deleted and updated emails, or "fast" to only detect newly received emails. Defaults to global setting.' + ) }; const googleProjectIdSchema = Joi.string().trim().allow('', false, null).max(256).example('project-name-425411').description('Google Cloud Project ID'); diff --git a/workers/api.js b/workers/api.js index 06505f048..026fcf628 100644 --- a/workers/api.js +++ b/workers/api.js @@ -2604,6 +2604,8 @@ const init = async () => { proxy: settingsSchema.proxyUrl, smtpEhloName: settingsSchema.smtpEhloName, + imapIndexer: accountSchemas.imapIndexer, + imap: Joi.object(imapSchema).allow(false).description('IMAP configuration').label('ImapConfiguration'), smtp: Joi.object(smtpSchema).allow(false).description('SMTP configuration').label('SmtpConfiguration'), @@ -3170,6 +3172,7 @@ const init = async () => { payload: Joi.object({ flush: Joi.boolean().truthy('Y', 'true', '1').falsy('N', 'false', 0).default(false).description('Only flush the account if true'), notifyFrom: accountSchemas.notifyFrom.default('now'), + imapIndexer: accountSchemas.imapIndexer, syncFrom: accountSchemas.syncFrom }).label('RequestFlush') }, @@ -3342,6 +3345,7 @@ const init = async () => { 'webhooks', 'proxy', 'smtpEhloName', + 'imapIndexer', 'imap', 'smtp', 'oauth2', diff --git a/workers/imap.js b/workers/imap.js index f69dcda81..cb5cadae1 100644 --- a/workers/imap.js +++ b/workers/imap.js @@ -203,7 +203,7 @@ class ConnectionHandler { } if (!accountObject.connection) { - let imapIndexer = typeof accountData.imap?.imapIndexer === 'string' && accountData.imap?.imapIndexer ? accountData.imap?.indexer : null; + let imapIndexer = typeof accountData.imapIndexer === 'string' && accountData.imapIndexer ? accountData.imapIndexer : 'full'; accountObject.connection = new IMAPClient(account, { runIndex,