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 ¬ice) { | |
| 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 |