From 2719f08e2693eea704a012410edbcf0da724c966 Mon Sep 17 00:00:00 2001 From: djmaze Date: Wed, 8 Dec 2021 11:06:42 +0100 Subject: [PATCH] Revamp mailbox management so that reloading of the whole folder tree is not needed --- dev/App/User.js | 25 +--------------- dev/Model/FolderCollection.js | 9 +++++- dev/Remote/AbstractFetch.js | 4 --- dev/Remote/User/Fetch.js | 12 +++----- dev/Settings/User/Folders.js | 29 +++++++++++++------ dev/View/Popup/FolderCreate.js | 28 ++++++++++++++---- .../app/libraries/MailSo/Mail/MailClient.php | 8 +++-- .../libraries/RainLoop/Actions/Folders.php | 12 ++++---- 8 files changed, 67 insertions(+), 60 deletions(-) diff --git a/dev/App/User.js b/dev/App/User.js index f6af760a1..a1a57d053 100644 --- a/dev/App/User.js +++ b/dev/App/User.js @@ -346,26 +346,6 @@ class AppUser extends AbstractApp { return false; } - /** - * @param {Function=} callback = null - */ - foldersReload(callback = null) { - Remote.foldersReload(callback); - } - - foldersPromisesActionHelper(promise, errorDefCode) { - Remote.abort('Folders') - .fastResolve(true) - .then(() => promise) - .then( - () => Remote.foldersReloadWithTimeout(), - error => { - FolderUserStore.folderListError(getNotification(error.code, '', errorDefCode) + '.\n' + error.message); - Remote.foldersReloadWithTimeout(); - } - ); - } - reloadOpenPgpKeys() { if (PgpUserStore.capaOpenPGP()) { const keys = [], @@ -720,7 +700,7 @@ class AppUser extends AbstractApp { SettingsUserStore.init(); ContactUserStore.init(); - this.foldersReload(value => { + Remote.foldersReload(value => { try { if (value) { startScreens([ @@ -738,9 +718,6 @@ class AppUser extends AbstractApp { this.folderInformationMultiply(); }, refreshFolders); - // Every 15 minutes - setInterval(()=>this.foldersReload(), 900000); - ContactUserStore.init(); this.accountsAndIdentities(); diff --git a/dev/Model/FolderCollection.js b/dev/Model/FolderCollection.js index 76d4b70dd..62d22c312 100644 --- a/dev/Model/FolderCollection.js +++ b/dev/Model/FolderCollection.js @@ -20,6 +20,8 @@ import { i18n, trigger as translatorTrigger } from 'Common/Translator'; import { AbstractModel } from 'Knoin/AbstractModel'; +//import { mailBox } from 'Common/Links'; + const normalizeFolder = sFolderFullName => ('' === sFolderFullName || UNUSED_OPTION_VALUE === sFolderFullName @@ -236,7 +238,10 @@ export class FolderModel extends AbstractModel { static reviveFromJson(json) { const folder = super.reviveFromJson(json); if (folder) { - folder.deep = json.FullName.split(folder.delimiter).length - 1; + const path = folder.fullName.split(folder.delimiter); + folder.deep = path.length - 1; + path.pop(); + folder.parentName = path.join(folder.delimiter); let type = (folder.metadata[FolderMetadataKeys.KolabFolderType] || folder.metadata[FolderMetadataKeys.KolabFolderTypeShared] @@ -360,6 +365,8 @@ export class FolderModel extends AbstractModel { !!folder.subFolders().find( folder => folder.hasUnreadMessages() | folder.hasSubscribedUnreadMessagesSubfolders() ) + +// ,href: () => folder.canBeSelected() && mailBox(folder.fullNameHash) }); folder.addSubscribables({ diff --git a/dev/Remote/AbstractFetch.js b/dev/Remote/AbstractFetch.js index 53965a33f..180d221ed 100644 --- a/dev/Remote/AbstractFetch.js +++ b/dev/Remote/AbstractFetch.js @@ -194,10 +194,6 @@ export class AbstractFetchRemote this.request('GetPublicKey', fCallback); } - fastResolve(mData) { - return Promise.resolve(mData); - } - setTrigger(trigger, value) { if (trigger) { value = !!value; diff --git a/dev/Remote/User/Fetch.js b/dev/Remote/User/Fetch.js index a9771d711..06d5e0a82 100644 --- a/dev/Remote/User/Fetch.js +++ b/dev/Remote/User/Fetch.js @@ -207,21 +207,17 @@ class RemoteUserFetch extends AbstractFetchRemote { * @param {?Function} fCallback */ foldersReload(fCallback) { + clearTimeout(this.foldersTimeout); this.abort('Folders') .post('Folders', FolderUserStore.foldersLoading) .then(data => { data = FolderCollectionModel.reviveFromJson(data.Result); data && data.storeIt(); fCallback && fCallback(true); + // Repeat every 15 minutes + this.foldersTimeout = setTimeout(() => this.foldersReload(), 900000); }) - .catch(() => fCallback && setTimeout(() => fCallback(false), 1)); - } - - foldersReloadWithTimeout() { - this.setTrigger(FolderUserStore.foldersLoading, true); - - clearTimeout(this.foldersTimeout); - this.foldersTimeout = setTimeout(() => this.foldersReload(), 500); + .catch(() => fCallback && setTimeout(fCallback, 1, false)); } /* diff --git a/dev/Settings/User/Folders.js b/dev/Settings/User/Folders.js index 88c0320de..f8a37c7c4 100644 --- a/dev/Settings/User/Folders.js +++ b/dev/Settings/User/Folders.js @@ -5,7 +5,7 @@ import { ClientSideKeyName, FolderMetadataKeys } from 'Common/EnumsUser'; import { Settings } from 'Common/Globals'; import { getNotification } from 'Common/Translator'; -import { setFolder, removeFolderFromCacheList } from 'Common/Cache'; +import { setFolder, getFolderFromCacheList, removeFolderFromCacheList } from 'Common/Cache'; import { Capa } from 'Common/Enums'; import { defaultOptionsAfterRender } from 'Common/Utils'; import { initOnStartOrLangChange, i18n } from 'Common/Translator'; @@ -71,21 +71,27 @@ export class FoldersUserSettings /*extends AbstractViewSettings*/ { if (nameToEdit && folder.name() !== nameToEdit) { Local.set(ClientSideKeyName.FoldersLashHash, ''); - Remote - .post('FolderRename', FolderUserStore.foldersRenaming, { + Remote.abort('Folders').post('FolderRename', FolderUserStore.foldersRenaming, { Folder: folder.fullName, NewFolderName: nameToEdit }) .then(data => { folder.name(nameToEdit/*data.Name*/); if (folder.subFolders.length) { - Remote.foldersReloadWithTimeout(); - // rename all subfolders folder.delimiter + Remote.setTrigger(FolderUserStore.foldersLoading, true); + clearTimeout(Remote.foldersTimeout); + Remote.foldersTimeout = setTimeout(() => Remote.foldersReload(), 500); + // TODO: rename all subfolders with folder.delimiter to prevent reload? } else { removeFolderFromCacheList(folder.fullName); data = data.Result; folder.fullName = data.FullName; setFolder(folder); +/* + const folder = getFolderFromCacheList(folder.parentName); + var collator = new Intl.Collator(undefined, {numeric: true, sensitivity: 'base'}); + console.log((folder ? folder.subFolders : FolderUserStore.folderList).sort(collator.compare)); +*/ } }) .catch(error => { @@ -131,14 +137,19 @@ export class FoldersUserSettings /*extends AbstractViewSettings*/ { if (folderToRemove) { Local.set(ClientSideKeyName.FoldersLashHash, ''); - // rl.app.foldersPromisesActionHelper Remote.abort('Folders').post('FolderDelete', FolderUserStore.foldersDeleting, { Folder: folderToRemove.fullName }).then( () => { - folderToRemove.selectable(false) - removeFolderFromCacheList(folderToRemove.fullName); - FolderUserStore.folderList(FolderUserStore.folderList.filter(folder => folder !== folderToRemove)); +// folderToRemove.flags.push('\\nonexistent'); + folderToRemove.selectable(false); +// folderToRemove.subscribed(false); +// folderToRemove.checkable(false); + if (!folderToRemove.subFolders.length) { + removeFolderFromCacheList(folderToRemove.fullName); + const folder = getFolderFromCacheList(folderToRemove.parentName); + (folder ? folder.subFolders : FolderUserStore.folderList).remove(folderToRemove); + } }, error => { FolderUserStore.folderListError( diff --git a/dev/View/Popup/FolderCreate.js b/dev/View/Popup/FolderCreate.js index 3cc8ce24e..caf38ef09 100644 --- a/dev/View/Popup/FolderCreate.js +++ b/dev/View/Popup/FolderCreate.js @@ -4,6 +4,7 @@ import { Notification } from 'Common/Enums'; import { UNUSED_OPTION_VALUE } from 'Common/Consts'; import { defaultOptionsAfterRender } from 'Common/Utils'; import { folderListOptionsBuilder } from 'Common/UtilsUser'; +import { getNotification } from 'Common/Translator'; import { FolderUserStore } from 'Stores/User/Folder'; @@ -12,6 +13,9 @@ import Remote from 'Remote/User/Fetch'; import { decorateKoCommands } from 'Knoin/Knoin'; import { AbstractViewPopup } from 'Knoin/AbstractViews'; +import { setFolder, getFolderFromCacheList } from 'Common/Cache'; +import { FolderModel } from 'Model/FolderCollection'; + class FolderCreatePopupView extends AbstractViewPopup { constructor() { super('FolderCreate'); @@ -49,13 +53,27 @@ class FolderCreatePopupView extends AbstractViewPopup { parentFolderName = FolderUserStore.namespace.substr(0, FolderUserStore.namespace.length - 1); } - rl.app.foldersPromisesActionHelper( - Remote.post('FolderCreate', FolderUserStore.foldersCreating, { + Remote.abort('Folders').post('FolderCreate', FolderUserStore.foldersCreating, { Folder: this.folderName(), Parent: parentFolderName - }), - Notification.CantCreateFolder - ); + }) + .then( + data => { + const folder = getFolderFromCacheList(parentFolderName), + subFolder = FolderModel.reviveFromJson(data.Result); + setFolder(subFolder); + (folder ? folder.subFolders : FolderUserStore.folderList).push(subFolder); +/* + var collator = new Intl.Collator(undefined, {numeric: true, sensitivity: 'base'}); + console.log((folder ? folder.subFolders : FolderUserStore.folderList).sort(collator.compare)); +*/ + }, + error => { + FolderUserStore.folderListError( + getNotification(error.code, '', Notification.CantCreateFolder) + + '.\n' + error.message); + } + ); this.cancelCommand(); } diff --git a/snappymail/v/0.0.0/app/libraries/MailSo/Mail/MailClient.php b/snappymail/v/0.0.0/app/libraries/MailSo/Mail/MailClient.php index c59bd66ec..d943f5326 100644 --- a/snappymail/v/0.0.0/app/libraries/MailSo/Mail/MailClient.php +++ b/snappymail/v/0.0.0/app/libraries/MailSo/Mail/MailClient.php @@ -1309,7 +1309,7 @@ class MailClient /** * @throws \MailSo\Base\Exceptions\InvalidArgumentException */ - public function FolderCreate(string $sFolderNameInUtf8, string $sFolderParentFullName = '', bool $bSubscribeOnCreation = true, string $sDelimiter = '') : self + public function FolderCreate(string $sFolderNameInUtf8, string $sFolderParentFullName = '', bool $bSubscribeOnCreation = true, string $sDelimiter = '') : ?Folder { $sFolderNameInUtf8 = \trim($sFolderNameInUtf8); $sFolderParentFullName = \trim($sFolderParentFullName); @@ -1353,7 +1353,11 @@ class MailClient $this->oImapClient->FolderSubscribe($sFullNameToCreate); } - return $this; + $aFolders = $this->oImapClient->IsSupported('LIST-STATUS') + ? $this->oImapClient->FolderStatusList($sFullNameToCreate, '') + : $this->oImapClient->FolderList($sFullNameToCreate, ''); + $oImapFolder = $aFolders[$sFullNameToCreate]; + return $oImapFolder ? new Folder($oImapFolder, $bSubscribeOnCreation) : null; } /** diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Folders.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Folders.php index 3e6c966b4..325a37e8c 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Folders.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Folders.php @@ -119,10 +119,7 @@ trait Folders { try { - if ($this->MailClient()->FolderCreate($mFolderNameToCreate, $sParent, true, $sDelimiter)) - { - $bDoItAgain = true; - } + $this->MailClient()->FolderCreate($mFolderNameToCreate, $sParent, true, $sDelimiter); } catch (\Throwable $oException) { @@ -198,15 +195,16 @@ trait Folders $sFolderNameInUtf = $this->GetActionParam('Folder', ''); $sFolderParentFullName = $this->GetActionParam('Parent', ''); - $this->MailClient()->FolderCreate($sFolderNameInUtf, $sFolderParentFullName, + $oFolder = $this->MailClient()->FolderCreate($sFolderNameInUtf, $sFolderParentFullName, !!$this->Config()->Get('labs', 'use_imap_list_subscribe', true)); + +// FolderInformation(string $sFolderName, int $iPrevUidNext = 0, array $aUids = array()) + return $this->DefaultResponse(__FUNCTION__, $oFolder); } catch (\Throwable $oException) { throw new ClientException(Notifications::CantCreateFolder, $oException); } - - return $this->TrueResponse(__FUNCTION__); } public function DoFolderSetMetadata() : array