1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "fileinfothread_p.h"
5#include <qdiriterator.h>
6#include <qpointer.h>
7#include <qtimer.h>
8
9#include <QDebug>
10#include <QtCore/qloggingcategory.h>
11
12QT_BEGIN_NAMESPACE
13
14Q_LOGGING_CATEGORY(lcFileInfoThread, "qt.labs.folderlistmodel.fileinfothread")
15
16FileInfoThread::FileInfoThread(QObject *parent)
17 : QThread(parent),
18 abort(false),
19 scanPending(false),
20#if QT_CONFIG(filesystemwatcher)
21 watcher(nullptr),
22#endif
23 sortFlags(QDir::Name),
24 needUpdate(true),
25 updateTypes(UpdateType::None),
26 showFiles(true),
27 showDirs(true),
28 showDirsFirst(false),
29 showDotAndDotDot(false),
30 showHidden(false),
31 showOnlyReadable(false),
32 caseSensitive(true)
33{
34#if QT_CONFIG(filesystemwatcher)
35 watcher = new QFileSystemWatcher(this);
36 connect(sender: watcher, SIGNAL(directoryChanged(QString)), receiver: this, SLOT(dirChanged(QString)));
37 connect(sender: watcher, SIGNAL(fileChanged(QString)), receiver: this, SLOT(updateFile(QString)));
38#endif // filesystemwatcher
39}
40
41FileInfoThread::~FileInfoThread()
42{
43 QMutexLocker locker(&mutex);
44 abort = true;
45 condition.wakeOne();
46 locker.unlock();
47 wait();
48}
49
50void FileInfoThread::clear()
51{
52 QMutexLocker locker(&mutex);
53#if QT_CONFIG(filesystemwatcher)
54 watcher->removePaths(files: watcher->files());
55 watcher->removePaths(files: watcher->directories());
56#endif
57}
58
59void FileInfoThread::removePath(const QString &path)
60{
61 QMutexLocker locker(&mutex);
62#if QT_CONFIG(filesystemwatcher)
63 if (!path.startsWith(c: QLatin1Char(':')))
64 watcher->removePath(file: path);
65#else
66 Q_UNUSED(path);
67#endif
68 currentPath.clear();
69}
70
71void FileInfoThread::setPath(const QString &path)
72{
73 qCDebug(lcFileInfoThread) << "setPath called with path" << path;
74 Q_ASSERT(!path.isEmpty());
75
76 QMutexLocker locker(&mutex);
77#if QT_CONFIG(filesystemwatcher)
78 if (!path.startsWith(c: QLatin1Char(':')))
79 watcher->addPath(file: path);
80#endif
81 currentPath = path;
82 needUpdate = true;
83 initiateScan();
84}
85
86void FileInfoThread::setRootPath(const QString &path)
87{
88 qCDebug(lcFileInfoThread) << "setRootPath called with path" << path;
89 Q_ASSERT(!path.isEmpty());
90
91 QMutexLocker locker(&mutex);
92 rootPath = path;
93}
94
95#if QT_CONFIG(filesystemwatcher)
96void FileInfoThread::dirChanged(const QString &directoryPath)
97{
98 qCDebug(lcFileInfoThread) << "dirChanged called with directoryPath" << directoryPath;
99 Q_UNUSED(directoryPath);
100 QMutexLocker locker(&mutex);
101 updateTypes |= UpdateType::Contents;
102 initiateScan();
103}
104#endif
105
106void FileInfoThread::setSortFlags(QDir::SortFlags flags)
107{
108 qCDebug(lcFileInfoThread) << "setSortFlags called with flags" << flags;
109 Q_ASSERT(flags != sortFlags);
110 QMutexLocker locker(&mutex);
111 sortFlags = flags;
112 updateTypes |= UpdateType::Sort;
113 needUpdate = true;
114 initiateScan();
115}
116
117void FileInfoThread::setNameFilters(const QStringList & filters)
118{
119 qCDebug(lcFileInfoThread) << "setNameFilters called with filters" << filters;
120 QMutexLocker locker(&mutex);
121 nameFilters = filters;
122 updateTypes |= UpdateType::Contents;
123 initiateScan();
124}
125
126void FileInfoThread::setShowFiles(bool show)
127{
128 qCDebug(lcFileInfoThread) << "setShowFiles called with show" << show;
129 QMutexLocker locker(&mutex);
130 showFiles = show;
131 updateTypes |= UpdateType::Contents;
132 initiateScan();
133}
134
135void FileInfoThread::setShowDirs(bool showFolders)
136{
137 qCDebug(lcFileInfoThread) << "setShowDirs called with showFolders" << showFolders;
138 QMutexLocker locker(&mutex);
139 showDirs = showFolders;
140 updateTypes |= UpdateType::Contents;
141 initiateScan();
142}
143
144void FileInfoThread::setShowDirsFirst(bool show)
145{
146 qCDebug(lcFileInfoThread) << "setShowDirsFirst called with show" << show;
147 QMutexLocker locker(&mutex);
148 showDirsFirst = show;
149 updateTypes |= UpdateType::Contents;
150 initiateScan();
151}
152
153void FileInfoThread::setShowDotAndDotDot(bool on)
154{
155 qCDebug(lcFileInfoThread) << "setShowDotAndDotDot called with on" << on;
156 QMutexLocker locker(&mutex);
157 showDotAndDotDot = on;
158 updateTypes |= UpdateType::Contents;
159 needUpdate = true;
160 initiateScan();
161}
162
163void FileInfoThread::setShowHidden(bool on)
164{
165 qCDebug(lcFileInfoThread) << "setShowHidden called with on" << on;
166 QMutexLocker locker(&mutex);
167 showHidden = on;
168 updateTypes |= UpdateType::Contents;
169 needUpdate = true;
170 initiateScan();
171}
172
173void FileInfoThread::setShowOnlyReadable(bool on)
174{
175 qCDebug(lcFileInfoThread) << "setShowOnlyReadable called with on" << on;
176 QMutexLocker locker(&mutex);
177 showOnlyReadable = on;
178 updateTypes |= UpdateType::Contents;
179 initiateScan();
180}
181
182void FileInfoThread::setCaseSensitive(bool on)
183{
184 qCDebug(lcFileInfoThread) << "setCaseSensitive called with on" << on;
185 QMutexLocker locker(&mutex);
186 caseSensitive = on;
187 updateTypes |= UpdateType::Contents;
188 initiateScan();
189}
190
191#if QT_CONFIG(filesystemwatcher)
192void FileInfoThread::updateFile(const QString &path)
193{
194 qCDebug(lcFileInfoThread) << "updateFile called with path" << path;
195 Q_UNUSED(path);
196 QMutexLocker locker(&mutex);
197 updateTypes |= UpdateType::Contents;
198 initiateScan();
199}
200#endif
201
202void FileInfoThread::run()
203{
204 forever {
205 bool updateFiles = false;
206 QMutexLocker locker(&mutex);
207 if (abort) {
208 return;
209 }
210 if (currentPath.isEmpty() || !needUpdate) {
211 emit statusChanged(status: currentPath.isEmpty() ? QQuickFolderListModel::Null : QQuickFolderListModel::Ready);
212 condition.wait(lockedMutex: &mutex);
213 }
214
215 if (abort) {
216 return;
217 }
218
219 if (!currentPath.isEmpty()) {
220 updateFiles = true;
221 emit statusChanged(status: QQuickFolderListModel::Loading);
222 }
223 if (updateFiles)
224 getFileInfos(path: currentPath);
225 locker.unlock();
226 }
227}
228
229void FileInfoThread::runOnce()
230{
231 if (scanPending)
232 return;
233 scanPending = true;
234 QPointer<FileInfoThread> guardedThis(this);
235
236 auto getFileInfosAsync = [guardedThis](){
237 if (!guardedThis)
238 return;
239 guardedThis->scanPending = false;
240 if (guardedThis->currentPath.isEmpty()) {
241 emit guardedThis->statusChanged(status: QQuickFolderListModel::Null);
242 return;
243 }
244 emit guardedThis->statusChanged(status: QQuickFolderListModel::Loading);
245 guardedThis->getFileInfos(path: guardedThis->currentPath);
246 emit guardedThis->statusChanged(status: QQuickFolderListModel::Ready);
247 };
248
249 QTimer::singleShot(interval: 0, slot&: getFileInfosAsync);
250}
251
252void FileInfoThread::initiateScan()
253{
254#if QT_CONFIG(thread)
255 qCDebug(lcFileInfoThread) << "initiateScan is about to call condition.wakeAll()";
256 condition.wakeAll();
257#else
258 qCDebug(lcFileInfoThread) << "initiateScan is about to call runOnce()";
259 runOnce();
260#endif
261}
262
263QString fileInfoListToString(const QFileInfoList &fileInfoList)
264{
265 return fileInfoList.size() <= 10
266 ? QDebug::toString(object: fileInfoList)
267 : QString::fromLatin1(ba: "%1 files").arg(a: fileInfoList.size());
268}
269
270void FileInfoThread::getFileInfos(const QString &path)
271{
272 qCDebug(lcFileInfoThread) << "getFileInfos called with path" << path << "- updateType" << updateTypes;
273
274 QDir::Filters filter;
275 if (caseSensitive)
276 filter = QDir::CaseSensitive;
277 if (showFiles)
278 filter = filter | QDir::Files;
279 if (showDirs)
280 filter = filter | QDir::AllDirs | QDir::Drives;
281 if (!showDotAndDotDot)
282 filter = filter | QDir::NoDot | QDir::NoDotDot;
283 else if (path == rootPath)
284 filter = filter | QDir::NoDotDot;
285 if (showHidden)
286 filter = filter | QDir::Hidden;
287 if (showOnlyReadable)
288 filter = filter | QDir::Readable;
289 if (showDirsFirst)
290 sortFlags = sortFlags | QDir::DirsFirst;
291
292 QDir currentDir(path, QString(), sortFlags);
293 QList<FileProperty> filePropertyList;
294
295 const QFileInfoList fileInfoList = currentDir.entryInfoList(nameFilters, filters: filter, sort: sortFlags);
296
297 if (!fileInfoList.isEmpty()) {
298 filePropertyList.reserve(asize: fileInfoList.size());
299 for (const QFileInfo &info : fileInfoList)
300 filePropertyList << FileProperty(info);
301
302 if (updateTypes & UpdateType::Contents) {
303 int fromIndex = 0;
304 int toIndex = currentFileList.size()-1;
305 findChangeRange(list: filePropertyList, fromIndex, toIndex);
306 currentFileList = filePropertyList;
307 qCDebug(lcFileInfoThread) << "- about to emit directoryUpdated with fromIndex" << fromIndex
308 << "toIndex" << toIndex << "fileInfoList" << fileInfoListToString(fileInfoList);
309 emit directoryUpdated(directory: path, list: filePropertyList, fromIndex, toIndex);
310 } else {
311 currentFileList = filePropertyList;
312 if (updateTypes & UpdateType::Sort) {
313 qCDebug(lcFileInfoThread) << "- about to emit sortFinished - fileInfoList:"
314 << fileInfoListToString(fileInfoList);
315 emit sortFinished(list: filePropertyList);
316 } else {
317 qCDebug(lcFileInfoThread) << "- about to emit directoryChanged - fileInfoList:"
318 << fileInfoListToString(fileInfoList);
319 emit directoryChanged(directory: path, list: filePropertyList);
320 }
321 }
322 } else {
323 // The directory is empty
324 if (updateTypes & UpdateType::Contents) {
325 int fromIndex = 0;
326 int toIndex = currentFileList.size()-1;
327 currentFileList.clear();
328 qCDebug(lcFileInfoThread) << "- directory is empty, about to emit directoryUpdated with fromIndex"
329 << fromIndex << "toIndex" << toIndex;
330 emit directoryUpdated(directory: path, list: filePropertyList, fromIndex, toIndex);
331 } else {
332 currentFileList.clear();
333 qCDebug(lcFileInfoThread) << "- directory is empty, about to emit directoryChanged";
334 emit directoryChanged(directory: path, list: filePropertyList);
335 }
336 }
337 updateTypes = UpdateType::None;
338 needUpdate = false;
339}
340
341void FileInfoThread::findChangeRange(const QList<FileProperty> &list, int &fromIndex, int &toIndex)
342{
343 if (currentFileList.size() == 0) {
344 fromIndex = 0;
345 toIndex = list.size();
346 return;
347 }
348
349 int i;
350 int listSize = list.size() < currentFileList.size() ? list.size() : currentFileList.size();
351 bool changeFound = false;
352
353 for (i=0; i < listSize; i++) {
354 if (list.at(i) != currentFileList.at(i)) {
355 changeFound = true;
356 break;
357 }
358 }
359
360 if (changeFound)
361 fromIndex = i;
362 else
363 fromIndex = i-1;
364
365 // For now I let the rest of the list be updated..
366 toIndex = list.size() > currentFileList.size() ? list.size() - 1 : currentFileList.size() - 1;
367}
368
369constexpr FileInfoThread::UpdateTypes operator|(FileInfoThread::UpdateType f1, FileInfoThread::UpdateTypes f2) noexcept
370{
371 return f2 | f1;
372}
373
374constexpr FileInfoThread::UpdateTypes operator&(FileInfoThread::UpdateType f1, FileInfoThread::UpdateTypes f2) noexcept
375{
376 return f2 & f1;
377}
378
379QT_END_NAMESPACE
380
381#include "moc_fileinfothread_p.cpp"
382

source code of qtdeclarative/src/labs/folderlistmodel/fileinfothread.cpp