Changeset View
Changeset View
Standalone View
Standalone View
src/kitemviews/private/kdirectorycontentscounter.cpp
Show All 18 Lines | |||||
19 | ***************************************************************************/ | 19 | ***************************************************************************/ | ||
20 | 20 | | |||
21 | #include "kdirectorycontentscounter.h" | 21 | #include "kdirectorycontentscounter.h" | ||
22 | #include "kitemviews/kfileitemmodel.h" | 22 | #include "kitemviews/kfileitemmodel.h" | ||
23 | 23 | | |||
24 | #include <KDirWatch> | 24 | #include <KDirWatch> | ||
25 | 25 | | |||
26 | #include <QFileInfo> | 26 | #include <QFileInfo> | ||
27 | #include <QDir> | ||||
27 | #include <QThread> | 28 | #include <QThread> | ||
28 | 29 | | |||
30 | namespace { | ||||
31 | /// cache of directory counting result | ||||
32 | static QHash<QString, QPair<int, long>> *s_cache; | ||||
meven: It would be great to be able to share this cache with baloo-widgets, so that those data can be… | |||||
meven: The Status bar info could be updated to get this data. | |||||
33 | } | ||||
34 | | ||||
29 | KDirectoryContentsCounter::KDirectoryContentsCounter(KFileItemModel* model, QObject* parent) : | 35 | KDirectoryContentsCounter::KDirectoryContentsCounter(KFileItemModel* model, QObject* parent) : | ||
30 | QObject(parent), | 36 | QObject(parent), | ||
31 | m_model(model), | 37 | m_model(model), | ||
32 | m_queue(), | 38 | m_queue(), | ||
33 | m_worker(nullptr), | 39 | m_worker(nullptr), | ||
34 | m_workerIsBusy(false), | 40 | m_workerIsBusy(false), | ||
35 | m_dirWatcher(nullptr), | 41 | m_dirWatcher(nullptr), | ||
36 | m_watchedDirs() | 42 | m_watchedDirs() | ||
37 | { | 43 | { | ||
38 | connect(m_model, &KFileItemModel::itemsRemoved, | 44 | connect(m_model, &KFileItemModel::itemsRemoved, | ||
39 | this, &KDirectoryContentsCounter::slotItemsRemoved); | 45 | this, &KDirectoryContentsCounter::slotItemsRemoved); | ||
40 | 46 | | |||
41 | if (!m_workerThread) { | 47 | if (!m_workerThread) { | ||
42 | m_workerThread = new QThread(); | 48 | m_workerThread = new QThread(); | ||
43 | m_workerThread->start(); | 49 | m_workerThread->start(); | ||
44 | } | 50 | } | ||
45 | 51 | | |||
52 | if (s_cache == nullptr) { | ||||
53 | s_cache = new QHash<QString, QPair<int, long>>(); | ||||
54 | } | ||||
55 | | ||||
46 | m_worker = new KDirectoryContentsCounterWorker(); | 56 | m_worker = new KDirectoryContentsCounterWorker(); | ||
47 | m_worker->moveToThread(m_workerThread); | 57 | m_worker->moveToThread(m_workerThread); | ||
48 | ++m_workersCount; | | |||
49 | 58 | | |||
50 | connect(this, &KDirectoryContentsCounter::requestDirectoryContentsCount, | 59 | connect(this, &KDirectoryContentsCounter::requestDirectoryContentsCount, | ||
51 | m_worker, &KDirectoryContentsCounterWorker::countDirectoryContents); | 60 | m_worker, &KDirectoryContentsCounterWorker::countDirectoryContents); | ||
52 | connect(m_worker, &KDirectoryContentsCounterWorker::result, | 61 | connect(m_worker, &KDirectoryContentsCounterWorker::result, | ||
53 | this, &KDirectoryContentsCounter::slotResult); | 62 | this, &KDirectoryContentsCounter::slotResult); | ||
54 | 63 | | |||
55 | m_dirWatcher = new KDirWatch(this); | 64 | m_dirWatcher = new KDirWatch(this); | ||
65 | m_dirWatcher->moveToThread(m_workerThread); | ||||
56 | connect(m_dirWatcher, &KDirWatch::dirty, this, &KDirectoryContentsCounter::slotDirWatchDirty); | 66 | connect(m_dirWatcher, &KDirWatch::dirty, this, &KDirectoryContentsCounter::slotDirWatchDirty); | ||
57 | } | 67 | } | ||
58 | 68 | | |||
59 | KDirectoryContentsCounter::~KDirectoryContentsCounter() | 69 | KDirectoryContentsCounter::~KDirectoryContentsCounter() | ||
60 | { | 70 | { | ||
61 | --m_workersCount; | 71 | if (m_workerThread->isRunning()) { | ||
elvisangelaccio: Why this change? What's wrong with `m_workersCount` ? | |||||
I intend to have a single worker KDirectoryContentsCounter, so why bother reimplementing a refCount ? meven: I intend to have a single worker KDirectoryContentsCounter, so why bother reimplementing a… | |||||
I see. This is valuable information, please put it in the commit message :) elvisangelaccio: I see. This is valuable information, please put it in the commit message :) | |||||
62 | | ||||
63 | if (m_workersCount > 0) { | | |||
64 | // The worker thread will continue running. It could even be running | 72 | // The worker thread will continue running. It could even be running | ||
65 | // a method of m_worker at the moment, so we delete it using | 73 | // a method of m_worker at the moment, so we delete it using | ||
66 | // deleteLater() to prevent a crash. | 74 | // deleteLater() to prevent a crash. | ||
67 | m_worker->deleteLater(); | 75 | m_worker->deleteLater(); | ||
68 | } else { | 76 | } else { | ||
69 | // There are no remaining workers -> stop the worker thread. | 77 | // There are no remaining workers -> stop the worker thread. | ||
70 | m_workerThread->quit(); | 78 | m_workerThread->quit(); | ||
71 | m_workerThread->wait(); | 79 | m_workerThread->wait(); | ||
72 | delete m_workerThread; | 80 | delete m_workerThread; | ||
73 | m_workerThread = nullptr; | 81 | m_workerThread = nullptr; | ||
74 | 82 | | |||
75 | // The worker thread has finished running now, so it's safe to delete | 83 | // The worker thread has finished running now, so it's safe to delete | ||
76 | // m_worker. deleteLater() would not work at all because the event loop | 84 | // m_worker. deleteLater() would not work at all because the event loop | ||
77 | // which would deliver the event to m_worker is not running any more. | 85 | // which would deliver the event to m_worker is not running any more. | ||
78 | delete m_worker; | 86 | delete m_worker; | ||
79 | } | 87 | } | ||
80 | } | 88 | } | ||
81 | 89 | | |||
82 | void KDirectoryContentsCounter::addDirectory(const QString& path) | 90 | void KDirectoryContentsCounter::scanDirectory(const QString& path) | ||
83 | { | 91 | { | ||
84 | startWorker(path); | 92 | startWorker(path); | ||
85 | } | 93 | } | ||
86 | 94 | | |||
87 | int KDirectoryContentsCounter::countDirectoryContentsSynchronously(const QString& path) | 95 | void KDirectoryContentsCounter::slotResult(const QString& path, int count, long size) | ||
88 | { | 96 | { | ||
89 | const QString resolvedPath = QFileInfo(path).canonicalFilePath(); | 97 | m_workerIsBusy = false; | ||
98 | | ||||
99 | const QFileInfo info = QFileInfo(path); | ||||
100 | const QString resolvedPath = info.canonicalFilePath(); | ||||
90 | 101 | | |||
91 | if (!m_dirWatcher->contains(resolvedPath)) { | 102 | if (!m_dirWatcher->contains(resolvedPath)) { | ||
92 | m_dirWatcher->addDir(resolvedPath); | 103 | m_dirWatcher->addDir(resolvedPath); | ||
93 | m_watchedDirs.insert(resolvedPath); | 104 | m_watchedDirs.insert(resolvedPath); | ||
94 | } | 105 | } | ||
95 | 106 | | |||
96 | KDirectoryContentsCounterWorker::Options options; | 107 | if (!m_queue.isEmpty()) { | ||
97 | 108 | startWorker(m_queue.dequeue()); | |||
98 | if (m_model->showHiddenFiles()) { | | |||
99 | options |= KDirectoryContentsCounterWorker::CountHiddenFiles; | | |||
100 | } | | |||
101 | | ||||
102 | if (m_model->showDirectoriesOnly()) { | | |||
103 | options |= KDirectoryContentsCounterWorker::CountDirectoriesOnly; | | |||
104 | } | 109 | } | ||
105 | 110 | | |||
106 | return KDirectoryContentsCounterWorker::subItemsCount(path, options); | 111 | if (s_cache->contains(resolvedPath)) { | ||
112 | const auto pair = s_cache->value(resolvedPath); | ||||
113 | if (pair.first == count && pair.second == size) { | ||||
114 | // no change no need to send another result event | ||||
115 | return; | ||||
107 | } | 116 | } | ||
108 | | ||||
109 | void KDirectoryContentsCounter::slotResult(const QString& path, int count) | | |||
110 | { | | |||
111 | m_workerIsBusy = false; | | |||
112 | | ||||
113 | const QString resolvedPath = QFileInfo(path).canonicalFilePath(); | | |||
114 | | ||||
115 | if (!m_dirWatcher->contains(resolvedPath)) { | | |||
116 | m_dirWatcher->addDir(resolvedPath); | | |||
117 | m_watchedDirs.insert(resolvedPath); | | |||
118 | } | 117 | } | ||
119 | 118 | | |||
120 | if (!m_queue.isEmpty()) { | 119 | if (info.dir().path() == m_model->rootItem().url().path()) { | ||
121 | startWorker(m_queue.dequeue()); | 120 | // update cache or overwrite value | ||
121 | // when path is a direct children of the current model root | ||||
122 | s_cache->insert(resolvedPath, QPair<int, long>(count, size)); | ||||
122 | } | 123 | } | ||
123 | 124 | | |||
124 | emit result(path, count); | 125 | // sends the results | ||
126 | emit result(resolvedPath, count, size); | ||||
125 | } | 127 | } | ||
126 | 128 | | |||
127 | void KDirectoryContentsCounter::slotDirWatchDirty(const QString& path) | 129 | void KDirectoryContentsCounter::slotDirWatchDirty(const QString& path) | ||
128 | { | 130 | { | ||
129 | const int index = m_model->index(QUrl::fromLocalFile(path)); | 131 | const int index = m_model->index(QUrl::fromLocalFile(path)); | ||
130 | if (index >= 0) { | 132 | if (index >= 0) { | ||
131 | if (!m_model->fileItem(index).isDir()) { | 133 | if (!m_model->fileItem(index).isDir()) { | ||
132 | // If INotify is used, KDirWatch issues the dirty() signal | 134 | // If INotify is used, KDirWatch issues the dirty() signal | ||
133 | // also for changed files inside the directory, even if we | 135 | // also for changed files inside the directory, even if we | ||
134 | // don't enable this behavior explicitly (see bug 309740). | 136 | // don't enable this behavior explicitly (see bug 309740). | ||
135 | return; | 137 | return; | ||
136 | } | 138 | } | ||
137 | 139 | | |||
138 | startWorker(path); | 140 | startWorker(path); | ||
139 | } | 141 | } | ||
140 | } | 142 | } | ||
141 | 143 | | |||
142 | void KDirectoryContentsCounter::slotItemsRemoved() | 144 | void KDirectoryContentsCounter::slotItemsRemoved() | ||
143 | { | 145 | { | ||
144 | const bool allItemsRemoved = (m_model->count() == 0); | 146 | const bool allItemsRemoved = (m_model->count() == 0); | ||
145 | 147 | | |||
146 | if (!m_watchedDirs.isEmpty()) { | 148 | if (!m_watchedDirs.isEmpty()) { | ||
147 | // Don't let KDirWatch watch for removed items | 149 | // Don't let KDirWatch watch for removed items | ||
148 | if (allItemsRemoved) { | 150 | if (allItemsRemoved) { | ||
149 | foreach (const QString& path, m_watchedDirs) { | 151 | for (const QString& path : qAsConst(m_watchedDirs)) { | ||
150 | m_dirWatcher->removeDir(path); | 152 | m_dirWatcher->removeDir(path); | ||
151 | } | 153 | } | ||
152 | m_watchedDirs.clear(); | 154 | m_watchedDirs.clear(); | ||
153 | m_queue.clear(); | 155 | m_queue.clear(); | ||
154 | } else { | 156 | } else { | ||
155 | QMutableSetIterator<QString> it(m_watchedDirs); | 157 | QMutableSetIterator<QString> it(m_watchedDirs); | ||
156 | while (it.hasNext()) { | 158 | while (it.hasNext()) { | ||
157 | const QString& path = it.next(); | 159 | const QString& path = it.next(); | ||
158 | if (m_model->index(QUrl::fromLocalFile(path)) < 0) { | 160 | if (m_model->index(QUrl::fromLocalFile(path)) < 0) { | ||
159 | m_dirWatcher->removeDir(path); | 161 | m_dirWatcher->removeDir(path); | ||
160 | it.remove(); | 162 | it.remove(); | ||
161 | } | 163 | } | ||
162 | } | 164 | } | ||
163 | } | 165 | } | ||
164 | } | 166 | } | ||
165 | } | 167 | } | ||
166 | 168 | | |||
167 | void KDirectoryContentsCounter::startWorker(const QString& path) | 169 | void KDirectoryContentsCounter::startWorker(const QString& path) | ||
168 | { | 170 | { | ||
171 | if (s_cache->contains(path)) { | ||||
172 | // fast path when in cache | ||||
173 | // will be updated later if result has changed | ||||
174 | const auto pair = s_cache->value(path); | ||||
175 | emit result(path, pair.first, pair.second); | ||||
176 | } | ||||
177 | | ||||
169 | if (m_workerIsBusy) { | 178 | if (m_workerIsBusy) { | ||
170 | m_queue.enqueue(path); | 179 | m_queue.enqueue(path); | ||
171 | } else { | 180 | } else { | ||
172 | KDirectoryContentsCounterWorker::Options options; | 181 | KDirectoryContentsCounterWorker::Options options; | ||
173 | 182 | | |||
174 | if (m_model->showHiddenFiles()) { | 183 | if (m_model->showHiddenFiles()) { | ||
175 | options |= KDirectoryContentsCounterWorker::CountHiddenFiles; | 184 | options |= KDirectoryContentsCounterWorker::CountHiddenFiles; | ||
176 | } | 185 | } | ||
177 | 186 | | |||
178 | if (m_model->showDirectoriesOnly()) { | 187 | if (m_model->showDirectoriesOnly()) { | ||
179 | options |= KDirectoryContentsCounterWorker::CountDirectoriesOnly; | 188 | options |= KDirectoryContentsCounterWorker::CountDirectoriesOnly; | ||
180 | } | 189 | } | ||
181 | 190 | | |||
182 | emit requestDirectoryContentsCount(path, options); | 191 | emit requestDirectoryContentsCount(path, options); | ||
183 | m_workerIsBusy = true; | 192 | m_workerIsBusy = true; | ||
184 | } | 193 | } | ||
185 | } | 194 | } | ||
186 | 195 | | |||
187 | QThread* KDirectoryContentsCounter::m_workerThread = nullptr; | 196 | QThread* KDirectoryContentsCounter::m_workerThread = nullptr; | ||
188 | int KDirectoryContentsCounter::m_workersCount = 0; | |
It would be great to be able to share this cache with baloo-widgets, so that those data can be used in the metadata widget