File indexing completed on 2024-11-10 05:02:44
0001 /* 0002 SPDX-FileCopyrightText: 2022 Fushan Wen <qydwhotmail@gmail.com> 0003 0004 SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "imageproxymodel.h" 0008 0009 #include <QDir> 0010 0011 #include <KConfigGroup> 0012 #include <KIO/OpenFileManagerWindowJob> 0013 #include <KSharedConfig> 0014 0015 #include "../finder/suffixcheck.h" 0016 #include "imagelistmodel.h" 0017 #include "packagelistmodel.h" 0018 0019 namespace 0020 { 0021 inline bool isChildItem(const QStringList &customPathsInKDirWatch, const QString &childPath) 0022 { 0023 return std::any_of(customPathsInKDirWatch.cbegin(), customPathsInKDirWatch.cend(), [&childPath](const QString &customPath) { 0024 if (customPath.endsWith(QDir::separator())) { 0025 return childPath.startsWith(customPath); 0026 } else { 0027 return childPath.startsWith(customPath + QDir::separator()); 0028 } 0029 }); 0030 } 0031 } 0032 0033 ImageProxyModel::ImageProxyModel(const QStringList &customPaths, 0034 const QBindable<QSize> &bindableTargetSize, 0035 const QBindable<bool> &bindableUsedInConfig, 0036 QObject *parent) 0037 : QConcatenateTablesProxyModel(parent) 0038 , m_imageModel(new ImageListModel(bindableTargetSize, bindableUsedInConfig, this)) 0039 , m_packageModel(new PackageListModel(bindableTargetSize, bindableUsedInConfig, this)) 0040 { 0041 connect(this, &ImageProxyModel::rowsInserted, this, &ImageProxyModel::countChanged); 0042 connect(this, &ImageProxyModel::rowsRemoved, this, &ImageProxyModel::countChanged); 0043 connect(this, &ImageProxyModel::modelReset, this, &ImageProxyModel::countChanged); 0044 0045 m_customPaths = customPaths; 0046 if (customPaths.empty()) { 0047 KConfigGroup cfg = KConfigGroup(KSharedConfig::openConfig(QStringLiteral("plasmarc")), QStringLiteral("Wallpapers")); 0048 m_customPaths = cfg.readEntry("usersWallpapers", QStringList{}); 0049 m_imageModel->m_removableWallpapers = m_customPaths; 0050 m_packageModel->m_removableWallpapers = m_customPaths; 0051 0052 m_customPaths += QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("wallpapers/"), QStandardPaths::LocateDirectory); 0053 } 0054 0055 connect(m_imageModel, &AbstractImageListModel::loaded, this, &ImageProxyModel::slotHandleLoaded); 0056 connect(m_packageModel, &AbstractImageListModel::loaded, this, &ImageProxyModel::slotHandleLoaded); 0057 0058 m_loading.setBinding([this] { 0059 return m_loaded.value() != 2; 0060 }); 0061 m_loaded = 0; 0062 0063 m_imageModel->load(m_customPaths); 0064 m_packageModel->load(m_customPaths); 0065 } 0066 0067 QHash<int, QByteArray> ImageProxyModel::roleNames() const 0068 { 0069 if (const auto models = sourceModels(); !models.empty()) { 0070 return models.at(0)->roleNames(); 0071 } 0072 0073 return QConcatenateTablesProxyModel::roleNames(); 0074 } 0075 0076 int ImageProxyModel::count() const 0077 { 0078 return rowCount(); 0079 } 0080 0081 int ImageProxyModel::indexOf(const QString &packagePath) const 0082 { 0083 int idx = -1; 0084 0085 for (const auto models{sourceModels()}; auto m : models) { 0086 idx = static_cast<const AbstractImageListModel *>(m)->indexOf(packagePath); 0087 0088 if (idx >= 0) { 0089 return mapFromSource(m->index(idx, 0)).row(); 0090 } 0091 } 0092 0093 return idx; 0094 } 0095 0096 QBindable<bool> ImageProxyModel::loading() const 0097 { 0098 return &m_loading; 0099 } 0100 0101 void ImageProxyModel::reload() 0102 { 0103 for (const auto models{sourceModels()}; auto m : models) { 0104 static_cast<AbstractImageListModel *>(m)->reload(); 0105 } 0106 0107 m_loaded = 0; 0108 } 0109 0110 QStringList ImageProxyModel::addBackground(const QString &_path) 0111 { 0112 QString path = _path; 0113 0114 if (constexpr QLatin1String prefix{"file://"}; path.startsWith(prefix)) { 0115 path.remove(0, prefix.size()); 0116 } 0117 0118 const QFileInfo info(path); 0119 0120 QStringList results; 0121 0122 if (info.isDir()) { 0123 if (!path.endsWith(QDir::separator())) { 0124 path += QDir::separator(); 0125 } 0126 0127 results = m_packageModel->addBackground(path); 0128 } else if (info.isFile()) { 0129 results = m_imageModel->addBackground(path); 0130 } 0131 0132 if (!results.empty()) { 0133 m_pendingAddition.append(results); 0134 0135 for (const QString &path : std::as_const(results)) { 0136 if (m_dirWatch.contains(path) || isChildItem(m_customPaths, path) /* KDirWatch already monitors the parent folder */) { 0137 continue; 0138 } 0139 0140 const QFileInfo info(path); 0141 if (info.isFile()) { 0142 m_dirWatch.addFile(path); 0143 } else if (info.isDir()) { 0144 m_dirWatch.addDir(path); 0145 } 0146 } 0147 } 0148 0149 return results; 0150 } 0151 0152 void ImageProxyModel::removeBackground(const QString &_packagePath) 0153 { 0154 QString packagePath = _packagePath; 0155 0156 if (constexpr QLatin1String prefix{"file://"}; packagePath.startsWith(prefix)) { 0157 packagePath.remove(0, prefix.size()); 0158 } 0159 0160 QStringList results; 0161 0162 // The file may be already deleted, so isFile/isDir won't work. 0163 if (const QFileInfo info(packagePath); isAcceptableSuffix(info.suffix())) { 0164 results = m_imageModel->removeBackground(packagePath); 0165 0166 if (!results.empty() && !isChildItem(m_customPaths, results.at(0))) { 0167 // Don't remove the file if its parent folder is in KDirWatch, otherwise KDirWatchPrivate::removeEntry will also remove the parent folder 0168 m_dirWatch.removeFile(results.at(0)); 0169 } 0170 } else { 0171 results = m_packageModel->removeBackground(packagePath); 0172 0173 if (!results.empty()) { 0174 // Because of KDirWatch::WatchSubDirs, some folders will still be added to KDirWatch 0175 m_dirWatch.removeDir(results.at(0)); 0176 } 0177 } 0178 0179 // The user may add a wallpaper and delete it later. 0180 for (const QString &path : std::as_const(results)) { 0181 m_pendingAddition.removeOne(path); 0182 } 0183 } 0184 0185 void ImageProxyModel::commitAddition() 0186 { 0187 if (m_pendingAddition.empty()) { 0188 return; 0189 } 0190 0191 KConfigGroup cfg = KConfigGroup(KSharedConfig::openConfig(QStringLiteral("plasmarc")), QStringLiteral("Wallpapers")); 0192 QStringList list = cfg.readEntry("usersWallpapers", QStringList{}); 0193 0194 list += m_pendingAddition; 0195 list.removeDuplicates(); 0196 0197 cfg.writeEntry("usersWallpapers", list); 0198 0199 m_pendingAddition.clear(); 0200 } 0201 0202 void ImageProxyModel::commitDeletion() 0203 { 0204 QStringList pendingList; 0205 0206 for (int row = 0; row < rowCount(); row++) { 0207 QModelIndex idx = index(row, 0); 0208 0209 if (idx.data(PendingDeletionRole).toBool()) { 0210 pendingList.append(idx.data(PackageNameRole).toString()); 0211 } 0212 } 0213 0214 for (const QString &p : std::as_const(pendingList)) { 0215 removeBackground(p); 0216 } 0217 0218 // Update the config 0219 KConfigGroup cfg = KConfigGroup(KSharedConfig::openConfig(QStringLiteral("plasmarc")), QStringLiteral("Wallpapers")); 0220 const QStringList list = cfg.readEntry("usersWallpapers", QStringList{}); 0221 QStringList updatedList; 0222 0223 // Check if the file still exists 0224 std::copy_if(list.cbegin(), list.cend(), std::back_inserter(updatedList), [&pendingList](const QString &_p) { 0225 QString p = _p; 0226 if (constexpr QLatin1String prefix{"file://"}; p.startsWith(prefix)) { 0227 p.remove(0, prefix.size()); 0228 } 0229 0230 return !pendingList.contains(p) && QFileInfo::exists(p); 0231 }); 0232 0233 cfg.writeEntry("usersWallpapers", updatedList); 0234 cfg.sync(); 0235 } 0236 0237 void ImageProxyModel::openContainingFolder(int row) const 0238 { 0239 KIO::highlightInFileManager({index(row, 0).data(PathRole).toUrl()}); 0240 } 0241 0242 void ImageProxyModel::slotHandleLoaded(AbstractImageListModel *model) 0243 { 0244 disconnect(model, &AbstractImageListModel::loaded, this, 0); 0245 if (m_loaded + 1 == 2) { 0246 // All models are loaded, now add them. 0247 addSourceModel(m_imageModel); 0248 addSourceModel(m_packageModel); 0249 0250 setupDirWatch(); 0251 } 0252 m_loaded = m_loaded + 1; 0253 } 0254 0255 void ImageProxyModel::slotDirWatchCreated(const QString &_path) 0256 { 0257 QString path = _path; 0258 0259 if (int idx = path.indexOf(QLatin1String("contents/images/")); idx > 0) { 0260 path = path.mid(0, idx); 0261 } 0262 0263 addBackground(path); 0264 } 0265 0266 void ImageProxyModel::slotDirWatchDeleted(const QString &path) 0267 { 0268 removeBackground(path); 0269 } 0270 0271 void ImageProxyModel::setupDirWatch() 0272 { 0273 // Monitor file changes in the custom directories for the slideshow backend. 0274 for (const QString &path : std::as_const(m_customPaths)) { 0275 if (QFileInfo(path).isDir()) { 0276 m_dirWatch.addDir(path, KDirWatch::WatchFiles | KDirWatch::WatchSubDirs); 0277 } 0278 } 0279 0280 connect(&m_dirWatch, &KDirWatch::created, this, &ImageProxyModel::slotDirWatchCreated); 0281 connect(&m_dirWatch, &KDirWatch::deleted, this, &ImageProxyModel::slotDirWatchDeleted); 0282 }