presentation/QuickComposeBackendMeeting.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | // Sprint 108 S108.8.1/8.2 (D-22): *Suggest times* — free slots over the | ||
| 2 | // user's shown calendars and the block that offers them in the message; | ||
| 3 | // S108.8.6 (D-25): the scheduling grid's busy times and the next time | ||
| 4 | // everyone is free; the event of an invitation that left (D-23). | ||
| 5 | #include "QuickComposeBackend.h" | ||
| 6 | #include "ComposeLog.h" | ||
| 7 | #include "ComposeSession.h" | ||
| 8 | |||
| 9 | #include "app/core/MailSessionCore.h" | ||
| 10 | #include "data/CalendarModels.h" | ||
| 11 | #include "data/CalendarStore.h" | ||
| 12 | #include "data/FreeSlots.h" | ||
| 13 | #include "data/ICalEventCodec.h" | ||
| 14 | #include "data/ICalTime.h" | ||
| 15 | #include "data/ICalendar.h" | ||
| 16 | #include "data/MessageLanguageText.h" | ||
| 17 | #include "data/RecurrenceExpansion.h" | ||
| 18 | #include "data/ImipInvitation.h" | ||
| 19 | #include "data/MailOutboxStore.h" | ||
| 20 | #include "app/core/CalendarSyncCoordinator.h" | ||
| 21 | #include "service/MimeParser.h" | ||
| 22 | #include "RecipientChipModel.h" | ||
| 23 | |||
| 24 | #include <QPointer> | ||
| 25 | |||
| 26 | #include <QJsonDocument> | ||
| 27 | #include <QJsonObject> | ||
| 28 | |||
| 29 | #include <QElapsedTimer> | ||
| 30 | #include <QLocale> | ||
| 31 | |||
| 32 | namespace { | ||
| 33 | |||
| 34 | // The stored VEVENT text — written from the event's fields when the event | ||
| 35 | // has none yet (created in MailJD and not synced), as the event editor does. | ||
| 36 | 1 | QString storedComponentText(const CalendarEvent &event) { | |
| 37 |
1/2✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 1 time.
|
1 | if (!event.icalRaw.isEmpty()) return event.icalRaw; |
| 38 | const ICal::Component calendar = | ||
| 39 |
3/11✓ Branch 6 → 7 taken 1 time.
✗ Branch 6 → 8 not taken.
✗ Branch 11 → 12 not taken.
✗ Branch 12 → 13 not taken.
✓ Branch 12 → 14 taken 1 time.
✗ Branch 14 → 15 not taken.
✓ Branch 14 → 16 taken 1 time.
✗ Branch 25 → 26 not taken.
✗ Branch 25 → 27 not taken.
✗ Branch 29 → 30 not taken.
✗ Branch 29 → 31 not taken.
|
1 | event.resourceIcal.isEmpty() ? ICal::Component{} : ICal::parseFirst(event.resourceIcal, QStringLiteral("VCALENDAR")); |
| 40 |
3/3✓ Branch 16 → 17 taken 1 time.
✓ Branch 17 → 18 taken 1 time.
✓ Branch 18 → 19 taken 1 time.
|
1 | return ICalEventCodec::componentText(ICalEventCodec::eventComponent(event, ICalTime::ZoneResolver(calendar))); |
| 41 | 1 | } | |
| 42 | |||
| 43 | 23 | QVariantList intervalList(const QList<FreeSlots::Interval> &intervals) { | |
| 44 | 23 | QVariantList out; | |
| 45 |
2/2✓ Branch 27 → 4 taken 11 times.
✓ Branch 27 → 28 taken 23 times.
|
34 | for (const FreeSlots::Interval &interval : intervals) |
| 46 |
3/5✓ Branch 15 → 16 taken 11 times.
✓ Branch 19 → 20 taken 22 times.
✓ Branch 19 → 21 taken 11 times.
✗ Branch 35 → 36 not taken.
✗ Branch 35 → 37 not taken.
|
55 | out.append(QVariantMap{{QStringLiteral("start"), interval.start}, {QStringLiteral("end"), interval.end}}); |
| 47 | 23 | return out; | |
| 48 | 33 | } | |
| 49 | |||
| 50 | 43 | QList<FreeSlots::Interval> intervalsFrom(const QVariantList &list) { | |
| 51 | 43 | QList<FreeSlots::Interval> out; | |
| 52 |
2/2✓ Branch 46 → 4 taken 44 times.
✓ Branch 46 → 47 taken 43 times.
|
87 | for (const QVariant &value : list) { |
| 53 |
1/1✓ Branch 5 → 6 taken 44 times.
|
44 | const QVariantMap map = value.toMap(); |
| 54 |
2/2✓ Branch 10 → 11 taken 44 times.
✓ Branch 11 → 12 taken 44 times.
|
88 | const QDateTime start = map.value(QStringLiteral("start")).toDateTime(); |
| 55 |
2/2✓ Branch 20 → 21 taken 44 times.
✓ Branch 21 → 22 taken 44 times.
|
88 | const QDateTime end = map.value(QStringLiteral("end")).toDateTime(); |
| 56 |
7/11✓ Branch 26 → 27 taken 44 times.
✓ Branch 27 → 28 taken 44 times.
✗ Branch 27 → 34 not taken.
✓ Branch 28 → 29 taken 44 times.
✓ Branch 29 → 30 taken 44 times.
✗ Branch 29 → 34 not taken.
✓ Branch 30 → 31 taken 44 times.
✓ Branch 32 → 33 taken 44 times.
✗ Branch 32 → 34 not taken.
✓ Branch 35 → 36 taken 44 times.
✗ Branch 35 → 41 not taken.
|
88 | if (start.isValid() && end.isValid() && end > start) out.append({start, end}); |
| 57 | 44 | } | |
| 58 | 43 | return out; | |
| 59 | 44 | } | |
| 60 | |||
| 61 | } // namespace | ||
| 62 | |||
| 63 | // The user's timed occurrences in the shown calendars (D-25: transparent, | ||
| 64 | // all-day and hidden-calendar entries do not block). | ||
| 65 | 23 | QList<FreeSlots::Interval> QuickComposeBackend::ownBusyIntervals(const QDate &from, int days, | |
| 66 | const QStringList &hiddenCalendars) const { | ||
| 67 | 23 | QList<FreeSlots::Interval> busy; | |
| 68 | 23 | CalendarStore *store = m_session.calendarStore(); | |
| 69 |
1/2✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 23 times.
|
23 | if (!store) return busy; |
| 70 |
1/1✓ Branch 5 → 6 taken 23 times.
|
23 | const QDate end = from.addDays(days); |
| 71 |
3/3✓ Branch 6 → 7 taken 23 times.
✓ Branch 7 → 8 taken 23 times.
✓ Branch 8 → 9 taken 23 times.
|
23 | const QDateTime rangeStart(from.addDays(-1), QTime(0, 0)); |
| 72 |
3/3✓ Branch 9 → 10 taken 23 times.
✓ Branch 10 → 11 taken 23 times.
✓ Branch 11 → 12 taken 23 times.
|
23 | const QDateTime rangeEnd(end.addDays(1), QTime(23, 59, 59)); |
| 73 |
1/1✓ Branch 12 → 13 taken 23 times.
|
23 | const auto events = store->eventsForDateRange(rangeStart, rangeEnd); |
| 74 |
1/1✓ Branch 13 → 14 taken 23 times.
|
23 | const QSet<qint64> transparent = store->transparentEventIds(rangeStart, rangeEnd); |
| 75 | 23 | QSet<QString> hidden; | |
| 76 |
3/3✓ Branch 18 → 19 taken 1 time.
✓ Branch 21 → 17 taken 1 time.
✓ Branch 21 → 22 taken 23 times.
|
24 | for (const QString &key : hiddenCalendars) hidden.insert(key); |
| 77 | 23 | for (const CalendarEvent &event : | |
| 78 |
5/5✓ Branch 23 → 24 taken 23 times.
✓ Branch 25 → 26 taken 23 times.
✓ Branch 26 → 27 taken 23 times.
✓ Branch 54 → 28 taken 34 times.
✓ Branch 54 → 55 taken 23 times.
|
80 | expandEventsForDisplay(events, from, end, false, {kDisplayOccurrencesPerSeries, {}})) { |
| 79 |
4/7✓ Branch 29 → 30 taken 34 times.
✗ Branch 29 → 32 not taken.
✓ Branch 30 → 31 taken 34 times.
✗ Branch 31 → 32 not taken.
✓ Branch 31 → 33 taken 34 times.
✗ Branch 34 → 35 not taken.
✓ Branch 34 → 36 taken 34 times.
|
34 | if (event.allDay || !event.dtStart.isValid()) continue; |
| 80 |
3/3✓ Branch 36 → 37 taken 34 times.
✓ Branch 39 → 40 taken 1 time.
✓ Branch 39 → 41 taken 33 times.
|
34 | if (hidden.contains(calendarSelectionKey(event.accountId, event.calendarPath))) continue; |
| 81 |
2/2✓ Branch 42 → 43 taken 1 time.
✓ Branch 42 → 44 taken 32 times.
|
33 | if (transparent.contains(event.id)) continue; |
| 82 |
1/2✓ Branch 46 → 47 taken 32 times.
✗ Branch 46 → 48 not taken.
|
64 | busy.append({event.dtStart, event.dtEnd.isValid() ? event.dtEnd : event.dtStart.addSecs(3600)}); |
| 83 | 23 | } | |
| 84 | 23 | return busy; | |
| 85 | 110 | } | |
| 86 | |||
| 87 | 8 | QVariantList QuickComposeBackend::ownBusy(const QStringList &hiddenCalendars, int days) const { | |
| 88 |
4/4✓ Branch 2 → 3 taken 8 times.
✓ Branch 3 → 4 taken 8 times.
✓ Branch 4 → 5 taken 8 times.
✓ Branch 5 → 6 taken 8 times.
|
8 | return intervalList(ownBusyIntervals(QDate::currentDate(), qBound(1, days, 60), hiddenCalendars)); |
| 89 | } | ||
| 90 | |||
| 91 | 7 | void QuickComposeBackend::requestAttendeeBusy(QObject *sessionObject, int days) { | |
| 92 |
1/1✓ Branch 2 → 3 taken 7 times.
|
7 | auto *session = qobject_cast<ComposeSession *>(sessionObject); |
| 93 | 7 | auto *sync = m_session.calendarSync(); | |
| 94 |
2/4✓ Branch 4 → 5 taken 7 times.
✗ Branch 4 → 6 not taken.
✗ Branch 5 → 6 not taken.
✓ Branch 5 → 7 taken 7 times.
|
7 | if (!session || !sync) return; |
| 95 | 7 | const QString sessionId = session->sessionId(); | |
| 96 |
2/2✓ Branch 13 → 14 taken 7 times.
✓ Branch 14 → 15 taken 7 times.
|
14 | const QString account = session->meeting().value(QStringLiteral("calendarAccount")).toString(); |
| 97 |
3/3✓ Branch 20 → 21 taken 7 times.
✓ Branch 25 → 26 taken 7 times.
✓ Branch 26 → 27 taken 7 times.
|
14 | const QString organizer = session->identityMap().value(QStringLiteral("email")).toString(); |
| 98 | 7 | QStringList attendees; | |
| 99 | 7 | QHash<QString, QString> names; | |
| 100 |
2/2✓ Branch 89 → 36 taken 14 times.
✓ Branch 89 → 90 taken 7 times.
|
21 | for (RecipientChipModel *field : {&session->to(), &session->cc()}) |
| 101 |
2/2✓ Branch 87 → 37 taken 15 times.
✓ Branch 87 → 88 taken 14 times.
|
29 | for (int row = 0; row < field->count(); ++row) { |
| 102 |
1/1✓ Branch 37 → 38 taken 15 times.
|
15 | const QVariantMap chip = field->chipAt(row); |
| 103 |
4/4✓ Branch 42 → 43 taken 15 times.
✓ Branch 43 → 44 taken 15 times.
✓ Branch 44 → 45 taken 15 times.
✓ Branch 45 → 46 taken 15 times.
|
30 | const QString email = chip.value(QStringLiteral("address")).toString().trimmed().toLower(); |
| 104 |
4/7✓ Branch 54 → 55 taken 15 times.
✓ Branch 55 → 56 taken 15 times.
✗ Branch 55 → 58 not taken.
✗ Branch 57 → 58 not taken.
✓ Branch 57 → 59 taken 15 times.
✗ Branch 60 → 61 not taken.
✓ Branch 60 → 62 taken 15 times.
|
15 | if (!email.contains(QLatin1Char('@')) || attendees.contains(email)) continue; |
| 105 |
1/1✓ Branch 62 → 63 taken 15 times.
|
15 | attendees.append(email); |
| 106 |
3/3✓ Branch 67 → 68 taken 15 times.
✓ Branch 68 → 69 taken 15 times.
✓ Branch 69 → 70 taken 15 times.
|
30 | names.insert(email, chip.value(QStringLiteral("name")).toString()); |
| 107 | 15 | } | |
| 108 |
3/3✓ Branch 90 → 91 taken 7 times.
✓ Branch 91 → 92 taken 7 times.
✓ Branch 92 → 93 taken 7 times.
|
7 | const QDateTime from(QDate::currentDate(), QTime(0, 0)); |
| 109 |
2/2✓ Branch 93 → 94 taken 7 times.
✓ Branch 94 → 95 taken 7 times.
|
7 | const QDateTime to = from.addDays(qBound(1, days, 60)); |
| 110 |
1/1✓ Branch 95 → 96 taken 7 times.
|
7 | QPointer<QuickComposeBackend> self(this); |
| 111 |
3/3✓ Branch 101 → 102 taken 7 times.
✓ Branch 102 → 103 taken 7 times.
✓ Branch 103 → 104 taken 7 times.
|
7 | sync->requestFreeBusy(account, organizer, attendees, from.toUTC(), to.toUTC(), |
| 112 |
1/7✓ Branch 100 → 101 taken 7 times.
✗ Branch 203 → 204 not taken.
✗ Branch 203 → 205 not taken.
✗ Branch 206 → 207 not taken.
✗ Branch 206 → 208 not taken.
✗ Branch 209 → 210 not taken.
✗ Branch 209 → 211 not taken.
|
14 | [self, sessionId, names, attendees](const QList<CalDavXml::FreeBusy> &answers) { |
| 113 |
1/2✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 7 times.
|
7 | if (!self) return; |
| 114 | 7 | QVariantList rows; | |
| 115 |
2/2✓ Branch 100 → 7 taken 15 times.
✓ Branch 100 → 101 taken 7 times.
|
22 | for (const QString &email : attendees) { |
| 116 | 30 | QVariantMap row{{QStringLiteral("email"), email}, | |
| 117 | 30 | {QStringLiteral("name"), names.value(email)}, | |
| 118 | 30 | {QStringLiteral("known"), false}, | |
| 119 | 30 | {QStringLiteral("noAnswer"), false}, | |
| 120 |
2/4✓ Branch 32 → 33 taken 75 times.
✓ Branch 32 → 34 taken 15 times.
✗ Branch 108 → 109 not taken.
✗ Branch 108 → 110 not taken.
|
105 | {QStringLiteral("busy"), QVariantList{}}}; |
| 121 |
2/2✓ Branch 93 → 48 taken 33 times.
✓ Branch 93 → 94 taken 15 times.
|
48 | for (const CalDavXml::FreeBusy &answer : answers) { |
| 122 |
2/2✓ Branch 50 → 51 taken 18 times.
✓ Branch 50 → 52 taken 15 times.
|
33 | if (answer.recipient != email) continue; |
| 123 |
1/1✓ Branch 56 → 57 taken 15 times.
|
30 | row.insert(QStringLiteral("noAnswer"), answer.noAnswer); |
| 124 | 15 | QList<FreeSlots::Interval> busy; | |
| 125 |
1/2✗ Branch 69 → 62 not taken.
✓ Branch 69 → 70 taken 15 times.
|
15 | for (const auto &interval : answer.busy) |
| 126 | ✗ | busy.append({interval.first.toLocalTime(), interval.second.toLocalTime()}); | |
| 127 |
1/1✓ Branch 74 → 75 taken 15 times.
|
30 | row.insert(QStringLiteral("known"), answer.known); |
| 128 |
2/2✓ Branch 78 → 79 taken 15 times.
✓ Branch 83 → 84 taken 15 times.
|
30 | row.insert(QStringLiteral("busy"), intervalList(busy)); |
| 129 | 15 | } | |
| 130 |
1/1✓ Branch 95 → 96 taken 15 times.
|
15 | rows.append(row); |
| 131 | 15 | } | |
| 132 |
1/1✓ Branch 102 → 103 taken 7 times.
|
7 | emit self->attendeeBusyReady(sessionId, rows); |
| 133 |
1/10✓ Branch 30 → 31 taken 15 times.
✗ Branch 63 → 64 not taken.
✗ Branch 64 → 65 not taken.
✗ Branch 65 → 66 not taken.
✗ Branch 149 → 150 not taken.
✗ Branch 149 → 153 not taken.
✗ Branch 151 → 152 not taken.
✗ Branch 151 → 153 not taken.
✗ Branch 166 → 167 not taken.
✗ Branch 166 → 168 not taken.
|
127 | }); |
| 134 | 7 | } | |
| 135 | |||
| 136 | 42 | QVariantList QuickComposeBackend::daySpans(const QVariantList &busy, const QDateTime &day, int dayStartHour, | |
| 137 | int dayEndHour) const { | ||
| 138 | 42 | QVariantList out; | |
| 139 |
6/11✓ Branch 2 → 3 taken 42 times.
✓ Branch 3 → 4 taken 42 times.
✗ Branch 3 → 7 not taken.
✓ Branch 4 → 5 taken 42 times.
✗ Branch 4 → 7 not taken.
✓ Branch 5 → 6 taken 42 times.
✗ Branch 5 → 7 not taken.
✗ Branch 6 → 7 not taken.
✓ Branch 6 → 8 taken 42 times.
✗ Branch 9 → 10 not taken.
✓ Branch 9 → 11 taken 42 times.
|
42 | if (!day.isValid() || dayStartHour < 0 || dayEndHour > 24 || dayEndHour <= dayStartHour) return out; |
| 140 |
2/4✗ Branch 11 → 12 not taken.
✓ Branch 11 → 13 taken 42 times.
✗ Branch 12 → 14 not taken.
✓ Branch 13 → 14 taken 42 times.
|
42 | const QTime end = dayEndHour == 24 ? QTime(23, 59, 59, 999) : QTime(dayEndHour, 0); |
| 141 | 42 | for (const FreeSlots::Span &span : | |
| 142 |
9/9✓ Branch 15 → 16 taken 42 times.
✓ Branch 16 → 17 taken 42 times.
✓ Branch 17 → 18 taken 42 times.
✓ Branch 18 → 19 taken 42 times.
✓ Branch 19 → 20 taken 42 times.
✓ Branch 23 → 24 taken 42 times.
✓ Branch 24 → 25 taken 42 times.
✓ Branch 49 → 26 taken 20 times.
✓ Branch 49 → 50 taken 42 times.
|
104 | FreeSlots::daySpans(intervalsFrom(busy), day.toLocalTime().date(), QTime(dayStartHour, 0), end)) |
| 143 |
3/5✓ Branch 37 → 38 taken 20 times.
✓ Branch 41 → 42 taken 40 times.
✓ Branch 41 → 43 taken 20 times.
✗ Branch 70 → 71 not taken.
✗ Branch 70 → 72 not taken.
|
142 | out.append(QVariantMap{{QStringLiteral("from"), span.from}, {QStringLiteral("to"), span.to}}); |
| 144 | 42 | return out; | |
| 145 | 60 | } | |
| 146 | |||
| 147 | 21 | QString QuickComposeBackend::busyDescription(const QVariantList &busy, const QDateTime &day, int dayStartHour, | |
| 148 | int dayEndHour) const { | ||
| 149 |
1/1✓ Branch 2 → 3 taken 21 times.
|
21 | const QVariantList spans = daySpans(busy, day, dayStartHour, dayEndHour); |
| 150 |
3/3✓ Branch 4 → 5 taken 14 times.
✓ Branch 4 → 7 taken 7 times.
✓ Branch 5 → 6 taken 14 times.
|
21 | if (spans.isEmpty()) return tr("free all day"); |
| 151 |
1/1✓ Branch 8 → 9 taken 7 times.
|
7 | const QVariantMap only = spans.first().toMap(); |
| 152 |
13/22✓ Branch 10 → 11 taken 4 times.
✓ Branch 10 → 26 taken 3 times.
✓ Branch 15 → 16 taken 4 times.
✓ Branch 16 → 17 taken 4 times.
✓ Branch 17 → 18 taken 2 times.
✓ Branch 17 → 26 taken 2 times.
✓ Branch 37 → 38 taken 4 times.
✓ Branch 37 → 39 taken 3 times.
✓ Branch 39 → 40 taken 4 times.
✓ Branch 39 → 41 taken 3 times.
✓ Branch 41 → 42 taken 4 times.
✓ Branch 41 → 43 taken 3 times.
✗ Branch 43 → 44 not taken.
✓ Branch 43 → 46 taken 7 times.
✗ Branch 126 → 127 not taken.
✗ Branch 126 → 128 not taken.
✗ Branch 130 → 131 not taken.
✗ Branch 130 → 132 not taken.
✗ Branch 134 → 135 not taken.
✗ Branch 134 → 136 not taken.
✗ Branch 138 → 139 not taken.
✗ Branch 138 → 140 not taken.
|
20 | if (spans.size() == 1 && only.value(QStringLiteral("from")).toDouble() <= 0.0 && |
| 153 |
13/22✓ Branch 22 → 23 taken 2 times.
✓ Branch 23 → 24 taken 2 times.
✗ Branch 24 → 25 not taken.
✓ Branch 24 → 26 taken 2 times.
✓ Branch 27 → 28 taken 2 times.
✓ Branch 27 → 29 taken 5 times.
✓ Branch 29 → 30 taken 2 times.
✓ Branch 29 → 31 taken 5 times.
✓ Branch 31 → 32 taken 2 times.
✓ Branch 31 → 33 taken 5 times.
✓ Branch 33 → 34 taken 2 times.
✓ Branch 33 → 35 taken 5 times.
✓ Branch 35 → 36 taken 4 times.
✓ Branch 35 → 37 taken 3 times.
✗ Branch 110 → 111 not taken.
✗ Branch 110 → 112 not taken.
✗ Branch 114 → 115 not taken.
✗ Branch 114 → 116 not taken.
✗ Branch 118 → 119 not taken.
✗ Branch 118 → 120 not taken.
✗ Branch 122 → 123 not taken.
✗ Branch 122 → 124 not taken.
|
11 | only.value(QStringLiteral("to")).toDouble() >= 1.0) |
| 154 | ✗ | return tr("busy all day"); | |
| 155 |
4/4✓ Branch 46 → 47 taken 7 times.
✓ Branch 47 → 48 taken 7 times.
✓ Branch 48 → 49 taken 7 times.
✓ Branch 49 → 50 taken 7 times.
|
7 | const QDateTime start(day.toLocalTime().date(), QTime(dayStartHour, 0)); |
| 156 | 7 | const qint64 length = qint64(dayEndHour - dayStartHour) * 3600 * 1000; | |
| 157 |
1/1✓ Branch 51 → 52 taken 7 times.
|
7 | const QLocale locale; |
| 158 | // Fractions back to wall times, on the minute (13:00, not 12:59). | ||
| 159 |
2/2✓ Branch 3 → 4 taken 20 times.
✓ Branch 4 → 5 taken 20 times.
|
20 | const auto at = [&](double fraction) { return start.addSecs(qRound64(fraction * length / 60000.0) * 60).time(); }; |
| 160 | 7 | QStringList stretches; | |
| 161 |
2/2✓ Branch 90 → 54 taken 10 times.
✓ Branch 90 → 91 taken 7 times.
|
17 | for (const QVariant &value : spans) { |
| 162 |
1/1✓ Branch 55 → 56 taken 10 times.
|
10 | const QVariantMap span = value.toMap(); |
| 163 |
3/3✓ Branch 60 → 61 taken 10 times.
✓ Branch 61 → 62 taken 10 times.
✓ Branch 62 → 63 taken 10 times.
|
20 | const QTime from = at(span.value(QStringLiteral("from")).toDouble()); |
| 164 |
3/3✓ Branch 71 → 72 taken 10 times.
✓ Branch 72 → 73 taken 10 times.
✓ Branch 73 → 74 taken 10 times.
|
20 | const QTime to = at(span.value(QStringLiteral("to")).toDouble()); |
| 165 |
5/5✓ Branch 78 → 79 taken 10 times.
✓ Branch 79 → 80 taken 10 times.
✓ Branch 80 → 81 taken 10 times.
✓ Branch 81 → 82 taken 10 times.
✓ Branch 82 → 83 taken 10 times.
|
10 | stretches.append(tr("%1 to %2").arg(locale.toString(from, QLocale::ShortFormat), locale.toString(to, QLocale::ShortFormat))); |
| 166 | 10 | } | |
| 167 |
3/3✓ Branch 91 → 92 taken 7 times.
✓ Branch 96 → 97 taken 7 times.
✓ Branch 97 → 98 taken 7 times.
|
14 | return tr("busy %1, free otherwise").arg(stretches.join(QStringLiteral(", "))); |
| 168 | 21 | } | |
| 169 | |||
| 170 | 1 | QVariantMap QuickComposeBackend::nextFreeForAll(const QVariantList &busy, const QDateTime &after, int minutes, | |
| 171 | int dayStartHour, int dayEndHour, const QVariantList &workdays) const { | ||
| 172 |
1/1✓ Branch 2 → 3 taken 1 time.
|
1 | FreeSlots::Options options; |
| 173 |
2/4✓ Branch 3 → 4 taken 1 time.
✓ Branch 4 → 5 taken 1 time.
✗ Branch 4 → 6 not taken.
✗ Branch 6 → 7 not taken.
|
1 | const QDateTime start = after.isValid() ? after : QDateTime::currentDateTime(); |
| 174 |
1/1✓ Branch 7 → 8 taken 1 time.
|
1 | options.from = start.date(); |
| 175 | 1 | options.days = 14; | |
| 176 |
2/2✓ Branch 8 → 9 taken 1 time.
✓ Branch 9 → 10 taken 1 time.
|
1 | options.dayStart = QTime(qBound(0, dayStartHour, 23), 0); |
| 177 |
3/5✗ Branch 10 → 11 not taken.
✓ Branch 10 → 12 taken 1 time.
✗ Branch 11 → 14 not taken.
✓ Branch 12 → 13 taken 1 time.
✓ Branch 13 → 14 taken 1 time.
|
1 | options.dayEnd = dayEndHour >= 24 ? QTime(23, 59) : QTime(qBound(1, dayEndHour, 23), 0); |
| 178 |
1/1✓ Branch 14 → 15 taken 1 time.
|
1 | options.durationMinutes = qBound(5, minutes, 480); |
| 179 |
2/2✓ Branch 15 → 16 taken 1 time.
✓ Branch 16 → 17 taken 1 time.
|
1 | options.notBefore = qMax(start, QDateTime::currentDateTime()); |
| 180 | 1 | options.maximum = 1; | |
| 181 | 1 | options.workingDays.clear(); | |
| 182 |
4/4✓ Branch 23 → 24 taken 7 times.
✓ Branch 24 → 25 taken 7 times.
✓ Branch 27 → 22 taken 7 times.
✓ Branch 27 → 28 taken 1 time.
|
8 | for (const QVariant &day : workdays) options.workingDays.insert(day.toInt()); |
| 183 |
1/3✗ Branch 29 → 30 not taken.
✓ Branch 29 → 34 taken 1 time.
✗ Branch 30 → 31 not taken.
|
1 | if (options.workingDays.isEmpty()) options.workingDays = {1, 2, 3, 4, 5}; |
| 184 |
2/2✓ Branch 34 → 35 taken 1 time.
✓ Branch 35 → 36 taken 1 time.
|
1 | const auto found = FreeSlots::find(intervalsFrom(busy), options); |
| 185 |
1/2✗ Branch 38 → 39 not taken.
✓ Branch 38 → 40 taken 1 time.
|
1 | if (found.isEmpty()) return {}; |
| 186 |
2/4✓ Branch 52 → 53 taken 2 times.
✓ Branch 52 → 54 taken 1 time.
✗ Branch 83 → 84 not taken.
✗ Branch 83 → 85 not taken.
|
5 | return {{QStringLiteral("start"), found.first().start}, {QStringLiteral("end"), found.first().end}}; |
| 187 | 4 | } | |
| 188 | |||
| 189 | 15 | QVariantList QuickComposeBackend::freeSlots(int durationMinutes, int dayStartHour, int dayEndHour, | |
| 190 | const QVariantList &workdays, const QStringList &hiddenCalendars) const { | ||
| 191 | 15 | QElapsedTimer timer; | |
| 192 | 15 | timer.start(); | |
| 193 |
1/1✓ Branch 3 → 4 taken 15 times.
|
15 | FreeSlots::Options options; |
| 194 |
1/1✓ Branch 4 → 5 taken 15 times.
|
15 | options.from = QDate::currentDate(); |
| 195 | 15 | options.days = 14; | |
| 196 |
2/2✓ Branch 5 → 6 taken 15 times.
✓ Branch 6 → 7 taken 15 times.
|
15 | options.dayStart = QTime(qBound(0, dayStartHour, 23), 0); |
| 197 |
5/5✓ Branch 7 → 8 taken 2 times.
✓ Branch 7 → 9 taken 13 times.
✓ Branch 8 → 11 taken 2 times.
✓ Branch 9 → 10 taken 13 times.
✓ Branch 10 → 11 taken 13 times.
|
15 | options.dayEnd = dayEndHour >= 24 ? QTime(23, 59) : QTime(qBound(1, dayEndHour, 23), 0); |
| 198 | 15 | options.durationMinutes = durationMinutes; | |
| 199 |
1/1✓ Branch 11 → 12 taken 15 times.
|
15 | options.notBefore = QDateTime::currentDateTime(); |
| 200 | 15 | options.workingDays.clear(); | |
| 201 |
4/4✓ Branch 18 → 19 taken 101 times.
✓ Branch 19 → 20 taken 101 times.
✓ Branch 22 → 17 taken 101 times.
✓ Branch 22 → 23 taken 15 times.
|
116 | for (const QVariant &day : workdays) options.workingDays.insert(day.toInt()); |
| 202 |
1/3✗ Branch 24 → 25 not taken.
✓ Branch 24 → 29 taken 15 times.
✗ Branch 25 → 26 not taken.
|
15 | if (options.workingDays.isEmpty()) options.workingDays = {1, 2, 3, 4, 5}; |
| 203 |
1/1✓ Branch 29 → 30 taken 15 times.
|
15 | const QList<FreeSlots::Interval> busy = ownBusyIntervals(options.from, options.days, hiddenCalendars); |
| 204 | 15 | QVariantList out; | |
| 205 |
1/1✓ Branch 30 → 31 taken 15 times.
|
15 | const QLocale locale; |
| 206 | // Hours and minutes only (the short format of some locales has seconds). | ||
| 207 |
1/1✓ Branch 31 → 32 taken 15 times.
|
15 | QString timeFormat = locale.timeFormat(QLocale::ShortFormat); |
| 208 |
1/1✓ Branch 35 → 36 taken 15 times.
|
15 | timeFormat.remove(QStringLiteral(":ss")); |
| 209 |
5/5✓ Branch 38 → 39 taken 15 times.
✓ Branch 39 → 40 taken 15 times.
✓ Branch 40 → 41 taken 15 times.
✓ Branch 100 → 42 taken 600 times.
✓ Branch 100 → 101 taken 15 times.
|
615 | for (const FreeSlots::Interval &slot : FreeSlots::find(busy, options)) |
| 210 |
1/1✓ Branch 75 → 76 taken 600 times.
|
1800 | out.append(QVariantMap{{QStringLiteral("start"), slot.start}, |
| 211 | 1200 | {QStringLiteral("end"), slot.end}, | |
| 212 |
2/2✓ Branch 57 → 58 taken 600 times.
✓ Branch 58 → 59 taken 600 times.
|
1800 | {QStringLiteral("day"), locale.toString(slot.start.date(), QStringLiteral("ddd d MMM"))}, |
| 213 |
3/3✓ Branch 68 → 69 taken 600 times.
✓ Branch 69 → 70 taken 600 times.
✓ Branch 70 → 71 taken 600 times.
|
2400 | {QStringLiteral("time"), locale.toString(slot.start.time(), timeFormat) + QStringLiteral("–") + |
| 214 |
5/7✓ Branch 63 → 64 taken 600 times.
✓ Branch 64 → 65 taken 600 times.
✓ Branch 71 → 72 taken 600 times.
✓ Branch 79 → 80 taken 2400 times.
✓ Branch 79 → 81 taken 600 times.
✗ Branch 148 → 149 not taken.
✗ Branch 148 → 150 not taken.
|
5415 | locale.toString(slot.end.time(), timeFormat)}}); |
| 215 |
2/10✓ Branch 102 → 103 taken 15 times.
✗ Branch 106 → 107 not taken.
✗ Branch 107 → 108 not taken.
✗ Branch 109 → 110 not taken.
✗ Branch 110 → 111 not taken.
✗ Branch 112 → 113 not taken.
✗ Branch 113 → 114 not taken.
✗ Branch 115 → 116 not taken.
✗ Branch 119 → 104 not taken.
✓ Branch 119 → 120 taken 15 times.
|
15 | qCInfo(lcCompose) << "free slots" << out.size() << "busy" << busy.size() << "ms" << timer.elapsed(); |
| 216 | 15 | return out; | |
| 217 | 3015 | } | |
| 218 | |||
| 219 | 2 | bool QuickComposeBackend::insertSuggestedTimes(QObject *sessionObject, const QVariantList &chosen) { | |
| 220 |
1/1✓ Branch 2 → 3 taken 2 times.
|
2 | auto *session = qobject_cast<ComposeSession *>(sessionObject); |
| 221 |
4/8✓ Branch 3 → 4 taken 2 times.
✗ Branch 3 → 8 not taken.
✓ Branch 5 → 6 taken 2 times.
✗ Branch 5 → 8 not taken.
✗ Branch 7 → 8 not taken.
✓ Branch 7 → 9 taken 2 times.
✗ Branch 10 → 11 not taken.
✓ Branch 10 → 12 taken 2 times.
|
2 | if (!session || chosen.isEmpty() || !session->document()) return false; |
| 222 |
1/1✓ Branch 13 → 14 taken 2 times.
|
2 | const MessageLanguageText language(session->language()); |
| 223 |
2/4✓ Branch 18 → 19 taken 2 times.
✓ Branch 18 → 20 taken 2 times.
✗ Branch 99 → 100 not taken.
✗ Branch 99 → 101 not taken.
|
4 | QStringList lines{language.suggestTimesIntro()}; |
| 224 |
5/5✓ Branch 20 → 21 taken 2 times.
✓ Branch 21 → 22 taken 2 times.
✓ Branch 22 → 23 taken 2 times.
✓ Branch 61 → 24 taken 5 times.
✓ Branch 61 → 62 taken 2 times.
|
7 | for (const QVariant &value : chosen.mid(0, 5)) { |
| 225 |
1/1✓ Branch 25 → 26 taken 5 times.
|
5 | const QVariantMap slot = value.toMap(); |
| 226 |
4/4✓ Branch 36 → 37 taken 5 times.
✓ Branch 37 → 38 taken 5 times.
✓ Branch 38 → 39 taken 5 times.
✓ Branch 43 → 44 taken 5 times.
|
20 | lines.append(QStringLiteral("• ") + language.timeSlot(slot.value(QStringLiteral("start")).toDateTime(), |
| 227 |
3/3✓ Branch 30 → 31 taken 5 times.
✓ Branch 31 → 32 taken 5 times.
✓ Branch 42 → 43 taken 5 times.
|
20 | slot.value(QStringLiteral("end")).toDateTime())); |
| 228 | 7 | } | |
| 229 |
3/3✓ Branch 63 → 64 taken 2 times.
✓ Branch 64 → 65 taken 2 times.
✓ Branch 65 → 66 taken 2 times.
|
2 | lines.append(language.timeZoneNote(QTimeZone::systemTimeZone())); |
| 230 |
3/3✓ Branch 73 → 74 taken 2 times.
✓ Branch 74 → 75 taken 2 times.
✓ Branch 75 → 76 taken 2 times.
|
2 | session->editor().insertPlainText(lines.join(QLatin1Char('\n')) + QLatin1Char('\n')); |
| 231 |
2/6✓ Branch 78 → 79 taken 2 times.
✗ Branch 82 → 83 not taken.
✗ Branch 83 → 84 not taken.
✗ Branch 86 → 87 not taken.
✗ Branch 90 → 80 not taken.
✓ Branch 90 → 91 taken 2 times.
|
2 | qCInfo(lcCompose) << "suggested times inserted" << qMin(int(chosen.size()), 5); |
| 232 | 2 | return true; | |
| 233 | 4 | } | |
| 234 | |||
| 235 | // D-23: the invitation left — its event goes into the chosen calendar now | ||
| 236 | // (so Undo never had anything to remove), read back from the sent iMIP | ||
| 237 | // part: the organizer's copy with every attendee waiting for an answer. | ||
| 238 | 2 | bool QuickComposeBackend::writeOrganizedEvent(const OutboxEntry &entry, const QByteArray &sentBytes, | |
| 239 | bool serverSchedules) { | ||
| 240 |
3/3✓ Branch 2 → 3 taken 2 times.
✓ Branch 3 → 4 taken 2 times.
✓ Branch 4 → 5 taken 2 times.
|
2 | const QJsonObject target = QJsonDocument::fromJson(entry.replyReference.toUtf8()).object(); |
| 241 |
2/2✓ Branch 10 → 11 taken 2 times.
✓ Branch 11 → 12 taken 2 times.
|
2 | const QString accountId = target.value(QStringLiteral("calendarAccount")).toString(); |
| 242 |
2/2✓ Branch 18 → 19 taken 2 times.
✓ Branch 19 → 20 taken 2 times.
|
2 | const QString path = target.value(QStringLiteral("calendarPath")).toString(); |
| 243 | 2 | auto *sync = m_session.calendarSync(); | |
| 244 |
4/8✓ Branch 24 → 25 taken 2 times.
✗ Branch 24 → 29 not taken.
✓ Branch 26 → 27 taken 2 times.
✗ Branch 26 → 29 not taken.
✗ Branch 28 → 29 not taken.
✓ Branch 28 → 30 taken 2 times.
✗ Branch 31 → 32 not taken.
✓ Branch 31 → 44 taken 2 times.
|
2 | if (!sync || accountId.isEmpty() || path.isEmpty()) { |
| 245 | ✗ | qCWarning(lcCompose) << "invitation sent without a calendar" << entry.id; | |
| 246 | ✗ | return false; | |
| 247 | } | ||
| 248 | 2 | QString calendarText; | |
| 249 |
4/5✓ Branch 44 → 45 taken 2 times.
✓ Branch 45 → 46 taken 2 times.
✓ Branch 46 → 47 taken 2 times.
✓ Branch 58 → 48 taken 2 times.
✗ Branch 58 → 59 not taken.
|
2 | for (const MimePart &part : MimeParser::parse(sentBytes).attachments) |
| 250 |
1/2✓ Branch 51 → 52 taken 2 times.
✗ Branch 51 → 56 not taken.
|
2 | if (part.contentType.compare(QLatin1String("text/calendar"), Qt::CaseInsensitive) == 0) { |
| 251 |
1/1✓ Branch 52 → 53 taken 2 times.
|
2 | calendarText = QString::fromUtf8(part.body); |
| 252 | 2 | break; | |
| 253 | 2 | } | |
| 254 |
1/1✓ Branch 60 → 61 taken 2 times.
|
2 | const auto invitation = Imip::parse(calendarText); |
| 255 |
3/6✓ Branch 62 → 63 taken 2 times.
✗ Branch 62 → 67 not taken.
✗ Branch 66 → 67 not taken.
✓ Branch 66 → 68 taken 2 times.
✗ Branch 69 → 70 not taken.
✓ Branch 69 → 82 taken 2 times.
|
2 | if (!invitation || invitation->method != QLatin1String("REQUEST")) { |
| 256 | ✗ | qCWarning(lcCompose) << "invitation without a readable calendar part" << entry.id; | |
| 257 | ✗ | return false; | |
| 258 | } | ||
| 259 | // S108.8.9: an invitation started from the event editor names a stored | ||
| 260 | // event: it takes the organizer, attendees and SEQUENCE and keeps the | ||
| 261 | // rest (reminders, people, recurrence) as the user saved it. | ||
| 262 |
3/3✓ Branch 83 → 84 taken 2 times.
✓ Branch 85 → 86 taken 1 time.
✓ Branch 85 → 124 taken 1 time.
|
2 | if (const auto stored = sync->eventFor(invitation->uid, accountId, path)) { |
| 263 | 1 | CalendarEvent updated = *stored; | |
| 264 |
2/2✓ Branch 93 → 94 taken 1 time.
✓ Branch 94 → 95 taken 1 time.
|
2 | updated.icalRaw = Imip::withMeetingAttendees(storedComponentText(*stored), invitation->organizerEmail, |
| 265 | 1 | invitation->organizerName, invitation->attendees, invitation->sequence, | |
| 266 | 1 | serverSchedules); | |
| 267 |
3/5✓ Branch 99 → 100 taken 1 time.
✗ Branch 99 → 103 not taken.
✓ Branch 100 → 101 taken 1 time.
✓ Branch 101 → 102 taken 1 time.
✗ Branch 101 → 103 not taken.
|
1 | const bool saved = !updated.icalRaw.isEmpty() && sync->saveEvent(updated, false); |
| 268 |
2/7✓ Branch 104 → 105 taken 1 time.
✗ Branch 108 → 109 not taken.
✗ Branch 109 → 110 not taken.
✗ Branch 110 → 111 not taken.
✗ Branch 112 → 113 not taken.
✗ Branch 116 → 106 not taken.
✓ Branch 116 → 117 taken 1 time.
|
1 | qCInfo(lcCompose) << "organized event updated" << saved << invitation->uid; |
| 269 |
1/4✗ Branch 117 → 118 not taken.
✓ Branch 117 → 122 taken 1 time.
✗ Branch 118 → 119 not taken.
✗ Branch 119 → 120 not taken.
|
1 | if (!saved) m_deps.reportError(tr("The invitation was sent, but the appointment could not be saved to the calendar.")); |
| 270 | 1 | return saved; | |
| 271 | 3 | } | |
| 272 |
1/1✓ Branch 132 → 134 taken 1 time.
|
1 | QList<CalendarEvent> rows = Imip::eventsToStore(*invitation, QString(), QString(), QString()); |
| 273 |
4/4✓ Branch 137 → 138 taken 1 time.
✓ Branch 138 → 139 taken 1 time.
✓ Branch 154 → 140 taken 1 time.
✓ Branch 154 → 155 taken 1 time.
|
2 | for (CalendarEvent &row : rows) { |
| 274 | 1 | row.accountId = accountId; | |
| 275 | 1 | row.calendarPath = path; | |
| 276 | // Server mode (D-24): the server schedules — no SCHEDULE-AGENT=CLIENT. | ||
| 277 |
1/2✗ Branch 143 → 144 not taken.
✓ Branch 143 → 152 taken 1 time.
|
1 | if (serverSchedules) |
| 278 | ✗ | row.icalRaw = Imip::withMeetingAttendees(row.icalRaw, invitation->organizerEmail, invitation->organizerName, | |
| 279 | ✗ | invitation->attendees, invitation->sequence, true); | |
| 280 | } | ||
| 281 |
1/1✓ Branch 156 → 157 taken 1 time.
|
1 | const bool stored = sync->restoreEventSeries(invitation->uid, accountId, path, rows); |
| 282 |
2/8✓ Branch 157 → 158 taken 1 time.
✗ Branch 161 → 162 not taken.
✗ Branch 162 → 163 not taken.
✗ Branch 163 → 164 not taken.
✗ Branch 164 → 165 not taken.
✗ Branch 166 → 167 not taken.
✗ Branch 170 → 159 not taken.
✓ Branch 170 → 171 taken 1 time.
|
1 | qCInfo(lcCompose) << "organized event written" << stored << "rows" << rows.size(); |
| 283 |
1/4✗ Branch 171 → 172 not taken.
✓ Branch 171 → 176 taken 1 time.
✗ Branch 172 → 173 not taken.
✗ Branch 173 → 174 not taken.
|
1 | if (!stored) m_deps.reportError(tr("The invitation was sent, but the appointment could not be saved to the calendar.")); |
| 284 | 1 | return stored; | |
| 285 | 2 | } | |
| 286 |