diff --git a/dev/App/User.js b/dev/App/User.js index 616451270..bb622b736 100644 --- a/dev/App/User.js +++ b/dev/App/User.js @@ -214,12 +214,13 @@ ); } - }, FolderStore.currentFolderFullNameRaw(), iOffset, SettingsStore.messagesPerPage(), MessageStore.messageListSearch()); + }, FolderStore.currentFolderFullNameRaw(), iOffset, SettingsStore.messagesPerPage(), + MessageStore.messageListSearch(), MessageStore.messageListThreadUid()); }; AppUser.prototype.recacheInboxMessageList = function () { - Remote.messageList(Utils.emptyFunction, Cache.getFolderInboxName(), 0, SettingsStore.messagesPerPage(), '', true); + Remote.messageList(Utils.emptyFunction, Cache.getFolderInboxName(), 0, SettingsStore.messagesPerPage(), '', '', true); }; /** diff --git a/dev/Common/Links.js b/dev/Common/Links.js index da26296b0..ac0e57c7c 100644 --- a/dev/Common/Links.js +++ b/dev/Common/Links.js @@ -257,23 +257,30 @@ * @param {string} sFolder * @param {number=} iPage = 1 * @param {string=} sSearch = '' + * @param {string=} sThreadUid = '' * @return {string} */ - Links.prototype.mailBox = function (sFolder, iPage, sSearch) + Links.prototype.mailBox = function (sFolder, iPage, sSearch, sThreadUid) { iPage = Utils.isNormal(iPage) ? Utils.pInt(iPage) : 1; sSearch = Utils.pString(sSearch); - var sResult = this.sBase + 'mailbox/'; + var + sResult = this.sBase + 'mailbox/', + iThreadUid = Utils.pInt(sThreadUid) + ; + if ('' !== sFolder) { - sResult += encodeURI(sFolder); + sResult += encodeURI(sFolder) + (0 < iThreadUid ? '|' + iThreadUid : ''); } + if (1 < iPage) { sResult = sResult.replace(/[\/]+$/, ''); sResult += '/p' + iPage; } + if ('' !== sSearch) { sResult = sResult.replace(/[\/]+$/, ''); diff --git a/dev/Remote/User/Ajax.js b/dev/Remote/User/Ajax.js index 6e77f856e..e7c8ee969 100644 --- a/dev/Remote/User/Ajax.js +++ b/dev/Remote/User/Ajax.js @@ -332,10 +332,10 @@ * @param {number=} iOffset = 0 * @param {number=} iLimit = 20 * @param {string=} sSearch = '' - * @param {boolean=} bSilent = false * @param {string=} sThreadUid = '' + * @param {boolean=} bSilent = false */ - RemoteUserAjax.prototype.messageList = function (fCallback, sFolderFullNameRaw, iOffset, iLimit, sSearch, bSilent, sThreadUid) + RemoteUserAjax.prototype.messageList = function (fCallback, sFolderFullNameRaw, iOffset, iLimit, sSearch, sThreadUid, bSilent) { sFolderFullNameRaw = Utils.pString(sFolderFullNameRaw); diff --git a/dev/Screen/User/MailBox.js b/dev/Screen/User/MailBox.js index 64bb526cf..c69dce10a 100644 --- a/dev/Screen/User/MailBox.js +++ b/dev/Screen/User/MailBox.js @@ -75,15 +75,23 @@ MailBoxUserScreen.prototype.onRoute = function (sFolderHash, iPage, sSearch) { var - sFolderFullNameRaw = Cache.getFolderFullNameRaw(sFolderHash), - oFolder = Cache.getFolderFromCacheList(sFolderFullNameRaw) + sThreadUid = sFolderHash.replace(/^(.+)\|([\d]+)$/, '$2'), + oFolder = Cache.getFolderFromCacheList(Cache.getFolderFullNameRaw( + sFolderHash.replace(/\|([\d]+)$/, ''))) ; if (oFolder) { + if (sFolderHash === sThreadUid) + { + sThreadUid = ''; + } + FolderStore.currentFolder(oFolder); + MessageStore.messageListPage(iPage); MessageStore.messageListSearch(sSearch); + MessageStore.messageListThreadUid(sThreadUid); require('App/User').reloadMessageList(); } diff --git a/dev/Stores/User/Message.js b/dev/Stores/User/Message.js index 55c503324..becf5fea4 100644 --- a/dev/Stores/User/Message.js +++ b/dev/Stores/User/Message.js @@ -41,6 +41,7 @@ this.messageListCount = ko.observable(0); this.messageListSearch = ko.observable(''); + this.messageListThreadUid = ko.observable(''); this.messageListPage = ko.observable(1); this.messageListError = ko.observable(''); @@ -813,6 +814,7 @@ this.messageListCount(iCount); this.messageListSearch(Utils.isNormal(oData.Result.Search) ? oData.Result.Search : ''); this.messageListPage(window.Math.ceil((iOffset / SettingsStore.messagesPerPage()) + 1)); + this.messageListThreadUid(Utils.isNormal(oData.Result.ThreadUid) ? Utils.pString(oData.Result.ThreadUid) : ''); this.messageListEndFolder(Utils.isNormal(oData.Result.Folder) ? oData.Result.Folder : ''); this.messageListEndSearch(Utils.isNormal(oData.Result.Search) ? oData.Result.Search : ''); diff --git a/dev/View/User/MailBox/MessageList.js b/dev/View/User/MailBox/MessageList.js index cd254167f..7eab8a1fc 100644 --- a/dev/View/User/MailBox/MessageList.js +++ b/dev/View/User/MailBox/MessageList.js @@ -62,6 +62,7 @@ this.selectorMessageFocused = MessageStore.selectorMessageFocused; this.isMessageSelected = MessageStore.isMessageSelected; this.messageListSearch = MessageStore.messageListSearch; + this.messageListThreadUid = MessageStore.messageListThreadUid; this.messageListError = MessageStore.messageListError; this.folderMenuForMove = FolderStore.folderMenuForMove; @@ -611,7 +612,8 @@ kn.setHash(Links.mailBox( FolderStore.currentFolderFullNameHash(), oPage.value, - MessageStore.messageListSearch() + MessageStore.messageListSearch(), + MessageStore.messageListThreadUid() )); } }; diff --git a/rainloop/v/0.0.0/app/libraries/MailSo/Mail/MailClient.php b/rainloop/v/0.0.0/app/libraries/MailSo/Mail/MailClient.php index f3f0041a1..e2450810d 100644 --- a/rainloop/v/0.0.0/app/libraries/MailSo/Mail/MailClient.php +++ b/rainloop/v/0.0.0/app/libraries/MailSo/Mail/MailClient.php @@ -1911,6 +1911,88 @@ class MailClient return $oMessageCollection->GetAsArray(); } + /** + * @param \MailSo\Cache\CacheClient|null $oCacher + * @param string $sSearch + * @param string $sFolderName + * @param string $sFolderHash + * + * @return array + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Imap\Exceptions\Exception + */ + public function GetUids($oCacher, $sSearch, $sFolderName, $sFolderHash) + { + $aResultUids = false; + $bUidsFromCacher = false; + $bUseCacheAfterSearch = true; + + $sSerializedHash = ''; + $sSerializedLog = ''; + + $bUseSortIfSupported = !!$this->oImapClient->IsSupported('SORT'); + + if (0 < \strlen($sSearch)) + { + $bUseSortIfSupported = false; + } + + $sSearchCriterias = $this->getImapSearchCriterias($sSearch, 0, $bUseCacheAfterSearch); + if ($bUseCacheAfterSearch && $oCacher && $oCacher->IsInited()) + { + $sSerializedHash = 'GetUids/'. + ($bUseSortIfSupported ? 'S': 'N').'/'. + $this->GenerateImapClientHash().'/'. + $sFolderName.'/'.$sSearchCriterias; + + $sSerializedLog = '"'.$sFolderName.'" / '.$sSearchCriterias.''; + + $sSerialized = $oCacher->Get($sSerializedHash); + if (!empty($sSerialized)) + { + $aSerialized = @\json_decode($sSerialized, true); + if (\is_array($aSerialized) && isset($aSerialized['FolderHash'], $aSerialized['Uids']) && + $sFolderHash === $aSerialized['FolderHash'] && + \is_array($aSerialized['Uids']) + ) + { + if ($this->oLogger) + { + $this->oLogger->Write('Get Serialized UIDS from cache ('.$sSerializedLog.') [count:'.\count($aSerialized['Uids']).']'); + } + + $aResultUids = $aSerialized['Uids']; + $bUidsFromCacher = true; + } + } + } + + if (!\is_array($aResultUids)) + { + $aResultUids = $bUseSortIfSupported ? + $this->oImapClient->MessageSimpleSort(array('REVERSE ARRIVAL'), $sSearchCriterias, true) : + $this->oImapClient->MessageSimpleSearch($sSearchCriterias, true) + ; + + if (!$bUidsFromCacher && $bUseCacheAfterSearch && \is_array($aResultUids) && $oCacher && $oCacher->IsInited() && 0 < \strlen($sSerializedHash)) + { + $oCacher->Set($sSerializedHash, @\json_encode(array( + 'FolderHash' => $sFolderHash, + 'Uids' => $aResultUids + ))); + + if ($this->oLogger) + { + $this->oLogger->Write('Save Serialized UIDS to cache ('.$sSerializedLog.') [count:'.\count($aResultUids).']'); + } + } + } + + return $aResultUids; + } + /** * @param string $sFolderName * @param int $iOffset = 0 @@ -1930,8 +2012,7 @@ class MailClient * @throws \MailSo\Imap\Exceptions\Exception */ public function MessageList($sFolderName, $iOffset = 0, $iLimit = 10, $sSearch = '', $sPrevUidNext = '', $oCacher = null, - $bUseSortIfSupported = false, $bUseThreadSortIfSupported = false, $bUseESearchOrESortRequest = false, - $sThreadUid = '') + $bUseSortIfSupported = false, $bUseThreadSortIfSupported = false, $sThreadUid = '') { $sSearch = \trim($sSearch); if (!\MailSo\Base\Validator::RangeInt($iOffset, 0) || @@ -1947,10 +2028,14 @@ class MailClient $oMessageCollection->Offset = $iOffset; $oMessageCollection->Limit = $iLimit; $oMessageCollection->Search = $sSearch; + $oMessageCollection->ThreadUid = $sThreadUid; - $aThreads = array(); + $aUids = array(); + $mAllSortedUids = null; + $mAllThreads = null; + + $iThreadUid = empty($sThreadUid) ? 0 : (int) $sThreadUid; - $iMessageCount = 0; $iMessageRealCount = 0; $iMessageUnseenCount = 0; $sUidNext = '0'; @@ -1971,11 +2056,15 @@ class MailClient } $this->initFolderValues($sFolderName, $iMessageRealCount, $iMessageUnseenCount, $sUidNext); - $iMessageCount = $iMessageRealCount; $oMessageCollection->FolderHash = $this->GenerateFolderHash($sFolderName, $iMessageRealCount, $iMessageUnseenCount, $sUidNext); $oMessageCollection->UidNext = $sUidNext; - $oMessageCollection->NewMessages = $this->getFolderNextMessageInformation($sFolderName, $sPrevUidNext, $sUidNext); + + if (empty($sThreadUid) && 0 < \strlen($sPrevUidNext) && 'INBOX' === $sFolderName) + { + $oMessageCollection->NewMessages = $this->getFolderNextMessageInformation( + $sFolderName, $sPrevUidNext, $sUidNext); + } $bSearch = false; $bMessageListOptimization = 0 < \MailSo\Config::$MessageListCountLimitTrigger && @@ -1985,104 +2074,98 @@ class MailClient { $bUseSortIfSupported = false; $bUseThreadSortIfSupported = false; - $bUseESearchOrESortRequest = false; } if (0 < $iMessageRealCount) { - $bIndexAsUid = false; - $aIndexOrUids = array(); + $mAllSortedUids = $this->GetUids($oCacher, '', $oMessageCollection->FolderName, $oMessageCollection->FolderHash); -// $mThreads = $this->MessageListThreadsMap($sFolderName, $sFolderHash, array(), $oCacher, true); -// if (\is_array($mThreads)) -// { -// -// } + $mAllThreads = $bUseThreadSortIfSupported ? $this->MessageListThreadsMap( + $oMessageCollection->FolderName, $oMessageCollection->FolderHash, $mAllSortedUids, $oCacher) : null; - if (0 < \strlen($sSearch)) + if ($bUseThreadSortIfSupported && 0 < $iThreadUid && \is_array($mAllThreads)) { - $aIndexOrUids = $this->getSearchUidsResult($sSearch, - $oMessageCollection->FolderName, $oMessageCollection->FolderHash, - $bUseSortIfSupported, $bUseESearchOrESortRequest, $oCacher); + $aUids = array(); + $aUids = \is_array($mAllThreads[$iThreadUid]) ? + $mAllThreads[$iThreadUid] : array(); - $bIndexAsUid = true; + if (\in_array($iThreadUid, $mAllSortedUids)) + { + \array_unshift($aUids, $iThreadUid); + } + } + else if ($bUseThreadSortIfSupported && \is_array($mAllThreads)) + { + $aUids = \array_keys($mAllThreads); } else { - if ($bUseThreadSortIfSupported && 1 < $iMessageCount) + $bUseThreadSortIfSupported = false; + $aUids = $mAllSortedUids; + } + + if (0 < \strlen($sSearch) && \is_array($aUids)) + { + $aSearchedUids = $this->GetUids($oCacher, $sSearch, $oMessageCollection->FolderName, $oMessageCollection->FolderHash); + if (\is_array($aSearchedUids) && 0 < \count($aSearchedUids)) { - $aIndexOrUids = $this->getSearchUidsResult('', - $oMessageCollection->FolderName, $oMessageCollection->FolderHash, - $bUseSortIfSupported, $bUseESearchOrESortRequest, $oCacher); + $aFlippedSearchedUids = \array_flip($aSearchedUids); - $aThreads = $this->MessageListThreadsMap( - $oMessageCollection->FolderName, $oMessageCollection->FolderHash, - $aIndexOrUids, $oCacher); + $bSearch = true; + $aNewUids = array(); - $aNewIndexOrUids = array(); - foreach ($aIndexOrUids as $iUid) + foreach ($aUids as $iUid) { - if (isset($aThreads[$iUid])) + if (isset($aFlippedSearchedUids[$iUid])) { - $aNewIndexOrUids[] = $iUid; + $aNewUids[] = $iUid; + } + else if ($bUseThreadSortIfSupported && 0 === $iThreadUid && isset($mAllThreads[$iUid]) && \is_array($mAllThreads[$iUid])) + { + foreach ($mAllThreads[$iUid] as $iSubUid) + { + if (isset($aFlippedSearchedUids[$iSubUid])) + { + $aNewUids[] = $iUid; + continue; + } + } } } - $aIndexOrUids = $aNewIndexOrUids; - unset($aNewIndexOrUids); - - $iMessageCount = \count($aIndexOrUids); - $bIndexAsUid = true; + $aUids = \array_unique($aNewUids); + unset($aNewUids); } else { - $aIndexOrUids = array(1); - $bIndexAsUid = false; - - if (1 < $iMessageCount) - { - if (0 === \MailSo\Config::$MessageListDateFilter && - ($bMessageListOptimization || !$bUseSortIfSupported)) - { - $aIndexOrUids = \array_reverse(\range(1, $iMessageCount)); - } - else - { - $aIndexOrUids = $this->getSearchUidsResult('', - $oMessageCollection->FolderName, $oMessageCollection->FolderHash, - $bUseSortIfSupported, $bUseESearchOrESortRequest, $oCacher); - - $bIndexAsUid = true; - } - } + $aUids = array(); } } - if (\is_array($aIndexOrUids)) + if (\is_array($aUids)) { $oMessageCollection->MessageCount = $iMessageRealCount; $oMessageCollection->MessageUnseenCount = $iMessageUnseenCount; - $oMessageCollection->MessageResultCount = 0 === \strlen($sSearch) - ? $iMessageCount : \count($aIndexOrUids); + $oMessageCollection->MessageResultCount = \count($aUids); - if (0 < \count($aIndexOrUids)) + if (0 < \count($aUids)) { $iOffset = (0 > $iOffset) ? 0 : $iOffset; - $aRequestIndexOrUids = \array_slice($aIndexOrUids, $iOffset, $iLimit); + $aRequestUids = \array_slice($aUids, $iOffset, $iLimit); - $this->MessageListByRequestIndexOrUids($oMessageCollection, $aRequestIndexOrUids, $bIndexAsUid); + $this->MessageListByRequestIndexOrUids($oMessageCollection, $aRequestUids, true); } } } - if (!$bSearch && $bUseThreadSortIfSupported && 0 < \count($aThreads)) + if ($bUseThreadSortIfSupported && 0 === $iThreadUid && \is_array($mAllThreads) && 0 < \count($mAllThreads)) { - $oMessageCollection->ForeachList(function (/* @var $oMessage \MailSo\Mail\Message */ $oMessage) use ($aThreads) { + $oMessageCollection->ForeachList(function (/* @var $oMessage \MailSo\Mail\Message */ $oMessage) use ($mAllThreads) { $iUid = $oMessage->Uid(); - if (isset($aThreads[$iUid]) && \is_array($aThreads[$iUid]) && 0 < \count($aThreads[$iUid])) + if (isset($mAllThreads[$iUid]) && \is_array($mAllThreads[$iUid]) && 0 < \count($mAllThreads[$iUid])) { - $aSubThreads = $aThreads[$iUid]; + $aSubThreads = $mAllThreads[$iUid]; \array_unshift($aSubThreads, $iUid); $oMessage->SetThreads(\array_map('trim', $aSubThreads)); diff --git a/rainloop/v/0.0.0/app/libraries/MailSo/Mail/MessageCollection.php b/rainloop/v/0.0.0/app/libraries/MailSo/Mail/MessageCollection.php index 34c8cf884..ec176924a 100644 --- a/rainloop/v/0.0.0/app/libraries/MailSo/Mail/MessageCollection.php +++ b/rainloop/v/0.0.0/app/libraries/MailSo/Mail/MessageCollection.php @@ -62,6 +62,11 @@ class MessageCollection extends \MailSo\Base\Collection */ public $UidNext; + /** + * @var string + */ + public $ThreadUid; + /** * @var array */ @@ -108,8 +113,9 @@ class MessageCollection extends \MailSo\Base\Collection $this->Limit = 0; $this->Search = ''; $this->UidNext = ''; + $this->ThreadUid = ''; $this->NewMessages = array(); - + $this->LastCollapsedThreadUids = array(); return $this; diff --git a/rainloop/v/0.0.0/app/libraries/RainLoop/Actions.php b/rainloop/v/0.0.0/app/libraries/RainLoop/Actions.php index 0946c1682..37aa0afc0 100644 --- a/rainloop/v/0.0.0/app/libraries/RainLoop/Actions.php +++ b/rainloop/v/0.0.0/app/libraries/RainLoop/Actions.php @@ -5564,7 +5564,7 @@ class Actions $sFolder, $iOffset, $iLimit, $sSearch, $sUidNext, $this->cacherForUids(), !!$this->Config()->Get('labs', 'use_imap_sort', false), - $bUseThreads, !!$this->Config()->Get('labs', 'use_imap_esearch_esort', false), + $bUseThreads, $sThreadUid ); } @@ -9372,8 +9372,8 @@ class Actions 'Folder' => $mResponse->FolderName, 'FolderHash' => $mResponse->FolderHash, 'UidNext' => $mResponse->UidNext, + 'ThreadUid' => $mResponse->ThreadUid, 'NewMessages' => $this->responseObject($mResponse->NewMessages), -// 'LastCollapsedThreadUids' => $mResponse->LastCollapsedThreadUids, 'Offset' => $mResponse->Offset, 'Limit' => $mResponse->Limit, 'Search' => $mResponse->Search diff --git a/rainloop/v/0.0.0/app/libraries/RainLoop/Config/Application.php b/rainloop/v/0.0.0/app/libraries/RainLoop/Config/Application.php index 64eca8f7b..8dcb131ce 100644 --- a/rainloop/v/0.0.0/app/libraries/RainLoop/Config/Application.php +++ b/rainloop/v/0.0.0/app/libraries/RainLoop/Config/Application.php @@ -282,7 +282,6 @@ Enables caching in the system'), 'use_app_debug_js' => array(false), 'use_app_debug_css' => array(false), 'use_imap_sort' => array(true), - 'use_imap_esearch_esort' => array(true), 'use_imap_force_selection' => array(false), 'use_imap_list_subscribe' => array(true), 'use_imap_thread' => array(true),