]> cloud.milkyroute.net Git - dolphin.git/blob - src/kitemviews/private/kdirectorycontentscounter.cpp
GIT_SILENT Sync po/docbooks with svn
[dolphin.git] / src / kitemviews / private / kdirectorycontentscounter.cpp
1 /*
2 * SPDX-FileCopyrightText: 2011 Peter Penz <peter.penz19@gmail.com>
3 * SPDX-FileCopyrightText: 2013 Frank Reininghaus <frank78ac@googlemail.com>
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 */
7
8 #include "kdirectorycontentscounter.h"
9 #include "dolphin_contentdisplaysettings.h"
10 #include "kitemviews/kfileitemmodel.h"
11
12 #include <KDirWatch>
13
14 #include <QDir>
15 #include <QFileInfo>
16 #include <QThread>
17
18 namespace
19 {
20
21 class LocalCache
22 {
23 public:
24 struct cacheData {
25 int count = 0;
26 long long size = 0;
27 ushort refCount = 0;
28 qint64 timestamp = 0;
29
30 inline operator bool() const
31 {
32 return timestamp != 0 && count != -1;
33 }
34 };
35
36 LocalCache()
37 : m_cache()
38 {
39 }
40
41 cacheData insert(const QString &key, cacheData data, bool inserted)
42 {
43 data.timestamp = QDateTime::currentMSecsSinceEpoch();
44 if (inserted) {
45 data.refCount += 1;
46 }
47 m_cache.insert(key, data);
48 return data;
49 }
50
51 cacheData value(const QString &key) const
52 {
53 return m_cache.value(key);
54 }
55
56 void unRefAll(const QSet<QString> &keys)
57 {
58 for (const auto &key : keys) {
59 auto entry = m_cache[key];
60 entry.refCount -= 1;
61 if (entry.refCount == 0) {
62 m_cache.remove(key);
63 }
64 }
65 }
66
67 void removeAll(const QSet<QString> &keys)
68 {
69 for (const auto &key : keys) {
70 m_cache.remove(key);
71 }
72 }
73
74 private:
75 QHash<QString, cacheData> m_cache;
76 };
77
78 /// cache of directory counting result
79 static LocalCache *s_cache;
80 static QThread *s_workerThread;
81 static KDirectoryContentsCounterWorker *s_worker;
82 }
83
84 KDirectoryContentsCounter::KDirectoryContentsCounter(KFileItemModel *model, QObject *parent)
85 : QObject(parent)
86 , m_model(model)
87 , m_priorityQueue()
88 , m_queue()
89 , m_workerIsBusy(false)
90 , m_dirWatcher(nullptr)
91 , m_watchedDirs()
92 {
93 if (s_cache == nullptr) {
94 s_cache = new LocalCache();
95 }
96
97 if (!s_workerThread) {
98 s_workerThread = new QThread();
99 s_workerThread->setObjectName(QStringLiteral("KDirectoryContentsCounterThread"));
100 s_workerThread->start();
101 }
102
103 if (!s_worker) {
104 s_worker = new KDirectoryContentsCounterWorker();
105 s_worker->moveToThread(s_workerThread);
106 }
107
108 connect(m_model, &KFileItemModel::itemsRemoved, this, &KDirectoryContentsCounter::slotItemsRemoved);
109 connect(m_model, &KFileItemModel::directoryRefreshing, this, &KDirectoryContentsCounter::slotDirectoryRefreshing);
110
111 connect(this, &KDirectoryContentsCounter::requestDirectoryContentsCount, s_worker, &KDirectoryContentsCounterWorker::countDirectoryContents);
112
113 connect(s_worker, &KDirectoryContentsCounterWorker::result, this, &KDirectoryContentsCounter::slotResult);
114 connect(s_worker, &KDirectoryContentsCounterWorker::intermediateResult, this, &KDirectoryContentsCounter::result);
115 connect(s_worker, &KDirectoryContentsCounterWorker::finished, this, &KDirectoryContentsCounter::scheduleNext);
116
117 m_dirWatcher = new KDirWatch(this);
118 connect(m_dirWatcher, &KDirWatch::dirty, this, &KDirectoryContentsCounter::slotDirWatchDirty);
119 }
120
121 KDirectoryContentsCounter::~KDirectoryContentsCounter()
122 {
123 s_cache->unRefAll(m_watchedDirs);
124 }
125
126 void KDirectoryContentsCounter::slotResult(const QString &path, int count, long long size)
127 {
128 const auto fileInfo = QFileInfo(path);
129 const QString resolvedPath = fileInfo.canonicalFilePath();
130 if (fileInfo.isReadable() && !m_watchedDirs.contains(resolvedPath)) {
131 m_dirWatcher->addDir(resolvedPath);
132 }
133 bool inserted = m_watchedDirs.insert(resolvedPath) == m_watchedDirs.end();
134
135 // update cache or overwrite value
136 s_cache->insert(resolvedPath, {count, size, true}, inserted);
137
138 // sends the results
139 Q_EMIT result(path, count, size);
140 }
141
142 void KDirectoryContentsCounter::slotDirWatchDirty(const QString &path)
143 {
144 const int index = m_model->index(QUrl::fromLocalFile(path));
145 if (index >= 0) {
146 if (!m_model->fileItem(index).isDir()) {
147 // If INotify is used, KDirWatch issues the dirty() signal
148 // also for changed files inside the directory, even if we
149 // don't enable this behavior explicitly (see bug 309740).
150 return;
151 }
152
153 scanDirectory(path, PathCountPriority::High);
154 }
155 }
156
157 void KDirectoryContentsCounter::slotItemsRemoved()
158 {
159 const bool allItemsRemoved = (m_model->count() == 0);
160
161 if (allItemsRemoved) {
162 s_cache->removeAll(m_watchedDirs);
163 stopWorker();
164 }
165
166 if (!m_watchedDirs.isEmpty()) {
167 // Don't let KDirWatch watch for removed items
168 if (allItemsRemoved) {
169 for (const QString &path : std::as_const(m_watchedDirs)) {
170 m_dirWatcher->removeDir(path);
171 }
172 m_watchedDirs.clear();
173 } else {
174 QMutableSetIterator<QString> it(m_watchedDirs);
175 while (it.hasNext()) {
176 const QString &path = it.next();
177 if (m_model->index(QUrl::fromLocalFile(path)) < 0) {
178 m_dirWatcher->removeDir(path);
179 it.remove();
180 }
181 }
182 }
183 }
184 }
185
186 void KDirectoryContentsCounter::slotDirectoryRefreshing()
187 {
188 s_cache->removeAll(m_watchedDirs);
189 }
190
191 void KDirectoryContentsCounter::scheduleNext()
192 {
193 if (!m_priorityQueue.empty()) {
194 m_currentPath = m_priorityQueue.front();
195 m_priorityQueue.pop_front();
196 } else if (!m_queue.empty()) {
197 m_currentPath = m_queue.front();
198 m_queue.pop_front();
199 } else {
200 m_currentPath.clear();
201 m_workerIsBusy = false;
202 return;
203 }
204
205 const auto fileInfo = QFileInfo(m_currentPath);
206 const QString resolvedPath = fileInfo.canonicalFilePath();
207 const auto pair = s_cache->value(resolvedPath);
208 if (pair) {
209 // fast path when in cache
210 // will be updated later if result has changed
211 Q_EMIT result(m_currentPath, pair.count, pair.size);
212 }
213
214 // if scanned fully recently, skip rescan
215 if (pair && pair.timestamp >= fileInfo.fileTime(QFile::FileModificationTime).toMSecsSinceEpoch()) {
216 scheduleNext();
217 return;
218 }
219
220 KDirectoryContentsCounterWorker::Options options;
221
222 if (m_model->showHiddenFiles()) {
223 options |= KDirectoryContentsCounterWorker::CountHiddenFiles;
224 }
225
226 m_workerIsBusy = true;
227 Q_EMIT requestDirectoryContentsCount(m_currentPath, options, ContentDisplaySettings::recursiveDirectorySizeLimit());
228 }
229
230 void KDirectoryContentsCounter::enqueuePathScanning(const QString &path, bool alreadyInCache, PathCountPriority priority)
231 {
232 // ensure to update the entry in the queue
233 auto it = std::find(m_queue.begin(), m_queue.end(), path);
234 if (it != m_queue.end()) {
235 m_queue.erase(it);
236 } else {
237 it = std::find(m_priorityQueue.begin(), m_priorityQueue.end(), path);
238 if (it != m_priorityQueue.end()) {
239 m_priorityQueue.erase(it);
240 }
241 }
242
243 if (priority == PathCountPriority::Normal) {
244 if (alreadyInCache) {
245 // we already knew the dir size
246 // otherwise it gets lower priority
247 m_queue.push_back(path);
248 } else {
249 m_queue.push_front(path);
250 }
251 } else {
252 // append to priority queue
253 m_priorityQueue.push_front(path);
254 }
255 }
256
257 void KDirectoryContentsCounter::scanDirectory(const QString &path, PathCountPriority priority)
258 {
259 if (m_workerIsBusy && m_currentPath == path) {
260 // already listing
261 return;
262 }
263
264 const auto fileInfo = QFileInfo(path);
265 const QString resolvedPath = fileInfo.canonicalFilePath();
266 const auto pair = s_cache->value(resolvedPath);
267 if (pair) {
268 // fast path when in cache
269 // will be updated later if result has changed
270 Q_EMIT result(path, pair.count, pair.size);
271 }
272
273 // if scanned fully recently, skip rescan
274 if (pair && pair.timestamp >= fileInfo.fileTime(QFile::FileModificationTime).toMSecsSinceEpoch()) {
275 return;
276 }
277
278 enqueuePathScanning(path, pair, priority);
279
280 if (!m_workerIsBusy && !s_worker->stopping()) {
281 scheduleNext();
282 }
283 }
284
285 void KDirectoryContentsCounter::stopWorker()
286 {
287 m_queue.clear();
288 m_priorityQueue.clear();
289
290 if (m_workerIsBusy && m_currentPath == s_worker->scannedPath()) {
291 s_worker->stop();
292 }
293 m_currentPath.clear();
294 }
295
296 #include "moc_kdirectorycontentscounter.cpp"