/* SPDX-FileCopyrightText: 2022 Fushan Wen SPDX-License-Identifier: GPL-2.0-or-later */ #include "imageproxymodel.h" #include #include #include #include #include "../finder/suffixcheck.h" #include "imagelistmodel.h" #include "packagelistmodel.h" namespace { inline bool isChildItem(const QStringList &customPathsInKDirWatch, const QString &childPath) { return std::any_of(customPathsInKDirWatch.cbegin(), customPathsInKDirWatch.cend(), [&childPath](const QString &customPath) { if (customPath.endsWith(QDir::separator())) { return childPath.startsWith(customPath); } else { return childPath.startsWith(customPath + QDir::separator()); } }); } } ImageProxyModel::ImageProxyModel(const QStringList &customPaths, const QBindable &bindableTargetSize, const QBindable &bindableUsedInConfig, QObject *parent) : QConcatenateTablesProxyModel(parent) , m_imageModel(new ImageListModel(bindableTargetSize, bindableUsedInConfig, this)) , m_packageModel(new PackageListModel(bindableTargetSize, bindableUsedInConfig, this)) { connect(this, &ImageProxyModel::rowsInserted, this, &ImageProxyModel::countChanged); connect(this, &ImageProxyModel::rowsRemoved, this, &ImageProxyModel::countChanged); connect(this, &ImageProxyModel::modelReset, this, &ImageProxyModel::countChanged); m_customPaths = customPaths; if (customPaths.empty()) { KConfigGroup cfg = KConfigGroup(KSharedConfig::openConfig(QStringLiteral("plasmarc")), QStringLiteral("Wallpapers")); m_customPaths = cfg.readEntry("usersWallpapers", QStringList{}); m_imageModel->m_removableWallpapers = m_customPaths; m_packageModel->m_removableWallpapers = m_customPaths; m_customPaths += QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("wallpapers/"), QStandardPaths::LocateDirectory); } connect(m_imageModel, &AbstractImageListModel::loaded, this, &ImageProxyModel::slotHandleLoaded); connect(m_packageModel, &AbstractImageListModel::loaded, this, &ImageProxyModel::slotHandleLoaded); m_loading.setBinding([this] { return m_loaded.value() != 2; }); m_loaded = 0; m_imageModel->load(m_customPaths); m_packageModel->load(m_customPaths); } QHash ImageProxyModel::roleNames() const { if (const auto models = sourceModels(); !models.empty()) { return models.at(0)->roleNames(); } return QConcatenateTablesProxyModel::roleNames(); } int ImageProxyModel::count() const { return rowCount(); } int ImageProxyModel::indexOf(const QString &packagePath) const { int idx = -1; for (const auto models{sourceModels()}; auto m : models) { idx = static_cast(m)->indexOf(packagePath); if (idx >= 0) { return mapFromSource(m->index(idx, 0)).row(); } } return idx; } QBindable ImageProxyModel::loading() const { return &m_loading; } void ImageProxyModel::reload() { for (const auto models{sourceModels()}; auto m : models) { static_cast(m)->reload(); } m_loaded = 0; } QStringList ImageProxyModel::addBackground(const QString &_path) { QString path = _path; if (constexpr QLatin1String prefix{"file://"}; path.startsWith(prefix)) { path.remove(0, prefix.size()); } const QFileInfo info(path); QStringList results; if (info.isDir()) { if (!path.endsWith(QDir::separator())) { path += QDir::separator(); } results = m_packageModel->addBackground(path); } else if (info.isFile()) { results = m_imageModel->addBackground(path); } if (!results.empty()) { m_pendingAddition.append(results); for (const QString &path : std::as_const(results)) { if (m_dirWatch.contains(path) || isChildItem(m_customPaths, path) /* KDirWatch already monitors the parent folder */) { continue; } const QFileInfo info(path); if (info.isFile()) { m_dirWatch.addFile(path); } else if (info.isDir()) { m_dirWatch.addDir(path); } } } return results; } void ImageProxyModel::removeBackground(const QString &_packagePath) { QString packagePath = _packagePath; if (constexpr QLatin1String prefix{"file://"}; packagePath.startsWith(prefix)) { packagePath.remove(0, prefix.size()); } QStringList results; // The file may be already deleted, so isFile/isDir won't work. if (const QFileInfo info(packagePath); isAcceptableSuffix(info.suffix())) { results = m_imageModel->removeBackground(packagePath); if (!results.empty() && !isChildItem(m_customPaths, results.at(0))) { // Don't remove the file if its parent folder is in KDirWatch, otherwise KDirWatchPrivate::removeEntry will also remove the parent folder m_dirWatch.removeFile(results.at(0)); } } else { results = m_packageModel->removeBackground(packagePath); if (!results.empty()) { // Because of KDirWatch::WatchSubDirs, some folders will still be added to KDirWatch m_dirWatch.removeDir(results.at(0)); } } // The user may add a wallpaper and delete it later. for (const QString &path : std::as_const(results)) { m_pendingAddition.removeOne(path); } } void ImageProxyModel::commitAddition() { if (m_pendingAddition.empty()) { return; } KConfigGroup cfg = KConfigGroup(KSharedConfig::openConfig(QStringLiteral("plasmarc")), QStringLiteral("Wallpapers")); QStringList list = cfg.readEntry("usersWallpapers", QStringList{}); list += m_pendingAddition; list.removeDuplicates(); cfg.writeEntry("usersWallpapers", list); m_pendingAddition.clear(); } void ImageProxyModel::commitDeletion() { QStringList pendingList; for (int row = 0; row < rowCount(); row++) { QModelIndex idx = index(row, 0); if (idx.data(PendingDeletionRole).toBool()) { pendingList.append(idx.data(PackageNameRole).toString()); } } for (const QString &p : std::as_const(pendingList)) { removeBackground(p); } // Update the config KConfigGroup cfg = KConfigGroup(KSharedConfig::openConfig(QStringLiteral("plasmarc")), QStringLiteral("Wallpapers")); const QStringList list = cfg.readEntry("usersWallpapers", QStringList{}); QStringList updatedList; // Check if the file still exists std::copy_if(list.cbegin(), list.cend(), std::back_inserter(updatedList), [&pendingList](const QString &_p) { QString p = _p; if (constexpr QLatin1String prefix{"file://"}; p.startsWith(prefix)) { p.remove(0, prefix.size()); } return !pendingList.contains(p) && QFileInfo::exists(p); }); cfg.writeEntry("usersWallpapers", updatedList); cfg.sync(); } void ImageProxyModel::openContainingFolder(int row) const { KIO::highlightInFileManager({index(row, 0).data(PathRole).toUrl()}); } void ImageProxyModel::slotHandleLoaded(AbstractImageListModel *model) { disconnect(model, &AbstractImageListModel::loaded, this, nullptr); if (m_loaded + 1 == 2) { // All models are loaded, now add them. addSourceModel(m_imageModel); addSourceModel(m_packageModel); setupDirWatch(); } m_loaded = m_loaded + 1; } void ImageProxyModel::slotDirWatchCreated(const QString &_path) { QString path = _path; if (int idx = path.indexOf(QLatin1String("contents/images/")); idx > 0) { path = path.mid(0, idx); } addBackground(path); } void ImageProxyModel::slotDirWatchDeleted(const QString &path) { removeBackground(path); } void ImageProxyModel::setupDirWatch() { // Monitor file changes in the custom directories for the slideshow backend. for (const QString &path : std::as_const(m_customPaths)) { if (QFileInfo(path).isDir()) { m_dirWatch.addDir(path, KDirWatch::WatchFiles | KDirWatch::WatchSubDirs); } } connect(&m_dirWatch, &KDirWatch::created, this, &ImageProxyModel::slotDirWatchCreated); connect(&m_dirWatch, &KDirWatch::deleted, this, &ImageProxyModel::slotDirWatchDeleted); }