MailJDTourDownloadDocsQualityTestsCoverage

Which source lines and branches the tests execute. Lines 91.5 %, branches 75.0 % (minimum 85 % and 72 %). main @ 399ffe3d · 21 October 2026 · pipeline 7238.

GCC Code Coverage Report


Directory: src/
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 91.6% 414 / 0 / 452
Functions: 91.2% 52 / 0 / 57
Branches: 67.3% 503 / 0 / 747

presentation/ComposeAttachmentModel.cpp
Line Branch Exec Source
1 #include "ComposeAttachmentModel.h"
2
3 #include "data/ComposeImageLocation.h"
4 #include "service/Rfc2822Builder.h"
5
6 #include <QBuffer>
7 #include <QDir>
8 #include <QFile>
9 #include <QFileInfo>
10 #include <QImageReader>
11 #include <QImageWriter>
12 #include <QJsonObject>
13 #include <QLocale>
14 #include <QMimeDatabase>
15 #include <QSaveFile>
16 #include <QUuid>
17 #include <QtConcurrent>
18
19 namespace {
20
21 // "300 KiB", "2.3 MiB", "25 MiB": one decimal below ten units, none above
22 // (QLocale's default two decimals read as noise in the tray).
23 575 QString sizeText(qint64 bytes) {
24
2/2
✓ Branch 2 → 3 taken 575 times.
✓ Branch 4 → 5 taken 575 times.
1150 const QString two = QLocale().formattedDataSize(bytes, 2);
25
3/3
✓ Branch 6 → 7 taken 575 times.
✓ Branch 10 → 11 taken 575 times.
✓ Branch 11 → 12 taken 575 times.
1150 const double value = QLocale().toDouble(two.section(QLatin1Char(' '), 0, 0));
26
6/6
✓ Branch 14 → 15 taken 575 times.
✓ Branch 16 → 17 taken 199 times.
✓ Branch 16 → 19 taken 376 times.
✓ Branch 17 → 18 taken 187 times.
✓ Branch 17 → 19 taken 12 times.
✓ Branch 20 → 21 taken 575 times.
1725 return QLocale().formattedDataSize(bytes, value >= 10 || bytes < 1024 ? 0 : 1);
27 575 }
28
29
30 53 QString typeLabel(const QString &mimeType, const QString &name) {
31
3/3
✓ Branch 2 → 3 taken 53 times.
✓ Branch 3 → 4 taken 53 times.
✓ Branch 4 → 5 taken 53 times.
53 const QString suffix = QFileInfo(name).suffix().toUpper();
32
3/6
✓ Branch 8 → 9 taken 53 times.
✗ Branch 8 → 12 not taken.
✓ Branch 10 → 11 taken 53 times.
✗ Branch 10 → 12 not taken.
✓ Branch 13 → 14 taken 53 times.
✗ Branch 13 → 16 not taken.
53 if (!suffix.isEmpty() && suffix.size() <= 5) return suffix;
33 ✗ return mimeType.section(QLatin1Char('/'), 1).toUpper();
34 53 }
35
36 53 QString iconFor(const QString &mimeType) {
37
3/3
✓ Branch 3 → 4 taken 53 times.
✓ Branch 4 → 5 taken 7 times.
✓ Branch 4 → 10 taken 46 times.
60 if (mimeType.startsWith(QLatin1String("image/"))) return QStringLiteral("file-image-outline");
38
2/2
✓ Branch 12 → 13 taken 16 times.
✓ Branch 12 → 18 taken 30 times.
62 if (mimeType == QLatin1String("application/pdf")) return QStringLiteral("file-pdf-box");
39
3/3
✓ Branch 19 → 20 taken 30 times.
✓ Branch 20 → 21 taken 2 times.
✓ Branch 20 → 26 taken 28 times.
32 if (mimeType.startsWith(QLatin1String("text/calendar"))) return QStringLiteral("calendar-outline");
40
7/8
✓ Branch 27 → 28 taken 28 times.
✓ Branch 28 → 29 taken 26 times.
✓ Branch 28 → 32 taken 2 times.
✓ Branch 30 → 31 taken 26 times.
✗ Branch 31 → 32 not taken.
✓ Branch 31 → 33 taken 26 times.
✓ Branch 34 → 35 taken 2 times.
✓ Branch 34 → 40 taken 26 times.
28 if (mimeType.startsWith(QLatin1String("text/vcard")) || mimeType.startsWith(QLatin1String("text/x-vcard")))
41 2 return QStringLiteral("card-account-details-outline");
42
7/8
✓ Branch 41 → 42 taken 26 times.
✓ Branch 42 → 43 taken 14 times.
✓ Branch 42 → 46 taken 12 times.
✓ Branch 44 → 45 taken 14 times.
✗ Branch 45 → 46 not taken.
✓ Branch 45 → 47 taken 14 times.
✓ Branch 48 → 49 taken 12 times.
✓ Branch 48 → 54 taken 14 times.
26 if (mimeType.contains(QLatin1String("spreadsheet")) || mimeType.contains(QLatin1String("excel")))
43 12 return QStringLiteral("file-table-outline");
44
5/8
✓ Branch 55 → 56 taken 14 times.
✓ Branch 56 → 57 taken 14 times.
✗ Branch 56 → 60 not taken.
✓ Branch 58 → 59 taken 14 times.
✗ Branch 59 → 60 not taken.
✓ Branch 59 → 61 taken 14 times.
✗ Branch 62 → 63 not taken.
✓ Branch 62 → 68 taken 14 times.
14 if (mimeType.contains(QLatin1String("word")) || mimeType.contains(QLatin1String("document")))
45 ✗ return QStringLiteral("file-document-outline");
46
5/8
✓ Branch 69 → 70 taken 14 times.
✓ Branch 70 → 71 taken 14 times.
✗ Branch 70 → 74 not taken.
✓ Branch 72 → 73 taken 14 times.
✗ Branch 73 → 74 not taken.
✓ Branch 73 → 75 taken 14 times.
✗ Branch 76 → 77 not taken.
✓ Branch 76 → 82 taken 14 times.
14 if (mimeType.contains(QLatin1String("zip")) || mimeType.contains(QLatin1String("compressed")))
47 ✗ return QStringLiteral("folder-zip-outline");
48
1/2
✗ Branch 84 → 85 not taken.
✓ Branch 84 → 90 taken 14 times.
14 if (mimeType == QLatin1String("message/rfc822")) return QStringLiteral("email-outline");
49 14 return QStringLiteral("file-outline");
50 }
51
52 6 int stepPixels(const QString &size) {
53
1/2
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 6 taken 6 times.
6 if (size == QLatin1String("large")) return 2048;
54
2/2
✓ Branch 8 → 9 taken 2 times.
✓ Branch 8 → 10 taken 4 times.
6 if (size == QLatin1String("medium")) return 1280;
55
2/2
✓ Branch 12 → 13 taken 2 times.
✓ Branch 12 → 14 taken 2 times.
4 if (size == QLatin1String("small")) return 800;
56 2 return 0;
57 }
58
59 // A file name safe in the draft directory (no separators, bounded).
60 63 QString safeFileName(QString name) {
61
1/1
✓ Branch 6 → 7 taken 63 times.
63 name.replace(QLatin1Char('/'), QLatin1Char('_'));
62
1/1
✓ Branch 11 → 12 taken 63 times.
63 name.replace(QLatin1Char('\\'), QLatin1Char('_'));
63
1/1
✓ Branch 13 → 14 taken 63 times.
63 name.remove(QChar::Null);
64
1/1
✓ Branch 14 → 15 taken 63 times.
63 name = name.trimmed();
65
4/8
✓ Branch 18 → 19 taken 63 times.
✗ Branch 18 → 25 not taken.
✓ Branch 21 → 22 taken 63 times.
✗ Branch 21 → 25 not taken.
✗ Branch 24 → 25 not taken.
✓ Branch 24 → 26 taken 63 times.
✗ Branch 27 → 28 not taken.
✓ Branch 27 → 35 taken 63 times.
63 if (name.isEmpty() || name == QLatin1String(".") || name == QLatin1String("..")) name = QStringLiteral("attachment");
66 63 return name.left(180);
67 }
68
69 } // namespace
70
71 199 ComposeAttachmentModel::ComposeAttachmentModel(const QString &directory, QObject *parent)
72
3/3
✓ Branch 7 → 8 taken 199 times.
✓ Branch 12 → 13 taken 199 times.
✓ Branch 13 → 14 taken 199 times.
398 : QAbstractListModel(parent), m_directory(directory), m_alive(std::make_shared<std::atomic_bool>(true)) {
73
1/1
✓ Branch 14 → 15 taken 199 times.
199 m_pool.setMaxThreadCount(1);
74
1/1
✓ Branch 15 → 16 taken 199 times.
199 m_noticeTimer.setSingleShot(true);
75
1/1
✓ Branch 16 → 17 taken 199 times.
199 m_noticeTimer.setInterval(8000);
76
2/2
✓ Branch 3 → 4 taken 4 times.
✓ Branch 17 → 18 taken 199 times.
203 connect(&m_noticeTimer, &QTimer::timeout, this, [this] { setNotice(QString()); });
77 199 }
78
79 392 ComposeAttachmentModel::~ComposeAttachmentModel() {
80 199 m_alive->store(false);
81 199 m_pool.waitForDone();
82 392 }
83
84 1112 int ComposeAttachmentModel::rowCount(const QModelIndex &parent) const {
85
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 1112 times.
1112 return parent.isValid() ? 0 : int(m_items.size());
86 }
87
88 636 QVariant ComposeAttachmentModel::data(const QModelIndex &index, int role) const {
89
3/6
✓ Branch 3 → 4 taken 636 times.
✗ Branch 3 → 7 not taken.
✗ Branch 6 → 7 not taken.
✓ Branch 6 → 8 taken 636 times.
✗ Branch 9 → 10 not taken.
✓ Branch 9 → 11 taken 636 times.
636 if (!index.isValid() || index.row() >= m_items.size()) return {};
90 636 const Item &item = m_items.at(index.row());
91
12/15
✓ Branch 13 → 14 taken 53 times.
✓ Branch 13 → 15 taken 53 times.
✗ Branch 13 → 16 not taken.
✓ Branch 13 → 18 taken 53 times.
✗ Branch 13 → 23 not taken.
✓ Branch 13 → 24 taken 53 times.
✓ Branch 13 → 28 taken 53 times.
✓ Branch 13 → 32 taken 53 times.
✓ Branch 13 → 33 taken 53 times.
✓ Branch 13 → 34 taken 53 times.
✓ Branch 13 → 35 taken 53 times.
✓ Branch 13 → 36 taken 53 times.
✓ Branch 13 → 58 taken 53 times.
✓ Branch 13 → 59 taken 53 times.
✗ Branch 13 → 60 not taken.
636 switch (role) {
92 53 case IdRole: return item.id;
93 53 case Qt::DisplayRole:
94 53 case NameRole: return item.name;
95 ✗ case SizeRole: return item.sentBytes();
96
1/1
✓ Branch 19 → 20 taken 53 times.
53 case SizeTextRole: return sizeText(item.sentBytes());
97 ✗ case MimeTypeRole: return item.mimeType;
98
1/1
✓ Branch 24 → 25 taken 53 times.
53 case TypeLabelRole: return typeLabel(item.mimeType, item.name);
99
1/1
✓ Branch 28 → 29 taken 53 times.
53 case IconRole: return iconFor(item.mimeType);
100 53 case StateRole: return item.state;
101 53 case ProgressRole: return item.progress;
102 53 case ErrorRole: return item.error;
103 53 case IsImageRole: return item.image;
104 53 case ThumbnailRole:
105
2/2
✓ Branch 39 → 40 taken 6 times.
✓ Branch 39 → 46 taken 1 time.
7 return item.image && item.state == QLatin1String("ready")
106
12/20
✓ Branch 36 → 37 taken 7 times.
✓ Branch 36 → 46 taken 46 times.
✓ Branch 43 → 44 taken 6 times.
✓ Branch 44 → 45 taken 6 times.
✓ Branch 49 → 50 taken 6 times.
✓ Branch 49 → 51 taken 47 times.
✓ Branch 51 → 52 taken 6 times.
✓ Branch 51 → 53 taken 47 times.
✓ Branch 53 → 54 taken 6 times.
✓ Branch 53 → 55 taken 47 times.
✓ Branch 55 → 56 taken 6 times.
✓ Branch 55 → 57 taken 47 times.
✗ Branch 65 → 66 not taken.
✗ Branch 65 → 67 not taken.
✗ Branch 69 → 70 not taken.
✗ Branch 69 → 71 not taken.
✗ Branch 73 → 74 not taken.
✗ Branch 73 → 75 not taken.
✗ Branch 77 → 78 not taken.
✗ Branch 77 → 79 not taken.
113 ? QString::fromLatin1(QUrl::toPercentEncoding(item.sentPath()))
107 53 : QString();
108 53 case InlineRole: return item.inlinePlaced;
109 53 case LocationRemovedRole: return item.locationRemoved;
110 ✗ default: return {};
111 }
112 }
113
114 110 QHash<int, QByteArray> ComposeAttachmentModel::roleNames() const {
115 ✗ return {{IdRole, "attachmentId"}, {NameRole, "name"}, {SizeRole, "size"},
116 ✗ {SizeTextRole, "sizeText"}, {MimeTypeRole, "mimeType"}, {TypeLabelRole, "typeLabel"},
117 ✗ {IconRole, "icon"}, {StateRole, "state"}, {ProgressRole, "progress"},
118 ✗ {ErrorRole, "error"}, {IsImageRole, "isImage"}, {ThumbnailRole, "thumbnail"},
119
2/4
✓ Branch 18 → 19 taken 1540 times.
✓ Branch 18 → 20 taken 110 times.
✗ Branch 23 → 24 not taken.
✗ Branch 23 → 25 not taken.
1650 {InlineRole, "placedInText"}, {LocationRemovedRole, "locationRemoved"}};
120 110 }
121
122 8 void ComposeAttachmentModel::setServerLimit(qint64 bytes) {
123
2/2
✓ Branch 2 → 3 taken 2 times.
✓ Branch 2 → 4 taken 6 times.
8 if (m_serverLimit == bytes) return;
124 6 m_serverLimit = qMax<qint64>(0, bytes);
125 6 emit changed();
126 }
127
128 ✗ qint64 ComposeAttachmentModel::encodedSize(qint64 bytes) {
129 ✗ const qint64 base64 = (bytes + 2) / 3 * 4;
130 ✗ return base64 + base64 / 76 * 2;
131 }
132
133 366 qint64 ComposeAttachmentModel::limitBytes() const {
134 // The server limit counts the whole encoded message; compare it in raw
135 // bytes the user knows (what still fits after encoding).
136
2/2
✓ Branch 2 → 3 taken 179 times.
✓ Branch 2 → 4 taken 187 times.
366 const qint64 server = m_serverLimit > 0 ? m_serverLimit * 3 / 4 * 76 / 78 : 0;
137
2/2
✓ Branch 5 → 6 taken 179 times.
✓ Branch 5 → 8 taken 187 times.
366 return server > 0 ? qMin(server, TotalLimit) : TotalLimit;
138 }
139
140 310 qint64 ComposeAttachmentModel::totalBytes() const {
141 310 qint64 total = 0;
142
2/2
✓ Branch 8 → 4 taken 601 times.
✓ Branch 8 → 9 taken 310 times.
911 for (const Item &item : m_items) total += item.sentBytes();
143 310 return total;
144 }
145
146 941 QString ComposeAttachmentModel::summary() const {
147
2/2
✓ Branch 3 → 4 taken 687 times.
✓ Branch 3 → 5 taken 254 times.
941 if (m_items.isEmpty()) return {};
148 // English singular and plural are separate strings (no English catalog).
149 254 const int files = int(m_items.size());
150
4/4
✓ Branch 6 → 7 taken 122 times.
✓ Branch 6 → 8 taken 132 times.
✓ Branch 7 → 9 taken 122 times.
✓ Branch 8 → 9 taken 132 times.
508 return (files == 1 ? tr("1 file · %1 of %2") : tr("%n files · %1 of %2", nullptr, files))
151
4/4
✓ Branch 10 → 11 taken 254 times.
✓ Branch 11 → 12 taken 254 times.
✓ Branch 12 → 13 taken 254 times.
✓ Branch 13 → 14 taken 254 times.
254 .arg(sizeText(totalBytes()), sizeText(limitBytes()));
152 }
153
154 236 bool ComposeAttachmentModel::busy() const {
155
2/2
✓ Branch 11 → 4 taken 203 times.
✓ Branch 11 → 12 taken 201 times.
404 for (const Item &item : m_items)
156
2/2
✓ Branch 7 → 8 taken 35 times.
✓ Branch 7 → 9 taken 168 times.
203 if (item.state == QLatin1String("copying")) return true;
157 201 return false;
158 }
159
160 1 bool ComposeAttachmentModel::hasImages() const {
161
1/2
✓ Branch 9 → 4 taken 1 time.
✗ Branch 9 → 10 not taken.
1 for (const Item &item : m_items)
162
1/2
✓ Branch 5 → 6 taken 1 time.
✗ Branch 5 → 7 not taken.
1 if (item.image) return true;
163 ✗ return false;
164 }
165
166 116 int ComposeAttachmentModel::indexOf(const QString &id) const {
167
2/2
✓ Branch 9 → 3 taken 387 times.
✓ Branch 9 → 10 taken 2 times.
389 for (qsizetype i = 0; i < m_items.size(); ++i)
168
2/2
✓ Branch 5 → 6 taken 114 times.
✓ Branch 5 → 7 taken 273 times.
387 if (m_items.at(i).id == id) return int(i);
169 2 return -1;
170 }
171
172 4 std::optional<ComposeAttachmentModel::Item> ComposeAttachmentModel::item(const QString &id) const {
173 4 const int row = indexOf(id);
174
2/2
✓ Branch 3 → 4 taken 2 times.
✓ Branch 3 → 5 taken 2 times.
4 if (row < 0) return std::nullopt;
175 2 return m_items.at(row);
176 }
177
178 3 QString ComposeAttachmentModel::pathFor(const QString &id) const {
179 3 const int row = indexOf(id);
180
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 3 times.
3 return row < 0 ? QString() : m_items.at(row).sentPath();
181 }
182
183 15 void ComposeAttachmentModel::setNotice(const QString &notice) {
184
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 15 times.
15 if (m_notice == notice) return;
185 15 m_notice = notice;
186
2/2
✓ Branch 7 → 8 taken 11 times.
✓ Branch 7 → 9 taken 4 times.
15 if (!notice.isEmpty()) m_noticeTimer.start();
187 15 emit noticeChanged();
188 }
189
190 57 bool ComposeAttachmentModel::admit(const QString &name, qint64 size, QString *reason) const {
191
2/2
✓ Branch 3 → 4 taken 1 time.
✓ Branch 3 → 14 taken 56 times.
57 if (m_items.size() >= MaxFiles) {
192
3/3
✓ Branch 4 → 5 taken 1 time.
✓ Branch 6 → 7 taken 1 time.
✓ Branch 8 → 9 taken 1 time.
3 *reason = tr("%1 was not added — a mail takes at most %2 files.").arg(name).arg(MaxFiles);
193 1 return false;
194 }
195
1/1
✓ Branch 15 → 16 taken 56 times.
56 const qint64 remaining = qMax<qint64>(0, limitBytes() - totalBytes());
196 56 const qint64 perFile = qMin(PerFileLimit, limitBytes());
197
3/4
✓ Branch 19 → 20 taken 49 times.
✓ Branch 19 → 21 taken 7 times.
✗ Branch 20 → 21 not taken.
✓ Branch 20 → 36 taken 49 times.
56 if (size > perFile || size > remaining) {
198
3/3
✓ Branch 21 → 22 taken 6 times.
✓ Branch 21 → 24 taken 1 time.
✓ Branch 25 → 26 taken 7 times.
7 const QString limit = sizeText(m_serverLimit > 0 ? qMin(m_serverLimit, TotalLimit) : perFile);
199
1/1
✓ Branch 26 → 27 taken 7 times.
7 *reason = tr("%1 is too large — the server accepts up to %2; %3 still fit.")
200
2/2
✓ Branch 28 → 29 taken 7 times.
✓ Branch 29 → 30 taken 7 times.
7 .arg(name, limit, sizeText(qMin(remaining, perFile)));
201 7 return false;
202 7 }
203 49 return true;
204 }
205
206 51 QString ComposeAttachmentModel::uniqueTarget(const QString &name) const {
207
2/2
✓ Branch 3 → 4 taken 51 times.
✓ Branch 4 → 5 taken 51 times.
51 QDir().mkpath(m_directory);
208
1/1
✓ Branch 9 → 10 taken 51 times.
51 QFile::setPermissions(m_directory, QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner);
209
3/3
✓ Branch 10 → 11 taken 51 times.
✓ Branch 11 → 12 taken 51 times.
✓ Branch 12 → 13 taken 51 times.
51 const QString id = QUuid::createUuid().toString(QUuid::Id128).left(12);
210
5/5
✓ Branch 14 → 15 taken 51 times.
✓ Branch 16 → 17 taken 51 times.
✓ Branch 19 → 20 taken 51 times.
✓ Branch 20 → 21 taken 51 times.
✓ Branch 21 → 22 taken 51 times.
102 return QDir(m_directory).filePath(id + QLatin1Char('-') + safeFileName(name));
211 51 }
212
213 10 int ComposeAttachmentModel::addUrls(const QList<QUrl> &urls) {
214 10 QStringList paths;
215 10 QStringList refused;
216
2/2
✓ Branch 27 → 4 taken 10 times.
✓ Branch 27 → 28 taken 10 times.
20 for (const QUrl &url : urls) {
217
11/14
✓ Branch 5 → 6 taken 10 times.
✓ Branch 6 → 7 taken 9 times.
✓ Branch 6 → 12 taken 1 time.
✓ Branch 8 → 9 taken 9 times.
✓ Branch 10 → 11 taken 9 times.
✗ Branch 10 → 12 not taken.
✓ Branch 13 → 14 taken 9 times.
✓ Branch 13 → 15 taken 1 time.
✓ Branch 15 → 16 taken 9 times.
✓ Branch 15 → 20 taken 1 time.
✓ Branch 16 → 17 taken 9 times.
✓ Branch 17 → 18 taken 9 times.
✗ Branch 39 → 40 not taken.
✗ Branch 39 → 41 not taken.
19 if (url.isLocalFile() && url.host().isEmpty()) paths.append(url.toLocalFile());
218
2/2
✓ Branch 21 → 22 taken 1 time.
✓ Branch 22 → 23 taken 1 time.
1 else refused.append(url.fileName());
219 }
220
1/1
✓ Branch 28 → 29 taken 10 times.
10 const int added = addPaths(paths);
221
4/4
✓ Branch 30 → 31 taken 1 time.
✓ Branch 30 → 35 taken 9 times.
✓ Branch 31 → 32 taken 1 time.
✓ Branch 32 → 33 taken 1 time.
10 if (!refused.isEmpty()) setNotice(tr("Only files on this computer can be attached."));
222 10 return added;
223 10 }
224
225 31 int ComposeAttachmentModel::addPaths(const QStringList &paths) {
226 31 int added = 0;
227
2/2
✓ Branch 94 → 4 taken 51 times.
✓ Branch 94 → 95 taken 31 times.
82 for (const QString &path : paths) {
228
1/1
✓ Branch 5 → 6 taken 51 times.
51 const QFileInfo info(path);
229 51 QString reason;
230
3/3
✓ Branch 6 → 7 taken 51 times.
✓ Branch 7 → 8 taken 1 time.
✓ Branch 7 → 17 taken 50 times.
51 if (info.isDir()) {
231
4/4
✓ Branch 8 → 9 taken 1 time.
✓ Branch 10 → 11 taken 1 time.
✓ Branch 11 → 12 taken 1 time.
✓ Branch 12 → 13 taken 1 time.
2 setNotice(tr("%1 is a folder — attach the files in it, or a zip archive of it.").arg(info.fileName()));
232 1 continue;
233 }
234
7/8
✓ Branch 17 → 18 taken 50 times.
✓ Branch 18 → 19 taken 49 times.
✓ Branch 18 → 21 taken 1 time.
✓ Branch 19 → 20 taken 49 times.
✗ Branch 20 → 21 not taken.
✓ Branch 20 → 22 taken 49 times.
✓ Branch 23 → 24 taken 1 time.
✓ Branch 23 → 33 taken 49 times.
50 if (!info.isFile() || !info.isReadable()) {
235
4/4
✓ Branch 24 → 25 taken 1 time.
✓ Branch 26 → 27 taken 1 time.
✓ Branch 27 → 28 taken 1 time.
✓ Branch 28 → 29 taken 1 time.
2 setNotice(tr("%1 cannot be read.").arg(info.fileName()));
236 1 continue;
237 }
238
5/5
✓ Branch 33 → 34 taken 49 times.
✓ Branch 34 → 35 taken 49 times.
✓ Branch 35 → 36 taken 49 times.
✓ Branch 37 → 38 taken 8 times.
✓ Branch 37 → 40 taken 41 times.
49 if (!admit(info.fileName(), info.size(), &reason)) {
239
1/1
✓ Branch 38 → 39 taken 8 times.
8 setNotice(reason);
240 8 continue;
241 }
242
1/1
✓ Branch 40 → 41 taken 41 times.
41 Item item;
243
2/2
✓ Branch 41 → 42 taken 41 times.
✓ Branch 42 → 43 taken 41 times.
41 item.id = QUuid::createUuid().toString(QUuid::WithoutBraces);
244
1/1
✓ Branch 45 → 46 taken 41 times.
41 item.name = info.fileName();
245
1/1
✓ Branch 48 → 49 taken 41 times.
41 item.sourcePath = info.absoluteFilePath();
246
1/1
✓ Branch 51 → 52 taken 41 times.
41 item.storedPath = uniqueTarget(item.name);
247
1/1
✓ Branch 54 → 55 taken 41 times.
41 item.size = info.size();
248
3/3
✓ Branch 56 → 57 taken 41 times.
✓ Branch 57 → 58 taken 41 times.
✓ Branch 58 → 59 taken 41 times.
41 item.mimeType = QString::fromLatin1(Rfc2822Builder::mimeTypeForFile(info.fileName()));
249
1/1
✓ Branch 65 → 66 taken 41 times.
41 item.image = item.mimeType.startsWith(QLatin1String("image/"));
250
1/1
✓ Branch 69 → 70 taken 41 times.
41 item.contentId = item.id + QStringLiteral("@mailjd");
251
1/1
✓ Branch 77 → 78 taken 41 times.
41 beginInsertRows({}, int(m_items.size()), int(m_items.size()));
252
1/1
✓ Branch 78 → 79 taken 41 times.
41 m_items.append(item);
253
1/1
✓ Branch 79 → 80 taken 41 times.
41 endInsertRows();
254
1/1
✓ Branch 80 → 81 taken 41 times.
41 startCopy(item.id, item.sourcePath, true);
255 41 ++added;
256 61 }
257 31 emit changed();
258 31 return added;
259 }
260
261 8 QString ComposeAttachmentModel::addData(const QString &name, const QByteArray &data, const QString &mimeType,
262 const QString &wantedId) {
263 8 QString reason;
264
2/3
✓ Branch 3 → 4 taken 8 times.
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 7 taken 8 times.
8 if (!admit(name, data.size(), &reason)) {
265 ✗ setNotice(reason);
266 ✗ return {};
267 }
268
1/1
✓ Branch 7 → 8 taken 8 times.
8 Item item;
269
7/10
✓ Branch 9 → 10 taken 2 times.
✓ Branch 9 → 13 taken 6 times.
✗ Branch 12 → 13 not taken.
✓ Branch 12 → 16 taken 2 times.
✓ Branch 13 → 14 taken 6 times.
✓ Branch 14 → 15 taken 6 times.
✓ Branch 19 → 20 taken 2 times.
✓ Branch 19 → 21 taken 6 times.
✗ Branch 83 → 84 not taken.
✗ Branch 83 → 85 not taken.
14 item.id = wantedId.isEmpty() || this->item(wantedId) ? QUuid::createUuid().toString(QUuid::WithoutBraces) : wantedId;
270
1/1
✓ Branch 22 → 23 taken 8 times.
8 item.name = safeFileName(name);
271
1/1
✓ Branch 26 → 27 taken 8 times.
8 item.storedPath = uniqueTarget(item.name);
272 8 item.size = data.size();
273
3/13
✗ Branch 31 → 32 not taken.
✓ Branch 31 → 35 taken 8 times.
✗ Branch 32 → 33 not taken.
✗ Branch 33 → 34 not taken.
✗ Branch 34 → 36 not taken.
✗ Branch 38 → 39 not taken.
✓ Branch 38 → 40 taken 8 times.
✗ Branch 40 → 41 not taken.
✓ Branch 40 → 42 taken 8 times.
✗ Branch 93 → 94 not taken.
✗ Branch 93 → 95 not taken.
✗ Branch 97 → 98 not taken.
✗ Branch 97 → 99 not taken.
16 item.mimeType = mimeType.isEmpty() ? QString::fromLatin1(Rfc2822Builder::mimeTypeForFile(name, data.left(4096)))
274 8 : mimeType;
275
1/1
✓ Branch 43 → 44 taken 8 times.
8 item.image = item.mimeType.startsWith(QLatin1String("image/"));
276
1/1
✓ Branch 47 → 48 taken 8 times.
8 item.contentId = item.id + QStringLiteral("@mailjd");
277 // In-memory data is written on the worker like a copy.
278
1/1
✓ Branch 52 → 53 taken 8 times.
8 const ComposeImageLocation::Result clean = ComposeImageLocation::stripJpeg(data);
279 8 item.locationRemoved = clean.removed;
280 8 item.size = clean.data.size();
281
1/1
✓ Branch 57 → 58 taken 8 times.
8 beginInsertRows({}, int(m_items.size()), int(m_items.size()));
282
1/1
✓ Branch 58 → 59 taken 8 times.
8 m_items.append(item);
283
1/1
✓ Branch 59 → 60 taken 8 times.
8 endInsertRows();
284
1/1
✓ Branch 60 → 61 taken 8 times.
8 emit changed();
285 8 const QString id = item.id, target = item.storedPath;
286
1/1
✓ Branch 63 → 64 taken 8 times.
8 QPointer<ComposeAttachmentModel> self(this);
287 8 auto alive = m_alive;
288
1/9
✓ Branch 70 → 71 taken 8 times.
✗ Branch 113 → 114 not taken.
✗ Branch 113 → 115 not taken.
✗ Branch 116 → 117 not taken.
✗ Branch 116 → 118 not taken.
✗ Branch 119 → 120 not taken.
✗ Branch 119 → 121 not taken.
✗ Branch 122 → 123 not taken.
✗ Branch 122 → 124 not taken.
8 m_pool.start([self, alive, id, target, bytes = clean.data] {
289
1/1
✓ Branch 2 → 3 taken 8 times.
8 QSaveFile file(target);
290
4/6
✓ Branch 4 → 5 taken 8 times.
✓ Branch 7 → 8 taken 8 times.
✓ Branch 8 → 9 taken 8 times.
✗ Branch 8 → 15 not taken.
✓ Branch 11 → 12 taken 8 times.
✗ Branch 11 → 15 not taken.
24 const bool ok = file.open(QIODevice::WriteOnly) && file.setPermissions(QFile::ReadOwner | QFile::WriteOwner) &&
291
4/6
✓ Branch 5 → 6 taken 8 times.
✗ Branch 5 → 15 not taken.
✓ Branch 9 → 10 taken 8 times.
✓ Branch 12 → 13 taken 8 times.
✓ Branch 13 → 14 taken 8 times.
✗ Branch 13 → 15 not taken.
24 file.write(bytes) == bytes.size() && file.commit();
292
1/3
✓ Branch 16 → 17 taken 8 times.
✗ Branch 16 → 18 not taken.
✗ Branch 18 → 19 not taken.
8 const QString error = ok ? QString() : file.errorString();
293
1/2
✗ Branch 21 → 22 not taken.
✓ Branch 21 → 23 taken 8 times.
8 if (!alive->load()) return;
294
1/5
✓ Branch 27 → 28 taken 8 times.
✗ Branch 43 → 44 not taken.
✗ Branch 43 → 45 not taken.
✗ Branch 46 → 47 not taken.
✗ Branch 46 → 48 not taken.
8 QMetaObject::invokeMethod(self, [self, id, ok, error] {
295
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 8 times.
8 if (!self) return;
296
1/1
✓ Branch 7 → 8 taken 8 times.
8 self->updateItem(id, [&](Item &item) {
297
3/6
✓ Branch 2 → 3 taken 8 times.
✗ Branch 2 → 6 not taken.
✗ Branch 11 → 12 not taken.
✓ Branch 11 → 13 taken 8 times.
✓ Branch 13 → 14 taken 8 times.
✗ Branch 13 → 15 not taken.
16 item.state = ok ? QStringLiteral("ready") : QStringLiteral("failed");
298
1/2
✓ Branch 15 → 16 taken 8 times.
✗ Branch 15 → 17 not taken.
8 item.progress = ok ? 100 : 0;
299 8 item.error = error;
300 8 });
301 8 emit self->copyFinished(id, ok);
302 }, Qt::QueuedConnection);
303
2/4
✓ Branch 31 → 32 taken 8 times.
✗ Branch 31 → 33 not taken.
✓ Branch 35 → 36 taken 8 times.
✗ Branch 35 → 38 not taken.
8 });
304 8 return id;
305 8 }
306
307 63 void ComposeAttachmentModel::updateItem(const QString &id, const std::function<void(Item &)> &change) {
308 63 const int row = indexOf(id);
309
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 63 times.
63 if (row < 0) return;
310 63 change(m_items[row]);
311
3/3
✓ Branch 9 → 10 taken 63 times.
✓ Branch 11 → 12 taken 63 times.
✓ Branch 12 → 13 taken 63 times.
63 emit dataChanged(index(row), index(row));
312 63 emit changed();
313 }
314
315 41 void ComposeAttachmentModel::startCopy(const QString &id, const QString &source, bool stripLocation) {
316 41 const int row = indexOf(id);
317
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 41 times.
41 if (row < 0) return;
318 41 const QString target = m_items.at(row).storedPath;
319
1/1
✓ Branch 7 → 8 taken 41 times.
41 QPointer<ComposeAttachmentModel> self(this);
320 41 auto alive = m_alive;
321
1/11
✓ Branch 14 → 15 taken 41 times.
✗ Branch 24 → 25 not taken.
✗ Branch 24 → 26 not taken.
✗ Branch 27 → 28 not taken.
✗ Branch 27 → 29 not taken.
✗ Branch 30 → 31 not taken.
✗ Branch 30 → 32 not taken.
✗ Branch 33 → 34 not taken.
✗ Branch 33 → 35 not taken.
✗ Branch 36 → 37 not taken.
✗ Branch 36 → 38 not taken.
41 m_pool.start([self, alive, id, source, target, stripLocation] {
322
1/1
✓ Branch 2 → 3 taken 41 times.
41 QFile in(source);
323 41 QString error;
324 41 bool removed = false;
325 41 qint64 written = 0;
326 41 QString mime;
327
2/3
✓ Branch 4 → 5 taken 41 times.
✗ Branch 5 → 6 not taken.
✓ Branch 5 → 10 taken 41 times.
41 if (!in.open(QIODevice::ReadOnly)) {
328 ✗ error = tr("file not readable");
329 } else {
330
1/1
✓ Branch 10 → 11 taken 41 times.
41 const qint64 total = qMax<qint64>(1, in.size());
331
1/1
✓ Branch 12 → 13 taken 41 times.
41 QSaveFile out(target);
332
5/8
✓ Branch 14 → 15 taken 41 times.
✓ Branch 15 → 16 taken 41 times.
✗ Branch 15 → 19 not taken.
✓ Branch 17 → 18 taken 41 times.
✗ Branch 18 → 19 not taken.
✓ Branch 18 → 20 taken 41 times.
✗ Branch 21 → 22 not taken.
✓ Branch 21 → 26 taken 41 times.
41 if (!out.open(QIODevice::WriteOnly) || !out.setPermissions(QFile::ReadOwner | QFile::WriteOwner)) {
333 ✗ error = tr("disk full or not writable");
334 } else {
335
1/1
✓ Branch 26 → 27 taken 41 times.
41 const QByteArray head = in.peek(4096);
336
2/2
✓ Branch 27 → 28 taken 41 times.
✓ Branch 28 → 29 taken 41 times.
41 mime = QString::fromLatin1(Rfc2822Builder::mimeTypeForFile(source, head));
337
6/8
✓ Branch 32 → 33 taken 41 times.
✗ Branch 32 → 38 not taken.
✓ Branch 35 → 36 taken 2 times.
✓ Branch 35 → 38 taken 39 times.
✓ Branch 36 → 37 taken 2 times.
✗ Branch 36 → 38 not taken.
✓ Branch 39 → 40 taken 2 times.
✓ Branch 39 → 53 taken 39 times.
41 if (stripLocation && mime == QLatin1String("image/jpeg") && total <= ComposeImageLocation::MaxBytes) {
338
2/2
✓ Branch 40 → 41 taken 2 times.
✓ Branch 41 → 42 taken 2 times.
2 const auto clean = ComposeImageLocation::stripJpeg(in.readAll());
339 2 removed = clean.removed;
340
2/4
✓ Branch 43 → 44 taken 2 times.
✗ Branch 45 → 46 not taken.
✓ Branch 45 → 50 taken 2 times.
✗ Branch 46 → 47 not taken.
2 if (out.write(clean.data) != clean.data.size()) error = tr("disk full or not writable");
341 2 written = clean.data.size();
342 2 } else {
343 39 int lastPercent = -1;
344
6/7
✓ Branch 83 → 84 taken 80 times.
✓ Branch 84 → 85 taken 41 times.
✓ Branch 84 → 88 taken 39 times.
✓ Branch 86 → 87 taken 41 times.
✗ Branch 86 → 88 not taken.
✓ Branch 89 → 54 taken 41 times.
✓ Branch 89 → 90 taken 39 times.
80 while (!in.atEnd() && error.isEmpty()) {
345
1/2
✗ Branch 56 → 57 not taken.
✓ Branch 56 → 58 taken 41 times.
41 if (!alive->load()) return;
346
1/1
✓ Branch 58 → 59 taken 41 times.
41 const QByteArray chunk = in.read(1024 * 1024);
347
4/8
✓ Branch 60 → 61 taken 41 times.
✗ Branch 60 → 64 not taken.
✓ Branch 61 → 62 taken 41 times.
✗ Branch 63 → 64 not taken.
✓ Branch 63 → 65 taken 41 times.
✗ Branch 66 → 67 not taken.
✓ Branch 66 → 71 taken 41 times.
✗ Branch 67 → 68 not taken.
41 if (chunk.isEmpty() || out.write(chunk) != chunk.size()) error = tr("disk full or not writable");
348 41 written += chunk.size();
349 41 const int percent = int(written * 100 / total);
350
3/4
✓ Branch 72 → 73 taken 41 times.
✗ Branch 72 → 80 not taken.
✓ Branch 73 → 74 taken 4 times.
✓ Branch 73 → 80 taken 37 times.
41 if (percent != lastPercent && percent < 100) {
351 4 lastPercent = percent;
352
1/5
✓ Branch 77 → 78 taken 4 times.
✗ Branch 158 → 159 not taken.
✗ Branch 158 → 160 not taken.
✗ Branch 161 → 162 not taken.
✗ Branch 161 → 163 not taken.
4 QMetaObject::invokeMethod(self, [self, id, percent] {
353
2/3
✓ Branch 3 → 4 taken 4 times.
✗ Branch 3 → 9 not taken.
✓ Branch 6 → 7 taken 4 times.
4 if (self) self->updateItem(id, [percent](Item &item) { item.progress = percent; });
354 4 }, Qt::QueuedConnection);
355 }
356 41 }
357 }
358
4/8
✓ Branch 91 → 92 taken 41 times.
✗ Branch 91 → 95 not taken.
✓ Branch 92 → 93 taken 41 times.
✗ Branch 93 → 94 not taken.
✓ Branch 93 → 95 taken 41 times.
✗ Branch 96 → 97 not taken.
✓ Branch 96 → 101 taken 41 times.
✗ Branch 97 → 98 not taken.
41 if (error.isEmpty() && !out.commit()) error = tr("disk full or not writable");
359 41 }
360 41 }
361
1/2
✗ Branch 115 → 116 not taken.
✓ Branch 115 → 117 taken 41 times.
41 if (!alive->load()) return;
362
1/7
✓ Branch 122 → 123 taken 41 times.
✗ Branch 177 → 178 not taken.
✗ Branch 177 → 179 not taken.
✗ Branch 180 → 181 not taken.
✗ Branch 180 → 182 not taken.
✗ Branch 183 → 184 not taken.
✗ Branch 183 → 185 not taken.
41 QMetaObject::invokeMethod(self, [self, id, error, removed, written, mime] {
363
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 41 times.
41 if (!self) return;
364 41 const bool ok = error.isEmpty();
365
2/2
✓ Branch 7 → 8 taken 41 times.
✓ Branch 8 → 9 taken 41 times.
41 self->updateItem(id, [&](Item &item) {
366
3/6
✓ Branch 2 → 3 taken 41 times.
✗ Branch 2 → 6 not taken.
✗ Branch 11 → 12 not taken.
✓ Branch 11 → 13 taken 41 times.
✓ Branch 13 → 14 taken 41 times.
✗ Branch 13 → 15 not taken.
82 item.state = ok ? QStringLiteral("ready") : QStringLiteral("failed");
367
1/2
✓ Branch 15 → 16 taken 41 times.
✗ Branch 15 → 17 not taken.
41 item.progress = ok ? 100 : 0;
368 41 item.error = error;
369 41 item.locationRemoved = removed;
370
1/2
✓ Branch 19 → 20 taken 41 times.
✗ Branch 19 → 21 not taken.
41 if (ok) item.size = written;
371
1/2
✓ Branch 22 → 23 taken 41 times.
✗ Branch 22 → 27 not taken.
41 if (!mime.isEmpty()) {
372 41 item.mimeType = mime;
373
1/1
✓ Branch 25 → 26 taken 41 times.
41 item.image = mime.startsWith(QLatin1String("image/"));
374 }
375 41 });
376
1/1
✓ Branch 11 → 12 taken 41 times.
41 emit self->copyFinished(id, ok);
377
3/7
✓ Branch 12 → 13 taken 41 times.
✗ Branch 12 → 18 not taken.
✗ Branch 16 → 17 not taken.
✓ Branch 16 → 18 taken 41 times.
✗ Branch 19 → 20 not taken.
✓ Branch 19 → 22 taken 41 times.
✗ Branch 21 → 22 not taken.
41 if (ok && self->m_imageSize != QLatin1String("original")) self->startResize(id);
378 }, Qt::QueuedConnection);
379
3/6
✓ Branch 126 → 127 taken 41 times.
✗ Branch 126 → 128 not taken.
✓ Branch 130 → 131 taken 41 times.
✗ Branch 130 → 132 not taken.
✓ Branch 134 → 135 taken 41 times.
✗ Branch 134 → 137 not taken.
41 });
380 41 }
381
382 ✗ void ComposeAttachmentModel::retry(const QString &id) {
383 ✗ const int row = indexOf(id);
384 ✗ if (row < 0 || m_items.at(row).state != QLatin1String("failed") || m_items.at(row).sourcePath.isEmpty()) return;
385 ✗ updateItem(id, [](Item &item) {
386 ✗ item.state = QStringLiteral("copying");
387 ✗ item.error.clear();
388 ✗ item.progress = 0;
389 ✗ });
390 ✗ startCopy(id, m_items.at(row).sourcePath, !m_items.at(row).keepLocation);
391 }
392
393 ✗ void ComposeAttachmentModel::keepLocation(const QString &id) {
394 ✗ const int row = indexOf(id);
395 ✗ if (row < 0 || m_items.at(row).sourcePath.isEmpty()) return;
396 ✗ updateItem(id, [](Item &item) {
397 ✗ item.keepLocation = true;
398 ✗ item.state = QStringLiteral("copying");
399 ✗ item.progress = 0;
400 ✗ });
401 ✗ startCopy(id, m_items.at(row).sourcePath, false);
402 }
403
404 1 void ComposeAttachmentModel::remove(const QString &id) {
405 1 const int row = indexOf(id);
406
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 1 time.
1 if (row < 0) return;
407
1/1
✓ Branch 6 → 7 taken 1 time.
1 beginRemoveRows({}, row, row);
408
2/2
✓ Branch 7 → 8 taken 1 time.
✓ Branch 9 → 10 taken 1 time.
1 m_removed.append({row, m_items.takeAt(row)});
409
1/1
✓ Branch 12 → 13 taken 1 time.
1 endRemoveRows();
410 // Files of removed cards stay until the undo window passed (removeAll).
411
1/1
✓ Branch 13 → 14 taken 1 time.
1 emit changed();
412 }
413
414 1 void ComposeAttachmentModel::undoRemove() {
415
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 1 time.
1 if (m_removed.isEmpty()) return;
416
1/1
✓ Branch 5 → 6 taken 1 time.
1 auto [row, item] = m_removed.takeLast();
417 1 row = qBound(0, row, int(m_items.size()));
418
1/1
✓ Branch 11 → 12 taken 1 time.
1 beginInsertRows({}, row, row);
419
1/1
✓ Branch 12 → 13 taken 1 time.
1 m_items.insert(row, item);
420
1/1
✓ Branch 13 → 14 taken 1 time.
1 endInsertRows();
421
1/1
✓ Branch 14 → 15 taken 1 time.
1 emit changed();
422 1 }
423
424 1 bool ComposeAttachmentModel::rename(const QString &id, const QString &name) {
425
1/1
✓ Branch 3 → 4 taken 1 time.
1 const QString clean = safeFileName(name);
426
3/6
✓ Branch 6 → 7 taken 1 time.
✗ Branch 6 → 9 not taken.
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 10 taken 1 time.
✗ Branch 11 → 12 not taken.
✓ Branch 11 → 13 taken 1 time.
1 if (indexOf(id) < 0 || clean.isEmpty()) return false;
427
1/1
✓ Branch 14 → 15 taken 1 time.
2 updateItem(id, [&clean](Item &item) { item.name = clean; });
428 1 return true;
429 1 }
430
431 7 void ComposeAttachmentModel::setInline(const QString &id, bool placed) {
432
2/4
✓ Branch 2 → 3 taken 7 times.
✗ Branch 2 → 5 not taken.
✓ Branch 3 → 4 taken 14 times.
✗ Branch 3 → 5 not taken.
14 updateItem(id, [placed](Item &item) { item.inlinePlaced = placed && item.image; });
433 7 }
434
435 3 void ComposeAttachmentModel::setImageSize(const QString &size) {
436
4/4
✓ Branch 3 → 4 taken 2 times.
✓ Branch 3 → 5 taken 1 time.
✓ Branch 8 → 9 taken 1 time.
✓ Branch 8 → 10 taken 2 times.
4 const QString value = stepPixels(size) > 0 ? size : QStringLiteral("original");
437
1/2
✗ Branch 11 → 12 not taken.
✓ Branch 11 → 13 taken 3 times.
3 if (m_imageSize == value) return;
438 3 m_imageSize = value;
439 3 ++m_resizeGeneration;
440
2/2
✓ Branch 28 → 17 taken 3 times.
✓ Branch 28 → 29 taken 3 times.
6 for (const Item &item : std::as_const(m_items))
441
4/7
✓ Branch 18 → 19 taken 3 times.
✗ Branch 18 → 23 not taken.
✓ Branch 21 → 22 taken 3 times.
✗ Branch 21 → 23 not taken.
✓ Branch 24 → 25 taken 3 times.
✗ Branch 24 → 26 not taken.
✓ Branch 25 → 26 taken 3 times.
3 if (item.image && item.state == QLatin1String("ready")) startResize(item.id);
442
1/1
✓ Branch 29 → 30 taken 3 times.
3 emit changed();
443 3 }
444
445 3 void ComposeAttachmentModel::startResize(const QString &id) {
446 3 const int row = indexOf(id);
447
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 3 times.
4 if (row < 0) return;
448 3 const Item item = m_items.at(row);
449
1/2
✗ Branch 7 → 8 not taken.
✓ Branch 7 → 9 taken 3 times.
3 if (!item.image) return;
450 3 const int pixels = stepPixels(m_imageSize);
451
2/2
✓ Branch 10 → 11 taken 1 time.
✓ Branch 10 → 18 taken 2 times.
3 if (pixels == 0) {
452
2/3
✓ Branch 12 → 13 taken 1 time.
✗ Branch 12 → 14 not taken.
✓ Branch 13 → 14 taken 1 time.
1 if (!item.resizedPath.isEmpty()) QFile::remove(item.resizedPath);
453
1/1
✓ Branch 15 → 16 taken 1 time.
1 updateItem(id, [](Item &value) {
454 1 value.resizedPath.clear();
455 1 value.resizedSize = 0;
456 1 });
457 1 return;
458 }
459
3/3
✓ Branch 30 → 31 taken 2 times.
✓ Branch 31 → 32 taken 2 times.
✓ Branch 32 → 33 taken 2 times.
4 const QString target = uniqueTarget(QFileInfo(item.name).completeBaseName() + QStringLiteral("-") +
460
3/5
✓ Branch 20 → 21 taken 1 time.
✓ Branch 20 → 24 taken 1 time.
✓ Branch 33 → 34 taken 2 times.
✗ Branch 99 → 100 not taken.
✗ Branch 99 → 101 not taken.
11 m_imageSize + (item.mimeType == QLatin1String("image/png") ? QStringLiteral(".png")
461
6/8
✓ Branch 34 → 35 taken 2 times.
✓ Branch 35 → 36 taken 2 times.
✓ Branch 44 → 45 taken 1 time.
✓ Branch 44 → 46 taken 1 time.
✓ Branch 46 → 47 taken 1 time.
✓ Branch 46 → 48 taken 1 time.
✗ Branch 95 → 96 not taken.
✗ Branch 95 → 97 not taken.
4 : QStringLiteral(".jpg")));
462 2 const quint64 generation = m_resizeGeneration;
463
1/1
✓ Branch 48 → 49 taken 2 times.
2 QPointer<ComposeAttachmentModel> self(this);
464 2 auto alive = m_alive;
465
1/11
✓ Branch 57 → 58 taken 2 times.
✗ Branch 109 → 110 not taken.
✗ Branch 109 → 111 not taken.
✗ Branch 112 → 113 not taken.
✗ Branch 112 → 114 not taken.
✗ Branch 115 → 116 not taken.
✗ Branch 115 → 117 not taken.
✗ Branch 118 → 119 not taken.
✗ Branch 118 → 120 not taken.
✗ Branch 121 → 122 not taken.
✗ Branch 121 → 123 not taken.
2 m_pool.start([self, alive, id, source = item.storedPath, target, pixels, generation, png = item.mimeType == QLatin1String("image/png")] {
466
1/1
✓ Branch 3 → 4 taken 2 times.
2 QImageReader reader(source);
467
1/1
✓ Branch 5 → 6 taken 2 times.
2 reader.setAutoTransform(true);
468
1/1
✓ Branch 6 → 7 taken 2 times.
2 const QSize original = reader.size();
469 2 QString resultPath;
470 2 qint64 resultSize = 0;
471
3/6
✓ Branch 8 → 9 taken 2 times.
✗ Branch 8 → 14 not taken.
✓ Branch 12 → 13 taken 2 times.
✗ Branch 12 → 14 not taken.
✓ Branch 15 → 16 taken 2 times.
✗ Branch 15 → 56 not taken.
2 if (original.isValid() && qMax(original.width(), original.height()) > pixels) {
472 2 QSize scaled = original;
473 2 scaled.scale(pixels, pixels, Qt::KeepAspectRatio);
474
1/1
✓ Branch 17 → 18 taken 2 times.
2 reader.setScaledSize(scaled);
475
1/1
✓ Branch 18 → 19 taken 2 times.
2 const QImage image = reader.read();
476
1/1
✓ Branch 19 → 20 taken 2 times.
2 QSaveFile out(target);
477
7/11
✓ Branch 20 → 21 taken 2 times.
✓ Branch 21 → 22 taken 2 times.
✗ Branch 21 → 29 not taken.
✓ Branch 23 → 24 taken 2 times.
✓ Branch 24 → 25 taken 2 times.
✗ Branch 24 → 29 not taken.
✓ Branch 26 → 27 taken 2 times.
✓ Branch 27 → 28 taken 2 times.
✗ Branch 27 → 29 not taken.
✓ Branch 30 → 31 taken 2 times.
✗ Branch 30 → 53 not taken.
2 if (!image.isNull() && out.open(QIODevice::WriteOnly) && out.setPermissions(QFile::ReadOwner | QFile::WriteOwner)) {
478
4/4
✓ Branch 31 → 32 taken 1 time.
✓ Branch 31 → 33 taken 1 time.
✓ Branch 34 → 35 taken 2 times.
✓ Branch 35 → 36 taken 2 times.
2 QImageWriter writer(&out, png ? "png" : "jpeg");
479
3/3
✓ Branch 37 → 38 taken 1 time.
✓ Branch 37 → 39 taken 1 time.
✓ Branch 38 → 39 taken 1 time.
2 if (!png) writer.setQuality(85);
480
5/8
✓ Branch 39 → 40 taken 2 times.
✓ Branch 40 → 41 taken 2 times.
✗ Branch 40 → 44 not taken.
✓ Branch 41 → 42 taken 2 times.
✓ Branch 42 → 43 taken 2 times.
✗ Branch 42 → 44 not taken.
✓ Branch 45 → 46 taken 2 times.
✗ Branch 45 → 51 not taken.
2 if (writer.write(image) && out.commit()) {
481 2 resultPath = target;
482
2/2
✓ Branch 47 → 48 taken 2 times.
✓ Branch 48 → 49 taken 2 times.
2 resultSize = QFileInfo(target).size();
483 }
484 2 }
485 2 }
486
1/2
✗ Branch 58 → 59 not taken.
✓ Branch 58 → 60 taken 2 times.
2 if (!alive->load()) return;
487
1/7
✓ Branch 64 → 65 taken 2 times.
✗ Branch 97 → 98 not taken.
✗ Branch 97 → 99 not taken.
✗ Branch 100 → 101 not taken.
✗ Branch 100 → 102 not taken.
✗ Branch 103 → 104 not taken.
✗ Branch 103 → 105 not taken.
2 QMetaObject::invokeMethod(self, [self, id, resultPath, resultSize, generation] {
488
3/6
✓ Branch 3 → 4 taken 1 time.
✗ Branch 3 → 6 not taken.
✗ Branch 5 → 6 not taken.
✓ Branch 5 → 7 taken 1 time.
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 13 taken 1 time.
1 if (!self || generation != self->m_resizeGeneration) {
489 ✗ if (!resultPath.isEmpty()) QFile::remove(resultPath);
490 ✗ return;
491 }
492
1/1
✓ Branch 15 → 16 taken 1 time.
1 self->updateItem(id, [&](Item &item) {
493
2/6
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 7 taken 1 time.
✗ Branch 5 → 6 not taken.
✗ Branch 5 → 7 not taken.
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 10 taken 1 time.
1 if (!item.resizedPath.isEmpty() && item.resizedPath != resultPath) QFile::remove(item.resizedPath);
494 1 item.resizedPath = resultPath; // empty: already small enough
495 1 item.resizedSize = resultSize;
496 1 });
497 }, Qt::QueuedConnection);
498
2/4
✓ Branch 68 → 69 taken 2 times.
✗ Branch 68 → 70 not taken.
✓ Branch 72 → 73 taken 2 times.
✗ Branch 72 → 75 not taken.
2 });
499 3 }
500
501 1114 QJsonArray ComposeAttachmentModel::manifest() const {
502 1114 QJsonArray out;
503
2/2
✓ Branch 73 → 5 taken 265 times.
✓ Branch 73 → 74 taken 1114 times.
1379 for (const Item &item : m_items) {
504
2/2
✓ Branch 8 → 9 taken 44 times.
✓ Branch 8 → 10 taken 221 times.
265 if (item.state != QLatin1String("ready")) continue;
505
1/1
✓ Branch 46 → 47 taken 221 times.
663 out.append(QJsonObject{{QStringLiteral("id"), item.id},
506 442 {QStringLiteral("name"), item.name},
507
2/2
✓ Branch 21 → 22 taken 221 times.
✓ Branch 22 → 23 taken 221 times.
442 {QStringLiteral("file"), QFileInfo(item.storedPath).fileName()},
508 442 {QStringLiteral("mimeType"), item.mimeType},
509 442 {QStringLiteral("size"), item.size},
510 442 {QStringLiteral("inline"), item.inlinePlaced},
511 442 {QStringLiteral("contentId"), item.contentId},
512
2/4
✓ Branch 50 → 51 taken 1768 times.
✓ Branch 50 → 52 taken 221 times.
✗ Branch 81 → 82 not taken.
✗ Branch 81 → 83 not taken.
2431 {QStringLiteral("locationRemoved"), item.locationRemoved}});
513 }
514 1114 return out;
515 1989 }
516
517 8 void ComposeAttachmentModel::restore(const QJsonArray &manifest) {
518 8 beginResetModel();
519 8 m_items.clear();
520
3/3
✓ Branch 5 → 6 taken 8 times.
✓ Branch 172 → 7 taken 4 times.
✓ Branch 172 → 173 taken 8 times.
12 for (const QJsonValue &value : manifest) {
521
1/2
✗ Branch 10 → 11 not taken.
✓ Branch 10 → 12 taken 4 times.
4 if (m_items.size() >= MaxFiles) break;
522
1/1
✓ Branch 12 → 13 taken 4 times.
4 const QJsonObject entry = value.toObject();
523
2/2
✓ Branch 16 → 17 taken 4 times.
✓ Branch 17 → 18 taken 4 times.
4 const QString file = entry.value(QStringLiteral("file")).toString();
524 // Only files inside this draft's directory.
525
10/13
✓ Branch 22 → 23 taken 4 times.
✗ Branch 22 → 35 not taken.
✓ Branch 25 → 26 taken 4 times.
✓ Branch 26 → 27 taken 3 times.
✓ Branch 26 → 35 taken 1 time.
✓ Branch 29 → 30 taken 3 times.
✓ Branch 30 → 31 taken 3 times.
✗ Branch 30 → 35 not taken.
✓ Branch 33 → 34 taken 3 times.
✗ Branch 34 → 35 not taken.
✓ Branch 34 → 36 taken 3 times.
✓ Branch 37 → 38 taken 1 time.
✓ Branch 37 → 39 taken 3 times.
4 if (file.isEmpty() || file.contains(QLatin1Char('/')) || file.contains(QLatin1Char('\\')) || file.startsWith(QLatin1Char('.')))
526 1 continue;
527
1/1
✓ Branch 39 → 40 taken 3 times.
3 Item item;
528
3/3
✓ Branch 43 → 44 taken 3 times.
✓ Branch 44 → 45 taken 3 times.
✓ Branch 45 → 46 taken 3 times.
3 item.id = entry.value(QStringLiteral("id")).toString().left(64);
529
3/3
✓ Branch 55 → 56 taken 3 times.
✓ Branch 56 → 57 taken 3 times.
✓ Branch 57 → 58 taken 3 times.
3 item.name = safeFileName(entry.value(QStringLiteral("name")).toString());
530
2/2
✓ Branch 64 → 65 taken 3 times.
✓ Branch 65 → 66 taken 3 times.
3 item.storedPath = QDir(m_directory).filePath(file);
531
3/3
✓ Branch 72 → 73 taken 3 times.
✓ Branch 73 → 74 taken 3 times.
✓ Branch 74 → 75 taken 3 times.
3 item.mimeType = entry.value(QStringLiteral("mimeType")).toString().left(128);
532
1/1
✓ Branch 82 → 83 taken 3 times.
3 item.image = item.mimeType.startsWith(QLatin1String("image/"));
533
6/18
✓ Branch 86 → 87 taken 3 times.
✓ Branch 87 → 88 taken 3 times.
✗ Branch 88 → 89 not taken.
✓ Branch 88 → 91 taken 3 times.
✗ Branch 89 → 90 not taken.
✗ Branch 89 → 91 not taken.
✓ Branch 92 → 93 taken 3 times.
✗ Branch 92 → 94 not taken.
✓ Branch 94 → 95 taken 3 times.
✗ Branch 94 → 96 not taken.
✓ Branch 96 → 97 taken 3 times.
✗ Branch 96 → 98 not taken.
✗ Branch 235 → 236 not taken.
✗ Branch 235 → 237 not taken.
✗ Branch 239 → 240 not taken.
✗ Branch 239 → 241 not taken.
✗ Branch 243 → 244 not taken.
✗ Branch 243 → 245 not taken.
6 item.inlinePlaced = entry.value(QStringLiteral("inline")).toBool() && item.image;
534
3/3
✓ Branch 101 → 102 taken 3 times.
✓ Branch 102 → 103 taken 3 times.
✓ Branch 103 → 104 taken 3 times.
3 item.contentId = entry.value(QStringLiteral("contentId")).toString().left(200);
535
2/2
✓ Branch 113 → 114 taken 3 times.
✓ Branch 114 → 115 taken 3 times.
3 item.locationRemoved = entry.value(QStringLiteral("locationRemoved")).toBool();
536
1/1
✓ Branch 118 → 119 taken 3 times.
3 const QFileInfo info(item.storedPath);
537
1/1
✓ Branch 119 → 120 taken 3 times.
3 item.size = info.size();
538
4/7
✓ Branch 120 → 121 taken 3 times.
✓ Branch 121 → 122 taken 3 times.
✗ Branch 121 → 125 not taken.
✗ Branch 130 → 131 not taken.
✓ Branch 130 → 132 taken 3 times.
✓ Branch 132 → 133 taken 3 times.
✗ Branch 132 → 134 not taken.
6 item.state = info.isFile() ? QStringLiteral("ready") : QStringLiteral("failed");
539
1/2
✓ Branch 136 → 137 taken 3 times.
✗ Branch 136 → 138 not taken.
3 item.progress = item.state == QLatin1String("ready") ? 100 : 0;
540
2/4
✓ Branch 139 → 140 taken 3 times.
✗ Branch 140 → 141 not taken.
✓ Branch 140 → 145 taken 3 times.
✗ Branch 141 → 142 not taken.
3 if (!info.isFile()) item.error = tr("the saved copy is missing");
541
1/4
✗ Branch 146 → 147 not taken.
✓ Branch 146 → 152 taken 3 times.
✗ Branch 147 → 148 not taken.
✗ Branch 148 → 149 not taken.
3 if (item.id.isEmpty()) item.id = QUuid::createUuid().toString(QUuid::WithoutBraces);
542
1/1
✓ Branch 152 → 153 taken 3 times.
3 m_items.append(item);
543 6 }
544 8 endResetModel();
545 8 emit changed();
546 8 }
547
548 48 void ComposeAttachmentModel::removeAll() {
549 48 m_alive->store(false);
550
1/1
✓ Branch 5 → 6 taken 48 times.
48 m_pool.waitForDone();
551
1/1
✓ Branch 6 → 7 taken 48 times.
48 m_alive = std::make_shared<std::atomic_bool>(true);
552 48 beginResetModel();
553 48 m_items.clear();
554 48 m_removed.clear();
555 48 endResetModel();
556
2/2
✓ Branch 13 → 14 taken 48 times.
✓ Branch 14 → 15 taken 48 times.
48 QDir(m_directory).removeRecursively();
557 48 emit changed();
558 48 }
559
560 7 void ComposeAttachmentModel::waitForJobs() {
561
1/1
✓ Branch 3 → 4 taken 7 times.
7 m_pool.waitForDone();
562
1/1
✓ Branch 5 → 6 taken 7 times.
7 QCoreApplication::processEvents();
563 7 }
564