presentation/QuickConflictResolver.h
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #pragma once | ||
| 2 | |||
| 3 | #include <QObject> | ||
| 4 | #include <QPointer> | ||
| 5 | #include <QSet> | ||
| 6 | #include <QVariantMap> | ||
| 7 | #include <functional> | ||
| 8 | |||
| 9 | #include "ConflictItemModel.h" | ||
| 10 | #include "ConflictValueText.h" | ||
| 11 | #include "data/MergePlan.h" | ||
| 12 | |||
| 13 | class CalendarOutbox; | ||
| 14 | class CalendarStore; | ||
| 15 | class ContactOutbox; | ||
| 16 | class ContactStore; | ||
| 17 | class QuickRulesBackend; | ||
| 18 | class SettingsSyncSession; | ||
| 19 | |||
| 20 | // The conflict resolver behind the sheet (Sprint 107 S107.4.3, §5.1): opens | ||
| 21 | // one conflict of any domain as a merge plan, keeps the user's choices, | ||
| 22 | // validates (D-10), applies through the domain's write path and reloads | ||
| 23 | // when the server changed again (D-9). Nothing is written before apply() | ||
| 24 | // (D-7); cancel() leaves stores and outboxes untouched (G5). | ||
| 25 | // | ||
| 26 | // Domains and identities (open()): | ||
| 27 | // "event", "task" {uid, accountId, calendarPath} | ||
| 28 | // "contact" {bookId, uid} or {key: "<bookId>/<uid>"} | ||
| 29 | // "rules" {} (the conflict QuickRulesBackend holds) | ||
| 30 | // "settings" {} (the conflict SettingsSyncSession shows) | ||
| 31 | // Whole-side results are written through the existing whole-side paths | ||
| 32 | // (resolveConflict "mine"/"server", useLocal/useRemote) so their bytes are | ||
| 33 | // exactly the banner's (G2). | ||
| 34 | // | ||
| 35 | // Logs (mailjd.conflict): domain, counts, durations, rule ids; never values. | ||
| 36 | class QuickConflictResolver : public QObject { | ||
| 37 | 175 | Q_OBJECT | |
| 38 | Q_PROPERTY(bool active READ active NOTIFY changed) | ||
| 39 | Q_PROPERTY(QString domain READ domain NOTIFY changed) | ||
| 40 | Q_PROPERTY(QString title READ title NOTIFY changed) | ||
| 41 | Q_PROPERTY(QString subtitle READ subtitle NOTIFY changed) | ||
| 42 | Q_PROPERTY(QString kind READ kind NOTIFY changed) | ||
| 43 | Q_PROPERTY(int openCount READ openCount NOTIFY changed) | ||
| 44 | Q_PROPERTY(int questionCount READ questionCount NOTIFY changed) | ||
| 45 | Q_PROPERTY(int combinedCount READ combinedCount NOTIFY changed) | ||
| 46 | Q_PROPERTY(bool canApply READ canApply NOTIFY changed) | ||
| 47 | Q_PROPERTY(bool canKeepBoth READ canKeepBoth NOTIFY changed) | ||
| 48 | Q_PROPERTY(bool comparable READ comparable NOTIFY changed) | ||
| 49 | Q_PROPERTY(bool showOriginal READ showOriginal WRITE setShowOriginal NOTIFY changed) | ||
| 50 | Q_PROPERTY(bool touched READ touched NOTIFY changed) | ||
| 51 | Q_PROPERTY(bool applying READ applying NOTIFY changed) | ||
| 52 | Q_PROPERTY(QString validationMessage READ validationMessage NOTIFY changed) | ||
| 53 | Q_PROPERTY(QString notice READ notice NOTIFY changed) | ||
| 54 | Q_PROPERTY(QString serverLabel READ serverLabel NOTIFY changed) | ||
| 55 | Q_PROPERTY(QVariantList deletionChanges READ deletionChanges NOTIFY changed) | ||
| 56 | Q_PROPERTY(ConflictItemModel *rows READ rows CONSTANT) | ||
| 57 | // Bumped with every new plan: the photo cells' URLs change with it. | ||
| 58 | Q_PROPERTY(int photoSerial READ photoSerial NOTIFY changed) | ||
| 59 | |||
| 60 | public: | ||
| 61 | struct Deps { | ||
| 62 | std::function<CalendarOutbox *()> calendarOutbox; | ||
| 63 | std::function<CalendarStore *()> calendarStore; | ||
| 64 | std::function<ContactOutbox *()> contactOutbox; | ||
| 65 | std::function<ContactStore *()> contactStore; | ||
| 66 | std::function<QuickRulesBackend *()> rules; | ||
| 67 | std::function<SettingsSyncSession *()> settings; | ||
| 68 | }; | ||
| 69 | explicit QuickConflictResolver(Deps deps, QObject *parent = nullptr); | ||
| 70 | |||
| 71 | 2340 | bool active() const { return m_active; } | |
| 72 | 1044 | QString domain() const { return m_domain; } | |
| 73 | 1894 | QString title() const { return m_title; } | |
| 74 | 948 | QString subtitle() const { return m_subtitle; } | |
| 75 | // "changed", "deletedOnServer", "deletedHere", "moved", "raw" | ||
| 76 | 2829 | QString kind() const { return m_kind; } | |
| 77 | 4937 | int openCount() const { return m_plan.openConflicts(); } | |
| 78 | int questionCount() const; | ||
| 79 | int combinedCount() const; | ||
| 80 | bool canApply() const; | ||
| 81 | bool canKeepBoth() const; | ||
| 82 | 1 | bool comparable() const { return m_plan.comparable; } | |
| 83 | 3025 | bool showOriginal() const { return m_showOriginal; } | |
| 84 | void setShowOriginal(bool show); | ||
| 85 | bool touched() const; | ||
| 86 | 3781 | bool applying() const { return m_applying; } | |
| 87 | 2861 | QString validationMessage() const { return m_validationMessage; } | |
| 88 | 1912 | QString notice() const { return m_notice; } | |
| 89 | 947 | QString serverLabel() const { return m_serverLabel; } | |
| 90 | 1895 | QVariantList deletionChanges() const { return m_deletionChanges; } | |
| 91 | 257 | ConflictItemModel *rows() { return &m_rows; } | |
| 92 | 486 | int photoSerial() const { return m_photoSerial; } | |
| 93 | const Merge::Plan &plan() const { return m_plan; } | ||
| 94 | |||
| 95 | Q_INVOKABLE bool open(const QString &domain, const QVariantMap &identity); | ||
| 96 | // The banner's count line (§4.2) without opening anything: | ||
| 97 | // {kind, questions, combined, keepBoth}; {} when there is no conflict. | ||
| 98 | Q_INVOKABLE QVariantMap preview(const QString &domain, const QVariantMap &identity) const; | ||
| 99 | Q_INVOKABLE bool choose(const QString &rowId, const QString &side); | ||
| 100 | Q_INVOKABLE void chooseAll(const QString &side); | ||
| 101 | Q_INVOKABLE bool setValueIncluded(const QString &rowId, const QString &valueId, bool included); | ||
| 102 | Q_INVOKABLE bool takeBoth(const QString &rowId); | ||
| 103 | Q_INVOKABLE bool chooseHunk(const QString &rowId, int hunk, const QString &side); | ||
| 104 | Q_INVOKABLE bool acceptText(const QString &rowId); | ||
| 105 | Q_INVOKABLE bool editText(const QString &rowId, const QString &text); | ||
| 106 | Q_INVOKABLE bool separate(const QString &rowId); | ||
| 107 | Q_INVOKABLE bool fixProblem(); | ||
| 108 | Q_INVOKABLE QString resultTextOf(const QString &rowId) const; | ||
| 109 | // The inline photo bytes of a photo row's side ("local", "server", | ||
| 110 | // "base"); empty for a linked or absent photo (ConflictPhotoProvider). | ||
| 111 | QByteArray photoBytes(const QString &rowId, const QString &side) const; | ||
| 112 | // Writes the result; the outcome arrives as applied()/applyFailed(). | ||
| 113 | Q_INVOKABLE void apply(); | ||
| 114 | // Whole sides of the banner and the deletion card (D-6): "mine", | ||
| 115 | // "server", "both". | ||
| 116 | Q_INVOKABLE void applyWholeSide(const QString &choice); | ||
| 117 | Q_INVOKABLE void cancel(); | ||
| 118 | // S107.3.6: ask the server again (also done once on open). | ||
| 119 | Q_INVOKABLE void checkServer(); | ||
| 120 | |||
| 121 | signals: | ||
| 122 | void changed(); | ||
| 123 | void applied(const QString &message); | ||
| 124 | void applyFailed(const QString &message); | ||
| 125 | // The open conflict went away by itself (resolved elsewhere, or combined | ||
| 126 | // with the server's newer version); the sheet closed. | ||
| 127 | void vanished(const QString &message); | ||
| 128 | void closed(); | ||
| 129 | |||
| 130 | private: | ||
| 131 | bool openEvent(const QVariantMap &identity, bool task); | ||
| 132 | bool openContact(const QVariantMap &identity); | ||
| 133 | bool openRules(); | ||
| 134 | bool openSettings(); | ||
| 135 | void setPlan(Merge::Plan plan, bool reload); | ||
| 136 | void rebuild(); | ||
| 137 | void validate(); | ||
| 138 | void finishApply(bool ok, const QString &message); | ||
| 139 | bool reloadIfChanged(); | ||
| 140 | void close(); | ||
| 141 | void connectSources(); | ||
| 142 | QList<const Merge::Item *> membersOf(const QString &rowId) const; | ||
| 143 | |||
| 144 | Deps m_deps; | ||
| 145 | ConflictItemModel m_rows; | ||
| 146 | ConflictValueText::Context m_context; | ||
| 147 | Merge::Plan m_plan; | ||
| 148 | QList<Merge::ValidationProblem> m_problems; | ||
| 149 | QVariantMap m_identity; | ||
| 150 | QString m_domain; | ||
| 151 | QString m_title; | ||
| 152 | QString m_subtitle; | ||
| 153 | QString m_kind; | ||
| 154 | QString m_serverEtag; // identity of the shown server version | ||
| 155 | QString m_validationMessage; | ||
| 156 | QString m_notice; | ||
| 157 | QString m_serverLabel; | ||
| 158 | QVariantList m_deletionChanges; | ||
| 159 | QSet<QString> m_changedAgain; // row ids whose server value changed in the last reload | ||
| 160 | bool m_active = false; | ||
| 161 | bool m_showOriginal = false; | ||
| 162 | bool m_applying = false; | ||
| 163 | qint64 m_openedAt = 0; | ||
| 164 | int m_photoSerial = 0; | ||
| 165 | }; | ||
| 166 |