File indexing completed on 2025-03-09 04:54:39

0001 /*
0002     SPDX-FileCopyrightText: 2007, 2008 Volker Krause <vkrause@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "mimetreemodel.h"
0008 #include "messageviewer_debug.h"
0009 
0010 #include <MimeTreeParser/NodeHelper>
0011 #include <MimeTreeParser/Util>
0012 
0013 #include <KMime/Content>
0014 #include <KMime/Message>
0015 
0016 #include <KIO/Global>
0017 #include <KLocalizedString>
0018 
0019 #include <QIcon>
0020 #include <QMimeData>
0021 #include <QMimeDatabase>
0022 #include <QTemporaryDir>
0023 #include <QUrl>
0024 
0025 using namespace MessageViewer;
0026 
0027 class Q_DECL_HIDDEN MimeTreeModel::MimeTreeModelPrivate
0028 {
0029 public:
0030     MimeTreeModelPrivate() = default;
0031 
0032     ~MimeTreeModelPrivate()
0033     {
0034         qDeleteAll(tempDirs);
0035     }
0036 
0037     void clearTempDir()
0038     {
0039         qDeleteAll(tempDirs);
0040         tempDirs.clear();
0041     }
0042 
0043     QString descriptionForContent(KMime::Content *content)
0044     {
0045         auto const message = dynamic_cast<KMime::Message *>(content);
0046         if (message && message->subject(false)) {
0047             return message->subject(false)->asUnicodeString();
0048         }
0049         const QString name = MimeTreeParser::NodeHelper::fileName(content);
0050         if (!name.isEmpty()) {
0051             return name;
0052         }
0053         if (auto ct = content->contentDescription(false)) {
0054             const QString desc = ct->asUnicodeString();
0055             if (!desc.isEmpty()) {
0056                 return desc;
0057             }
0058         }
0059         return i18n("body part");
0060     }
0061 
0062     QString mimeTypeForContent(KMime::Content *content)
0063     {
0064         if (auto ct = content->contentType(false)) {
0065             return QString::fromLatin1(ct->mimeType());
0066         }
0067         return {};
0068     }
0069 
0070     QString typeForContent(KMime::Content *content)
0071     {
0072         if (auto ct = content->contentType(false)) {
0073             const QString contentMimeType = QString::fromLatin1(ct->mimeType());
0074             const auto mimeType = m_mimeDb.mimeTypeForName(contentMimeType);
0075             if (!mimeType.isValid()) {
0076                 return contentMimeType;
0077             }
0078             return mimeType.comment();
0079         } else {
0080             return {};
0081         }
0082     }
0083 
0084     QString sizeOfContent(KMime::Content *content)
0085     {
0086         if (content->body().isEmpty()) {
0087             return {};
0088         }
0089         return KIO::convertSize(content->body().size());
0090     }
0091 
0092     QIcon iconForContent(KMime::Content *content)
0093     {
0094         if (auto ct = content->contentType(false)) {
0095             auto iconName = MimeTreeParser::Util::iconNameForMimetype(QLatin1StringView(ct->mimeType()));
0096 
0097             auto mimeType = m_mimeDb.mimeTypeForName(QString::fromLatin1(ct->mimeType()));
0098             if (!mimeType.isValid() || mimeType.name() == QLatin1StringView("application/octet-stream")) {
0099                 const QString name = descriptionForContent(content);
0100                 mimeType = MimeTreeParser::Util::mimetype(name);
0101             }
0102 
0103             if (mimeType.isValid() && mimeType.name().startsWith(QLatin1StringView("multipart/"))) {
0104                 return QIcon::fromTheme(QStringLiteral("folder"));
0105             } else if (!iconName.isEmpty() && iconName != QLatin1StringView("unknown")) {
0106                 return QIcon::fromTheme(iconName);
0107             } else if (mimeType.isValid() && !mimeType.iconName().isEmpty()) {
0108                 return QIcon::fromTheme(mimeType.iconName());
0109             }
0110 
0111             return {};
0112         } else {
0113             return {};
0114         }
0115     }
0116 
0117     QList<QTemporaryDir *> tempDirs;
0118     KMime::Content *root = nullptr;
0119     QMimeDatabase m_mimeDb;
0120 };
0121 
0122 MimeTreeModel::MimeTreeModel(QObject *parent)
0123     : QAbstractItemModel(parent)
0124     , d(new MimeTreeModelPrivate)
0125 {
0126 }
0127 
0128 MimeTreeModel::~MimeTreeModel() = default;
0129 
0130 void MimeTreeModel::setRoot(KMime::Content *root)
0131 {
0132     if (d->root != root) {
0133         beginResetModel();
0134         d->clearTempDir();
0135         d->root = root;
0136         endResetModel();
0137     }
0138 }
0139 
0140 KMime::Content *MimeTreeModel::root()
0141 {
0142     return d->root;
0143 }
0144 
0145 QModelIndex MimeTreeModel::index(int row, int column, const QModelIndex &parent) const
0146 {
0147     if (!parent.isValid()) {
0148         if (row != 0) {
0149             return {};
0150         }
0151         return createIndex(row, column, d->root);
0152     }
0153 
0154     auto parentContent = static_cast<KMime::Content *>(parent.internalPointer());
0155     if (!parentContent || parentContent->contents().count() <= row || row < 0) {
0156         return {};
0157     }
0158     KMime::Content *content = parentContent->contents().at(row);
0159     return createIndex(row, column, content);
0160 }
0161 
0162 QModelIndex MimeTreeModel::parent(const QModelIndex &index) const
0163 {
0164     if (!index.isValid()) {
0165         return {};
0166     }
0167     auto currentContent = static_cast<KMime::Content *>(index.internalPointer());
0168     if (!currentContent) {
0169         return {};
0170     }
0171 
0172     KMime::ContentIndex currentIndex = d->root->indexForContent(currentContent);
0173     if (!currentIndex.isValid()) {
0174         return {};
0175     }
0176     currentIndex.up();
0177     KMime::Content *parentContent = d->root->content(currentIndex);
0178     int row = 0;
0179     if (currentIndex.isValid()) {
0180         row = currentIndex.up() - 1; // 1 based -> 0 based
0181     }
0182 
0183     return createIndex(row, 0, parentContent);
0184 }
0185 
0186 int MimeTreeModel::rowCount(const QModelIndex &parent) const
0187 {
0188     if (!d->root) {
0189         return 0;
0190     }
0191     if (!parent.isValid()) {
0192         return 1;
0193     }
0194     auto parentContent = static_cast<KMime::Content *>(parent.internalPointer());
0195     if (parentContent) {
0196         return parentContent->contents().count();
0197     }
0198     return 0;
0199 }
0200 
0201 int MimeTreeModel::columnCount(const QModelIndex &parent) const
0202 {
0203     Q_UNUSED(parent)
0204     return 3;
0205 }
0206 
0207 QVariant MimeTreeModel::data(const QModelIndex &index, int role) const
0208 {
0209     auto content = static_cast<KMime::Content *>(index.internalPointer());
0210     if (!content) {
0211         return {};
0212     }
0213     if (role == Qt::ToolTipRole) {
0214         // TODO
0215         // return d->root->indexForContent( content ).toString();
0216         return {};
0217     }
0218     if (role == Qt::DisplayRole) {
0219         switch (index.column()) {
0220         case 0:
0221             return d->descriptionForContent(content);
0222         case 1:
0223             return d->typeForContent(content);
0224         case 2:
0225             return d->sizeOfContent(content);
0226         }
0227     }
0228     if (role == Qt::DecorationRole && index.column() == 0) {
0229         return d->iconForContent(content);
0230     }
0231     if (role == ContentIndexRole) {
0232         return QVariant::fromValue(d->root->indexForContent(content));
0233     }
0234     if (role == ContentRole) {
0235         return QVariant::fromValue(content);
0236     }
0237     if (role == MimeTypeRole) {
0238         return d->mimeTypeForContent(content);
0239     }
0240     if (role == MainBodyPartRole) {
0241         auto topLevelMsg = dynamic_cast<KMime::Message *>(d->root);
0242         if (!topLevelMsg) {
0243             return false;
0244         }
0245         return topLevelMsg->mainBodyPart() == content;
0246     }
0247     if (role == AlternativeBodyPartRole) {
0248         auto topLevelMsg = dynamic_cast<KMime::Message *>(d->root);
0249         if (!topLevelMsg) {
0250             return false;
0251         }
0252         return topLevelMsg->mainBodyPart(content->contentType()->mimeType()) == content;
0253     }
0254     return {};
0255 }
0256 
0257 QVariant MimeTreeModel::headerData(int section, Qt::Orientation orientation, int role) const
0258 {
0259     if (orientation == Qt::Horizontal && role == Qt::DisplayRole) {
0260         switch (section) {
0261         case 0:
0262             return i18n("Description");
0263         case 1:
0264             return i18n("Type");
0265         case 2:
0266             return i18n("Size");
0267         }
0268     }
0269     return QAbstractItemModel::headerData(section, orientation, role);
0270 }
0271 
0272 QMimeData *MimeTreeModel::mimeData(const QModelIndexList &indexes) const
0273 {
0274     QList<QUrl> urls;
0275     for (const QModelIndex &index : indexes) {
0276         // create dnd for one item not for all three columns.
0277         if (index.column() != 0) {
0278             continue;
0279         }
0280         auto content = static_cast<KMime::Content *>(index.internalPointer());
0281         if (!content) {
0282             continue;
0283         }
0284         const QByteArray data = content->decodedContent();
0285         if (data.isEmpty()) {
0286             continue;
0287         }
0288 
0289         auto tempDir = new QTemporaryDir; // Will remove the directory on destruction.
0290         d->tempDirs.append(tempDir);
0291         const QString fileName = tempDir->path() + QLatin1Char('/') + d->descriptionForContent(content);
0292         QFile f(fileName);
0293         if (!f.open(QIODevice::WriteOnly)) {
0294             qCWarning(MESSAGEVIEWER_LOG) << "Cannot write attachment:" << f.errorString();
0295             continue;
0296         }
0297         if (f.write(data) != data.length()) {
0298             qCWarning(MESSAGEVIEWER_LOG) << "Failed to write all data to file!";
0299             continue;
0300         }
0301         f.close();
0302 
0303         const QUrl url = QUrl::fromLocalFile(fileName);
0304         qCDebug(MESSAGEVIEWER_LOG) << " temporary file " << url;
0305         urls.append(url);
0306     }
0307 
0308     auto mimeData = new QMimeData;
0309     mimeData->setUrls(urls);
0310     return mimeData;
0311 }
0312 
0313 Qt::ItemFlags MimeTreeModel::flags(const QModelIndex &index) const
0314 {
0315     Qt::ItemFlags defaultFlags = QAbstractItemModel::flags(index);
0316     return Qt::ItemIsDragEnabled | defaultFlags;
0317 }
0318 
0319 QStringList MimeTreeModel::mimeTypes() const
0320 {
0321     const QStringList types = {QStringLiteral("text/uri-list")};
0322     return types;
0323 }
0324 
0325 #include "moc_mimetreemodel.cpp"