service/ImapBodyFetch.h
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #pragma once | ||
| 2 | |||
| 3 | #include <QByteArray> | ||
| 4 | #include <QString> | ||
| 5 | |||
| 6 | #include <functional> | ||
| 7 | |||
| 8 | #include "data/MailRequest.h" | ||
| 9 | #include "service/ImapResponseParser.h" | ||
| 10 | #include "service/ImapService.h" | ||
| 11 | |||
| 12 | class ImapCommandTable; | ||
| 13 | class ImapPushSession; | ||
| 14 | class QObject; | ||
| 15 | |||
| 16 | // Body downloads of one IMAP connection (Sprint 92 S92.1.5, extracted from | ||
| 17 | // ImapService): uncorrelated and request-id correlated body fetches | ||
| 18 | // (BODY.PEEK[]<0.limit>), the pipelined SELECT + FETCH of the body | ||
| 19 | // connection with selected-mailbox reuse (P90.9), the configured message | ||
| 20 | // size limit, the one retry after an empty OK, and the single terminal | ||
| 21 | // BodyResult of the active request (with the E2E completion knobs). | ||
| 22 | // | ||
| 23 | // Owner: ImapService (std::unique_ptr), connection thread. Plain class. | ||
| 24 | // It owns the active body request; the line reader correlates literals | ||
| 25 | // through references to it and the service's SELECT handler reads it. The | ||
| 26 | // selected-mailbox state stays with the service and is read/written | ||
| 27 | // through SelectedMailbox references; commands go through the command | ||
| 28 | // table, push restart through the push session; the bodyRequestCompleted, | ||
| 29 | // bodyMailboxSelected and errorOccurred signals through notifiers (the | ||
| 30 | // emitter stays ImapService). | ||
| 31 | class ImapBodyFetch { | ||
| 32 | public: | ||
| 33 | using State = ImapService::State; | ||
| 34 | |||
| 35 | // References to the service's selected-mailbox state. | ||
| 36 | struct SelectedMailbox { | ||
| 37 | const QString &selectedFolder; | ||
| 38 | int &messageCount; | ||
| 39 | quint32 &uidValidity; | ||
| 40 | quint64 &highestModseq; | ||
| 41 | }; | ||
| 42 | struct Deps { | ||
| 43 | // ImapService signals. | ||
| 44 | std::function<void(const BodyResult &result)> bodyRequestCompleted; | ||
| 45 | std::function<void(quint64 requestId, bool success)> bodyMailboxSelected; | ||
| 46 | std::function<void(const QString &error)> errorOccurred; | ||
| 47 | }; | ||
| 48 | ImapBodyFetch(QObject *owner, ImapCommandTable &commands, ImapPushSession &push, | ||
| 49 | const State &state, SelectedMailbox mailbox, Deps deps); | ||
| 50 | |||
| 51 | void fetchBody(qint64 uid); | ||
| 52 | void fetchBody(qint64 uid, qint64 maxBytes); | ||
| 53 | void requestBody(quint64 requestId, qint64 uid); | ||
| 54 | static qint64 configuredMaxMessageBytes(); | ||
| 55 | void fetchBodyWithLimits(quint64 requestId, qint64 uid, | ||
| 56 | qint64 acceptedBytes, qint64 requestedBytes); | ||
| 57 | void selectAndFetchBody(const QString &folderPath, qint64 uid); | ||
| 58 | void requestBodyInFolder(quint64 requestId, const QString &folderPath, | ||
| 59 | qint64 uid); | ||
| 60 | static QString buildFetchBodyCommand(qint64 uid, qint64 maxBytes = -1); | ||
| 61 | void invalidateBodyFetch(); | ||
| 62 | // Forgets the active request without completing it (session lifecycle; | ||
| 63 | // the caller completes the request first). | ||
| 64 | void resetActiveRequest(); | ||
| 65 | void completeBodyRequest(BodyOutcome outcome, QByteArray bytes = {}, | ||
| 66 | const QString &error = {}, qint64 maxBytes = 0); | ||
| 67 | // Completes a request whose FETCH was never written (dispatched=false). | ||
| 68 | void completeUndispatched(quint64 requestId, const QString &folderPath, qint64 uid); | ||
| 69 | // S106-001: completes every request still parked in the serialized queue | ||
| 70 | // as undispatched. The session lifecycle calls it right after it cleared | ||
| 71 | // the command queue, which destroys the parked lambdas — without a | ||
| 72 | // completion the body transport stayed busy for the rest of the session. | ||
| 73 | void cancelParkedRequests(); | ||
| 74 | #ifdef MAILJD_E2E_TESTING | ||
| 75 | void setNextBodyCompletionForE2e(int delayMs, bool fail, | ||
| 76 | const QString &folderPath = {}, qint64 uid = 0); | ||
| 77 | #endif | ||
| 78 | void handleTaggedFetchBody(const TaggedResponse &response); | ||
| 79 | |||
| 80 | // Active body request (single owner: this unit; the line reader and the | ||
| 81 | // service's SELECT handler and lifecycle use these references). | ||
| 82 | 1417 | qint64 &activeBodyFetchUid() { return m_activeBodyFetchUid; } | |
| 83 | 1479 | quint64 &activeBodyRequestId() { return m_activeBodyRequestId; } | |
| 84 | 5 | QString &activeBodyRequestFolder() { return m_activeBodyRequestFolder; } | |
| 85 | 1409 | qint64 &activeBodyFetchLimit() { return m_activeBodyFetchLimit; } | |
| 86 | 1409 | qint64 &activeBodyFetchRequestBytes() { return m_activeBodyFetchRequestBytes; } | |
| 87 | 1499 | bool &bodyFetchRequiresSelect() { return m_bodyFetchRequiresSelect; } | |
| 88 | |||
| 89 | private: | ||
| 90 | QObject *m_owner; // context of the delayed E2E completion | ||
| 91 | ImapCommandTable &m_commands; | ||
| 92 | ImapPushSession &m_push; | ||
| 93 | const State &m_state; // ImapService::m_state | ||
| 94 | SelectedMailbox m_mailbox; | ||
| 95 | Deps m_deps; | ||
| 96 | |||
| 97 | qint64 m_activeBodyFetchUid = -1; | ||
| 98 | quint64 m_activeBodyRequestId = 0; | ||
| 99 | QString m_activeBodyRequestFolder; | ||
| 100 | qint64 m_activeBodyFetchLimit = 0; | ||
| 101 | qint64 m_activeBodyFetchRequestBytes = 0; | ||
| 102 | bool m_bodyFetchRequiresSelect = false; | ||
| 103 | // One re-fetch after an empty OK body response (see handleTaggedFetchBody). | ||
| 104 | bool m_bodyFetchRetried = false; | ||
| 105 | // Identified requests waiting in the serialized command queue (S106-001). | ||
| 106 | struct ParkedRequest { | ||
| 107 | quint64 ticket = 0; | ||
| 108 | quint64 requestId = 0; | ||
| 109 | QString folderPath; | ||
| 110 | qint64 uid = 0; | ||
| 111 | }; | ||
| 112 | QList<ParkedRequest> m_parkedRequests; | ||
| 113 | quint64 m_nextParkTicket = 0; | ||
| 114 | // Parks `run` in the serialized queue; an identified request is tracked | ||
| 115 | // until the queue runs it or the session drops it. | ||
| 116 | void park(quint64 requestId, const QString &folderPath, qint64 uid, std::function<void()> run); | ||
| 117 | // Keep the object layout identical for all consumers of mailjd_app_lib. | ||
| 118 | // mailjd_app_lib defines MAILJD_E2E_TESTING for its own code only, so | ||
| 119 | // conditionally compiling data members would make non-E2E test executables | ||
| 120 | // allocate a smaller object than its constructor initializes. | ||
| 121 | int m_e2eNextBodyCompletionDelayMs = 0; | ||
| 122 | bool m_e2eFailNextBodyCompletion = false; | ||
| 123 | QString m_e2eBodyCompletionFolder; | ||
| 124 | qint64 m_e2eBodyCompletionUid = 0; | ||
| 125 | }; | ||
| 126 |