generated from Nodarx/template
feat: speed improvements
Signed-off-by: Sebastian Krupinski <root@LAPTOP-7DVOR6NC>
This commit is contained in:
@@ -31,8 +31,11 @@ class CollectionProperties extends CollectionPropertiesMutableAbstract
|
||||
*/
|
||||
public function fromImap(Mailbox $mailbox): static
|
||||
{
|
||||
$this->data['label'] = $mailbox->name;
|
||||
$this->data['delimiter'] = $mailbox->hierarchyDelimiter;
|
||||
$delimiter = $mailbox->hierarchyDelimiter;
|
||||
$this->data['label'] = ($delimiter !== '' && str_contains($mailbox->name, $delimiter))
|
||||
? substr($mailbox->name, strrpos($mailbox->name, $delimiter) + strlen($delimiter))
|
||||
: $mailbox->name;
|
||||
$this->data['delimiter'] = $delimiter;
|
||||
$this->data['attributes'] = $mailbox->nameAttributes;
|
||||
$this->data['subscribed'] = in_array('\Subscribed', $mailbox->nameAttributes, true);
|
||||
$this->data['total'] = 0;
|
||||
|
||||
@@ -31,9 +31,8 @@ class EntityResource extends EntityMutableAbstract {
|
||||
*
|
||||
* @param FetchData $fetchData result from IMAP FETCH command
|
||||
* @param string $mailbox IMAP mailbox name (used as collection)
|
||||
* @param Part|null $bodyPart MIME Part tree for body content (optional)
|
||||
*/
|
||||
public function fromImap(FetchData $fetchData, string $mailbox, ?Part $bodyPart = null): static {
|
||||
public function fromImap(FetchData $fetchData, string $mailbox): static {
|
||||
|
||||
// Collection = the IMAP mailbox name
|
||||
$this->data['collection'] = $mailbox;
|
||||
@@ -46,13 +45,7 @@ class EntityResource extends EntityMutableAbstract {
|
||||
$this->data['created'] = $fetchData->internalDate->format(DateTimeInterface::ATOM);
|
||||
}
|
||||
|
||||
$this->getProperties()->fromImap(
|
||||
flags: $fetchData->flags ?? [],
|
||||
envelope: $fetchData->envelope,
|
||||
bodyStructure: $fetchData->bodyStructure,
|
||||
size: $fetchData->rfc822Size ?? 0,
|
||||
bodyPart: $bodyPart,
|
||||
);
|
||||
$this->getProperties()->fromImap($fetchData);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -93,8 +93,10 @@ class MessagePart extends MessagePartMutableAbstract {
|
||||
}
|
||||
|
||||
// Recursively process sub-parts
|
||||
// When this part has no section ID (root multipart) children are
|
||||
// numbered "1", "2", … to match IMAP section numbering.
|
||||
foreach ($part->parts as $index => $subPart) {
|
||||
$subPartId = $partId . '.' . ($index + 1);
|
||||
$subPartId = ($partId === '') ? (string)($index + 1) : $partId . '.' . ($index + 1);
|
||||
$this->parts[] = (new MessagePart())->fromImap($subPart, $subPartId);
|
||||
}
|
||||
}
|
||||
@@ -135,42 +137,49 @@ class MessagePart extends MessagePartMutableAbstract {
|
||||
}
|
||||
|
||||
/**
|
||||
* Inject decoded body content from a parallel gricob Mime Part tree.
|
||||
* Inject decoded body content from a map of IMAP section-ID → raw encoded text.
|
||||
*
|
||||
* Walks the gricob Mime Part tree alongside this MessagePart tree and
|
||||
* sets 'content' on each leaf single-part node from its decoded body.
|
||||
* Walks the MessagePart tree recursively. For each text/* leaf part whose
|
||||
* partId is present in $sectionMap the raw text is decoded according to the
|
||||
* part's Content-Transfer-Encoding and converted to UTF-8 before being
|
||||
* stored in 'content'. Binary parts (images, PDFs, …) are skipped.
|
||||
*
|
||||
* @param \Gricob\IMAP\Mime\Part\Part $mimePart Corresponding gricob Mime Part node
|
||||
* @param array<string,string> $sectionMap Keys: IMAP section IDs (e.g. "1", "1.2");
|
||||
* Values: raw (transfer-encoded) body text
|
||||
*/
|
||||
public function injectBodyContent(\Gricob\IMAP\Mime\Part\Part $mimePart): void
|
||||
public function injectSections(array $sectionMap): void
|
||||
{
|
||||
if ($mimePart instanceof \Gricob\IMAP\Mime\Part\MultiPart) {
|
||||
foreach ($mimePart->parts as $index => $childMimePart) {
|
||||
$childPart = $this->parts[$index] ?? null;
|
||||
// MultiPart: recurse into children
|
||||
if (!empty($this->parts)) {
|
||||
foreach ($this->parts as $childPart) {
|
||||
if ($childPart instanceof MessagePart) {
|
||||
$childPart->injectBodyContent($childMimePart);
|
||||
$childPart->injectSections($sectionMap);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if ($mimePart instanceof \Gricob\IMAP\Mime\Part\SinglePart) {
|
||||
// Only inject content for text/* parts; binary parts (images, PDFs, …)
|
||||
// produce raw bytes that cannot be JSON-encoded as UTF-8 strings.
|
||||
$type = strtolower($this->data['type'] ?? '');
|
||||
if (!str_starts_with($type, 'text/')) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
$decoded = $mimePart->decodedBody();
|
||||
} catch (\Throwable) {
|
||||
return;
|
||||
}
|
||||
if ($decoded !== null && $decoded !== '') {
|
||||
$charset = $mimePart->charset() ?? 'utf-8';
|
||||
$this->data['content'] = MessageProperties::toUtf8($decoded, $charset);
|
||||
}
|
||||
// SinglePart: only inject decoded content for text/* MIME types
|
||||
$type = strtolower($this->data['type'] ?? '');
|
||||
if (!str_starts_with($type, 'text/')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$partId = $this->data['partId'] ?? null;
|
||||
if ($partId === null || !array_key_exists($partId, $sectionMap)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$raw = $sectionMap[$partId];
|
||||
$encoding = strtolower($this->data['encoding'] ?? '7bit');
|
||||
$decoded = match ($encoding) {
|
||||
'quoted-printable' => quoted_printable_decode($raw),
|
||||
'base64' => base64_decode($raw, strict: false),
|
||||
default => $raw, // 7bit, 8bit, binary
|
||||
};
|
||||
|
||||
$charset = $this->data['charset'] ?? 'us-ascii';
|
||||
$this->data['content'] = MessageProperties::toUtf8($decoded, $charset);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -11,12 +11,10 @@ namespace KTXM\ProviderImapMail\Providers;
|
||||
|
||||
use DateTimeImmutable;
|
||||
use DateTimeInterface;
|
||||
use Gricob\IMAP\Mime\Part\Part as MimePart;
|
||||
use Gricob\IMAP\Protocol\Response\Line\Data\Fetch\BodyStructure;
|
||||
use Gricob\IMAP\Protocol\Response\Line\Data\Fetch\BodyStructure\MultiPart;
|
||||
use Gricob\IMAP\Protocol\Response\Line\Data\Fetch\BodyStructure\Part;
|
||||
use Gricob\IMAP\Protocol\Response\Line\Data\Fetch\BodyStructure\SinglePart;
|
||||
use Gricob\IMAP\Protocol\Response\Line\Data\Fetch\Envelope;
|
||||
use Gricob\IMAP\Protocol\Response\Line\Data\FetchData;
|
||||
use KTXF\Mail\Object\MessagePropertiesMutableAbstract;
|
||||
|
||||
/**
|
||||
@@ -27,25 +25,16 @@ class MessageProperties extends MessagePropertiesMutableAbstract {
|
||||
/**
|
||||
* Convert IMAP data to mail message properties object
|
||||
*
|
||||
* @param array $flags IMAP flags (e.g. ['\Seen', '\Flagged', ...])
|
||||
* @param ?Envelope $envelope parsed envelope from gricob
|
||||
* @param ?BodyStructure $bodyStructure parsed body structure from gricob
|
||||
* @param int $size RFC822.SIZE byte count
|
||||
* @param FetchData $fetchData result from IMAP FETCH command
|
||||
*/
|
||||
public function fromImap(
|
||||
array $flags,
|
||||
?Envelope $envelope,
|
||||
?BodyStructure $bodyStructure,
|
||||
int $size = 0,
|
||||
?MimePart $bodyPart = null,
|
||||
): static {
|
||||
public function fromImap(FetchData $fetchData): static {
|
||||
|
||||
// ── Size ──────────────────────────────────────────────────────
|
||||
$this->data['size'] = $size;
|
||||
$this->data['size'] = $fetchData->rfc822Size ?? 0;
|
||||
|
||||
// ── Flags ─────────────────────────────────────────────────────
|
||||
$this->data['flags'] = [];
|
||||
foreach ($flags as $flag) {
|
||||
foreach ($fetchData->flags ?? [] as $flag) {
|
||||
$flag = ltrim($flag, '\\');
|
||||
$normalized = match (strtolower($flag)) {
|
||||
'seen' => 'read',
|
||||
@@ -59,7 +48,8 @@ class MessageProperties extends MessagePropertiesMutableAbstract {
|
||||
}
|
||||
|
||||
// ── Envelope ──────────────────────────────────────────────────
|
||||
if ($envelope !== null) {
|
||||
if ($fetchData->envelope !== null) {
|
||||
$envelope = $fetchData->envelope;
|
||||
|
||||
if ($envelope->messageId !== null) {
|
||||
$this->data['urid'] = trim($envelope->messageId, '<>');
|
||||
@@ -114,19 +104,28 @@ class MessageProperties extends MessagePropertiesMutableAbstract {
|
||||
}
|
||||
|
||||
// ── Body Structure ────────────────────────────────────────────
|
||||
if ($bodyStructure !== null) {
|
||||
$rootPart = (new MessagePart())->fromImap($bodyStructure->part, '1');
|
||||
if ($fetchData->bodyStructure !== null) {
|
||||
$bodyStructure = $fetchData->bodyStructure;
|
||||
// Root multipart containers have no fetchable section ID; their
|
||||
// children are numbered "1", "2", … to match IMAP section IDs.
|
||||
$isRootMultipart = $bodyStructure->part instanceof MultiPart;
|
||||
$rootPartId = $isRootMultipart ? '' : '1';
|
||||
$rootPart = (new MessagePart())->fromImap($bodyStructure->part, $rootPartId);
|
||||
|
||||
// ── Body Content: inject decoded content onto part nodes ──────
|
||||
if ($bodyPart !== null) {
|
||||
$rootPart->injectBodyContent($bodyPart);
|
||||
if (!empty($fetchData->bodySections)) {
|
||||
$sectionMap = [];
|
||||
foreach ($fetchData->bodySections as $bs) {
|
||||
$sectionMap[$bs->section] = $bs->text;
|
||||
}
|
||||
$rootPart->injectSections($sectionMap);
|
||||
}
|
||||
|
||||
$this->data['body'] = $rootPart->toStore();
|
||||
|
||||
// Collect attachments: non-body parts with name or attachment disposition
|
||||
$attachments = [];
|
||||
self::collectAttachments($bodyStructure->part, '1', $attachments);
|
||||
self::collectAttachments($bodyStructure->part, $rootPartId, $attachments);
|
||||
if (!empty($attachments)) {
|
||||
$this->data['attachments'] = $attachments;
|
||||
}
|
||||
@@ -198,7 +197,8 @@ class MessageProperties extends MessagePropertiesMutableAbstract {
|
||||
}
|
||||
} elseif ($part instanceof MultiPart) {
|
||||
foreach ($part->parts as $index => $subPart) {
|
||||
self::collectAttachments($subPart, $partId . '.' . ($index + 1), $attachments);
|
||||
$subPartId = ($partId === '') ? (string)($index + 1) : $partId . '.' . ($index + 1);
|
||||
self::collectAttachments($subPart, $subPartId, $attachments);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -445,11 +445,17 @@ class Service implements ServiceBaseInterface, ServiceMutableInterface, ServiceC
|
||||
{
|
||||
$this->initialize();
|
||||
|
||||
// Unfiltered + unpaginated: skip the SEARCH round-trip and use FETCH 1:*
|
||||
if ($filter === null && $range === null) {
|
||||
return $this->mailService->entityFetchAll((string) $collection);
|
||||
}
|
||||
|
||||
// Filtered or paginated: SEARCH to get a UID list, then FETCH by UIDs
|
||||
$uids = $this->mailService->entityList((string) $collection, $filter, $range);
|
||||
if (empty($uids)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
|
||||
return $this->mailService->entityFetch((string) $collection, ...$uids);
|
||||
}
|
||||
|
||||
@@ -457,6 +463,13 @@ class Service implements ServiceBaseInterface, ServiceMutableInterface, ServiceC
|
||||
{
|
||||
$this->initialize();
|
||||
|
||||
// Unfiltered: skip the SEARCH round-trip and stream via FETCH 1:*
|
||||
if ($filter === null) {
|
||||
yield from $this->mailService->entityFetchAllStream((string) $collection);
|
||||
return;
|
||||
}
|
||||
|
||||
// Filtered: SEARCH for matching UIDs then stream only those messages
|
||||
$uids = $this->mailService->entityList((string) $collection, $filter, $range);
|
||||
if (empty($uids)) {
|
||||
return;
|
||||
@@ -483,10 +496,6 @@ class Service implements ServiceBaseInterface, ServiceMutableInterface, ServiceC
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Delta sync is not supported for IMAP (no CONDSTORE/QRESYNC initially).
|
||||
* Returns an empty Delta so callers detect the absence of changes gracefully.
|
||||
*/
|
||||
public function entityDelta(string|int $collection, string $signature, string $detail = 'ids'): Delta
|
||||
{
|
||||
return new Delta(signature: $signature);
|
||||
|
||||
Reference in New Issue
Block a user