data/MailSort.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #include "MailSort.h" | ||
| 2 | |||
| 3 | #include "ContactIndex.h" | ||
| 4 | #include <QRegularExpression> | ||
| 5 | |||
| 6 | 828 | bool MailSortOrder::columnFromId(const QString &id, Column *column) { | |
| 7 | static const QHash<QString, Column> columns = { | ||
| 8 | 66 | {QStringLiteral("date"), Column::Date}, {QStringLiteral("from"), Column::Sender}, | |
| 9 | 66 | {QStringLiteral("to"), Column::Recipient}, {QStringLiteral("subject"), Column::Subject}, | |
| 10 |
5/10✓ Branch 2 → 3 taken 22 times.
✓ Branch 2 → 48 taken 806 times.
✓ Branch 4 → 5 taken 22 times.
✗ Branch 4 → 48 not taken.
✓ Branch 33 → 34 taken 132 times.
✓ Branch 33 → 35 taken 22 times.
✗ Branch 60 → 61 not taken.
✗ Branch 60 → 62 not taken.
✗ Branch 111 → 112 not taken.
✗ Branch 111 → 113 not taken.
|
1048 | {QStringLiteral("size"), Column::Size}, {QStringLiteral("star"), Column::Star}}; |
| 11 | 828 | const auto found = columns.constFind(id); | |
| 12 |
2/2✓ Branch 51 → 52 taken 1 time.
✓ Branch 51 → 53 taken 827 times.
|
828 | if (found == columns.constEnd()) return false; |
| 13 |
1/2✓ Branch 53 → 54 taken 827 times.
✗ Branch 53 → 56 not taken.
|
827 | if (column) *column = found.value(); |
| 14 | 827 | return true; | |
| 15 | 154 | } | |
| 16 | |||
| 17 | 1776 | QString MailSortOrder::idOf(Column column) { | |
| 18 |
6/7✓ Branch 2 → 3 taken 1646 times.
✓ Branch 2 → 8 taken 52 times.
✓ Branch 2 → 13 taken 2 times.
✓ Branch 2 → 18 taken 72 times.
✓ Branch 2 → 23 taken 2 times.
✓ Branch 2 → 28 taken 2 times.
✗ Branch 2 → 33 not taken.
|
1776 | switch (column) { |
| 19 | 3292 | case Column::Date: return QStringLiteral("date"); | |
| 20 | 104 | case Column::Sender: return QStringLiteral("from"); | |
| 21 | 4 | case Column::Recipient: return QStringLiteral("to"); | |
| 22 | 144 | case Column::Subject: return QStringLiteral("subject"); | |
| 23 | 4 | case Column::Size: return QStringLiteral("size"); | |
| 24 | 4 | case Column::Star: return QStringLiteral("star"); | |
| 25 | } | ||
| 26 | ✗ | return QStringLiteral("date"); | |
| 27 | } | ||
| 28 | |||
| 29 | 6 | bool MailSortOrder::defaultAscending(Column column) { | |
| 30 |
5/6✓ Branch 2 → 3 taken 5 times.
✓ Branch 2 → 5 taken 1 time.
✓ Branch 3 → 4 taken 5 times.
✗ Branch 3 → 5 not taken.
✓ Branch 4 → 5 taken 3 times.
✓ Branch 4 → 6 taken 2 times.
|
6 | return column == Column::Sender || column == Column::Recipient || column == Column::Subject; |
| 31 | } | ||
| 32 | |||
| 33 | 977 | QString MailSortOrder::toString() const { | |
| 34 |
8/12✓ Branch 2 → 3 taken 92 times.
✓ Branch 2 → 6 taken 885 times.
✓ Branch 9 → 10 taken 977 times.
✓ Branch 10 → 11 taken 977 times.
✓ Branch 14 → 15 taken 885 times.
✓ Branch 14 → 16 taken 92 times.
✓ Branch 16 → 17 taken 92 times.
✓ Branch 16 → 18 taken 885 times.
✗ Branch 25 → 26 not taken.
✗ Branch 25 → 27 not taken.
✗ Branch 29 → 30 not taken.
✗ Branch 29 → 31 not taken.
|
1954 | return idOf(column) + (ascending ? QStringLiteral(":asc") : QStringLiteral(":desc")); |
| 35 | } | ||
| 36 | |||
| 37 | 1615 | MailSortOrder MailSortOrder::fromString(const QString &text) { | |
| 38 | 1615 | MailSortOrder order; | |
| 39 |
1/1✓ Branch 5 → 6 taken 1615 times.
|
1615 | const auto parts = text.split(QLatin1Char(':')); |
| 40 |
7/8✓ Branch 7 → 8 taken 817 times.
✓ Branch 7 → 19 taken 798 times.
✓ Branch 11 → 12 taken 805 times.
✓ Branch 11 → 16 taken 12 times.
✓ Branch 15 → 16 taken 805 times.
✗ Branch 15 → 19 not taken.
✓ Branch 21 → 22 taken 799 times.
✓ Branch 21 → 23 taken 816 times.
|
2432 | if (parts.size() != 2 || (parts.at(1) != QLatin1String("asc") && parts.at(1) != QLatin1String("desc")) || |
| 41 |
3/3✓ Branch 17 → 18 taken 817 times.
✓ Branch 18 → 19 taken 1 time.
✓ Branch 18 → 20 taken 816 times.
|
817 | !columnFromId(parts.at(0), &order.column)) |
| 42 | 799 | return {}; | |
| 43 | 816 | order.ascending = parts.at(1) == QLatin1String("asc"); | |
| 44 | 816 | return order; | |
| 45 | 1615 | } | |
| 46 | |||
| 47 | namespace MailSort { | ||
| 48 | |||
| 49 | namespace { | ||
| 50 | // The first entry of an address list; commas inside quotes or <> do not split. | ||
| 51 | 100384 | QString firstAddressEntry(const QString &addresses) { | |
| 52 | 100384 | bool quoted = false; | |
| 53 | 100384 | int angle = 0; | |
| 54 |
2/2✓ Branch 41 → 3 taken 2458346 times.
✓ Branch 41 → 42 taken 83005 times.
|
2541351 | for (qsizetype i = 0; i < addresses.size(); ++i) { |
| 55 | 2458346 | const QChar c = addresses.at(i); | |
| 56 |
2/2✓ Branch 7 → 8 taken 39384 times.
✓ Branch 7 → 9 taken 2418962 times.
|
2458346 | if (c == QLatin1Char('"')) quoted = !quoted; |
| 57 |
6/6✓ Branch 9 → 10 taken 2241734 times.
✓ Branch 9 → 15 taken 177228 times.
✓ Branch 13 → 14 taken 77834 times.
✓ Branch 13 → 15 taken 2163900 times.
✓ Branch 16 → 17 taken 77834 times.
✓ Branch 16 → 18 taken 2341128 times.
|
2418962 | else if (!quoted && c == QLatin1Char('<')) ++angle; |
| 58 |
7/8✓ Branch 18 → 19 taken 2163900 times.
✓ Branch 18 → 25 taken 177228 times.
✓ Branch 22 → 23 taken 77834 times.
✓ Branch 22 → 25 taken 2086066 times.
✓ Branch 23 → 24 taken 77834 times.
✗ Branch 23 → 25 not taken.
✓ Branch 26 → 27 taken 77834 times.
✓ Branch 26 → 28 taken 2263294 times.
|
2341128 | else if (!quoted && c == QLatin1Char('>') && angle > 0) --angle; |
| 59 |
9/9✓ Branch 28 → 29 taken 2086066 times.
✓ Branch 28 → 35 taken 177228 times.
✓ Branch 29 → 30 taken 763993 times.
✓ Branch 29 → 35 taken 1322073 times.
✓ Branch 33 → 34 taken 17379 times.
✓ Branch 33 → 35 taken 746614 times.
✓ Branch 36 → 37 taken 17379 times.
✓ Branch 36 → 39 taken 2245915 times.
✓ Branch 37 → 38 taken 17379 times.
|
2263294 | else if (!quoted && angle == 0 && c == QLatin1Char(',')) return addresses.left(i); |
| 60 | } | ||
| 61 | 83005 | return addresses; | |
| 62 | } | ||
| 63 | } // namespace | ||
| 64 | |||
| 65 | 100384 | QString personKey(const QString &addresses) { | |
| 66 |
2/2✓ Branch 2 → 3 taken 100384 times.
✓ Branch 3 → 4 taken 100384 times.
|
100384 | const QString entry = firstAddressEntry(addresses).trimmed(); |
| 67 | 100384 | const qsizetype open = entry.indexOf(QLatin1Char('<')); | |
| 68 |
3/3✓ Branch 8 → 9 taken 22550 times.
✓ Branch 8 → 11 taken 77834 times.
✓ Branch 9 → 10 taken 22550 times.
|
100384 | if (open < 0) return ContactIndex::foldForSort(entry); |
| 69 |
2/2✓ Branch 11 → 12 taken 77834 times.
✓ Branch 12 → 13 taken 77834 times.
|
77834 | const QString name = ContactIndex::foldForSort(entry.left(open)); |
| 70 |
2/2✓ Branch 15 → 16 taken 60365 times.
✓ Branch 15 → 17 taken 17469 times.
|
77834 | if (!name.isEmpty()) return name; |
| 71 | 17469 | const qsizetype close = entry.indexOf(QLatin1Char('>'), open); | |
| 72 |
3/4✓ Branch 20 → 21 taken 17469 times.
✗ Branch 20 → 22 not taken.
✓ Branch 23 → 24 taken 17469 times.
✓ Branch 24 → 25 taken 17469 times.
|
17469 | return ContactIndex::foldForSort(entry.mid(open + 1, close < 0 ? -1 : close - open - 1)); |
| 73 | 100384 | } | |
| 74 | |||
| 75 | 56904 | QString subjectKey(const QString &subject) { | |
| 76 | // Reply and forward markers of common clients and languages, optionally | ||
| 77 | // numbered ("Re[2]:", "AW(3):"). | ||
| 78 | static const QRegularExpression prefix( | ||
| 79 | 6 | QStringLiteral("^\\s*(re|fw|fwd|aw|wg|sv|vs|antw|tr|rif|enc|r)\\s*(\\[\\d+\\]|\\(\\d+\\))?\\s*:\\s*"), | |
| 80 |
4/7✓ Branch 2 → 3 taken 3 times.
✓ Branch 2 → 15 taken 56901 times.
✓ Branch 4 → 5 taken 3 times.
✗ Branch 4 → 15 not taken.
✓ Branch 9 → 10 taken 3 times.
✗ Branch 50 → 51 not taken.
✗ Branch 50 → 52 not taken.
|
56910 | QRegularExpression::CaseInsensitiveOption); |
| 81 | 56904 | QString text = subject; | |
| 82 |
1/2✓ Branch 37 → 17 taken 94220 times.
✗ Branch 37 → 38 not taken.
|
94220 | for (int guard = 0; guard < 16; ++guard) { |
| 83 |
1/1✓ Branch 18 → 19 taken 94220 times.
|
94220 | const auto match = prefix.match(text); |
| 84 |
7/8✓ Branch 19 → 20 taken 94220 times.
✓ Branch 20 → 21 taken 37316 times.
✓ Branch 20 → 23 taken 56904 times.
✓ Branch 21 → 22 taken 37316 times.
✗ Branch 22 → 23 not taken.
✓ Branch 22 → 24 taken 37316 times.
✓ Branch 25 → 26 taken 56904 times.
✓ Branch 25 → 27 taken 37316 times.
|
94220 | if (!match.hasMatch() || match.capturedLength() == 0) break; |
| 85 |
2/2✓ Branch 27 → 28 taken 37316 times.
✓ Branch 28 → 29 taken 37316 times.
|
37316 | text = text.mid(match.capturedLength()); |
| 86 | 94220 | } | |
| 87 |
1/1✓ Branch 38 → 39 taken 56904 times.
|
113808 | return ContactIndex::foldForSort(text); |
| 88 | 56904 | } | |
| 89 | |||
| 90 | 36511 | QVariant key(const MailHeader &header, MailSortOrder::Column column) { | |
| 91 |
5/7✓ Branch 2 → 3 taken 8603 times.
✓ Branch 2 → 7 taken 6842 times.
✓ Branch 2 → 11 taken 8060 times.
✓ Branch 2 → 15 taken 6694 times.
✓ Branch 2 → 16 taken 6312 times.
✗ Branch 2 → 17 not taken.
✗ Branch 2 → 18 not taken.
|
36511 | switch (column) { |
| 92 |
1/1✓ Branch 3 → 4 taken 8603 times.
|
8603 | case MailSortOrder::Column::Sender: return personKey(header.from); |
| 93 |
1/1✓ Branch 7 → 8 taken 6842 times.
|
6842 | case MailSortOrder::Column::Recipient: return personKey(header.to); |
| 94 |
1/1✓ Branch 11 → 12 taken 8060 times.
|
8060 | case MailSortOrder::Column::Subject: return subjectKey(header.subject); |
| 95 | 6694 | case MailSortOrder::Column::Size: return qint64(header.size); | |
| 96 | 6312 | case MailSortOrder::Column::Star: return qint64(header.flags & MailFlag::Flagged); | |
| 97 | ✗ | case MailSortOrder::Column::Date: break; | |
| 98 | } | ||
| 99 | ✗ | return {}; | |
| 100 | } | ||
| 101 | |||
| 102 | namespace { | ||
| 103 | // Canonical (descending) order; see MailSortOrder. | ||
| 104 | 20723 | bool canonicalBefore(const MailHeader &left, const MailHeader &right, MailSortOrder::Column column) { | |
| 105 |
2/2✓ Branch 2 → 3 taken 3714 times.
✓ Branch 2 → 16 taken 17009 times.
|
20723 | if (column == MailSortOrder::Column::Date) { |
| 106 | // MailCache: ORDER BY date DESC, uid DESC; SQLite lists NULL dates last. | ||
| 107 |
2/2✓ Branch 3 → 4 taken 3714 times.
✓ Branch 4 → 5 taken 3714 times.
|
3714 | const bool leftDated = left.date.isValid(), rightDated = right.date.isValid(); |
| 108 |
2/2✓ Branch 5 → 6 taken 45 times.
✓ Branch 5 → 7 taken 3669 times.
|
3714 | if (leftDated != rightDated) return leftDated; |
| 109 |
8/8✓ Branch 7 → 8 taken 3667 times.
✓ Branch 7 → 11 taken 2 times.
✓ Branch 8 → 9 taken 3667 times.
✓ Branch 9 → 10 taken 3627 times.
✓ Branch 9 → 11 taken 40 times.
✓ Branch 12 → 13 taken 3627 times.
✓ Branch 12 → 15 taken 42 times.
✓ Branch 13 → 14 taken 3627 times.
|
3669 | if (leftDated && left.date != right.date) return left.date > right.date; |
| 110 | 42 | return left.uid > right.uid; | |
| 111 | } | ||
| 112 |
2/2✓ Branch 16 → 17 taken 17009 times.
✓ Branch 17 → 18 taken 17009 times.
|
17009 | const QVariant leftKey = key(left, column), rightKey = key(right, column); |
| 113 |
3/3✓ Branch 18 → 19 taken 17009 times.
✓ Branch 19 → 20 taken 10576 times.
✓ Branch 19 → 32 taken 6433 times.
|
17009 | if (leftKey.typeId() == QMetaType::QString) { |
| 114 |
4/4✓ Branch 20 → 21 taken 10576 times.
✓ Branch 21 → 22 taken 10576 times.
✓ Branch 22 → 23 taken 10576 times.
✓ Branch 23 → 24 taken 10576 times.
|
10576 | const int compared = leftKey.toString().toUtf8().compare(rightKey.toString().toUtf8()); |
| 115 |
2/2✓ Branch 30 → 31 taken 6218 times.
✓ Branch 30 → 38 taken 4358 times.
|
10576 | if (compared != 0) return compared > 0; |
| 116 |
4/4✓ Branch 32 → 33 taken 6433 times.
✓ Branch 33 → 34 taken 6433 times.
✓ Branch 34 → 35 taken 3221 times.
✓ Branch 34 → 38 taken 3212 times.
|
6433 | } else if (leftKey.toLongLong() != rightKey.toLongLong()) { |
| 117 |
2/2✓ Branch 35 → 36 taken 3221 times.
✓ Branch 36 → 37 taken 3221 times.
|
3221 | return leftKey.toLongLong() > rightKey.toLongLong(); |
| 118 | } | ||
| 119 | 7570 | return left.uid > right.uid; | |
| 120 | 17009 | } | |
| 121 | } // namespace | ||
| 122 | |||
| 123 | 20723 | bool listedBefore(const MailHeader &left, const MailHeader &right, const MailSortOrder &order) { | |
| 124 |
2/2✓ Branch 2 → 3 taken 10711 times.
✓ Branch 2 → 5 taken 10012 times.
|
20723 | return order.ascending ? canonicalBefore(right, left, order.column) |
| 125 | 20723 | : canonicalBefore(left, right, order.column); | |
| 126 | } | ||
| 127 | |||
| 128 | 808 | MailSortOrder threadOrder(const MailSortOrder &order) { | |
| 129 | using Column = MailSortOrder::Column; | ||
| 130 |
6/6✓ Branch 2 → 3 taken 9 times.
✓ Branch 2 → 5 taken 799 times.
✓ Branch 3 → 4 taken 7 times.
✓ Branch 3 → 5 taken 2 times.
✓ Branch 4 → 5 taken 6 times.
✓ Branch 4 → 6 taken 1 time.
|
808 | if (order.column == Column::Date || order.column == Column::Sender || order.column == Column::Subject) |
| 131 | 807 | return order; | |
| 132 | 1 | return {}; | |
| 133 | } | ||
| 134 | |||
| 135 | 1212 | QString sqlKey(MailSortOrder::Column column, const QString &alias) { | |
| 136 |
3/3✓ Branch 3 → 4 taken 762 times.
✓ Branch 3 → 5 taken 450 times.
✓ Branch 7 → 8 taken 450 times.
|
1212 | const QString prefix = alias.isEmpty() ? QString() : alias + QLatin1Char('.'); |
| 137 |
5/7✓ Branch 8 → 9 taken 298 times.
✓ Branch 8 → 17 taken 212 times.
✓ Branch 8 → 25 taken 278 times.
✓ Branch 8 → 33 taken 212 times.
✓ Branch 8 → 41 taken 212 times.
✗ Branch 8 → 52 not taken.
✗ Branch 8 → 53 not taken.
|
1212 | switch (column) { |
| 138 |
1/1✓ Branch 13 → 14 taken 298 times.
|
894 | case MailSortOrder::Column::Sender: return QStringLiteral("mailjd_sort_key(1,%1from_addr)").arg(prefix); |
| 139 |
1/1✓ Branch 21 → 22 taken 212 times.
|
636 | case MailSortOrder::Column::Recipient: return QStringLiteral("mailjd_sort_key(1,%1to_addr)").arg(prefix); |
| 140 |
1/1✓ Branch 29 → 30 taken 278 times.
|
834 | case MailSortOrder::Column::Subject: return QStringLiteral("mailjd_sort_key(2,%1subject)").arg(prefix); |
| 141 |
1/1✓ Branch 37 → 38 taken 212 times.
|
636 | case MailSortOrder::Column::Size: return QStringLiteral("coalesce(%1size,0)").arg(prefix); |
| 142 |
2/2✓ Branch 45 → 46 taken 212 times.
✓ Branch 47 → 48 taken 212 times.
|
848 | case MailSortOrder::Column::Star: return QStringLiteral("(%1flags & %2)").arg(prefix).arg(MailFlag::Flagged); |
| 143 | ✗ | case MailSortOrder::Column::Date: break; | |
| 144 | } | ||
| 145 | ✗ | return prefix + QStringLiteral("date"); | |
| 146 | 1212 | } | |
| 147 | |||
| 148 | 6671 | QByteArray descendingKey(const QByteArray &utf8) { | |
| 149 | 6671 | QByteArray reversed(utf8.size() + 1, '\xff'); | |
| 150 |
3/3✓ Branch 6 → 7 taken 73039 times.
✓ Branch 9 → 5 taken 73039 times.
✓ Branch 9 → 10 taken 6671 times.
|
79710 | for (qsizetype i = 0; i < utf8.size(); ++i) reversed[i] = char(~uchar(utf8.at(i))); |
| 151 | 6671 | return reversed; | |
| 152 | ✗ | } | |
| 153 | |||
| 154 | } // namespace MailSort | ||
| 155 |