File indexing completed on 2024-05-12 05:47:29
0001 /* 0002 * SPDX-FileCopyrightText: 2011 Peter Penz <peter.penz19@gmail.com> 0003 * SPDX-FileCopyrightText: 2013 Frank Reininghaus <frank78ac@googlemail.com> 0004 * 0005 * SPDX-License-Identifier: GPL-2.0-or-later 0006 */ 0007 0008 #include "kdirectorycontentscounter.h" 0009 #include "dolphin_contentdisplaysettings.h" 0010 #include "kitemviews/kfileitemmodel.h" 0011 0012 #include <KDirWatch> 0013 0014 #include <QDir> 0015 #include <QFileInfo> 0016 #include <QThread> 0017 0018 namespace 0019 { 0020 0021 class LocalCache 0022 { 0023 public: 0024 struct cacheData { 0025 int count = 0; 0026 long long size = 0; 0027 ushort refCount = 0; 0028 qint64 timestamp = 0; 0029 0030 inline operator bool() const 0031 { 0032 return timestamp != 0 && count != -1; 0033 } 0034 }; 0035 0036 LocalCache() 0037 : m_cache() 0038 { 0039 } 0040 0041 cacheData insert(const QString &key, cacheData data, bool inserted) 0042 { 0043 data.timestamp = QDateTime::currentMSecsSinceEpoch(); 0044 if (inserted) { 0045 data.refCount += 1; 0046 } 0047 m_cache.insert(key, data); 0048 return data; 0049 } 0050 0051 cacheData value(const QString &key) const 0052 { 0053 return m_cache.value(key); 0054 } 0055 0056 void unRefAll(const QSet<QString> &keys) 0057 { 0058 for (const auto &key : keys) { 0059 auto entry = m_cache[key]; 0060 entry.refCount -= 1; 0061 if (entry.refCount == 0) { 0062 m_cache.remove(key); 0063 } 0064 } 0065 } 0066 0067 void removeAll(const QSet<QString> &keys) 0068 { 0069 for (const auto &key : keys) { 0070 m_cache.remove(key); 0071 } 0072 } 0073 0074 private: 0075 QHash<QString, cacheData> m_cache; 0076 }; 0077 0078 /// cache of directory counting result 0079 static LocalCache *s_cache; 0080 static QThread *s_workerThread; 0081 static KDirectoryContentsCounterWorker *s_worker; 0082 } 0083 0084 KDirectoryContentsCounter::KDirectoryContentsCounter(KFileItemModel *model, QObject *parent) 0085 : QObject(parent) 0086 , m_model(model) 0087 , m_priorityQueue() 0088 , m_queue() 0089 , m_workerIsBusy(false) 0090 , m_dirWatcher(nullptr) 0091 , m_watchedDirs() 0092 { 0093 if (s_cache == nullptr) { 0094 s_cache = new LocalCache(); 0095 } 0096 0097 if (!s_workerThread) { 0098 s_workerThread = new QThread(); 0099 s_workerThread->setObjectName(QStringLiteral("KDirectoryContentsCounterThread")); 0100 s_workerThread->start(); 0101 } 0102 0103 if (!s_worker) { 0104 s_worker = new KDirectoryContentsCounterWorker(); 0105 s_worker->moveToThread(s_workerThread); 0106 } 0107 0108 connect(m_model, &KFileItemModel::itemsRemoved, this, &KDirectoryContentsCounter::slotItemsRemoved); 0109 connect(m_model, &KFileItemModel::directoryRefreshing, this, &KDirectoryContentsCounter::slotDirectoryRefreshing); 0110 0111 connect(this, &KDirectoryContentsCounter::requestDirectoryContentsCount, s_worker, &KDirectoryContentsCounterWorker::countDirectoryContents); 0112 0113 connect(s_worker, &KDirectoryContentsCounterWorker::result, this, &KDirectoryContentsCounter::slotResult); 0114 connect(s_worker, &KDirectoryContentsCounterWorker::intermediateResult, this, &KDirectoryContentsCounter::result); 0115 connect(s_worker, &KDirectoryContentsCounterWorker::finished, this, &KDirectoryContentsCounter::scheduleNext); 0116 0117 m_dirWatcher = new KDirWatch(this); 0118 connect(m_dirWatcher, &KDirWatch::dirty, this, &KDirectoryContentsCounter::slotDirWatchDirty); 0119 } 0120 0121 KDirectoryContentsCounter::~KDirectoryContentsCounter() 0122 { 0123 s_cache->unRefAll(m_watchedDirs); 0124 } 0125 0126 void KDirectoryContentsCounter::slotResult(const QString &path, int count, long long size) 0127 { 0128 const auto fileInfo = QFileInfo(path); 0129 const QString resolvedPath = fileInfo.canonicalFilePath(); 0130 if (fileInfo.isReadable() && !m_watchedDirs.contains(resolvedPath)) { 0131 m_dirWatcher->addDir(resolvedPath); 0132 } 0133 bool inserted = m_watchedDirs.insert(resolvedPath) == m_watchedDirs.end(); 0134 0135 // update cache or overwrite value 0136 s_cache->insert(resolvedPath, {count, size, true}, inserted); 0137 0138 // sends the results 0139 Q_EMIT result(path, count, size); 0140 } 0141 0142 void KDirectoryContentsCounter::slotDirWatchDirty(const QString &path) 0143 { 0144 const int index = m_model->index(QUrl::fromLocalFile(path)); 0145 if (index >= 0) { 0146 if (!m_model->fileItem(index).isDir()) { 0147 // If INotify is used, KDirWatch issues the dirty() signal 0148 // also for changed files inside the directory, even if we 0149 // don't enable this behavior explicitly (see bug 309740). 0150 return; 0151 } 0152 0153 scanDirectory(path, PathCountPriority::High); 0154 } 0155 } 0156 0157 void KDirectoryContentsCounter::slotItemsRemoved() 0158 { 0159 const bool allItemsRemoved = (m_model->count() == 0); 0160 0161 if (allItemsRemoved) { 0162 s_cache->removeAll(m_watchedDirs); 0163 stopWorker(); 0164 } 0165 0166 if (!m_watchedDirs.isEmpty()) { 0167 // Don't let KDirWatch watch for removed items 0168 if (allItemsRemoved) { 0169 for (const QString &path : std::as_const(m_watchedDirs)) { 0170 m_dirWatcher->removeDir(path); 0171 } 0172 m_watchedDirs.clear(); 0173 } else { 0174 QMutableSetIterator<QString> it(m_watchedDirs); 0175 while (it.hasNext()) { 0176 const QString &path = it.next(); 0177 if (m_model->index(QUrl::fromLocalFile(path)) < 0) { 0178 m_dirWatcher->removeDir(path); 0179 it.remove(); 0180 } 0181 } 0182 } 0183 } 0184 } 0185 0186 void KDirectoryContentsCounter::slotDirectoryRefreshing() 0187 { 0188 s_cache->removeAll(m_watchedDirs); 0189 } 0190 0191 void KDirectoryContentsCounter::scheduleNext() 0192 { 0193 if (!m_priorityQueue.empty()) { 0194 m_currentPath = m_priorityQueue.front(); 0195 m_priorityQueue.pop_front(); 0196 } else if (!m_queue.empty()) { 0197 m_currentPath = m_queue.front(); 0198 m_queue.pop_front(); 0199 } else { 0200 m_currentPath.clear(); 0201 m_workerIsBusy = false; 0202 return; 0203 } 0204 0205 const auto fileInfo = QFileInfo(m_currentPath); 0206 const QString resolvedPath = fileInfo.canonicalFilePath(); 0207 const auto pair = s_cache->value(resolvedPath); 0208 if (pair) { 0209 // fast path when in cache 0210 // will be updated later if result has changed 0211 Q_EMIT result(m_currentPath, pair.count, pair.size); 0212 } 0213 0214 // if scanned fully recently, skip rescan 0215 if (pair && pair.timestamp >= fileInfo.fileTime(QFile::FileModificationTime).toMSecsSinceEpoch()) { 0216 scheduleNext(); 0217 return; 0218 } 0219 0220 KDirectoryContentsCounterWorker::Options options; 0221 0222 if (m_model->showHiddenFiles()) { 0223 options |= KDirectoryContentsCounterWorker::CountHiddenFiles; 0224 } 0225 0226 m_workerIsBusy = true; 0227 Q_EMIT requestDirectoryContentsCount(m_currentPath, options, ContentDisplaySettings::recursiveDirectorySizeLimit()); 0228 } 0229 0230 void KDirectoryContentsCounter::enqueuePathScanning(const QString &path, bool alreadyInCache, PathCountPriority priority) 0231 { 0232 // ensure to update the entry in the queue 0233 auto it = std::find(m_queue.begin(), m_queue.end(), path); 0234 if (it != m_queue.end()) { 0235 m_queue.erase(it); 0236 } else { 0237 it = std::find(m_priorityQueue.begin(), m_priorityQueue.end(), path); 0238 if (it != m_priorityQueue.end()) { 0239 m_priorityQueue.erase(it); 0240 } 0241 } 0242 0243 if (priority == PathCountPriority::Normal) { 0244 if (alreadyInCache) { 0245 // we already knew the dir size 0246 // otherwise it gets lower priority 0247 m_queue.push_back(path); 0248 } else { 0249 m_queue.push_front(path); 0250 } 0251 } else { 0252 // append to priority queue 0253 m_priorityQueue.push_front(path); 0254 } 0255 } 0256 0257 void KDirectoryContentsCounter::scanDirectory(const QString &path, PathCountPriority priority) 0258 { 0259 if (m_workerIsBusy && m_currentPath == path) { 0260 // already listing 0261 return; 0262 } 0263 0264 const auto fileInfo = QFileInfo(path); 0265 const QString resolvedPath = fileInfo.canonicalFilePath(); 0266 const auto pair = s_cache->value(resolvedPath); 0267 if (pair) { 0268 // fast path when in cache 0269 // will be updated later if result has changed 0270 Q_EMIT result(path, pair.count, pair.size); 0271 } 0272 0273 // if scanned fully recently, skip rescan 0274 if (pair && pair.timestamp >= fileInfo.fileTime(QFile::FileModificationTime).toMSecsSinceEpoch()) { 0275 return; 0276 } 0277 0278 enqueuePathScanning(path, pair, priority); 0279 0280 if (!m_workerIsBusy && !s_worker->stopping()) { 0281 scheduleNext(); 0282 } 0283 } 0284 0285 void KDirectoryContentsCounter::stopWorker() 0286 { 0287 m_queue.clear(); 0288 m_priorityQueue.clear(); 0289 0290 if (m_workerIsBusy && m_currentPath == s_worker->scannedPath()) { 0291 s_worker->stop(); 0292 } 0293 m_currentPath.clear(); 0294 } 0295 0296 #include "moc_kdirectorycontentscounter.cpp"