diff options
author | Richard Steinmetz <richard@steinmetz.cloud> | 2021-12-17 19:11:27 +0300 |
---|---|---|
committer | Richard Steinmetz <richard@steinmetz.cloud> | 2022-03-28 12:44:36 +0300 |
commit | 6efad68afae361eee60b3850c29680038edb4657 (patch) | |
tree | 887d475301c5d3a8431535aa313a1264dac24560 | |
parent | fdfce08e2a05f401e55cb30776366af6f1870e9f (diff) |
Load itineraries asynchronouslyenh/4823/async-itineraries
Signed-off-by: Richard Steinmetz <richard@steinmetz.cloud>
-rw-r--r-- | appinfo/routes.php | 5 | ||||
-rwxr-xr-x | lib/Controller/MessagesController.php | 32 | ||||
-rw-r--r-- | lib/Service/ItineraryService.php | 28 | ||||
-rw-r--r-- | src/components/Message.vue | 8 | ||||
-rw-r--r-- | src/components/ThreadEnvelope.vue | 25 | ||||
-rw-r--r-- | src/service/MessageService.js | 17 | ||||
-rw-r--r-- | src/store/actions.js | 10 | ||||
-rw-r--r-- | src/store/mutations.js | 8 |
8 files changed, 113 insertions, 20 deletions
diff --git a/appinfo/routes.php b/appinfo/routes.php index 6242fe0e2..39b1bf824 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -175,6 +175,11 @@ return [ 'verb' => 'GET' ], [ + 'name' => 'messages#getItineraries', + 'url' => '/api/messages/{id}/itineraries', + 'verb' => 'GET' + ], + [ 'name' => 'messages#getSource', 'url' => '/api/messages/{id}/source', 'verb' => 'GET' diff --git a/lib/Controller/MessagesController.php b/lib/Controller/MessagesController.php index e3cc7a3e7..f2f083d38 100755 --- a/lib/Controller/MessagesController.php +++ b/lib/Controller/MessagesController.php @@ -11,6 +11,7 @@ declare(strict_types=1); * @author Lukas Reschke <lukas@owncloud.com> * @author Thomas Imbreckx <zinks@iozero.be> * @author Thomas Müller <thomas.mueller@tmit.eu> + * @author Richard Steinmetz <richard@steinmetz.cloud> * * Mail * @@ -248,11 +249,10 @@ class MessagesController extends Controller { $message->getUid(), true )->getFullMessage($id); - $json['itineraries'] = $this->itineraryService->extract( - $account, - $mailbox->getName(), - $message->getUid() - ); + $itineraries = $this->itineraryService->getCached($account, $mailbox, $message->getUid()); + if ($itineraries) { + $json['itineraries'] = $itineraries; + } $json['attachments'] = array_map(function ($a) use ($id) { return $this->enrichDownloadUrl( $id, @@ -272,6 +272,28 @@ class MessagesController extends Controller { return $response; } + /** + * @NoAdminRequired + * @TrapError + * + * @param int $id + * + * @return JSONResponse + * + * @throws ClientException + */ + public function getItineraries(int $id): JSONResponse { + try { + $message = $this->mailManager->getMessage($this->currentUserId, $id); + $mailbox = $this->mailManager->getMailbox($this->currentUserId, $message->getMailboxId()); + $account = $this->accountService->find($this->currentUserId, $mailbox->getAccountId()); + } catch (DoesNotExistException $e) { + return new JSONResponse([], Http::STATUS_FORBIDDEN); + } + + return new JsonResponse($this->itineraryService->extract($account, $mailbox, $message->getUid())); + } + private function isSenderTrusted(Message $message): bool { $from = $message->getFrom(); $first = $from->first(); diff --git a/lib/Service/ItineraryService.php b/lib/Service/ItineraryService.php index a4c0ee7bd..b7fd29acc 100644 --- a/lib/Service/ItineraryService.php +++ b/lib/Service/ItineraryService.php @@ -6,6 +6,7 @@ declare(strict_types=1); * @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at> * * @author 2019 Christoph Wurst <christoph@winzerhof-wurst.at> + * @author 2021 Richard Steinmetz <richard@steinmetz.cloud> * * @license GNU AGPL version 3 or any later version * @@ -27,7 +28,7 @@ namespace OCA\Mail\Service; use ChristophWurst\KItinerary\Itinerary; use OCA\Mail\Account; -use OCA\Mail\Db\MailboxMapper; +use OCA\Mail\Db\Mailbox; use OCA\Mail\IMAP\IMAPClientFactory; use OCA\Mail\IMAP\MessageMapper; use OCA\Mail\Integration\KItinerary\ItineraryExtractor; @@ -43,9 +44,6 @@ class ItineraryService { /** @var IMAPClientFactory */ private $clientFactory; - /** @var MailboxMapper */ - private $mailboxMapper; - /** @var MessageMapper */ private $messageMapper; @@ -59,27 +57,34 @@ class ItineraryService { private $logger; public function __construct(IMAPClientFactory $clientFactory, - MailboxMapper $mailboxMapper, MessageMapper $messageMapper, ItineraryExtractor $extractor, ICacheFactory $cacheFactory, LoggerInterface $logger) { $this->clientFactory = $clientFactory; - $this->mailboxMapper = $mailboxMapper; $this->messageMapper = $messageMapper; $this->extractor = $extractor; $this->cache = $cacheFactory->createLocal(); $this->logger = $logger; } - public function extract(Account $account, string $mailbox, int $id): Itinerary { - $mailbox = $this->mailboxMapper->find($account, $mailbox); + private function buildCacheKey(Account $account, Mailbox $mailbox, int $id): string { + return 'mail_itinerary_' . $account->getId() . '_' . $mailbox->getName() . '_' . $id; + } - $cacheKey = 'mail_itinerary_' . $account->getId() . '_' . $mailbox->getName() . '_' . $id; - if ($cached = ($this->cache->get($cacheKey))) { + public function getCached(Account $account, Mailbox $mailbox, int $id): ?Itinerary { + if ($cached = ($this->cache->get($this->buildCacheKey($account, $mailbox, $id)))) { return Itinerary::fromJson($cached); } + return null; + } + + public function extract(Account $account, Mailbox $mailbox, int $id): Itinerary { + if ($cached = ($this->getCached($account, $mailbox, $id))) { + return $cached; + } + $client = $this->clientFactory->getClient($account); $itinerary = new Itinerary(); @@ -104,7 +109,8 @@ class ItineraryService { $final = $this->extractor->extract(json_encode($itinerary)); $this->logger->debug('Reduced ' . count($itinerary) . ' itinerary entries to ' . count($final) . ' entries'); - $this->cache->set($cacheKey, json_encode($final)); + $cache_key = $this->buildCacheKey($account, $mailbox, $id); + $this->cache->set($cache_key, json_encode($final)); return $final; } diff --git a/src/components/Message.vue b/src/components/Message.vue index 8be67c500..d059296d0 100644 --- a/src/components/Message.vue +++ b/src/components/Message.vue @@ -2,6 +2,7 @@ - @copyright 2020 Christoph Wurst <christoph@winzerhof-wurst.at> - - @author 2020 Christoph Wurst <christoph@winzerhof-wurst.at> + - @author 2021 Richard Steinmetz <richard@steinmetz.cloud> - - @license GNU AGPL version 3 or any later version - @@ -23,8 +24,8 @@ <div :class="[message.hasHtmlBody ? 'mail-message-body mail-message-body-html' : 'mail-message-body']" role="region" :aria-label="t('mail','Message body')"> - <div v-if="message.itineraries.length > 0" class="message-itinerary"> - <Itinerary :entries="message.itineraries" :message-id="message.messageId" /> + <div v-if="itineraries.length > 0" class="message-itinerary"> + <Itinerary :entries="itineraries" :message-id="message.messageId" /> </div> <MessageHTMLBody v-if="message.hasHtmlBody" :url="htmlUrl" @@ -90,6 +91,9 @@ export default { isEncrypted() { return isPgpgMessage(this.message.hasHtmlBody ? html(this.message.body) : plain(this.message.body)) }, + itineraries() { + return this.message.itineraries ?? [] + }, }, } </script> diff --git a/src/components/ThreadEnvelope.vue b/src/components/ThreadEnvelope.vue index a1a7e6fda..08b913961 100644 --- a/src/components/ThreadEnvelope.vue +++ b/src/components/ThreadEnvelope.vue @@ -2,6 +2,7 @@ - @copyright 2020 Christoph Wurst <christoph@winzerhof-wurst.at> - - @author 2020 Christoph Wurst <christoph@winzerhof-wurst.at> + - @author 2021 Richard Steinmetz <richard@steinmetz.cloud> - - @license GNU AGPL version 3 or any later version - @@ -255,8 +256,8 @@ export default { logger.debug(`fetching thread message ${this.envelope.databaseId}`) try { - const message = this.message = await this.$store.dispatch('fetchMessage', this.envelope.databaseId) - logger.debug(`message ${this.envelope.databaseId} fetched`, { message }) + this.message = await this.$store.dispatch('fetchMessage', this.envelope.databaseId) + logger.debug(`message ${this.envelope.databaseId} fetched`, { message: this.message }) if (!this.envelope.flags.seen) { logger.info('Starting timer to mark message as seen/read') @@ -270,6 +271,26 @@ export default { } catch (error) { logger.error('Could not fetch message', { error }) } + + // Fetch itineraries if they haven't been included in the message data + if (this.message && !this.message.itineraries) { + await this.fetchItineraries() + } + }, + async fetchItineraries() { + // Sanity check before actually making the request + if (!this.message.hasHtmlBody && this.message.attachments.length === 0) { + return + } + + logger.debug(`Fetching itineraries for message ${this.envelope.databaseId}`) + + try { + const itineraries = await this.$store.dispatch('fetchItineraries', this.envelope.databaseId) + logger.debug(`Itineraries of message ${this.envelope.databaseId} fetched`, { itineraries }) + } catch (error) { + logger.error(`Could not fetch itineraries of message ${this.envelope.databaseId}`, { error }) + } }, scrollToCurrentEnvelope() { // Account for global navigation bar and thread header diff --git a/src/service/MessageService.js b/src/service/MessageService.js index 049227243..8473ae56b 100644 --- a/src/service/MessageService.js +++ b/src/service/MessageService.js @@ -166,6 +166,23 @@ export async function fetchMessage(id) { } } +export async function fetchMessageItineraries(id) { + const url = generateUrl('/apps/mail/api/messages/{id}/itineraries', { + id, + }) + + try { + const resp = await axios.get(url) + return resp.data + } catch (error) { + if (error.response && error.response.status === 404) { + return undefined + } + + throw parseErrorResponse(error.response) + } +} + export async function saveDraft(accountId, data) { const url = generateUrl('/apps/mail/api/accounts/{accountId}/draft', { accountId, diff --git a/src/store/actions.js b/src/store/actions.js index ee3d99032..3bc0ea0da 100644 --- a/src/store/actions.js +++ b/src/store/actions.js @@ -2,6 +2,7 @@ * @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at> * * @author 2019 Christoph Wurst <christoph@winzerhof-wurst.at> + * @author 2021 Richard Steinmetz <richard@steinmetz.cloud> * * @license GNU AGPL version 3 or any later version * @@ -63,6 +64,7 @@ import { fetchEnvelope, fetchEnvelopes, fetchMessage, + fetchMessageItineraries, fetchThread, moveMessage, removeEnvelopeTag, @@ -751,6 +753,14 @@ export default { } return message }, + async fetchItineraries({ commit }, id) { + const itineraries = await fetchMessageItineraries(id) + commit('addMessageItineraries', { + id, + itineraries, + }) + return itineraries + }, async deleteMessage({ getters, commit }, { id }) { commit('removeEnvelope', { id }) diff --git a/src/store/mutations.js b/src/store/mutations.js index f6fdd76b0..cfe22fb6b 100644 --- a/src/store/mutations.js +++ b/src/store/mutations.js @@ -2,6 +2,7 @@ * @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at> * * @author 2019 Christoph Wurst <christoph@winzerhof-wurst.at> + * @author 2021 Richard Steinmetz <richard@steinmetz.cloud> * * @license GNU AGPL version 3 or any later version * @@ -300,6 +301,13 @@ export default { addMessage(state, { message }) { Vue.set(state.messages, message.databaseId, message) }, + addMessageItineraries(state, { id, itineraries }) { + const message = state.messages[id] + if (!message) { + return + } + Vue.set(message, 'itineraries', itineraries) + }, addEnvelopeThread(state, { id, thread }) { // Store the envelopes, merge into any existing object if one exists thread.forEach(e => { |