]> cloud.milkyroute.net Git - dolphin.git/blob - src/kitemviews/private/kdirectorycontentscounter.cpp
37e852ab9eea2b210b8196ae3dcd972bc609a8ef
[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 "kitemviews/kfileitemmodel.h"
10
11 #include <KDirWatch>
12
13 #include <QDir>
14 #include <QFileInfo>
15 #include <QThread>
16
17 namespace
18 {
19 /// cache of directory counting result
20 static QHash<QString, QPair<int, long>> *s_cache;
21 }
22
23 KDirectoryContentsCounter::KDirectoryContentsCounter(KFileItemModel *model, QObject *parent)
24 : QObject(parent)
25 , m_model(model)
26 , m_queue()
27 , m_worker(nullptr)
28 , m_workerIsBusy(false)
29 , m_dirWatcher(nullptr)
30 , m_watchedDirs()
31 {
32 connect(m_model, &KFileItemModel::itemsRemoved, this, &KDirectoryContentsCounter::slotItemsRemoved);
33
34 if (!m_workerThread) {
35 m_workerThread = new QThread();
36 m_workerThread->start();
37 }
38
39 if (s_cache == nullptr) {
40 s_cache = new QHash<QString, QPair<int, long>>();
41 }
42
43 m_worker = new KDirectoryContentsCounterWorker();
44 m_worker->moveToThread(m_workerThread);
45
46 connect(this, &KDirectoryContentsCounter::requestDirectoryContentsCount, m_worker, &KDirectoryContentsCounterWorker::countDirectoryContents);
47 connect(this, &KDirectoryContentsCounter::stop, m_worker, &KDirectoryContentsCounterWorker::stop);
48 connect(m_worker, &KDirectoryContentsCounterWorker::result, this, &KDirectoryContentsCounter::slotResult);
49
50 m_dirWatcher = new KDirWatch(this);
51 connect(m_dirWatcher, &KDirWatch::dirty, this, &KDirectoryContentsCounter::slotDirWatchDirty);
52 }
53
54 KDirectoryContentsCounter::~KDirectoryContentsCounter()
55 {
56 if (m_workerThread->isRunning()) {
57 // The worker thread will continue running. It could even be running
58 // a method of m_worker at the moment, so we delete it using
59 // deleteLater() to prevent a crash.
60 m_worker->deleteLater();
61 } else {
62 // There are no remaining workers -> stop the worker thread.
63 m_workerThread->quit();
64 m_workerThread->wait();
65 delete m_workerThread;
66 m_workerThread = nullptr;
67
68 // The worker thread has finished running now, so it's safe to delete
69 // m_worker. deleteLater() would not work at all because the event loop
70 // which would deliver the event to m_worker is not running any more.
71 delete m_worker;
72 }
73 }
74
75 void KDirectoryContentsCounter::slotResult(const QString &path, int count, long size)
76 {
77 m_workerIsBusy = false;
78
79 const QFileInfo info = QFileInfo(path);
80 const QString resolvedPath = info.canonicalFilePath();
81
82 if (!m_dirWatcher->contains(resolvedPath)) {
83 m_dirWatcher->addDir(resolvedPath);
84 m_watchedDirs.insert(resolvedPath);
85 }
86
87 if (!m_priorityQueue.empty()) {
88 const QString firstPath = m_priorityQueue.front();
89 m_priorityQueue.pop_front();
90 scanDirectory(firstPath, PathCountPriority::High);
91 } else if (!m_queue.empty()) {
92 const QString firstPath = m_queue.front();
93 m_queue.pop_front();
94 scanDirectory(firstPath, PathCountPriority::Normal);
95 }
96
97 if (s_cache->contains(resolvedPath)) {
98 const auto pair = s_cache->value(resolvedPath);
99 if (pair.first == count && pair.second == size) {
100 // no change no need to send another result event
101 return;
102 }
103 }
104
105 if (info.dir().path() == m_model->rootItem().url().path()) {
106 // update cache or overwrite value
107 // when path is a direct children of the current model root
108 s_cache->insert(resolvedPath, QPair<int, long>(count, size));
109 }
110
111 // sends the results
112 Q_EMIT result(path, count, size);
113 }
114
115 void KDirectoryContentsCounter::slotDirWatchDirty(const QString &path)
116 {
117 const int index = m_model->index(QUrl::fromLocalFile(path));
118 if (index >= 0) {
119 if (!m_model->fileItem(index).isDir()) {
120 // If INotify is used, KDirWatch issues the dirty() signal
121 // also for changed files inside the directory, even if we
122 // don't enable this behavior explicitly (see bug 309740).
123 return;
124 }
125
126 scanDirectory(path, PathCountPriority::High);
127 }
128 }
129
130 void KDirectoryContentsCounter::slotItemsRemoved()
131 {
132 const bool allItemsRemoved = (m_model->count() == 0);
133
134 if (!m_watchedDirs.isEmpty()) {
135 // Don't let KDirWatch watch for removed items
136 if (allItemsRemoved) {
137 for (const QString &path : qAsConst(m_watchedDirs)) {
138 m_dirWatcher->removeDir(path);
139 }
140 m_watchedDirs.clear();
141 m_queue.clear();
142 } else {
143 QMutableSetIterator<QString> it(m_watchedDirs);
144 while (it.hasNext()) {
145 const QString &path = it.next();
146 if (m_model->index(QUrl::fromLocalFile(path)) < 0) {
147 m_dirWatcher->removeDir(path);
148 it.remove();
149 }
150 }
151 }
152 }
153 }
154
155 void KDirectoryContentsCounter::scanDirectory(const QString &path, PathCountPriority priority)
156 {
157 const QString resolvedPath = QFileInfo(path).canonicalFilePath();
158 const bool alreadyInCache = s_cache->contains(resolvedPath);
159 if (alreadyInCache) {
160 // fast path when in cache
161 // will be updated later if result has changed
162 const auto pair = s_cache->value(resolvedPath);
163 Q_EMIT result(path, pair.first, pair.second);
164 }
165
166 if (m_workerIsBusy) {
167 // only enqueue path not yet in queue
168 if (std::find(m_queue.begin(), m_queue.end(), path) == m_queue.end()
169 && std::find(m_priorityQueue.begin(), m_priorityQueue.end(), path) == m_priorityQueue.end()) {
170 if (priority == PathCountPriority::Normal) {
171 if (alreadyInCache) {
172 // if we already knew the dir size, it gets lower priority
173 m_queue.push_back(path);
174 } else {
175 m_queue.push_front(path);
176 }
177 } else {
178 // append to priority queue
179 m_priorityQueue.push_back(path);
180 }
181 }
182 } else {
183 KDirectoryContentsCounterWorker::Options options;
184
185 if (m_model->showHiddenFiles()) {
186 options |= KDirectoryContentsCounterWorker::CountHiddenFiles;
187 }
188
189 if (m_model->showDirectoriesOnly()) {
190 options |= KDirectoryContentsCounterWorker::CountDirectoriesOnly;
191 }
192
193 Q_EMIT requestDirectoryContentsCount(path, options);
194 m_workerIsBusy = true;
195 }
196 }
197
198 void KDirectoryContentsCounter::stopWorker()
199 {
200 m_queue.clear();
201 m_priorityQueue.clear();
202 Q_EMIT stop();
203 }
204
205 QThread *KDirectoryContentsCounter::m_workerThread = nullptr;