File indexing completed on 2024-11-10 05:02:45

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 "packagelistmodel.h"
0009 
0010 #include <QDir>
0011 #include <QGuiApplication>
0012 #include <QPalette>
0013 #include <QPixmap>
0014 #include <QStandardPaths>
0015 #include <QThreadPool>
0016 
0017 #include <KAboutData>
0018 #include <KPackage/PackageLoader>
0019 
0020 #include "../finder/packagefinder.h"
0021 #include "../finder/suffixcheck.h"
0022 
0023 PackageListModel::PackageListModel(const QBindable<QSize> &bindableTargetSize, const QBindable<bool> &bindableUsedInConfig, QObject *parent)
0024     : AbstractImageListModel(bindableTargetSize, bindableUsedInConfig, parent)
0025 {
0026     qRegisterMetaType<QList<KPackage::Package>>();
0027 }
0028 
0029 int PackageListModel::rowCount(const QModelIndex &parent) const
0030 {
0031     return parent.isValid() ? 0 : m_packages.size();
0032 }
0033 
0034 QVariant PackageListModel::data(const QModelIndex &index, int role) const
0035 {
0036     if (!index.isValid()) {
0037         return QVariant();
0038     }
0039 
0040     const KPackage::Package &b = m_packages.at(index.row());
0041 
0042     if (!b.isValid()) {
0043         Q_UNREACHABLE(); // Should be already filtered out by the finder
0044     }
0045 
0046     switch (role) {
0047     case Qt::DisplayRole:
0048         return PackageFinder::packageDisplayName(b);
0049 
0050     case ScreenshotRole: {
0051         QStringList paths{b.filePath(QByteArrayLiteral("preferred"))};
0052         const QString darkPath = b.filePath(QByteArrayLiteral("preferredDark"));
0053 
0054         if (!darkPath.isEmpty()) {
0055             paths.append(darkPath);
0056         }
0057 
0058         QPixmap *cachedPreview = m_imageCache.object(paths);
0059 
0060         if (cachedPreview) {
0061             return *cachedPreview;
0062         }
0063 
0064         asyncGetPreview(paths, QPersistentModelIndex(index));
0065 
0066         return QVariant();
0067     }
0068 
0069     case AuthorRole: {
0070         if (!b.metadata().authors().empty()) {
0071             return b.metadata().authors().at(0).name();
0072         }
0073 
0074         return QString();
0075     }
0076 
0077     case ResolutionRole: {
0078         const QString path = b.filePath("preferred");
0079 
0080         QSize *size = m_imageSizeCache.object(path);
0081 
0082         if (size && size->isValid()) {
0083             return QStringLiteral("%1x%2").arg(size->width()).arg(size->height());
0084         }
0085 
0086         asyncGetMediaMetadata(path, QPersistentModelIndex(index));
0087 
0088         return QString();
0089     }
0090 
0091     case PathRole: {
0092         if (qGray(qGuiApp->palette().window().color().rgb()) < 192) {
0093             const QString darkPath = b.filePath(QByteArrayLiteral("preferredDark"));
0094             if (!darkPath.isEmpty()) {
0095                 return QUrl::fromLocalFile(darkPath);
0096             }
0097         }
0098 
0099         return QUrl::fromLocalFile(b.filePath("preferred"));
0100     }
0101 
0102     case PackageNameRole:
0103         return b.path();
0104 
0105     case RemovableRole: {
0106         const QString path = b.path();
0107 
0108         return path.startsWith(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/wallpapers/"))
0109             || m_removableWallpapers.contains(path);
0110     }
0111 
0112     case PendingDeletionRole:
0113         return m_pendingDeletion.value(b.path(), false);
0114     }
0115     Q_UNREACHABLE();
0116 }
0117 
0118 bool PackageListModel::setData(const QModelIndex &index, const QVariant &value, int role)
0119 {
0120     if (!index.isValid()) {
0121         return false;
0122     }
0123 
0124     if (role == PendingDeletionRole) {
0125         const KPackage::Package &b = m_packages.at(index.row());
0126         m_pendingDeletion[b.path()] = value.toBool();
0127 
0128         Q_EMIT dataChanged(index, index, {PendingDeletionRole});
0129         return true;
0130     }
0131 
0132     return false;
0133 }
0134 
0135 int PackageListModel::indexOf(const QString &_path) const
0136 {
0137     QString path = _path.endsWith(QDir::separator()) ? _path : _path + QDir::separator();
0138 
0139     if (path.startsWith(QLatin1String("file://"))) {
0140         path.remove(0, 7);
0141     }
0142 
0143     const auto it = std::find_if(m_packages.cbegin(), m_packages.cend(), [&path](const KPackage::Package &p) {
0144         return path == p.path();
0145     });
0146 
0147     if (it == m_packages.cend()) {
0148         return -1;
0149     }
0150 
0151     return std::distance(m_packages.cbegin(), it);
0152 }
0153 
0154 void PackageListModel::load(const QStringList &customPaths)
0155 {
0156     if (m_loading || customPaths.empty()) {
0157         return;
0158     }
0159 
0160     AbstractImageListModel::load(customPaths);
0161 
0162     PackageFinder *finder = new PackageFinder(m_customPaths, m_targetSize);
0163     connect(finder, &PackageFinder::packageFound, this, &PackageListModel::slotHandlePackageFound);
0164     QThreadPool::globalInstance()->start(finder);
0165 }
0166 
0167 QStringList PackageListModel::addBackground(const QString &path)
0168 {
0169     if (path.isEmpty() || indexOf(path) >= 0 || !QFileInfo(path).isDir()) {
0170         return {};
0171     }
0172 
0173     KPackage::Package package = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Wallpaper/Images"));
0174     package.setPath(path);
0175 
0176     if (!package.isValid() || !package.metadata().isValid()) {
0177         return {};
0178     }
0179 
0180     // Check if there are any available images.
0181     QDir imageDir(package.filePath("images"));
0182     imageDir.setFilter(QDir::Files | QDir::Readable);
0183     imageDir.setNameFilters(suffixes());
0184 
0185     if (imageDir.entryInfoList().empty()) {
0186         // This is an empty package. Skip it.
0187         return {};
0188     }
0189 
0190     PackageFinder::findPreferredImageInPackage(package, m_targetSize);
0191 
0192     if (m_usedInConfig) {
0193         beginInsertRows(QModelIndex(), 0, 0);
0194 
0195         m_removableWallpapers.prepend(package.path());
0196         m_packages.prepend(package);
0197 
0198         endInsertRows();
0199     } else {
0200         // In a slideshow, append to last so the random order can be kept
0201         const int count = rowCount();
0202         beginInsertRows(QModelIndex(), count, count);
0203 
0204         m_removableWallpapers.append(package.path());
0205         m_packages.append(package);
0206 
0207         endInsertRows();
0208     }
0209 
0210     return {package.path()};
0211 }
0212 
0213 QStringList PackageListModel::removeBackground(const QString &_path)
0214 {
0215     QStringList results;
0216 
0217     if (_path.isEmpty()) {
0218         return results;
0219     }
0220 
0221     const QString path = _path.endsWith(QDir::separator()) ? _path : _path + QDir::separator();
0222 
0223     const int idx = indexOf(path);
0224 
0225     if (idx < 0) {
0226         return results;
0227     }
0228 
0229     beginRemoveRows(QModelIndex(), idx, idx);
0230 
0231     m_pendingDeletion.remove(m_packages.at(idx).path());
0232     m_removableWallpapers.removeOne(m_packages.at(idx).path());
0233     results.append(m_packages.takeAt(idx).path());
0234 
0235     // Uninstall local package
0236     if (path.startsWith(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/wallpapers/"))) {
0237         QDir f(path);
0238 
0239         if (f.exists()) {
0240             f.removeRecursively();
0241         }
0242     }
0243 
0244     endRemoveRows();
0245 
0246     return results;
0247 }
0248 
0249 void PackageListModel::slotHandlePackageFound(const QList<KPackage::Package> &packages)
0250 {
0251     beginResetModel();
0252 
0253     m_packages = packages;
0254     clearCache();
0255 
0256     endResetModel();
0257 
0258     m_loading = false;
0259     Q_EMIT loaded(this);
0260 }