service/MailPayloadExportService.h
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #pragma once | ||
| 2 | |||
| 3 | #include "data/MailPayloadReader.h" | ||
| 4 | #include <QObject> | ||
| 5 | #include <QPointer> | ||
| 6 | #include <QThreadPool> | ||
| 7 | #include <functional> | ||
| 8 | #include <memory> | ||
| 9 | #include <optional> | ||
| 10 | |||
| 11 | // A complete private snapshot. Keeping the lease alive keeps the file alive; | ||
| 12 | // no QObject or open SQLite handle crosses the worker boundary. | ||
| 13 | class MailPayloadPreview { | ||
| 14 | public: | ||
| 15 | MailPayloadPreview(const MailPayloadPreview &) = delete; | ||
| 16 | MailPayloadPreview &operator=(const MailPayloadPreview &) = delete; | ||
| 17 | ~MailPayloadPreview(); | ||
| 18 | 10 | const QString &path() const { return m_path; } | |
| 19 | 6 | const MailPayloadReadResult &result() const { return m_result; } | |
| 20 | private: | ||
| 21 | friend class MailPayloadExportService; | ||
| 22 | 5 | MailPayloadPreview(QString path, MailPayloadReadResult result) | |
| 23 | 5 | : m_path(std::move(path)), m_result(std::move(result)) {} | |
| 24 | QString m_path; | ||
| 25 | MailPayloadReadResult m_result; | ||
| 26 | }; | ||
| 27 | |||
| 28 | // Explicit file exports queue identity and destination metadata, never BLOBs. | ||
| 29 | // One application worker owns SQLite and the temporary output file. | ||
| 30 | class MailPayloadExportService : public QObject { | ||
| 31 | public: | ||
| 32 | using Callback = std::function<void(MailPayloadReadResult)>; | ||
| 33 | using Preview = std::shared_ptr<const MailPayloadPreview>; | ||
| 34 | using PreviewCallback = std::function<void(Preview, MailPayloadReadResult)>; | ||
| 35 | static constexpr int MaximumRequests = 8; | ||
| 36 | explicit MailPayloadExportService(QObject *parent = nullptr); | ||
| 37 | ~MailPayloadExportService() override; | ||
| 38 | static MailPayloadExportService *shared(); | ||
| 39 | void save(QObject *owner, MailPayloadRequest request, QString destination, | ||
| 40 | bool overwriteExisting, Callback callback); | ||
| 41 | void saveInDirectory(QObject *owner, MailPayloadRequest request, QString directory, | ||
| 42 | QString suggestedName, Callback callback); | ||
| 43 | void cancel(QObject *owner); | ||
| 44 | void preparePreview(QObject *owner, MailPayloadRequest request, QString cacheDirectory, | ||
| 45 | PreviewCallback callback); | ||
| 46 | void savePreview(QObject *owner, Preview preview, QString destination, Callback callback); | ||
| 47 | // T-87.G1: bounded in-memory window into a stored payload (source viewer). | ||
| 48 | // Delivered on the GUI thread; bytes never exceed MailPayloadRequest:: | ||
| 49 | // maxBytes and are capped by MaximumWindowBytes. | ||
| 50 | static constexpr qint64 MaximumWindowBytes = 8LL * 1024 * 1024; | ||
| 51 | using BytesCallback = std::function<void(QByteArray bytes, MailPayloadReadResult)>; | ||
| 52 | void readWindow(QObject *owner, MailPayloadRequest request, BytesCallback callback); | ||
| 53 | 28 | int pendingCount() const { return m_requests.size(); } | |
| 54 | |||
| 55 | private: | ||
| 56 | friend class TestMailPayloadExport; | ||
| 57 | struct Request { | ||
| 58 | QPointer<QObject> owner; | ||
| 59 | QMetaObject::Connection destroyed; | ||
| 60 | std::atomic_bool cancelled{false}; | ||
| 61 | 46 | ~Request() { QObject::disconnect(destroyed); } | |
| 62 | }; | ||
| 63 | void stop(); | ||
| 64 | struct Result { MailPayloadReadResult read; Preview preview; QByteArray bytes; }; | ||
| 65 | using Work = std::function<Result(const std::atomic_bool &)>; | ||
| 66 | void submit(QObject *owner, Work work, std::function<void(Result)> callback); | ||
| 67 | void enqueue(QObject *owner, MailPayloadRequest request, QString destination, | ||
| 68 | bool overwriteExisting, Callback callback, | ||
| 69 | std::optional<QString> suggestedName); | ||
| 70 | QThreadPool m_pool; | ||
| 71 | QList<std::shared_ptr<Request>> m_requests; | ||
| 72 | bool m_stopping = false; | ||
| 73 | }; | ||
| 74 |