File indexing completed on 2024-05-19 04:35:50

0001 // SPDX-FileCopyrightText: 2021 Carl Schwan <carlschwan@kde.org>
0002 // SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
0003 
0004 #include "imagemodel.h"
0005 
0006 #include "config.h"
0007 #include "consolelog.h"
0008 #include "optimizer.h"
0009 #include <KIO/JobUiDelegateFactory>
0010 #include <KIO/OpenFileManagerWindowJob>
0011 #include <KIO/OpenUrlJob>
0012 #include <KPropertiesDialog>
0013 #include <QDebug>
0014 #include <QFileInfo>
0015 #include <QMimeDatabase>
0016 
0017 using namespace Qt::Literals::StringLiterals;
0018 
0019 ImageModel::ImageModel(QObject *parent)
0020     : QAbstractListModel(parent)
0021 {
0022 }
0023 
0024 QVariant ImageModel::data(const QModelIndex &index, int role) const
0025 {
0026     Q_ASSERT(checkIndex(index, QAbstractItemModel::CheckIndexOption::IndexIsValid));
0027 
0028     const auto &imageInfo = m_images[index.row()];
0029     switch (role) {
0030     case Qt::DisplayRole:
0031         return imageInfo.path.fileName();
0032     case FileNameRole:
0033         return imageInfo.path.toDisplayString();
0034     case NewSizeRole:
0035         return imageInfo.size != -1 && imageInfo.size != imageInfo.oldSize ? imageInfo.size : -1;
0036     case AlreadyOptimizedRole:
0037         return imageInfo.size != -1 && imageInfo.size == imageInfo.oldSize;
0038     case SizeRole:
0039         return imageInfo.oldSize != -1 ? imageInfo.oldSize : QVariant();
0040     case ProcessedRole:
0041         return imageInfo.processed;
0042     default:
0043         return {};
0044     }
0045 }
0046 
0047 int ImageModel::rowCount(const QModelIndex &parent) const
0048 {
0049     Q_UNUSED(parent);
0050     return m_images.count();
0051 }
0052 
0053 void ImageModel::addImages(const QList<QUrl> &paths)
0054 {
0055     const auto config = Config::self();
0056     for (const auto &path : paths) {
0057         beginInsertRows({}, m_images.count(), m_images.count());
0058         QFileInfo fileInfo(path.toLocalFile());
0059         QMimeDatabase db;
0060         QMimeType type = db.mimeTypeForFile(path.toLocalFile());
0061 
0062         ImageInfo info;
0063         info.path = path;
0064         if (config->safeMode()) {
0065             info.newPath = QUrl(info.path.toString() + config->suffix());
0066         } else {
0067             info.newPath = path;
0068         }
0069         info.oldSize = fileInfo.size();
0070         if (!path.isLocalFile() || !fileInfo.isWritable()) {
0071             info.error = true;
0072         }
0073 
0074         if (type.name() == u"image/png"_s) {
0075             info.imageType = ImageType::PNG;
0076         } else if (type.name() == u"image/jpeg"_s) {
0077             info.imageType = ImageType::JPEG;
0078         } else if (type.name() == u"image/svg+xml"_s) {
0079             info.imageType = ImageType::SVG;
0080         } else if (type.name() == u"image/webp"_s) {
0081             info.imageType = ImageType::WEBP;
0082         } else {
0083             info.imageType = ImageType::UNSURPORTED;
0084         }
0085         m_images << info;
0086         endInsertRows();
0087     }
0088 
0089     optimize();
0090 }
0091 
0092 QCoro::Task<> ImageModel::optimize()
0093 {
0094     if (m_running) {
0095         co_return;
0096     }
0097     m_running = true;
0098     Q_EMIT runningChanged();
0099 
0100     bool processedImages = false;
0101 
0102     const auto &config = Config::self();
0103     auto engine = qmlEngine(this);
0104     auto consoleLog = engine->singletonInstance<ConsoleLog *>(u"org.kde.optiimage"_s, u"ConsoleLog"_s);
0105     Q_ASSERT(consoleLog);
0106 
0107     int i = 0;
0108     for (auto &image : m_images) {
0109         if (image.processed) {
0110             i++;
0111             continue;
0112         }
0113         image.processed = true;
0114         processedImages = true;
0115 
0116         if (image.imageType == ImageType::UNSURPORTED) {
0117             i++;
0118             continue;
0119         }
0120 
0121         if (image.imageType == ImageType::PNG) {
0122             co_await optimizePng(config, image, consoleLog);
0123         } else if (image.imageType == ImageType::JPEG) {
0124             co_await optimizeJpeg(config, image, consoleLog);
0125         } else if (image.imageType == ImageType::SVG) {
0126             co_await optimizeSvg(config, image, consoleLog);
0127         } else if (image.imageType == ImageType::WEBP) {
0128             co_await optimizeWebp(config, image, consoleLog);
0129         }
0130 
0131         QFileInfo fileInfo(image.newPath.toLocalFile());
0132         image.size = fileInfo.size();
0133         image.processed = true;
0134         Q_EMIT dataChanged(index(i, 0), index(i, 0), {NewSizeRole, ProcessedRole, AlreadyOptimizedRole});
0135 
0136         i++;
0137     }
0138     m_running = false;
0139     Q_EMIT runningChanged();
0140 
0141     if (processedImages) {
0142         // In case new images still need to be looped over
0143         optimize();
0144     }
0145 }
0146 
0147 QHash<int, QByteArray> ImageModel::roleNames() const
0148 {
0149     return {{Qt::DisplayRole, QByteArrayLiteral("displayName")},
0150             {FileNameRole, QByteArrayLiteral("filename")},
0151             {SizeRole, QByteArrayLiteral("size")},
0152             {NewSizeRole, QByteArrayLiteral("newSize")},
0153             {AlreadyOptimizedRole, QByteArrayLiteral("alreadyOptimized")},
0154             {ProcessedRole, QByteArrayLiteral("processed")}};
0155 }
0156 
0157 bool ImageModel::running() const
0158 {
0159     return m_running;
0160 }
0161 
0162 void ImageModel::highlightInFileManager(const QString &url)
0163 {
0164     KIO::highlightInFileManager({QUrl(url)});
0165 }
0166 
0167 void ImageModel::open(const QString &url)
0168 {
0169     auto job = new KIO::OpenUrlJob(QUrl(url));
0170     job->setUiDelegate(KIO::createDefaultJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, nullptr));
0171     job->start();
0172 }
0173 
0174 void ImageModel::openProperties(const QString &url)
0175 {
0176     auto dialog = new KPropertiesDialog({QUrl(url)});
0177     dialog->setAttribute(Qt::WA_DeleteOnClose);
0178     dialog->setModal(true);
0179     dialog->show();
0180 }