File indexing completed on 2024-06-23 05:32:15

0001 /*
0002     SPDX-FileCopyrightText: 2007 Paolo Capriotti <p.capriotti@gmail.com>
0003     SPDX-FileCopyrightText: 2022 Fushan Wen <qydwhotmail@gmail.com>
0004 
0005     SPDX-License-Identifier: GPL-2.0-or-later
0006 */
0007 
0008 #include "packagefinder.h"
0009 
0010 #include <limits>
0011 
0012 #include <QDir>
0013 
0014 #include <KPackage/PackageLoader>
0015 
0016 #include "findsymlinktarget.h"
0017 #include "suffixcheck.h"
0018 
0019 namespace
0020 {
0021 /**
0022  * Computes difference of two areas
0023  */
0024 double distance(const QSize &size, const QSize &desired)
0025 {
0026     const double desiredAspectRatio = (desired.height() > 0) ? desired.width() / static_cast<double>(desired.height()) : 0;
0027     const double candidateAspectRatio = (size.height() > 0) ? size.width() / static_cast<double>(size.height()) : std::numeric_limits<double>::max();
0028 
0029     double delta = size.width() - desired.width();
0030     delta = delta >= 0.0 ? delta : -delta * 2; // Penalize for scaling up
0031 
0032     return std::abs(candidateAspectRatio - desiredAspectRatio) * 25000 + delta;
0033 }
0034 
0035 /**
0036  * @return size from the filename
0037  */
0038 QSize resSize(QStringView str)
0039 {
0040     const int index = str.indexOf(QLatin1Char('x'));
0041 
0042     if (index != -1) {
0043         return QSize(str.left(index).toInt(), str.mid(index + 1).toInt());
0044     }
0045 
0046     return QSize();
0047 }
0048 }
0049 
0050 PackageFinder::PackageFinder(const QStringList &paths, const QSize &targetSize, QObject *parent)
0051     : QObject(parent)
0052     , m_paths(paths)
0053     , m_targetSize(targetSize)
0054 {
0055 }
0056 
0057 void PackageFinder::run()
0058 {
0059     QList<KPackage::Package> packages;
0060     QStringList folders;
0061 
0062     QDir dir;
0063     dir.setFilter(QDir::Dirs | QDir::Readable | QDir::NoDotAndDotDot);
0064 
0065     KPackage::Package package = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Wallpaper/Images"));
0066 
0067     const auto addPackage = [this, &package, &packages, &folders](const QString &_folderPath) {
0068         const QString folderPath = findSymlinkTarget(QFileInfo(_folderPath)).absoluteFilePath();
0069 
0070         if (folders.contains(folderPath)) {
0071             // The folder has been added, return true to skip it.
0072             return true;
0073         }
0074 
0075         if (!QFile::exists(folderPath + QLatin1String("/metadata.desktop")) && !QFile::exists(folderPath + QLatin1String("/metadata.json"))) {
0076             folders << folderPath;
0077             return false;
0078         }
0079 
0080         package.setPath(folderPath);
0081 
0082         if (package.isValid() && package.metadata().isValid()) {
0083             // Check if there are any available images.
0084             QDir imageDir(package.filePath("images"));
0085             imageDir.setFilter(QDir::Files | QDir::Readable);
0086             imageDir.setNameFilters(suffixes());
0087 
0088             if (imageDir.entryInfoList().empty()) {
0089                 // This is an empty package. Skip it.
0090                 folders << folderPath;
0091                 return true;
0092             }
0093 
0094             findPreferredImageInPackage(package, m_targetSize);
0095             packages << package;
0096             folders << folderPath;
0097 
0098             return true;
0099         }
0100 
0101         folders << folderPath;
0102         return false; // Not found
0103     };
0104 
0105     int i;
0106 
0107     for (i = 0; i < m_paths.size(); ++i) {
0108         const QString &path = m_paths.at(i);
0109         const QFileInfo info(path);
0110 
0111         if (!info.isDir()) {
0112             continue;
0113         }
0114 
0115         // Check the path itself is a package
0116         if (addPackage(path)) {
0117             continue;
0118         }
0119 
0120         dir.setPath(path);
0121         const QFileInfoList files = dir.entryInfoList();
0122 
0123         for (const QFileInfo &wp : files) {
0124             if (!addPackage(wp.filePath())) {
0125                 // Add this to the directories we should be looking at
0126                 m_paths.append(wp.filePath());
0127             }
0128         }
0129     }
0130 
0131     Q_EMIT packageFound(packages);
0132 }
0133 
0134 void PackageFinder::findPreferredImageInPackage(KPackage::Package &package, const QSize &targetSize)
0135 {
0136     if (!package.isValid()) {
0137         return;
0138     }
0139 
0140     QSize tSize = targetSize;
0141 
0142     if (tSize.isEmpty()) {
0143         tSize = QSize(1920, 1080);
0144     }
0145 
0146     // find preferred size
0147     auto findBestMatch = [&package, &tSize](const QByteArray &folder) {
0148         QString preferred;
0149         const QStringList images = package.entryList(folder);
0150 
0151         if (images.empty()) {
0152             return preferred;
0153         }
0154 
0155         double best = std::numeric_limits<double>::max();
0156 
0157         for (const QString &entry : images) {
0158             QSize candidate = resSize(QFileInfo(entry).baseName());
0159 
0160             if (candidate.isEmpty()) {
0161                 continue;
0162             }
0163 
0164             const double dist = distance(candidate, tSize);
0165 
0166             if (preferred.isEmpty() || dist < best) {
0167                 preferred = entry;
0168                 best = dist;
0169             }
0170         }
0171 
0172         return preferred;
0173     };
0174 
0175     const QString preferred = findBestMatch(QByteArrayLiteral("images"));
0176     const QString preferredDark = findBestMatch(QByteArrayLiteral("images_dark"));
0177 
0178     package.removeDefinition("preferred");
0179     package.addFileDefinition("preferred", QStringLiteral("images/%1").arg(preferred));
0180 
0181     if (!preferredDark.isEmpty()) {
0182         package.removeDefinition("preferredDark");
0183         package.addFileDefinition("preferredDark", QStringLiteral("images_dark/%1").arg(preferredDark));
0184     }
0185 }
0186 
0187 QString PackageFinder::packageDisplayName(const KPackage::Package &b)
0188 {
0189     const QString title = b.metadata().name();
0190 
0191     if (title.isEmpty()) {
0192         return QFileInfo(b.filePath("preferred")).completeBaseName();
0193     }
0194 
0195     return title;
0196 }