Conversation
dd69469 to
68be4e1
Compare
b4e3752 to
498f4e3
Compare
| $cacheKey = $account->getId() . $mailbox->getId() . $message->getUid(); | ||
| $cached = $cache->get($cacheKey); | ||
|
|
||
| if (is_array($cached)) { |
There was a problem hiding this comment.
if ($cached) => if (is_array($cached))
Is the main change. No attachments (empty array) is a valid state. if ([]) is false, and we try to fetch the data over and over again.
There was a problem hiding this comment.
Pull request overview
This PR fixes attachment-name caching behavior in AttachmentService::getAttachmentNames() so cached “no attachments” results are treated as valid cache hits, reducing repeated IMAP fetches, and adds unit tests to cover the updated caching and exception-handling paths.
Changes:
- Update attachment-name cache lookup logic to treat cached empty arrays as hits and add an early return when structure analysis already confirmed no attachments.
- Switch attachment-name caching to use a distributed cache instance keyed per user.
- Add unit tests for cache hit/miss scenarios and for
SmimeDecryptException/ServiceExceptionbehavior; add a clarifying TODO inIMAPMessage::getAttachments()docblock.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
lib/Service/Attachment/AttachmentService.php |
Adjusts attachment-name caching logic and URL building; adds early exit for confirmed no-attachment messages. |
tests/Unit/Service/Attachment/AttachmentServiceTest.php |
Adds focused unit tests covering cache hit/miss and exception cases for getAttachmentNames(). |
lib/Model/IMAPMessage.php |
Adds a TODO note documenting a return-type mismatch for getAttachments(). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| return []; | ||
| } | ||
|
|
||
| $cache = $this->cacheFactory->createDistributed('mail_attachment_names'); |
There was a problem hiding this comment.
Is the main change 2 ;)
It was a local cache before, as discussed in #11596.
Redis is usually always faster than IMAP, and therefore I would use a distributed cache here as well. In a setup with multiple application servers using only a local cache leads to unncessary imap requests.
Signed-off-by: Daniel Kesselberg <mail@danielkesselberg.de>
21fc08e to
b261f25
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| $result = array_values(array_map(function ($attachment) use ($message) { | ||
| $downloadUrl = $this->urlGenerator->linkToRouteAbsolute('mail.messages.downloadAttachment', [ | ||
| 'id' => $message->getId(), | ||
| 'attachmentId' => $attachment['id'], | ||
| ]); | ||
| $mimeUrl = $this->mimeTypeDetector->mimeTypeIcon($attachment['mime']); | ||
| return ['id' => $attachment['id'] , 'fileName' => $attachment['fileName'], 'mime' => $attachment['mime'], 'downloadUrl' => $downloadUrl, 'mimeUrl' => $mimeUrl ]; | ||
| }, $attachments); | ||
| $this->cache->set($uniqueCacheId, $result); | ||
| return ['id' => $attachment['id'], 'fileName' => $attachment['fileName'], 'mime' => $attachment['mime'], 'downloadUrl' => $downloadUrl, 'mimeUrl' => $mimeUrl]; | ||
| }, $attachments)); |
There was a problem hiding this comment.
The cached payload includes downloadUrl, which is derived from $message->getId(), but the cache key is built from user/account/mailbox IDs and the IMAP UID only. If the DB message id changes (e.g., re-sync/re-import), cache hits can return stale/broken download URLs. Consider caching only attachment metadata (id/fileName/mime) and always generating URLs at runtime, or include the DB message id in the cache key.
| * @return Horde_Mime_Part[] | ||
| * @todo Actually returns list<array{id: string, messageId: int, fileName: string|null, mime: string, size: int, cid: string|null, disposition: string|null}>, violating the IMessage contract. |
There was a problem hiding this comment.
The docblock still declares @return Horde_Mime_Part[], but the added @todo states the method actually returns a list of associative arrays. This contradictory annotation can mislead IDEs/static analysis; consider updating the return annotation to the actual structure (e.g., with @return list<array{...}> / @psalm-return ...) and tracking the interface mismatch separately.
| * @return Horde_Mime_Part[] | |
| * @todo Actually returns list<array{id: string, messageId: int, fileName: string|null, mime: string, size: int, cid: string|null, disposition: string|null}>, violating the IMessage contract. | |
| * @return list<array{id: string, messageId: int, fileName: string|null, mime: string, size: int, cid: string|null, disposition: string|null}> | |
| * @psalm-return list<array{id: string, messageId: int, fileName: string|null, mime: string, size: int, cid: string|null, disposition: string|null}> | |
| * @todo This concrete return type violates the IMessage contract, which expects Horde_Mime_Part[]. |
There was a problem hiding this comment.
Tracked in #12530.
Psalm is okay with the current version. Adding the correct return type leads to a warning about breaking the interface that needs baseline update or suppressing.
| return ['id' => $attachment['id'], 'fileName' => $attachment['fileName'], 'mime' => $attachment['mime'], 'downloadUrl' => $downloadUrl, 'mimeUrl' => $mimeUrl]; | ||
| }, $attachments)); | ||
|
|
||
| $cache->set($cacheKey, $result); |
There was a problem hiding this comment.
$result is cached without a TTL, and it includes empty arrays as well. Since PreviewEnhancer::process() calls getAttachmentNames() for every message in mailbox listings, this can create an unbounded number of distributed-cache entries over time. Consider setting a reasonable TTL (and/or caching only non-empty results) to avoid long-term cache growth and memory pressure.
| $cache->set($cacheKey, $result); | |
| if (!empty($result)) { | |
| // Cache non-empty attachment lists for 1 hour to avoid unbounded cache growth. | |
| $cache->set($cacheKey, $result, 3600); | |
| } |
Based on / Requires #12520