From 883bf6b026c1c7874518b6a52d0eb8b46005bb5b Mon Sep 17 00:00:00 2001 From: djmaze Date: Fri, 26 Mar 2021 15:07:14 +0100 Subject: [PATCH] Start https://github.com/the-djmaze/snappymail/issues/67#issuecomment-806948346 --- dev/App/User.js | 14 +++-- dev/Common/EnumsUser.js | 16 +++++ dev/Model/FolderCollection.js | 1 + dev/Remote/User/Fetch.js | 60 ++++++++----------- dev/Stores/User/Folder.js | 10 +++- .../app/libraries/MailSo/Imap/ImapClient.php | 8 +++ .../MailSo/Mail/FolderCollection.php | 7 +++ .../app/libraries/MailSo/Mail/MailClient.php | 60 +++++++++++++++---- .../libraries/RainLoop/Actions/Messages.php | 21 ++++--- 9 files changed, 135 insertions(+), 62 deletions(-) diff --git a/dev/App/User.js b/dev/App/User.js index becf923ff..36f7f2836 100644 --- a/dev/App/User.js +++ b/dev/App/User.js @@ -187,16 +187,18 @@ class AppUser extends AbstractApp { } MessageUserStore.listLoading(false); }, - FolderUserStore.currentFolderFullNameRaw(), - iOffset, - SettingsUserStore.messagesPerPage(), - MessageUserStore.listSearch(), - MessageUserStore.listThreadUid() + { + Folder: FolderUserStore.currentFolderFullNameRaw(), + Offset: iOffset, + Limit: SettingsUserStore.messagesPerPage(), + Search: MessageUserStore.listSearch(), + UidNext: MessageUserStore.listThreadUid() + } ); } recacheInboxMessageList() { - Remote.messageList(()=>{}, getFolderInboxName(), 0, SettingsUserStore.messagesPerPage(), '', '', true); + Remote.messageList(null, {Folder: getFolderInboxName()}, true); } /** diff --git a/dev/Common/EnumsUser.js b/dev/Common/EnumsUser.js index 19d382c70..12514e334 100644 --- a/dev/Common/EnumsUser.js +++ b/dev/Common/EnumsUser.js @@ -14,6 +14,22 @@ export const FolderType = { User: 99 }; +/** + * @enum {string} + */ +export const FolderSortType = { + DateDesc: '', // default 'REVERSE DATE' + DateAsc: 'DATE', + FromDesc: 'REVERSE FROM', + FromAsc: 'FROM', + SizeDesc: 'REVERSE SIZE', + SizeAsc: 'SIZE', + SubjectDesc: 'REVERSE SUBJECT', + SubjectAsc: 'SUBJECT' +// ToDesc: 'REVERSE TO', +// ToAsc: 'TO', +}; + /** * @enum {string} */ diff --git a/dev/Model/FolderCollection.js b/dev/Model/FolderCollection.js index 017fa37a9..cc172d464 100644 --- a/dev/Model/FolderCollection.js +++ b/dev/Model/FolderCollection.js @@ -113,6 +113,7 @@ export class FolderCollectionModel extends AbstractCollectionModel AppUserStore.threadsAllowed(!!(Settings.app('useImapThread') && this.IsThreadsSupported)); FolderUserStore.folderListOptimized(!!this.Optimized); + FolderUserStore.sortSupported(!!this.IsSortSupported); let update = false; diff --git a/dev/Remote/User/Fetch.js b/dev/Remote/User/Fetch.js index 182f15a23..64bf5704e 100644 --- a/dev/Remote/User/Fetch.js +++ b/dev/Remote/User/Fetch.js @@ -297,54 +297,44 @@ class RemoteUserFetch extends AbstractFetchRemote { /** * @param {Function} fCallback - * @param {string} sFolderFullNameRaw - * @param {number=} iOffset = 0 - * @param {number=} iLimit = 20 - * @param {string=} sSearch = '' - * @param {string=} sThreadUid = '' + * @param {object} params * @param {boolean=} bSilent = false */ - messageList(fCallback, sFolderFullNameRaw, iOffset = 0, iLimit = 20, sSearch = '', sThreadUid = '', bSilent = false) { - sFolderFullNameRaw = pString(sFolderFullNameRaw); - - const folderHash = getFolderHash(sFolderFullNameRaw), - useThreads = AppUserStore.threadsAllowed() && SettingsUserStore.useThreads(), + messageList(fCallback, params, bSilent = false) { + const + sFolderFullNameRaw = pString(params.Folder), + folderHash = getFolderHash(sFolderFullNameRaw), + useThreads = AppUserStore.threadsAllowed() && SettingsUserStore.useThreads() ? 1 : 0, inboxUidNext = getFolderInboxName() === sFolderFullNameRaw ? getFolderUidNext(sFolderFullNameRaw) : ''; - let params = {}, sGetAdd = ''; + params.Folder = sFolderFullNameRaw; + params.ThreadUid = useThreads ? params.ThreadUid : ''; + params = Object.assign({ + Folder: '', + Offset: 0, + Limit: SettingsUserStore.messagesPerPage(), + Search: '', + UidNext: inboxUidNext, + UseThreads: useThreads, + ThreadUid: '', + Sort: FolderUserStore.sortMode() + }, params); - if (folderHash && (!sSearch || !sSearch.includes('is:'))) { + let sGetAdd = ''; + + if (folderHash && (!params.Search || !params.Search.includes('is:'))) { sGetAdd = 'MessageList/' + SUB_QUERY_PREFIX + '/' + - urlsafeArray([ - sFolderFullNameRaw, - iOffset, - iLimit, - sSearch, - SettingsGet('ProjectHash'), - folderHash, - inboxUidNext, - useThreads ? 1 : 0, - useThreads ? sThreadUid : '' - ]); - } else { - params = { - Folder: sFolderFullNameRaw, - Offset: iOffset, - Limit: iLimit, - Search: sSearch, - UidNext: inboxUidNext, - UseThreads: useThreads ? 1 : 0, - ThreadUid: useThreads ? sThreadUid : '' - }; + urlsafeArray([SettingsGet('ProjectHash'),folderHash].concat(Object.values(params))); + params = {}; } this.defaultRequest( fCallback, 'MessageList', params, - sSearch ? 300000 : 30000, + 30000, sGetAdd, bSilent ? [] : ['MessageList'] ); @@ -562,7 +552,7 @@ class RemoteUserFetch extends AbstractFetchRemote { * @param {Object} oData */ sendMessage(fCallback, oData) { - this.defaultRequest(fCallback, 'SendMessage', oData, 300000); + this.defaultRequest(fCallback, 'SendMessage', oData, 30000); } /** diff --git a/dev/Stores/User/Folder.js b/dev/Stores/User/Folder.js index 0cc19c389..f8ab9b6a5 100644 --- a/dev/Stores/User/Folder.js +++ b/dev/Stores/User/Folder.js @@ -1,6 +1,6 @@ import ko from 'ko'; -import { FolderType } from 'Common/EnumsUser'; +import { FolderType, FolderSortType } from 'Common/EnumsUser'; import { UNUSED_OPTION_VALUE } from 'Common/Consts'; import { addObservablesTo, addSubscribablesTo } from 'Common/Utils'; import { folderListOptionsBuilder } from 'Common/UtilsUser'; @@ -19,6 +19,12 @@ export const FolderUserStore = new class { */ displaySpecSetting: false, + /** + * If the IMAP server supports SORT + */ + sortSupported: false, +// sortMode: '', + sentFolder: '', draftFolder: '', spamFolder: '', @@ -36,6 +42,8 @@ export const FolderUserStore = new class { foldersInboxUnreadCount: 0 }); + this.sortMode = ko.observable('').extend({ limitedList: Object.values(FolderSortType) }); + this.namespace = ''; this.folderList = ko.observableArray(); diff --git a/snappymail/v/0.0.0/app/libraries/MailSo/Imap/ImapClient.php b/snappymail/v/0.0.0/app/libraries/MailSo/Imap/ImapClient.php index 9ea88e869..e6d243c6e 100644 --- a/snappymail/v/0.0.0/app/libraries/MailSo/Imap/ImapClient.php +++ b/snappymail/v/0.0.0/app/libraries/MailSo/Imap/ImapClient.php @@ -293,6 +293,13 @@ class ImapClient extends \MailSo\Net\NetClient } /** + * Test support for things like: + * IMAP4rev1 SASL-IR LOGIN-REFERRALS ID ENABLE IDLE SORT SORT=DISPLAY + * THREAD=REFERENCES THREAD=REFS THREAD=ORDEREDSUBJECT MULTIAPPEND + * URL-PARTIAL CATENATE UNSELECT CHILDREN NAMESPACE UIDPLUS LIST-EXTENDED + * I18NLEVEL=1 CONDSTORE QRESYNC ESEARCH ESORT SEARCHRES WITHIN CONTEXT=SEARCH + * LIST-STATUS BINARY MOVE SNIPPET=FUZZY PREVIEW=FUZZY STATUS=SIZE LITERAL+ NOTIFY SPECIAL-USE + * * @throws \MailSo\Net\Exceptions\Exception * @throws \MailSo\Imap\Exceptions\Exception */ @@ -596,6 +603,7 @@ class ImapClient extends \MailSo\Net\NetClient } /** + * See https://tools.ietf.org/html/rfc5256 * @throws \MailSo\Base\Exceptions\InvalidArgumentException * @throws \MailSo\Net\Exceptions\Exception * @throws \MailSo\Imap\Exceptions\Exception diff --git a/snappymail/v/0.0.0/app/libraries/MailSo/Mail/FolderCollection.php b/snappymail/v/0.0.0/app/libraries/MailSo/Mail/FolderCollection.php index 8db19093f..a6e0a1044 100644 --- a/snappymail/v/0.0.0/app/libraries/MailSo/Mail/FolderCollection.php +++ b/snappymail/v/0.0.0/app/libraries/MailSo/Mail/FolderCollection.php @@ -32,6 +32,11 @@ class FolderCollection extends \MailSo\Base\Collection */ public $IsThreadsSupported; + /** + * @var bool + */ + public $IsSortSupported; + /** * @var bool */ @@ -50,6 +55,7 @@ class FolderCollection extends \MailSo\Base\Collection $this->FoldersHash = ''; $this->SystemFolders = array(); $this->IsThreadsSupported = false; + $this->IsSortSupported = false; $this->Optimized = false; } @@ -239,6 +245,7 @@ class FolderCollection extends \MailSo\Base\Collection 'Namespace' => $this->GetNamespace(), 'FoldersHash' => $this->FoldersHash ?: '', 'IsThreadsSupported' => $this->IsThreadsSupported, + 'IsSortSupported' => $this->IsSortSupported, 'Optimized' => $this->Optimized, 'CountRec' => $this->CountRec(), 'SystemFolders' => isset($this->SystemFolders) && \is_array($this->SystemFolders) ? 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 e7bad6d30..12f25fa8e 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 @@ -1473,8 +1473,47 @@ class MailClient * @throws \MailSo\Net\Exceptions\Exception * @throws \MailSo\Imap\Exceptions\Exception */ - public function GetUids(?\MailSo\Cache\CacheClient $oCacher, string $sSearch, string $sFilter, string $sFolderName, string $sFolderHash, bool $bUseSortIfSupported = false) : array + public function GetUids(?\MailSo\Cache\CacheClient $oCacher, string $sSearch, + string $sFilter, string $sFolderName, string $sFolderHash, + bool $bUseSortIfSupported = false, string $sSort = '') : array { + /* TODO: Validate $sSort + ARRIVAL + Internal date and time of the message. This differs from the + ON criteria in SEARCH, which uses just the internal date. + + CC + [IMAP] addr-mailbox of the first "cc" address. + + DATE + Sent date and time, as described in section 2.2. + + FROM + [IMAP] addr-mailbox of the first "From" address. + + REVERSE + Followed by another sort criterion, has the effect of that + criterion but in reverse (descending) order. + Note: REVERSE only reverses a single criterion, and does not + affect the implicit "sequence number" sort criterion if all + other criteria are identical. Consequently, a sort of + REVERSE SUBJECT is not the same as a reverse ordering of a + SUBJECT sort. This can be avoided by use of additional + criteria, e.g., SUBJECT DATE vs. REVERSE SUBJECT REVERSE + DATE. In general, however, it's better (and faster, if the + client has a "reverse current ordering" command) to reverse + the results in the client instead of issuing a new SORT. + + SIZE + Size of the message in octets. + + SUBJECT + Base subject text. + + TO + [IMAP] addr-mailbox of the first "To" address. + */ + $aResultUids = false; $bUidsFromCacher = false; $bUseCacheAfterSearch = true; @@ -1482,18 +1521,13 @@ class MailClient $sSerializedHash = ''; $sSerializedLog = ''; - $bUseSortIfSupported = $bUseSortIfSupported ? !!$this->oImapClient->IsSupported('SORT') : false; - - if (0 < \strlen($sSearch)) - { - $bUseSortIfSupported = false; - } + $bUseSortIfSupported = $bUseSortIfSupported && !\strlen($sSearch) && $this->oImapClient->IsSupported('SORT'); $sSearchCriterias = $this->getImapSearchCriterias($sSearch, $sFilter, 0, $bUseCacheAfterSearch); if ($bUseCacheAfterSearch && $oCacher && $oCacher->IsInited()) { $sSerializedHash = 'GetUids/'. - ($bUseSortIfSupported ? 'S': 'N').'/'. + ($bUseSortIfSupported ? 'S' . $sSort : 'N').'/'. $this->GenerateImapClientHash().'/'. $sFolderName.'/'.$sSearchCriterias; @@ -1522,7 +1556,7 @@ class MailClient if (!\is_array($aResultUids)) { $aResultUids = $bUseSortIfSupported ? - $this->oImapClient->MessageSimpleSort(array('REVERSE DATE'), $sSearchCriterias, true) : + $this->oImapClient->MessageSimpleSort(array($sSort ?: 'REVERSE DATE'), $sSearchCriterias, true) : $this->oImapClient->MessageSimpleSearch($sSearchCriterias, true, \MailSo\Base\Utils::IsAscii($sSearchCriterias) ? '' : 'UTF-8') ; @@ -1551,7 +1585,7 @@ class MailClient public function MessageList(string $sFolderName, int $iOffset = 0, int $iLimit = 10, string $sSearch = '', string $sPrevUidNext = '', ?\MailSo\Cache\CacheClient $oCacher = null, bool $bUseSortIfSupported = false, bool $bUseThreadSortIfSupported = false, - string $sThreadUid = '', string $sFilter = '') : MessageCollection + string $sThreadUid = '', string $sFilter = '', string $sSort = '') : MessageCollection { $sFilter = \trim($sFilter); $sSearch = \trim($sSearch); @@ -1584,7 +1618,7 @@ class MailClient $sUidNext = '0'; $sHighestModSeq = ''; - $bUseSortIfSupported = $bUseSortIfSupported ? $this->oImapClient->IsSupported('SORT') : false; + $bUseSortIfSupported = $bUseSortIfSupported && $this->oImapClient->IsSupported('SORT'); $bUseThreadSortIfSupported = $bUseThreadSortIfSupported ? ($this->oImapClient->IsSupported('THREAD=REFS') || $this->oImapClient->IsSupported('THREAD=REFERENCES') || $this->oImapClient->IsSupported('THREAD=ORDEREDSUBJECT')) : false; @@ -1630,7 +1664,7 @@ class MailClient if (0 < $iMessageRealCount && !$bMessageListOptimization) { $mAllSortedUids = $this->GetUids($oCacher, '', $sFilter, - $oMessageCollection->FolderName, $oMessageCollection->FolderHash, $bUseSortIfSupported); + $oMessageCollection->FolderName, $oMessageCollection->FolderHash, $bUseSortIfSupported, $sSort); $mAllThreads = $bUseThreadSortIfSupported ? $this->MessageListThreadsMap( $oMessageCollection->FolderName, $oMessageCollection->FolderHash, $mAllSortedUids, $oCacher) : null; @@ -2010,6 +2044,8 @@ class MailClient } $oFolderCollection->IsThreadsSupported = $this->IsThreadsSupported(); + + $oFolderCollection->IsSortSupported = $this->oImapClient->IsSupported('SORT'); } return $oFolderCollection; diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Messages.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Messages.php index ec894f8e2..8b8f0352e 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Messages.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Messages.php @@ -24,16 +24,17 @@ trait Messages $sUidNext = ''; $bUseThreads = false; $sThreadUid = ''; + $sSort = ''; $sRawKey = $this->GetActionParam('RawKey', ''); - $aValues = $this->getDecodedClientRawKeyValue($sRawKey, 9); + $aValues = $this->getDecodedClientRawKeyValue($sRawKey, 10); if ($aValues && 7 < \count($aValues)) { - $sFolder =(string) $aValues[0]; - $iOffset = (int) $aValues[1]; - $iLimit = (int) $aValues[2]; - $sSearch = (string) $aValues[3]; + $sFolder = (string) $aValues[2]; + $iOffset = (int) $aValues[3]; + $iLimit = (int) $aValues[4]; + $sSearch = (string) $aValues[5]; $sUidNext = (string) $aValues[6]; $bUseThreads = (bool) $aValues[7]; @@ -42,6 +43,8 @@ trait Messages $sThreadUid = isset($aValues[8]) ? (string) $aValues[8] : ''; } + $sSort = isset($aValues[9]) ? (string) $aValues[9] : ''; + $this->verifyCacheByKey($sRawKey); } else @@ -50,8 +53,9 @@ trait Messages $iOffset = (int) $this->GetActionParam('Offset', 0); $iLimit = (int) $this->GetActionParam('Limit', 10); $sSearch = $this->GetActionParam('Search', ''); + $sSort = $this->GetActionParam('Sort', ''); $sUidNext = $this->GetActionParam('UidNext', ''); - $bUseThreads = '1' === (string) $this->GetActionParam('UseThreads', '0'); + $bUseThreads = !empty($this->GetActionParam('UseThreads', '0')); if ($bUseThreads) { @@ -76,10 +80,11 @@ trait Messages $oMessageList = $this->MailClient()->MessageList( $sFolder, $iOffset, $iLimit, $sSearch, $sUidNext, $this->cacherForUids(), - !!$this->Config()->Get('labs', 'use_imap_sort', false), + !!$this->Config()->Get('labs', 'use_imap_sort', true), $bUseThreads, $sThreadUid, - '' + '', + $sSort ); } catch (\Throwable $oException)