Line data Source code
1 : #pragma once
2 :
3 : #include <QObject>
4 : #include <QString>
5 : #include <QTimer>
6 :
7 : #include "data/AccountConfig.h"
8 : #include "data/Models.h"
9 : #include "data/SettingsSyncModels.h"
10 : #include "service/ImapService.h"
11 :
12 : class ConnectionHealthMonitor;
13 :
14 : // T-312/T-314: Manages settings synchronisation via a dedicated IMAP connection.
15 : // Stores settings as a virtual mail (JSON body) in a configurable IMAP folder.
16 : // Uses IMAP IDLE for real-time change detection from other clients.
17 : //
18 : // Architecture: owns its own ImapService instance (separate from the main
19 : // connection) to avoid interference with regular mail operations.
20 : //
21 : // T-720: auto-reconnect (and dead-socket detection) for the dedicated IMAP
22 : // connection is delegated to ConnectionHealthMonitor (Sprint 72). The
23 : // m_reconnectTimer/m_reconnectAttempts members and handleDisconnect()/
24 : // onReconnectTimer() methods have been removed.
25 : class SettingsSyncService : public QObject {
26 2 : Q_OBJECT
27 : #ifdef MAILJD_UNIT_TEST
28 : friend class TestSettingsSyncService;
29 : friend class TestSprint79Services;
30 : #endif
31 :
32 : public:
33 : explicit SettingsSyncService(QObject *parent = nullptr);
34 : ~SettingsSyncService() override;
35 :
36 : // Configure and connect
37 : void configure(const ImapConfig &config, const QString &syncFolder);
38 : void setEnabled(bool enabled);
39 11 : bool isEnabled() const { return m_enabled; }
40 4 : QString syncFolder() const { return m_syncFolder; }
41 :
42 : // Upload current settings to IMAP
43 : void uploadSettings(const SyncPayload &payload);
44 :
45 : // Fetch latest settings from IMAP
46 : void fetchLatestSettings();
47 :
48 : // Start/stop IDLE-watching on the sync folder
49 : void startWatching();
50 : void stopWatching();
51 :
52 : // Disconnect and clean up
53 : void shutdown();
54 :
55 : signals:
56 : void settingsReceived(const SyncPayload &payload);
57 : void uploadComplete();
58 : void syncError(const QString &error);
59 : void syncFolderReady();
60 : void connected();
61 :
62 : private slots:
63 : void onStateChanged(ImapService::State newState);
64 : void onFolderListReceived(const QList<FolderInfo> &folders);
65 : void onFolderSelected(const QString &path, int messageCount,
66 : quint32 uidValidity, quint64 highestModseq);
67 : void onFolderCreated(const QString &folderPath);
68 : void onSearchResultReceived(const QList<qint64> &uids);
69 : void onRawBodyReceived(qint64 uid, const QByteArray &rawBody);
70 : void onMessageAppended(const QString &folder, qint64 uid);
71 : void onIdleNewMessages(int newCount);
72 : void onAppendError(const QString &error);
73 : void onFolderOperationError(const QString &op, const QString &error);
74 : void onExpungeComplete();
75 :
76 : private:
77 : enum class Phase {
78 : Idle,
79 : CheckingFolder, // LIST to check if sync folder exists
80 : CreatingFolder, // CREATE sync folder
81 : SelectingFolder, // SELECT sync folder
82 : Fetching, // SEARCH + FETCH latest settings
83 : Uploading, // APPEND new settings
84 : CleaningUp, // STORE \Deleted + EXPUNGE old settings
85 : WatchingIdle, // IDLE on sync folder
86 : };
87 :
88 : void checkOrCreateFolder();
89 : void doFetch();
90 : void doUpload();
91 : void cleanupOldSettings(qint64 keepUid);
92 : void startIdleWatch();
93 :
94 : ImapService *m_imap = nullptr;
95 : // T-720: Health monitor for the dedicated IMAP connection. Periodic
96 : // liveness probes + exponential-backoff reconnect + suspend/network
97 : // reactivity — same code path as the main + body + search connections.
98 : ConnectionHealthMonitor *m_health = nullptr;
99 : ImapConfig m_config;
100 : QString m_syncFolder;
101 : bool m_enabled = false;
102 : Phase m_phase = Phase::Idle;
103 :
104 : // State tracking
105 : qint64 m_currentSettingsUid = -1; // UID of the current settings mail
106 : int m_messageCount = 0; // Messages in sync folder
107 : QByteArray m_pendingUpload; // RFC-2822 message to upload
108 : qint64 m_newAppendedUid = -1; // UID of just-appended message
109 : };
|