controller/MailMoveCommands.h
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #pragma once | ||
| 2 | |||
| 3 | #include <QList> | ||
| 4 | #include <QObject> | ||
| 5 | #include <QPair> | ||
| 6 | #include <QPointer> | ||
| 7 | #include <QString> | ||
| 8 | #include <QStringList> | ||
| 9 | #include <functional> | ||
| 10 | #include <memory> | ||
| 11 | #include <optional> | ||
| 12 | |||
| 13 | #include "controller/FolderContext.h" | ||
| 14 | #include "data/MailSelectionMutation.h" | ||
| 15 | #include "data/Models.h" | ||
| 16 | |||
| 17 | class ImapService; | ||
| 18 | class MailCache; | ||
| 19 | class MailListModel; | ||
| 20 | class MailMutationPipeline; | ||
| 21 | class MailThreadModel; | ||
| 22 | class UndoManager; | ||
| 23 | |||
| 24 | // Move commands of the controller (Sprint 91 S91.4.4, plan correction: | ||
| 25 | // extracted from MailController to bring it below 3,000 lines): visual-first | ||
| 26 | // moves and worker-journaled selection moves, cross-folder moves on the | ||
| 27 | // body connection, move results of both connections, rollback and the | ||
| 28 | // Message-ID based undo. Callers reach it as `controller.moves()`. | ||
| 29 | // | ||
| 30 | // Owner: MailController (value member). GUI thread. Durable dispatch goes | ||
| 31 | // through the mutation pipeline (reference). The connections, the body | ||
| 32 | // connection's selected folder, the body-fetch SELECT suppression flag, the | ||
| 33 | // folder context, the undo manager and the selection-action generation stay | ||
| 34 | // owned by the controller and are read (and where the moved code wrote them, | ||
| 35 | // written) by reference. The controller connects the move-result signals of | ||
| 36 | // both connections to this unit at their former positions. Folder reopen, | ||
| 37 | // resync, folder-id resolution, loaded-row headers and the body connection | ||
| 38 | // are reached through Deps; controller signals keep their emitter. | ||
| 39 | class MailMoveCommands final : public QObject { | ||
| 40 | 102 | Q_OBJECT | |
| 41 | public: | ||
| 42 | struct Deps { | ||
| 43 | std::function<void()> ensureBodyConnection; | ||
| 44 | std::function<std::optional<MailHeader>(qint64 folderId, qint64 uid)> loadedHeader; | ||
| 45 | std::function<void(const QString &folderPath)> onFolderSelected; | ||
| 46 | std::function<qint64(const QString &folderPath)> resolveFolderId; | ||
| 47 | std::function<void()> resyncCurrentFolder; | ||
| 48 | // MailController signals (the emitter stays the controller). | ||
| 49 | std::function<void(const QString &)> statusMessage; | ||
| 50 | std::function<void(const QString &)> actionError; | ||
| 51 | std::function<void(qint64 folderId, const QList<qint64> &uids)> movesVisuallyPending; | ||
| 52 | std::function<void(qint64 folderId, const QList<qint64> &uids)> movesVisuallySettled; | ||
| 53 | std::function<void(const QString &folder, int count)> unreadCountChanged; | ||
| 54 | }; | ||
| 55 | MailMoveCommands(ImapService *imap, ImapService *const &bodyImap, | ||
| 56 | QString &bodyImapSelectedFolder, bool &bodyFetchSelect, MailCache *cache, | ||
| 57 | MailListModel *model, MailThreadModel *const &threadModel, | ||
| 58 | FolderContext &folder, MailMutationPipeline &mutations, | ||
| 59 | const QPointer<UndoManager> &undoManager, | ||
| 60 | const quint64 &selectionActionGeneration, Deps deps, | ||
| 61 | QObject *parent = nullptr); | ||
| 62 | |||
| 63 | // Adopt worker-journaled intent without rebuilding a loaded-header UID list. | ||
| 64 | bool adoptSelectionMove(const MailSelectionMoveResult &action); | ||
| 65 | void moveSelection(std::shared_ptr<const MailSelectionSnapshot> selection, | ||
| 66 | const QString &targetFolder, | ||
| 67 | std::function<void(MailSelectionMoveResult)> callback = {}); | ||
| 68 | void moveMailToFolder(qint64 uid, const QString &targetFolder); | ||
| 69 | void moveMailsToFolder(const QList<qint64> &uids, const QString &targetFolder); | ||
| 70 | void moveMailsToFolderFrom(const QList<qint64> &uids, qint64 srcFolderId, | ||
| 71 | const QString &srcFolder, | ||
| 72 | const QString &targetFolder); | ||
| 73 | // T-407: Cross-folder move via m_bodyImap | ||
| 74 | void crossFolderMove(const QString &srcFolder, const QList<qint64> &uids, | ||
| 75 | const QString &targetFolder); | ||
| 76 | // T-211: Undo a mail move (async IMAP flow) | ||
| 77 | void undoMove(const QList<MailHeader> &headers, | ||
| 78 | const QString &sourceFolder, const QString &fromFolder); | ||
| 79 | // Visual-first move of loaded rows and its rollback after a failed | ||
| 80 | // journal write (also driven directly by tests). | ||
| 81 | void startMove(qint64 folderId, const QString &folderPath, | ||
| 82 | const QList<qint64> &uids, const QString &targetFolder); | ||
| 83 | void rollbackVisualMove(qint64 folderId, const QList<MailHeader> &headers, | ||
| 84 | const QString &error); | ||
| 85 | |||
| 86 | public slots: | ||
| 87 | // T-100: Move result handlers | ||
| 88 | void onMessageMoved(qint64 uid, const QString &targetFolder); | ||
| 89 | void onMessagesMoved(const QList<qint64> &uids, const QString &targetFolder); | ||
| 90 | void onMoveError(const QString &error); | ||
| 91 | |||
| 92 | // T-79.E2/M6: move results on the body connection (cross-folder moves) | ||
| 93 | void onBodyImapMessagesMoved(const QList<qint64> &uids, | ||
| 94 | const QString &targetFolder); | ||
| 95 | void onBodyImapMoveError(const QString &error); | ||
| 96 | |||
| 97 | // Watchdog of the undo-move SELECT/SEARCH chain (30 s; tests shorten it). | ||
| 98 | 1 | void setUndoChainTimeoutMs(int ms) { m_undoChainTimeoutMs = ms; } | |
| 99 | |||
| 100 | private: | ||
| 101 | // S106-187: state of one undo-move Message-ID search chain. | ||
| 102 | struct UndoSearchChain { | ||
| 103 | struct Outcome { | ||
| 104 | QString messageId; | ||
| 105 | QList<qint64> uids; | ||
| 106 | }; | ||
| 107 | QStringList messageIds; | ||
| 108 | QList<Outcome> outcomes; | ||
| 109 | QString fencedFolder; | ||
| 110 | quint32 fencedUidValidity = 0; | ||
| 111 | quint64 awaitedSequence = 0; | ||
| 112 | QMetaObject::Connection selectConnection; | ||
| 113 | QMetaObject::Connection searchConnection; | ||
| 114 | bool done = false; | ||
| 115 | }; | ||
| 116 | |||
| 117 | // undoMove step (S91.4.3). | ||
| 118 | void journalAndDispatchUndo(const QList<qint64> &foundUids, const QStringList &messageIds, | ||
| 119 | const QString &sourceFolder, const QString &fromFolder, | ||
| 120 | const QString &capturedFolder); | ||
| 121 | |||
| 122 | ImapService *m_imap; | ||
| 123 | ImapService *const &m_bodyImap; // MailController (lazy body connection) | ||
| 124 | QString &m_bodyImapSelectedFolder; // connection state, MailController | ||
| 125 | bool &m_bodyFetchSelect; // SELECT suppression, MailController | ||
| 126 | MailCache *m_cache; | ||
| 127 | MailListModel *m_model; | ||
| 128 | MailThreadModel *const &m_threadModel; // MailController::setThreadModel | ||
| 129 | FolderContext &m_folder; // MailController::m_folder | ||
| 130 | MailMutationPipeline &m_mutations; // MailController::m_mutations | ||
| 131 | const QPointer<UndoManager> &m_undoManager; | ||
| 132 | const quint64 &m_selectionActionGeneration; | ||
| 133 | Deps m_deps; | ||
| 134 | int m_undoChainTimeoutMs = 30000; | ||
| 135 | // T-79.E2/M6: cross-folder moves in flight on the body connection: | ||
| 136 | // (source folderId, uids). Success removes the source-folder cache rows; | ||
| 137 | // failure restores the optimistically removed model rows. | ||
| 138 | QList<QPair<qint64, QList<qint64>>> m_pendingCrossFolderMoves; | ||
| 139 | }; | ||
| 140 |