1 | /* |
2 | This file is part of the KDE project |
3 | SPDX-FileCopyrightText: 1998, 1999 Torben Weis <weis@kde.org> |
4 | SPDX-FileCopyrightText: 2000 Carsten Pfeiffer <pfeiffer@kde.org> |
5 | SPDX-FileCopyrightText: 2003-2005 David Faure <faure@kde.org> |
6 | SPDX-FileCopyrightText: 2001-2006 Michael Brade <brade@kde.org> |
7 | SPDX-FileCopyrightText: 2023 Alexander Lohnau <alexander.lohnau@kde.org> |
8 | |
9 | SPDX-License-Identifier: LGPL-2.0-or-later |
10 | */ |
11 | |
12 | #include "kcoredirlister.h" |
13 | #include "kcoredirlister_p.h" |
14 | |
15 | #include "../utils_p.h" |
16 | #include "kiocoredebug.h" |
17 | #include "kmountpoint.h" |
18 | #include <kio/listjob.h> |
19 | |
20 | #include <KJobUiDelegate> |
21 | #include <KLocalizedString> |
22 | |
23 | #include <QDir> |
24 | #include <QFile> |
25 | #include <QFileInfo> |
26 | #include <QMimeDatabase> |
27 | #include <QRegularExpression> |
28 | #include <QTextStream> |
29 | #include <QThreadStorage> |
30 | |
31 | #include <QLoggingCategory> |
32 | Q_DECLARE_LOGGING_CATEGORY(KIO_CORE_DIRLISTER) |
33 | Q_LOGGING_CATEGORY(KIO_CORE_DIRLISTER, "kf.kio.core.dirlister" , QtWarningMsg) |
34 | |
35 | // Enable this to get printDebug() called often, to see the contents of the cache |
36 | // #define DEBUG_CACHE |
37 | |
38 | // Make really sure it doesn't get activated in the final build |
39 | #ifdef NDEBUG |
40 | #undef DEBUG_CACHE |
41 | #endif |
42 | |
43 | QThreadStorage<KCoreDirListerCache> s_kDirListerCache; |
44 | |
45 | KCoreDirListerCache::KCoreDirListerCache() |
46 | : itemsCached(10) |
47 | , // keep the last 10 directories around |
48 | m_cacheHiddenFiles(10) // keep the last 10 ".hidden" files around |
49 | { |
50 | qCDebug(KIO_CORE_DIRLISTER); |
51 | |
52 | connect(sender: &pendingUpdateTimer, signal: &QTimer::timeout, context: this, slot: &KCoreDirListerCache::processPendingUpdates); |
53 | pendingUpdateTimer.setSingleShot(true); |
54 | |
55 | connect(sender: KDirWatch::self(), signal: &KDirWatch::dirty, context: this, slot: &KCoreDirListerCache::slotFileDirty); |
56 | connect(sender: KDirWatch::self(), signal: &KDirWatch::created, context: this, slot: &KCoreDirListerCache::slotFileCreated); |
57 | connect(sender: KDirWatch::self(), signal: &KDirWatch::deleted, context: this, slot: &KCoreDirListerCache::slotFileDeleted); |
58 | |
59 | #ifndef KIO_ANDROID_STUB |
60 | kdirnotify = new org::kde::KDirNotify(QString(), QString(), QDBusConnection::sessionBus(), this); |
61 | connect(sender: kdirnotify, signal: &org::kde::KDirNotify::FileRenamedWithLocalPath, context: this, slot: &KCoreDirListerCache::slotFileRenamed); |
62 | connect(sender: kdirnotify, signal: &org::kde::KDirNotify::FilesAdded, context: this, slot: &KCoreDirListerCache::slotFilesAdded); |
63 | connect(sender: kdirnotify, signal: &org::kde::KDirNotify::FilesChanged, context: this, slot: &KCoreDirListerCache::slotFilesChanged); |
64 | connect(sender: kdirnotify, signal: &org::kde::KDirNotify::FilesRemoved, context: this, slot: qOverload<const QStringList &>(&KCoreDirListerCache::slotFilesRemoved)); |
65 | #endif |
66 | } |
67 | |
68 | KCoreDirListerCache::~KCoreDirListerCache() |
69 | { |
70 | qCDebug(KIO_CORE_DIRLISTER); |
71 | |
72 | qDeleteAll(c: itemsInUse); |
73 | itemsInUse.clear(); |
74 | |
75 | itemsCached.clear(); |
76 | directoryData.clear(); |
77 | m_cacheHiddenFiles.clear(); |
78 | |
79 | if (KDirWatch::exists()) { |
80 | KDirWatch::self()->disconnect(receiver: this); |
81 | } |
82 | } |
83 | |
84 | // setting _reload to true will emit the old files and |
85 | // call updateDirectory |
86 | bool KCoreDirListerCache::listDir(KCoreDirLister *lister, const QUrl &dirUrl, bool _keep, bool _reload) |
87 | { |
88 | QUrl _url(dirUrl); |
89 | _url.setPath(path: QDir::cleanPath(path: _url.path())); // kill consecutive slashes |
90 | |
91 | // like this we don't have to worry about trailing slashes any further |
92 | _url = _url.adjusted(options: QUrl::StripTrailingSlash); |
93 | |
94 | QString resolved; |
95 | if (_url.isLocalFile()) { |
96 | // Resolve symlinks (#213799) |
97 | const QString local = _url.toLocalFile(); |
98 | resolved = QFileInfo(local).canonicalFilePath(); |
99 | if (local != resolved) { |
100 | canonicalUrls[QUrl::fromLocalFile(localfile: resolved)].append(t: _url); |
101 | } |
102 | // TODO: remove entry from canonicalUrls again in forgetDirs |
103 | // Note: this is why we use a QStringList value in there rather than a std::set: |
104 | // we can just remove one entry and not have to worry about other dirlisters |
105 | // (the non-unicity of the stringlist gives us the refcounting, basically). |
106 | } |
107 | |
108 | qCDebug(KIO_CORE_DIRLISTER) << lister << "url=" << _url << "keep=" << _keep << "reload=" << _reload; |
109 | #ifdef DEBUG_CACHE |
110 | printDebug(); |
111 | #endif |
112 | |
113 | if (!_keep) { |
114 | // stop any running jobs for lister |
115 | stop(lister, silent: true /*silent*/); |
116 | |
117 | // clear our internal list for lister |
118 | forgetDirs(lister); |
119 | |
120 | lister->d->rootFileItem = KFileItem(); |
121 | } else if (lister->d->lstDirs.contains(t: _url)) { |
122 | // stop the job listing _url for this lister |
123 | stopListingUrl(lister, _url, silent: true /*silent*/); |
124 | |
125 | // remove the _url as well, it will be added in a couple of lines again! |
126 | // forgetDirs with three args does not do this |
127 | // TODO: think about moving this into forgetDirs |
128 | lister->d->lstDirs.removeAll(t: _url); |
129 | |
130 | // clear _url for lister |
131 | forgetDirs(lister, _url, notify: true); |
132 | |
133 | if (lister->d->url == _url) { |
134 | lister->d->rootFileItem = KFileItem(); |
135 | } |
136 | } |
137 | |
138 | lister->d->complete = false; |
139 | |
140 | lister->d->lstDirs.append(t: _url); |
141 | |
142 | if (lister->d->url.isEmpty() || !_keep) { // set toplevel URL only if not set yet |
143 | lister->d->url = _url; |
144 | } |
145 | |
146 | DirItem *itemU = itemsInUse.value(key: _url); |
147 | |
148 | KCoreDirListerCacheDirectoryData &dirData = directoryData[_url]; // find or insert |
149 | |
150 | if (dirData.listersCurrentlyListing.isEmpty()) { |
151 | // if there is an update running for _url already we get into |
152 | // the following case - it will just be restarted by updateDirectory(). |
153 | |
154 | dirData.listersCurrentlyListing.append(t: lister); |
155 | |
156 | DirItem *itemFromCache = nullptr; |
157 | if (itemU || (!_reload && (itemFromCache = itemsCached.take(key: _url)))) { |
158 | if (itemU) { |
159 | qCDebug(KIO_CORE_DIRLISTER) << "Entry already in use:" << _url; |
160 | // if _reload is set, then we'll emit cached items and then updateDirectory. |
161 | } else { |
162 | qCDebug(KIO_CORE_DIRLISTER) << "Entry in cache:" << _url; |
163 | itemsInUse.insert(key: _url, value: itemFromCache); |
164 | itemU = itemFromCache; |
165 | } |
166 | if (lister->d->autoUpdate) { |
167 | itemU->incAutoUpdate(); |
168 | } |
169 | if (itemFromCache && itemFromCache->watchedWhileInCache) { |
170 | // item is promoted from cache, update item autoupdate refcount accordingly |
171 | itemFromCache->watchedWhileInCache = false; |
172 | itemFromCache->decAutoUpdate(); |
173 | } |
174 | |
175 | Q_EMIT lister->started(dirUrl: _url); |
176 | |
177 | // List items from the cache in a delayed manner, just like things would happen |
178 | // if we were not using the cache. |
179 | new KCoreDirListerPrivate::CachedItemsJob(lister, _url, _reload); |
180 | |
181 | } else { |
182 | // dir not in cache or _reload is true |
183 | if (_reload) { |
184 | qCDebug(KIO_CORE_DIRLISTER) << "Reloading directory:" << _url; |
185 | itemsCached.remove(key: _url); |
186 | } else { |
187 | qCDebug(KIO_CORE_DIRLISTER) << "Listing directory:" << _url; |
188 | } |
189 | |
190 | itemU = new DirItem(_url, resolved); |
191 | itemsInUse.insert(key: _url, value: itemU); |
192 | if (lister->d->autoUpdate) { |
193 | itemU->incAutoUpdate(); |
194 | } |
195 | |
196 | KIO::ListJob *job = KIO::listDir(url: _url, flags: KIO::HideProgressInfo); |
197 | if (lister->requestMimeTypeWhileListing()) { |
198 | job->addMetaData(QStringLiteral("details" ), value: QString::number(KIO::StatDefaultDetails | KIO::StatMimeType)); |
199 | } |
200 | runningListJobs.insert(key: job, value: KIO::UDSEntryList()); |
201 | |
202 | lister->jobStarted(job); |
203 | lister->d->connectJob(job); |
204 | |
205 | connect(sender: job, signal: &KIO::ListJob::entries, context: this, slot: &KCoreDirListerCache::slotEntries); |
206 | connect(sender: job, signal: &KJob::result, context: this, slot: &KCoreDirListerCache::slotResult); |
207 | connect(sender: job, signal: &KIO::ListJob::redirection, context: this, slot: &KCoreDirListerCache::slotRedirection); |
208 | |
209 | Q_EMIT lister->started(dirUrl: _url); |
210 | |
211 | qCDebug(KIO_CORE_DIRLISTER) << "Entry now being listed by" << dirData.listersCurrentlyListing; |
212 | } |
213 | } else { |
214 | qCDebug(KIO_CORE_DIRLISTER) << "Entry currently being listed:" << _url << "by" << dirData.listersCurrentlyListing; |
215 | #ifdef DEBUG_CACHE |
216 | printDebug(); |
217 | #endif |
218 | |
219 | Q_EMIT lister->started(dirUrl: _url); |
220 | |
221 | // Maybe listersCurrentlyListing/listersCurrentlyHolding should be QSets? |
222 | Q_ASSERT(!dirData.listersCurrentlyListing.contains(lister)); |
223 | dirData.listersCurrentlyListing.append(t: lister); |
224 | |
225 | // a new lister is listing this _url, incr watch refcount |
226 | if (lister->d->autoUpdate) { |
227 | itemU->incAutoUpdate(); |
228 | } |
229 | |
230 | KIO::ListJob *job = jobForUrl(url: _url); |
231 | // job will be 0 if we were listing from cache rather than listing from a kio job. |
232 | if (job) { |
233 | lister->jobStarted(job); |
234 | lister->d->connectJob(job); |
235 | } |
236 | Q_ASSERT(itemU); |
237 | |
238 | // List existing items in a delayed manner, just like things would happen |
239 | // if we were not using the cache. |
240 | qCDebug(KIO_CORE_DIRLISTER) << "Listing" << itemU->lstItems.count() << "cached items soon" ; |
241 | auto *cachedItemsJob = new KCoreDirListerPrivate::CachedItemsJob(lister, _url, _reload); |
242 | if (job) { |
243 | // The ListJob will take care of emitting completed. |
244 | // ### If it finishes before the CachedItemsJob, then we'll emit cached items after completed(), not sure how bad this is. |
245 | cachedItemsJob->setEmitCompleted(false); |
246 | } |
247 | |
248 | #ifdef DEBUG_CACHE |
249 | printDebug(); |
250 | #endif |
251 | } |
252 | |
253 | return true; |
254 | } |
255 | |
256 | KCoreDirListerPrivate::CachedItemsJob *KCoreDirListerPrivate::cachedItemsJobForUrl(const QUrl &url) const |
257 | { |
258 | for (CachedItemsJob *job : m_cachedItemsJobs) { |
259 | if (job->url() == url) { |
260 | return job; |
261 | } |
262 | } |
263 | return nullptr; |
264 | } |
265 | |
266 | KCoreDirListerPrivate::CachedItemsJob::CachedItemsJob(KCoreDirLister *lister, const QUrl &url, bool reload) |
267 | : KJob(lister) |
268 | , m_lister(lister) |
269 | , m_url(url) |
270 | , m_reload(reload) |
271 | , m_emitCompleted(true) |
272 | { |
273 | qCDebug(KIO_CORE_DIRLISTER) << "Creating CachedItemsJob" << this << "for lister" << lister << url; |
274 | if (lister->d->cachedItemsJobForUrl(url)) { |
275 | qCWarning(KIO_CORE) << "Lister" << lister << "has a cached items job already for" << url; |
276 | } |
277 | lister->d->m_cachedItemsJobs.append(t: this); |
278 | setAutoDelete(true); |
279 | start(); |
280 | } |
281 | |
282 | // Called by start() via QueuedConnection |
283 | void KCoreDirListerPrivate::CachedItemsJob::done() |
284 | { |
285 | if (!m_lister) { // job was already killed, but waiting deletion due to deleteLater |
286 | return; |
287 | } |
288 | s_kDirListerCache.localData().emitItemsFromCache(job: this, lister: m_lister, url: m_url, reload: m_reload, emitCompleted: m_emitCompleted); |
289 | emitResult(); |
290 | } |
291 | |
292 | bool KCoreDirListerPrivate::CachedItemsJob::doKill() |
293 | { |
294 | qCDebug(KIO_CORE_DIRLISTER) << this; |
295 | s_kDirListerCache.localData().forgetCachedItemsJob(job: this, lister: m_lister, url: m_url); |
296 | if (!property(name: "_kdlc_silent" ).toBool()) { |
297 | Q_EMIT m_lister->listingDirCanceled(dirUrl: m_url); |
298 | |
299 | Q_EMIT m_lister->canceled(); |
300 | } |
301 | m_lister = nullptr; |
302 | return true; |
303 | } |
304 | |
305 | void KCoreDirListerCache::emitItemsFromCache(KCoreDirListerPrivate::CachedItemsJob *cachedItemsJob, |
306 | KCoreDirLister *lister, |
307 | const QUrl &_url, |
308 | bool _reload, |
309 | bool _emitCompleted) |
310 | { |
311 | lister->d->complete = false; |
312 | |
313 | DirItem *itemU = s_kDirListerCache.localData().itemsInUse.value(key: _url); |
314 | if (!itemU) { |
315 | qCWarning(KIO_CORE) << "Can't find item for directory" << _url << "anymore" ; |
316 | } else { |
317 | const QList<KFileItem> items = itemU->lstItems; |
318 | const KFileItem rootItem = itemU->rootItem; |
319 | _reload = _reload || !itemU->complete; |
320 | |
321 | if (lister->d->rootFileItem.isNull() && !rootItem.isNull() && lister->d->url == _url) { |
322 | lister->d->rootFileItem = rootItem; |
323 | } |
324 | if (!items.isEmpty()) { |
325 | qCDebug(KIO_CORE_DIRLISTER) << "emitting" << items.count() << "for lister" << lister; |
326 | lister->d->addNewItems(directoryUrl: _url, items); |
327 | lister->d->emitItems(); |
328 | } |
329 | } |
330 | |
331 | forgetCachedItemsJob(job: cachedItemsJob, lister, url: _url); |
332 | |
333 | // Emit completed, unless we were told not to, |
334 | // or if listDir() was called while another directory listing for this dir was happening, |
335 | // so we "joined" it. We detect that using jobForUrl to ensure it's a real ListJob, |
336 | // not just a lister-specific CachedItemsJob (which wouldn't emit completed for us). |
337 | if (_emitCompleted) { |
338 | lister->d->complete = true; |
339 | |
340 | Q_EMIT lister->listingDirCompleted(dirUrl: _url); |
341 | Q_EMIT lister->completed(); |
342 | |
343 | if (_reload) { |
344 | updateDirectory(dir: _url); |
345 | } |
346 | } |
347 | } |
348 | |
349 | void KCoreDirListerCache::forgetCachedItemsJob(KCoreDirListerPrivate::CachedItemsJob *cachedItemsJob, KCoreDirLister *lister, const QUrl &_url) |
350 | { |
351 | // Modifications to data structures only below this point; |
352 | // so that addNewItems is called with a consistent state |
353 | |
354 | lister->d->m_cachedItemsJobs.removeAll(t: cachedItemsJob); |
355 | |
356 | KCoreDirListerCacheDirectoryData &dirData = directoryData[_url]; |
357 | Q_ASSERT(dirData.listersCurrentlyListing.contains(lister)); |
358 | |
359 | KIO::ListJob *listJob = jobForUrl(url: _url); |
360 | if (!listJob) { |
361 | Q_ASSERT(!dirData.listersCurrentlyHolding.contains(lister)); |
362 | qCDebug(KIO_CORE_DIRLISTER) << "Moving from listing to holding, because no more job" << lister << _url; |
363 | dirData.listersCurrentlyHolding.append(t: lister); |
364 | dirData.listersCurrentlyListing.removeAll(t: lister); |
365 | } else { |
366 | qCDebug(KIO_CORE_DIRLISTER) << "Still having a listjob" << listJob << ", so not moving to currently-holding." ; |
367 | } |
368 | } |
369 | |
370 | void KCoreDirListerCache::stop(KCoreDirLister *lister, bool silent) |
371 | { |
372 | qCDebug(KIO_CORE_DIRLISTER) << "lister:" << lister << "silent=" << silent; |
373 | |
374 | const QList<QUrl> urls = lister->d->lstDirs; |
375 | for (const QUrl &url : urls) { |
376 | stopListingUrl(lister, url: url, silent); |
377 | } |
378 | } |
379 | |
380 | void KCoreDirListerCache::stopListingUrl(KCoreDirLister *lister, const QUrl &_u, bool silent) |
381 | { |
382 | QUrl url(_u); |
383 | url = url.adjusted(options: QUrl::StripTrailingSlash); |
384 | |
385 | KCoreDirListerPrivate::CachedItemsJob *cachedItemsJob = lister->d->cachedItemsJobForUrl(url); |
386 | if (cachedItemsJob) { |
387 | if (silent) { |
388 | cachedItemsJob->setProperty(name: "_kdlc_silent" , value: true); |
389 | } |
390 | cachedItemsJob->kill(); // removes job from list, too |
391 | } |
392 | |
393 | // TODO: consider to stop all the "child jobs" of url as well |
394 | qCDebug(KIO_CORE_DIRLISTER) << lister << " url=" << url; |
395 | |
396 | const auto dirit = directoryData.find(key: url); |
397 | if (dirit == directoryData.end()) { |
398 | return; |
399 | } |
400 | KCoreDirListerCacheDirectoryData &dirData = dirit.value(); |
401 | if (dirData.listersCurrentlyListing.contains(t: lister)) { |
402 | qCDebug(KIO_CORE_DIRLISTER) << " found lister" << lister << "in list - for" << url; |
403 | if (dirData.listersCurrentlyListing.count() == 1) { |
404 | // This was the only dirlister interested in the list job -> kill the job |
405 | stopListJob(url, silent); |
406 | } else { |
407 | // Leave the job running for the other dirlisters, just unsubscribe us. |
408 | dirData.listersCurrentlyListing.removeAll(t: lister); |
409 | if (!silent) { |
410 | Q_EMIT lister->canceled(); |
411 | Q_EMIT lister->listingDirCanceled(dirUrl: url); |
412 | } |
413 | } |
414 | } |
415 | } |
416 | |
417 | // Helper for stop() and stopListingUrl() |
418 | void KCoreDirListerCache::stopListJob(const QUrl &url, bool silent) |
419 | { |
420 | // Old idea: if it's an update job, let's just leave the job running. |
421 | // After all, update jobs do run for "listersCurrentlyHolding", |
422 | // so there's no reason to kill them just because @p lister is now a holder. |
423 | |
424 | // However it could be a long-running non-local job (e.g. filenamesearch), which |
425 | // the user wants to abort, and which will never be used for updating... |
426 | // And in any case slotEntries/slotResult is not meant to be called by update jobs. |
427 | // So, change of plan, let's kill it after all, in a way that triggers slotResult/slotUpdateResult. |
428 | |
429 | KIO::ListJob *job = jobForUrl(url); |
430 | if (job) { |
431 | qCDebug(KIO_CORE_DIRLISTER) << "Killing list job" << job << "for" << url; |
432 | if (silent) { |
433 | job->setProperty(name: "_kdlc_silent" , value: true); |
434 | } |
435 | job->kill(verbosity: KJob::EmitResult); |
436 | } |
437 | } |
438 | |
439 | void KCoreDirListerCache::setAutoUpdate(KCoreDirLister *lister, bool enable) |
440 | { |
441 | // IMPORTANT: this method does not check for the current autoUpdate state! |
442 | |
443 | for (const QUrl &url : std::as_const(t&: lister->d->lstDirs)) { |
444 | DirItem *dirItem = itemsInUse.value(key: url); |
445 | Q_ASSERT(dirItem); |
446 | if (enable) { |
447 | dirItem->incAutoUpdate(); |
448 | } else { |
449 | dirItem->decAutoUpdate(); |
450 | } |
451 | } |
452 | } |
453 | |
454 | void KCoreDirListerCache::forgetDirs(KCoreDirLister *lister) |
455 | { |
456 | qCDebug(KIO_CORE_DIRLISTER) << lister; |
457 | |
458 | Q_EMIT lister->clear(); |
459 | // clear lister->d->lstDirs before calling forgetDirs(), so that |
460 | // it doesn't contain things that itemsInUse doesn't. When emitting |
461 | // the canceled signals, lstDirs must not contain anything that |
462 | // itemsInUse does not contain. (otherwise it might crash in findByName()). |
463 | const QList<QUrl> lstDirsCopy = lister->d->lstDirs; |
464 | lister->d->lstDirs.clear(); |
465 | |
466 | qCDebug(KIO_CORE_DIRLISTER) << "Iterating over dirs" << lstDirsCopy; |
467 | for (const QUrl &dir : lstDirsCopy) { |
468 | forgetDirs(lister, url: dir, notify: false); |
469 | } |
470 | } |
471 | |
472 | static bool manually_mounted(const QString &path, const KMountPoint::List &possibleMountPoints) |
473 | { |
474 | KMountPoint::Ptr mp = possibleMountPoints.findByPath(path); |
475 | if (!mp) { // not listed in fstab -> yes, manually mounted |
476 | if (possibleMountPoints.isEmpty()) { // no fstab at all -> don't assume anything |
477 | return false; |
478 | } |
479 | return true; |
480 | } |
481 | // noauto -> manually mounted. Otherwise, mounted at boot time, won't be unmounted any time soon hopefully. |
482 | return mp->mountOptions().contains(str: QLatin1String("noauto" )); |
483 | } |
484 | |
485 | void KCoreDirListerCache::forgetDirs(KCoreDirLister *lister, const QUrl &_url, bool notify) |
486 | { |
487 | qCDebug(KIO_CORE_DIRLISTER) << lister << " _url: " << _url; |
488 | |
489 | const QUrl url = _url.adjusted(options: QUrl::StripTrailingSlash); |
490 | |
491 | DirectoryDataHash::iterator dit = directoryData.find(key: url); |
492 | if (dit == directoryData.end()) { |
493 | return; |
494 | } |
495 | KCoreDirListerCacheDirectoryData &dirData = *dit; |
496 | dirData.listersCurrentlyHolding.removeAll(t: lister); |
497 | |
498 | // This lister doesn't care for updates running in <url> anymore |
499 | KIO::ListJob *job = jobForUrl(url); |
500 | if (job) { |
501 | lister->d->jobDone(job); |
502 | } |
503 | |
504 | DirItem *item = itemsInUse.value(key: url); |
505 | Q_ASSERT(item); |
506 | bool insertIntoCache = false; |
507 | |
508 | if (dirData.listersCurrentlyHolding.isEmpty() && dirData.listersCurrentlyListing.isEmpty()) { |
509 | // item not in use anymore -> move into cache if complete |
510 | directoryData.erase(it: dit); |
511 | itemsInUse.remove(key: url); |
512 | |
513 | // this job is a running update which nobody cares about anymore |
514 | if (job) { |
515 | killJob(job); |
516 | qCDebug(KIO_CORE_DIRLISTER) << "Killing update job for " << url; |
517 | |
518 | // Well, the user of KCoreDirLister doesn't really care that we're stopping |
519 | // a background-running job from a previous URL (in listDir) -> commented out. |
520 | // stop() already emitted canceled. |
521 | // emit lister->canceled( url ); |
522 | if (lister->d->numJobs() == 0) { |
523 | lister->d->complete = true; |
524 | // emit lister->canceled(); |
525 | } |
526 | } |
527 | |
528 | if (notify) { |
529 | lister->d->lstDirs.removeAll(t: url); |
530 | Q_EMIT lister->clearDir(dirUrl: url); |
531 | } |
532 | |
533 | insertIntoCache = item->complete; |
534 | if (insertIntoCache) { |
535 | // TODO(afiestas): remove use of KMountPoint+manually_mounted and port to Solid: |
536 | // 1) find Volume for the local path "item->url.toLocalFile()" (which could be anywhere |
537 | // under the mount point) -- probably needs a new operator in libsolid query parser |
538 | // 2) [**] becomes: if (Drive is hotpluggable or Volume is removable) "set to dirty" else "keep watch" |
539 | const KMountPoint::List possibleMountPoints = KMountPoint::possibleMountPoints(infoNeeded: KMountPoint::NeedMountOptions); |
540 | |
541 | // Should we forget the dir for good, or keep a watch on it? |
542 | // Generally keep a watch, except when it would prevent |
543 | // unmounting a removable device (#37780) |
544 | const bool isLocal = item->url.isLocalFile(); |
545 | bool isManuallyMounted = false; |
546 | bool containsManuallyMounted = false; |
547 | if (isLocal) { |
548 | isManuallyMounted = manually_mounted(path: item->url.toLocalFile(), possibleMountPoints); |
549 | if (!isManuallyMounted) { |
550 | // Look for a manually-mounted directory inside |
551 | // If there's one, we can't keep a watch either, FAM would prevent unmounting the CDROM |
552 | // I hope this isn't too slow |
553 | auto kit = item->lstItems.constBegin(); |
554 | const auto kend = item->lstItems.constEnd(); |
555 | for (; kit != kend && !containsManuallyMounted; ++kit) { |
556 | if ((*kit).isDir() && manually_mounted(path: (*kit).url().toLocalFile(), possibleMountPoints)) { |
557 | containsManuallyMounted = true; |
558 | } |
559 | } |
560 | } |
561 | } |
562 | |
563 | if (isManuallyMounted || containsManuallyMounted) { // [**] |
564 | qCDebug(KIO_CORE_DIRLISTER) << "Not adding a watch on " << item->url << " because it " |
565 | << (isManuallyMounted ? "is manually mounted" : "contains a manually mounted subdir" ); |
566 | item->complete = false; // set to "dirty" |
567 | } else { |
568 | item->incAutoUpdate(); // keep watch, incr refcount to account for cache |
569 | item->watchedWhileInCache = true; |
570 | } |
571 | } else { |
572 | delete item; |
573 | item = nullptr; |
574 | } |
575 | } |
576 | |
577 | if (item && lister->d->autoUpdate) { |
578 | item->decAutoUpdate(); |
579 | } |
580 | |
581 | // Inserting into QCache must be done last, since it might delete the item |
582 | if (item && insertIntoCache) { |
583 | qCDebug(KIO_CORE_DIRLISTER) << lister << "item moved into cache:" << url; |
584 | itemsCached.insert(key: url, object: item); |
585 | } |
586 | } |
587 | |
588 | void KCoreDirListerCache::updateDirectory(const QUrl &_dir) |
589 | { |
590 | qCDebug(KIO_CORE_DIRLISTER) << _dir; |
591 | |
592 | QUrl dir = _dir.adjusted(options: QUrl::StripTrailingSlash); |
593 | if (!checkUpdate(url: dir)) { |
594 | auto parentDir = dir.adjusted(options: QUrl::RemoveFilename | QUrl::StripTrailingSlash); |
595 | if (checkUpdate(url: parentDir)) { |
596 | // if the parent is in use, update it instead |
597 | dir = parentDir; |
598 | } else { |
599 | return; |
600 | } |
601 | } |
602 | |
603 | // A job can be running to |
604 | // - only list a new directory: the listers are in listersCurrentlyListing |
605 | // - only update a directory: the listers are in listersCurrentlyHolding |
606 | // - update a currently running listing: the listers are in both |
607 | |
608 | KCoreDirListerCacheDirectoryData &dirData = directoryData[dir]; |
609 | const QList<KCoreDirLister *> listers = dirData.listersCurrentlyListing; |
610 | const QList<KCoreDirLister *> holders = dirData.listersCurrentlyHolding; |
611 | |
612 | qCDebug(KIO_CORE_DIRLISTER) << dir << "listers=" << listers << "holders=" << holders; |
613 | |
614 | bool killed = false; |
615 | KIO::ListJob *job = jobForUrl(url: dir); |
616 | if (job) { |
617 | // the job is running already, tell it to do another update at the end |
618 | // (don't kill it, we would keep doing that during a long download to a slow sshfs mount) |
619 | job->setProperty(name: "need_another_update" , value: true); |
620 | return; |
621 | } else { |
622 | // Emit any cached items. |
623 | // updateDirectory() is about the diff compared to the cached items... |
624 | for (const KCoreDirLister *kdl : listers) { |
625 | KCoreDirListerPrivate::CachedItemsJob *cachedItemsJob = kdl->d->cachedItemsJobForUrl(url: dir); |
626 | if (cachedItemsJob) { |
627 | cachedItemsJob->setEmitCompleted(false); |
628 | cachedItemsJob->done(); // removes from cachedItemsJobs list |
629 | delete cachedItemsJob; |
630 | killed = true; |
631 | } |
632 | } |
633 | } |
634 | qCDebug(KIO_CORE_DIRLISTER) << "Killed=" << killed; |
635 | |
636 | // we don't need to emit canceled signals since we only replaced the job, |
637 | // the listing is continuing. |
638 | |
639 | if (!(listers.isEmpty() || killed)) { |
640 | qCWarning(KIO_CORE) << "The unexpected happened." ; |
641 | qCWarning(KIO_CORE) << "listers for" << dir << "=" << listers; |
642 | qCWarning(KIO_CORE) << "job=" << job; |
643 | for (const KCoreDirLister *kdl : listers) { |
644 | qCDebug(KIO_CORE_DIRLISTER) << "lister" << kdl << "m_cachedItemsJobs=" << kdl->d->m_cachedItemsJobs; |
645 | } |
646 | #ifndef NDEBUG |
647 | printDebug(); |
648 | #endif |
649 | } |
650 | Q_ASSERT(listers.isEmpty() || killed); |
651 | |
652 | job = KIO::listDir(url: dir, flags: KIO::HideProgressInfo); |
653 | runningListJobs.insert(key: job, value: KIO::UDSEntryList()); |
654 | |
655 | const bool requestFromListers = std::any_of(first: listers.cbegin(), last: listers.cend(), pred: [](KCoreDirLister *lister) { |
656 | return lister->requestMimeTypeWhileListing(); |
657 | }); |
658 | const bool requestFromholders = std::any_of(first: holders.cbegin(), last: holders.cend(), pred: [](KCoreDirLister *lister) { |
659 | return lister->requestMimeTypeWhileListing(); |
660 | }); |
661 | |
662 | if (requestFromListers || requestFromholders) { |
663 | job->addMetaData(QStringLiteral("details" ), value: QString::number(KIO::StatDefaultDetails | KIO::StatMimeType)); |
664 | } |
665 | |
666 | connect(sender: job, signal: &KIO::ListJob::entries, context: this, slot: &KCoreDirListerCache::slotUpdateEntries); |
667 | connect(sender: job, signal: &KJob::result, context: this, slot: &KCoreDirListerCache::slotUpdateResult); |
668 | |
669 | qCDebug(KIO_CORE_DIRLISTER) << "update started in" << dir; |
670 | |
671 | for (KCoreDirLister *kdl : listers) { |
672 | kdl->jobStarted(job); |
673 | } |
674 | |
675 | if (!holders.isEmpty()) { |
676 | if (!killed) { |
677 | for (KCoreDirLister *kdl : holders) { |
678 | kdl->jobStarted(job); |
679 | Q_EMIT kdl->started(dirUrl: dir); |
680 | } |
681 | } else { |
682 | for (KCoreDirLister *kdl : holders) { |
683 | kdl->jobStarted(job); |
684 | } |
685 | } |
686 | } |
687 | } |
688 | |
689 | bool KCoreDirListerCache::checkUpdate(const QUrl &_dir) |
690 | { |
691 | if (!itemsInUse.contains(key: _dir)) { |
692 | DirItem *item = itemsCached[_dir]; |
693 | if (item && item->complete) { |
694 | item->complete = false; |
695 | item->watchedWhileInCache = false; |
696 | item->decAutoUpdate(); |
697 | qCDebug(KIO_CORE_DIRLISTER) << "directory " << _dir << " not in use, marked dirty." ; |
698 | } |
699 | // else |
700 | qCDebug(KIO_CORE_DIRLISTER) << "aborted, directory " << _dir << " not in cache." ; |
701 | return false; |
702 | } else { |
703 | return true; |
704 | } |
705 | } |
706 | |
707 | KFileItem KCoreDirListerCache::itemForUrl(const QUrl &url) const |
708 | { |
709 | return findByUrl(lister: nullptr, url); |
710 | } |
711 | |
712 | KCoreDirListerCache::DirItem *KCoreDirListerCache::dirItemForUrl(const QUrl &dir) const |
713 | { |
714 | const QUrl url = dir.adjusted(options: QUrl::StripTrailingSlash); |
715 | DirItem *item = itemsInUse.value(key: url); |
716 | if (!item) { |
717 | item = itemsCached[url]; |
718 | } |
719 | return item; |
720 | } |
721 | |
722 | QList<KFileItem> *KCoreDirListerCache::itemsForDir(const QUrl &dir) const |
723 | { |
724 | DirItem *item = dirItemForUrl(dir); |
725 | return item ? &item->lstItems : nullptr; |
726 | } |
727 | |
728 | KFileItem KCoreDirListerCache::findByName(const KCoreDirLister *lister, const QString &_name) const |
729 | { |
730 | Q_ASSERT(lister); |
731 | |
732 | auto isMatch = [&_name](const KFileItem &item) { |
733 | return _name == item.name(); |
734 | }; |
735 | |
736 | for (const auto &dirUrl : std::as_const(t&: lister->d->lstDirs)) { |
737 | DirItem *dirItem = itemsInUse.value(key: dirUrl); |
738 | Q_ASSERT(dirItem); |
739 | |
740 | auto it = std::find_if(first: dirItem->lstItems.cbegin(), last: dirItem->lstItems.cend(), pred: isMatch); |
741 | if (it != dirItem->lstItems.cend()) { |
742 | return *it; |
743 | } |
744 | } |
745 | |
746 | return {}; |
747 | } |
748 | |
749 | KFileItem KCoreDirListerCache::findByUrl(const KCoreDirLister *lister, const QUrl &_u) const |
750 | { |
751 | QUrl url(_u); |
752 | url = url.adjusted(options: QUrl::StripTrailingSlash); |
753 | |
754 | const QUrl parentDir = url.adjusted(options: QUrl::RemoveFilename | QUrl::StripTrailingSlash); |
755 | DirItem *dirItem = dirItemForUrl(dir: parentDir); |
756 | if (dirItem) { |
757 | // If lister is set, check that it contains this dir |
758 | if (!lister || lister->d->lstDirs.contains(t: parentDir)) { |
759 | // Binary search |
760 | auto it = std::lower_bound(first: dirItem->lstItems.begin(), last: dirItem->lstItems.end(), val: url); |
761 | if (it != dirItem->lstItems.end() && it->url() == url) { |
762 | return *it; |
763 | } |
764 | } |
765 | } |
766 | |
767 | // Maybe _u is a directory itself? (see KDirModelTest::testChmodDirectory) |
768 | // We check this last, though, we prefer returning a kfileitem with an actual |
769 | // name if possible (and we make it '.' for root items later). |
770 | dirItem = dirItemForUrl(dir: url); |
771 | if (dirItem && !dirItem->rootItem.isNull() && dirItem->rootItem.url() == url) { |
772 | // If lister is set, check that it contains this dir |
773 | if (!lister || lister->d->lstDirs.contains(t: url)) { |
774 | return dirItem->rootItem; |
775 | } |
776 | } |
777 | |
778 | return KFileItem(); |
779 | } |
780 | |
781 | void KCoreDirListerCache::slotFilesAdded(const QString &dir /*url*/) // from KDirNotify signals |
782 | { |
783 | QUrl urlDir(dir); |
784 | itemsAddedInDirectory(url: urlDir); |
785 | } |
786 | |
787 | void KCoreDirListerCache::itemsAddedInDirectory(const QUrl &urlDir) |
788 | { |
789 | qCDebug(KIO_CORE_DIRLISTER) << urlDir; |
790 | const QList<QUrl> urls = directoriesForCanonicalPath(dir: urlDir); |
791 | for (const QUrl &u : urls) { |
792 | updateDirectory(dir: u); |
793 | } |
794 | } |
795 | |
796 | void KCoreDirListerCache::slotFilesRemoved(const QStringList &fileList) // from KDirNotify signals |
797 | { |
798 | slotFilesRemoved(urls: QUrl::fromStringList(uris: fileList)); |
799 | } |
800 | |
801 | void KCoreDirListerCache::slotFilesRemoved(const QList<QUrl> &fileList) |
802 | { |
803 | qCDebug(KIO_CORE_DIRLISTER) << fileList.count(); |
804 | // Group notifications by parent dirs (usually there would be only one parent dir) |
805 | QMap<QUrl, KFileItemList> removedItemsByDir; |
806 | QList<QUrl> deletedSubdirs; |
807 | |
808 | for (const QUrl &url : fileList) { |
809 | const QList<QUrl> dirUrls = directoriesForCanonicalPath(dir: url); |
810 | for (const QUrl &dir : dirUrls) { |
811 | DirItem *dirItem = dirItemForUrl(dir); // is it a listed directory? |
812 | if (dirItem) { |
813 | deletedSubdirs.append(t: dir); |
814 | if (!dirItem->rootItem.isNull()) { |
815 | removedItemsByDir[url].append(t: dirItem->rootItem); |
816 | } |
817 | } |
818 | } |
819 | |
820 | const QUrl parentDir = url.adjusted(options: QUrl::RemoveFilename | QUrl::StripTrailingSlash); |
821 | const QList<QUrl> parentDirUrls = directoriesForCanonicalPath(dir: parentDir); |
822 | for (const QUrl &dir : parentDirUrls) { |
823 | DirItem *dirItem = dirItemForUrl(dir); |
824 | if (!dirItem) { |
825 | continue; |
826 | } |
827 | |
828 | const auto dirItemIt = std::find_if(first: dirItem->lstItems.cbegin(), last: dirItem->lstItems.cend(), pred: [&url](const KFileItem &fitem) { |
829 | return fitem.name() == url.fileName(); |
830 | }); |
831 | if (dirItemIt != dirItem->lstItems.cend()) { |
832 | const KFileItem fileitem = *dirItemIt; |
833 | removedItemsByDir[dir].append(t: fileitem); |
834 | // If we found a fileitem, we can test if it's a dir. If not, we'll go to deleteDir just in case. |
835 | if (fileitem.isNull() || fileitem.isDir()) { |
836 | deletedSubdirs.append(t: url); |
837 | } |
838 | dirItem->lstItems.erase(pos: dirItemIt); // remove fileitem from list |
839 | } |
840 | } |
841 | } |
842 | |
843 | for (auto rit = removedItemsByDir.constBegin(), cend = removedItemsByDir.constEnd(); rit != cend; ++rit) { |
844 | // Tell the views about it before calling deleteDir. |
845 | // They might need the subdirs' file items (see the dirtree). |
846 | auto dit = directoryData.constFind(key: rit.key()); |
847 | if (dit != directoryData.constEnd()) { |
848 | itemsDeleted(listers: (*dit).listersCurrentlyHolding, deletedItems: rit.value()); |
849 | } |
850 | } |
851 | |
852 | for (const QUrl &url : std::as_const(t&: deletedSubdirs)) { |
853 | // in case of a dir, check if we have any known children, there's much to do in that case |
854 | // (stopping jobs, removing dirs from cache etc.) |
855 | deleteDir(dirUrl: url); |
856 | } |
857 | } |
858 | |
859 | void KCoreDirListerCache::slotFilesChanged(const QStringList &fileList) // from KDirNotify signals |
860 | { |
861 | qCDebug(KIO_CORE_DIRLISTER) << fileList; |
862 | QList<QUrl> dirsToUpdate; |
863 | for (const QString &fileUrl : fileList) { |
864 | const QUrl url(fileUrl); |
865 | const KFileItem &fileitem = findByUrl(lister: nullptr, u: url); |
866 | if (fileitem.isNull()) { |
867 | qCDebug(KIO_CORE_DIRLISTER) << "item not found for" << url; |
868 | continue; |
869 | } |
870 | if (url.isLocalFile()) { |
871 | pendingUpdates.insert(x: url.toLocalFile()); // delegate the work to processPendingUpdates |
872 | } else { |
873 | pendingRemoteUpdates.insert(x: fileitem); |
874 | // For remote files, we won't be able to figure out the new information, |
875 | // we have to do a update (directory listing) |
876 | const QUrl dir = url.adjusted(options: QUrl::RemoveFilename | QUrl::StripTrailingSlash); |
877 | if (!dirsToUpdate.contains(t: dir)) { |
878 | dirsToUpdate.prepend(t: dir); |
879 | } |
880 | } |
881 | } |
882 | |
883 | for (const QUrl &dirUrl : std::as_const(t&: dirsToUpdate)) { |
884 | updateDirectory(dir: dirUrl); |
885 | } |
886 | // ## TODO problems with current jobs listing/updating that dir |
887 | // ( see kde-2.2.2's kdirlister ) |
888 | |
889 | processPendingUpdates(); |
890 | } |
891 | |
892 | void KCoreDirListerCache::slotFileRenamed(const QString &_src, const QString &_dst, const QString &dstPath) // from KDirNotify signals |
893 | { |
894 | QUrl src(_src); |
895 | QUrl dst(_dst); |
896 | qCDebug(KIO_CORE_DIRLISTER) << src << "->" << dst; |
897 | #ifdef DEBUG_CACHE |
898 | printDebug(); |
899 | #endif |
900 | |
901 | QUrl oldurl = src.adjusted(options: QUrl::StripTrailingSlash); |
902 | KFileItem fileitem = findByUrl(lister: nullptr, u: oldurl); |
903 | if (fileitem.isNull()) { |
904 | qCDebug(KIO_CORE_DIRLISTER) << "Item not found:" << oldurl; |
905 | return; |
906 | } |
907 | |
908 | const KFileItem oldItem = fileitem; |
909 | |
910 | // Dest already exists? Was overwritten then (testcase: #151851) |
911 | // We better emit it as deleted -before- doing the renaming, otherwise |
912 | // the "update" mechanism will emit the old one as deleted and |
913 | // kdirmodel will delete the new (renamed) one! |
914 | const KFileItem &existingDestItem = findByUrl(lister: nullptr, u: dst); |
915 | if (!existingDestItem.isNull()) { |
916 | qCDebug(KIO_CORE_DIRLISTER) << dst << "already existed, let's delete it" ; |
917 | slotFilesRemoved(fileList: QList<QUrl>{dst}); |
918 | } |
919 | |
920 | // If the item had a UDS_URL as well as UDS_NAME set, the user probably wants |
921 | // to be updating the name only (since they can't see the URL). |
922 | // Check to see if a URL exists, and if so, if only the file part has changed, |
923 | // only update the name and not the underlying URL. |
924 | bool nameOnly = !fileitem.entry().stringValue(field: KIO::UDSEntry::UDS_URL).isEmpty(); |
925 | nameOnly = nameOnly && src.adjusted(options: QUrl::RemoveFilename) == dst.adjusted(options: QUrl::RemoveFilename); |
926 | |
927 | if (!nameOnly && fileitem.isDir()) { |
928 | renameDir(oldUrl: oldurl, url: dst); |
929 | // #172945 - if the fileitem was the root item of a DirItem that was just removed from the cache, |
930 | // then it's a dangling pointer now... |
931 | fileitem = findByUrl(lister: nullptr, u: oldurl); |
932 | if (fileitem.isNull()) { // deleted from cache altogether, #188807 |
933 | return; |
934 | } |
935 | } |
936 | |
937 | // Now update the KFileItem representing that file or dir (not exclusive with the above!) |
938 | if (!oldItem.isLocalFile() && !oldItem.localPath().isEmpty() |
939 | && dstPath.isEmpty()) { // it uses UDS_LOCAL_PATH and we don't know the new path? needs an update then |
940 | slotFilesChanged(fileList: QStringList{src.toString()}); |
941 | } else { |
942 | const QUrl &itemOldUrl = fileitem.url(); |
943 | if (nameOnly) { |
944 | fileitem.setName(dst.fileName()); |
945 | } else { |
946 | fileitem.setUrl(dst); |
947 | } |
948 | |
949 | if (!dstPath.isEmpty()) { |
950 | fileitem.setLocalPath(dstPath); |
951 | } |
952 | |
953 | fileitem.refreshMimeType(); |
954 | fileitem.determineMimeType(); |
955 | reinsert(item: fileitem, oldUrl: itemOldUrl); |
956 | |
957 | const std::set<KCoreDirLister *> listers = emitRefreshItem(oldItem, fileitem); |
958 | for (KCoreDirLister *kdl : listers) { |
959 | kdl->d->emitItems(); |
960 | } |
961 | } |
962 | |
963 | #ifdef DEBUG_CACHE |
964 | printDebug(); |
965 | #endif |
966 | } |
967 | |
968 | std::set<KCoreDirLister *> KCoreDirListerCache::emitRefreshItem(const KFileItem &oldItem, const KFileItem &fileitem) |
969 | { |
970 | qCDebug(KIO_CORE_DIRLISTER) << "old:" << oldItem.name() << oldItem.url() << "new:" << fileitem.name() << fileitem.url(); |
971 | // Look whether this item was shown in any view, i.e. held by any dirlister |
972 | const QUrl parentDir = oldItem.url().adjusted(options: QUrl::RemoveFilename | QUrl::StripTrailingSlash); |
973 | DirectoryDataHash::iterator dit = directoryData.find(key: parentDir); |
974 | QList<KCoreDirLister *> listers; |
975 | // Also look in listersCurrentlyListing, in case the user manages to rename during a listing |
976 | if (dit != directoryData.end()) { |
977 | listers += (*dit).listersCurrentlyHolding + (*dit).listersCurrentlyListing; |
978 | } |
979 | if (oldItem.isDir()) { |
980 | // For a directory, look for dirlisters where it's the root item. |
981 | dit = directoryData.find(key: oldItem.url()); |
982 | if (dit != directoryData.end()) { |
983 | listers += (*dit).listersCurrentlyHolding + (*dit).listersCurrentlyListing; |
984 | } |
985 | } |
986 | std::set<KCoreDirLister *> listersToRefresh; |
987 | for (KCoreDirLister *kdl : std::as_const(t&: listers)) { |
988 | // deduplicate listers |
989 | listersToRefresh.insert(x: kdl); |
990 | } |
991 | for (KCoreDirLister *kdl : std::as_const(t&: listersToRefresh)) { |
992 | // For a directory, look for dirlisters where it's the root item. |
993 | QUrl directoryUrl(oldItem.url()); |
994 | if (oldItem.isDir() && kdl->d->rootFileItem == oldItem) { |
995 | const KFileItem oldRootItem = kdl->d->rootFileItem; |
996 | kdl->d->rootFileItem = fileitem; |
997 | kdl->d->addRefreshItem(directoryUrl, oldItem: oldRootItem, item: fileitem); |
998 | } else { |
999 | directoryUrl = directoryUrl.adjusted(options: QUrl::RemoveFilename | QUrl::StripTrailingSlash); |
1000 | kdl->d->addRefreshItem(directoryUrl, oldItem, item: fileitem); |
1001 | } |
1002 | } |
1003 | return listersToRefresh; |
1004 | } |
1005 | |
1006 | QList<QUrl> KCoreDirListerCache::directoriesForCanonicalPath(const QUrl &dir) const |
1007 | { |
1008 | QList<QUrl> urlList = canonicalUrls.value(key: dir); |
1009 | // make unique |
1010 | if (urlList.size() > 1) { |
1011 | std::sort(first: urlList.begin(), last: urlList.end()); |
1012 | auto end_unique = std::unique(first: urlList.begin(), last: urlList.end()); |
1013 | urlList.erase(abegin: end_unique, aend: urlList.end()); |
1014 | } |
1015 | |
1016 | QList<QUrl> dirs({dir}); |
1017 | dirs.append(l: urlList); |
1018 | |
1019 | if (dirs.count() > 1) { |
1020 | qCDebug(KIO_CORE_DIRLISTER) << dir << "known as" << dirs; |
1021 | } |
1022 | return dirs; |
1023 | } |
1024 | |
1025 | // private slots |
1026 | |
1027 | // Called by KDirWatch - usually when a dir we're watching has been modified, |
1028 | // but it can also be called for a file. |
1029 | void KCoreDirListerCache::slotFileDirty(const QString &path) |
1030 | { |
1031 | qCDebug(KIO_CORE_DIRLISTER) << path; |
1032 | QUrl url = QUrl::fromLocalFile(localfile: path).adjusted(options: QUrl::StripTrailingSlash); |
1033 | // File or dir? |
1034 | bool isDir; |
1035 | const KFileItem item = itemForUrl(url); |
1036 | |
1037 | if (!item.isNull()) { |
1038 | isDir = item.isDir(); |
1039 | } else { |
1040 | QFileInfo info(path); |
1041 | if (!info.exists()) { |
1042 | return; // error |
1043 | } |
1044 | isDir = info.isDir(); |
1045 | } |
1046 | |
1047 | if (isDir) { |
1048 | const QList<QUrl> urls = directoriesForCanonicalPath(dir: url); |
1049 | for (const QUrl &dir : urls) { |
1050 | handleDirDirty(url: dir); |
1051 | } |
1052 | } |
1053 | // Also do this for dirs, e.g. to handle permission changes |
1054 | const QList<QUrl> urls = directoriesForCanonicalPath(dir: url.adjusted(options: QUrl::RemoveFilename | QUrl::StripTrailingSlash)); |
1055 | for (const QUrl &dir : urls) { |
1056 | QUrl aliasUrl(dir); |
1057 | aliasUrl.setPath(path: Utils::concatPaths(path1: aliasUrl.path(), path2: url.fileName())); |
1058 | handleFileDirty(url: aliasUrl); |
1059 | } |
1060 | } |
1061 | |
1062 | // Called by slotFileDirty |
1063 | void KCoreDirListerCache::handleDirDirty(const QUrl &url) |
1064 | { |
1065 | // A dir: launch an update job if anyone cares about it |
1066 | |
1067 | // This also means we can forget about pending updates to individual files in that dir |
1068 | const QString dir = url.toLocalFile(); |
1069 | const QString dirPath = Utils::slashAppended(s: dir); |
1070 | |
1071 | for (auto pendingIt = pendingUpdates.cbegin(); pendingIt != pendingUpdates.cend(); /* */) { |
1072 | const QString updPath = *pendingIt; |
1073 | qCDebug(KIO_CORE_DIRLISTER) << "had pending update" << updPath; |
1074 | if (updPath.startsWith(s: dirPath) && updPath.indexOf(c: QLatin1Char('/'), from: dirPath.length()) == -1) { // direct child item |
1075 | qCDebug(KIO_CORE_DIRLISTER) << "forgetting about individual update to" << updPath; |
1076 | pendingIt = pendingUpdates.erase(position: pendingIt); |
1077 | } else { |
1078 | ++pendingIt; |
1079 | } |
1080 | } |
1081 | |
1082 | if (checkUpdate(dir: url)) { |
1083 | const auto [it, isInserted] = pendingDirectoryUpdates.insert(x: dir); |
1084 | if (isInserted && !pendingUpdateTimer.isActive()) { |
1085 | pendingUpdateTimer.start(msec: 200); |
1086 | } |
1087 | } |
1088 | } |
1089 | |
1090 | // Called by slotFileDirty, for every alias of <url> |
1091 | void KCoreDirListerCache::handleFileDirty(const QUrl &url) |
1092 | { |
1093 | // A file: do we know about it already? |
1094 | const KFileItem &existingItem = findByUrl(lister: nullptr, u: url); |
1095 | const QUrl dir = url.adjusted(options: QUrl::RemoveFilename | QUrl::StripTrailingSlash); |
1096 | if (existingItem.isNull()) { |
1097 | // No - update the parent dir then |
1098 | handleDirDirty(url: dir); |
1099 | } |
1100 | |
1101 | // Delay updating the file, FAM is flooding us with events |
1102 | if (checkUpdate(dir: dir)) { |
1103 | const QString filePath = url.toLocalFile(); |
1104 | const auto [it, isInserted] = pendingUpdates.insert(x: filePath); |
1105 | if (isInserted && !pendingUpdateTimer.isActive()) { |
1106 | pendingUpdateTimer.start(msec: 200); |
1107 | } |
1108 | } |
1109 | } |
1110 | |
1111 | void KCoreDirListerCache::slotFileCreated(const QString &path) // from KDirWatch |
1112 | { |
1113 | qCDebug(KIO_CORE_DIRLISTER) << path; |
1114 | // XXX: how to avoid a complete rescan here? |
1115 | // We'd need to stat that one file separately and refresh the item(s) for it. |
1116 | QUrl fileUrl(QUrl::fromLocalFile(localfile: path)); |
1117 | itemsAddedInDirectory(urlDir: fileUrl.adjusted(options: QUrl::RemoveFilename | QUrl::StripTrailingSlash)); |
1118 | } |
1119 | |
1120 | void KCoreDirListerCache::slotFileDeleted(const QString &path) // from KDirWatch |
1121 | { |
1122 | qCDebug(KIO_CORE_DIRLISTER) << path; |
1123 | const QString fileName = QFileInfo(path).fileName(); |
1124 | QUrl dirUrl(QUrl::fromLocalFile(localfile: path)); |
1125 | QStringList fileUrls; |
1126 | const QList<QUrl> urls = directoriesForCanonicalPath(dir: dirUrl.adjusted(options: QUrl::RemoveFilename | QUrl::StripTrailingSlash)); |
1127 | for (const QUrl &url : urls) { |
1128 | QUrl urlInfo(url); |
1129 | urlInfo.setPath(path: Utils::concatPaths(path1: urlInfo.path(), path2: fileName)); |
1130 | fileUrls << urlInfo.toString(); |
1131 | } |
1132 | slotFilesRemoved(fileList: fileUrls); |
1133 | } |
1134 | |
1135 | void KCoreDirListerCache::slotEntries(KIO::Job *job, const KIO::UDSEntryList &entries) |
1136 | { |
1137 | QUrl url(joburl(job: static_cast<KIO::ListJob *>(job))); |
1138 | url = url.adjusted(options: QUrl::StripTrailingSlash); |
1139 | |
1140 | qCDebug(KIO_CORE_DIRLISTER) << "new entries for " << url; |
1141 | |
1142 | DirItem *dir = itemsInUse.value(key: url); |
1143 | if (!dir) { |
1144 | qCWarning(KIO_CORE) << "Internal error: job is listing" << url << "but itemsInUse only knows about" << itemsInUse.keys(); |
1145 | Q_ASSERT(dir); |
1146 | return; |
1147 | } |
1148 | |
1149 | DirectoryDataHash::iterator dit = directoryData.find(key: url); |
1150 | if (dit == directoryData.end()) { |
1151 | qCWarning(KIO_CORE) << "Internal error: job is listing" << url << "but directoryData doesn't know about that url, only about:" << directoryData.keys(); |
1152 | Q_ASSERT(dit != directoryData.end()); |
1153 | return; |
1154 | } |
1155 | KCoreDirListerCacheDirectoryData &dirData = *dit; |
1156 | const QList<KCoreDirLister *> listers = dirData.listersCurrentlyListing; |
1157 | if (listers.isEmpty()) { |
1158 | qCWarning(KIO_CORE) << "Internal error: job is listing" << url << "but directoryData says no listers are currently listing " << url; |
1159 | #ifndef NDEBUG |
1160 | printDebug(); |
1161 | #endif |
1162 | Q_ASSERT(!listers.isEmpty()); |
1163 | return; |
1164 | } |
1165 | |
1166 | // check if anyone wants the MIME types immediately |
1167 | bool delayedMimeTypes = true; |
1168 | for (const KCoreDirLister *kdl : listers) { |
1169 | delayedMimeTypes &= kdl->d->delayedMimeTypes; |
1170 | } |
1171 | |
1172 | CacheHiddenFile *cachedHidden = nullptr; |
1173 | bool dotHiddenChecked = false; |
1174 | KFileItemList newItems; |
1175 | for (const auto &entry : entries) { |
1176 | const QString name = entry.stringValue(field: KIO::UDSEntry::UDS_NAME); |
1177 | |
1178 | Q_ASSERT(!name.isEmpty()); |
1179 | if (name.isEmpty()) { |
1180 | continue; |
1181 | } |
1182 | |
1183 | if (name == QLatin1Char('.')) { |
1184 | // Try to reuse an existing KFileItem (if we listed the parent dir) |
1185 | // rather than creating a new one. There are many reasons: |
1186 | // 1) renames and permission changes to the item would have to emit the signals |
1187 | // twice, otherwise, so that both views manage to recognize the item. |
1188 | // 2) with kio_ftp we can only know that something is a symlink when |
1189 | // listing the parent, so prefer that item, which has more info. |
1190 | // Note that it gives a funky name() to the root item, rather than "." ;) |
1191 | dir->rootItem = itemForUrl(url); |
1192 | if (dir->rootItem.isNull()) { |
1193 | dir->rootItem = KFileItem(entry, url, delayedMimeTypes, true); |
1194 | } |
1195 | |
1196 | for (KCoreDirLister *kdl : listers) { |
1197 | if (kdl->d->rootFileItem.isNull() && kdl->d->url == url) { |
1198 | kdl->d->rootFileItem = dir->rootItem; |
1199 | } |
1200 | } |
1201 | } else if (name != QLatin1String(".." )) { |
1202 | KFileItem item(entry, url, delayedMimeTypes, true); |
1203 | |
1204 | // get the names of the files listed in ".hidden", if it exists and is a local file |
1205 | if (!dotHiddenChecked) { |
1206 | const QString localPath = item.localPath(); |
1207 | if (!localPath.isEmpty()) { |
1208 | const QString rootItemPath = QFileInfo(localPath).absolutePath(); |
1209 | cachedHidden = cachedDotHiddenForDir(dir: rootItemPath); |
1210 | } |
1211 | dotHiddenChecked = true; |
1212 | } |
1213 | |
1214 | // hide file if listed in ".hidden" |
1215 | if (cachedHidden && cachedHidden->listedFiles.find(x: name) != cachedHidden->listedFiles.cend()) { |
1216 | item.setHidden(); |
1217 | } |
1218 | |
1219 | qCDebug(KIO_CORE_DIRLISTER) << "Adding item: " << item.url(); |
1220 | newItems.append(t: item); |
1221 | } |
1222 | } |
1223 | |
1224 | // sort by url using KFileItem::operator< |
1225 | std::sort(first: newItems.begin(), last: newItems.end()); |
1226 | |
1227 | // Add the items sorted by url, needed by findByUrl |
1228 | dir->insertSortedItems(items: newItems); |
1229 | |
1230 | for (KCoreDirLister *kdl : listers) { |
1231 | kdl->d->addNewItems(directoryUrl: url, items: newItems); |
1232 | } |
1233 | |
1234 | for (KCoreDirLister *kdl : listers) { |
1235 | kdl->d->emitItems(); |
1236 | } |
1237 | } |
1238 | |
1239 | void KCoreDirListerCache::slotResult(KJob *j) |
1240 | { |
1241 | #ifdef DEBUG_CACHE |
1242 | // printDebug(); |
1243 | #endif |
1244 | |
1245 | Q_ASSERT(j); |
1246 | KIO::ListJob *job = static_cast<KIO::ListJob *>(j); |
1247 | runningListJobs.remove(key: job); |
1248 | |
1249 | QUrl jobUrl(joburl(job)); |
1250 | jobUrl = jobUrl.adjusted(options: QUrl::StripTrailingSlash); // need remove trailing slashes again, in case of redirections |
1251 | |
1252 | qCDebug(KIO_CORE_DIRLISTER) << "finished listing" << jobUrl; |
1253 | |
1254 | const auto dit = directoryData.find(key: jobUrl); |
1255 | if (dit == directoryData.end()) { |
1256 | qCWarning(KIO_CORE) << "Nothing found in directoryData for URL" << jobUrl; |
1257 | #ifndef NDEBUG |
1258 | printDebug(); |
1259 | #endif |
1260 | Q_ASSERT(dit != directoryData.end()); |
1261 | return; |
1262 | } |
1263 | KCoreDirListerCacheDirectoryData &dirData = *dit; |
1264 | if (dirData.listersCurrentlyListing.isEmpty()) { |
1265 | qCWarning(KIO_CORE) << "OOOOPS, nothing in directoryData.listersCurrentlyListing for" << jobUrl; |
1266 | // We're about to assert; dump the current state... |
1267 | #ifndef NDEBUG |
1268 | printDebug(); |
1269 | #endif |
1270 | Q_ASSERT(!dirData.listersCurrentlyListing.isEmpty()); |
1271 | } |
1272 | const QList<KCoreDirLister *> listers = dirData.listersCurrentlyListing; |
1273 | |
1274 | // move all listers to the holding list, do it before emitting |
1275 | // the signals to make sure it exists in KCoreDirListerCache in case someone |
1276 | // calls listDir during the signal emission |
1277 | Q_ASSERT(dirData.listersCurrentlyHolding.isEmpty()); |
1278 | dirData.moveListersWithoutCachedItemsJob(url: jobUrl); |
1279 | |
1280 | if (job->error()) { |
1281 | bool errorShown = false; |
1282 | for (KCoreDirLister *kdl : listers) { |
1283 | kdl->d->jobDone(job); |
1284 | if (job->error() != KJob::KilledJobError) { |
1285 | Q_EMIT kdl->jobError(job); |
1286 | if (kdl->d->m_autoErrorHandling && !errorShown) { |
1287 | errorShown = true; // do it only once |
1288 | if (job->uiDelegate()) { |
1289 | job->uiDelegate()->showErrorMessage(); |
1290 | } |
1291 | } |
1292 | } |
1293 | const bool silent = job->property(name: "_kdlc_silent" ).toBool(); |
1294 | if (!silent) { |
1295 | Q_EMIT kdl->listingDirCanceled(dirUrl: jobUrl); |
1296 | } |
1297 | |
1298 | if (kdl->d->numJobs() == 0) { |
1299 | kdl->d->complete = true; |
1300 | if (!silent) { |
1301 | Q_EMIT kdl->canceled(); |
1302 | } |
1303 | } |
1304 | } |
1305 | } else { |
1306 | DirItem *dir = itemsInUse.value(key: jobUrl); |
1307 | Q_ASSERT(dir); |
1308 | dir->complete = true; |
1309 | |
1310 | for (KCoreDirLister *kdl : listers) { |
1311 | kdl->d->jobDone(job); |
1312 | Q_EMIT kdl->listingDirCompleted(dirUrl: jobUrl); |
1313 | if (kdl->d->numJobs() == 0) { |
1314 | kdl->d->complete = true; |
1315 | Q_EMIT kdl->completed(); |
1316 | } |
1317 | } |
1318 | } |
1319 | |
1320 | // TODO: hmm, if there was an error and job is a parent of one or more |
1321 | // of the pending urls we should cancel it/them as well |
1322 | processPendingUpdates(); |
1323 | |
1324 | if (job->property(name: "need_another_update" ).toBool()) { |
1325 | updateDirectory(dir: jobUrl); |
1326 | } |
1327 | |
1328 | #ifdef DEBUG_CACHE |
1329 | printDebug(); |
1330 | #endif |
1331 | } |
1332 | |
1333 | void KCoreDirListerCache::slotRedirection(KIO::Job *j, const QUrl &url) |
1334 | { |
1335 | Q_ASSERT(j); |
1336 | KIO::ListJob *job = static_cast<KIO::ListJob *>(j); |
1337 | |
1338 | QUrl oldUrl(job->url()); // here we really need the old url! |
1339 | QUrl newUrl(url); |
1340 | |
1341 | // strip trailing slashes |
1342 | oldUrl = oldUrl.adjusted(options: QUrl::StripTrailingSlash); |
1343 | newUrl = newUrl.adjusted(options: QUrl::StripTrailingSlash); |
1344 | |
1345 | if (oldUrl == newUrl) { |
1346 | qCDebug(KIO_CORE_DIRLISTER) << "New redirection url same as old, giving up." ; |
1347 | return; |
1348 | } else if (newUrl.isEmpty()) { |
1349 | qCDebug(KIO_CORE_DIRLISTER) << "New redirection url is empty, giving up." ; |
1350 | return; |
1351 | } |
1352 | |
1353 | qCDebug(KIO_CORE_DIRLISTER) << oldUrl << "->" << newUrl; |
1354 | |
1355 | #ifdef DEBUG_CACHE |
1356 | // Can't do that here. KCoreDirListerCache::joburl() will use the new url already, |
1357 | // while our data structures haven't been updated yet -> assert fail. |
1358 | // printDebug(); |
1359 | #endif |
1360 | |
1361 | // I don't think there can be dirItems that are children of oldUrl. |
1362 | // Am I wrong here? And even if so, we don't need to delete them, right? |
1363 | // DF: redirection happens before listDir emits any item. Makes little sense otherwise. |
1364 | |
1365 | // oldUrl cannot be in itemsCached because only completed items are moved there |
1366 | DirItem *dir = itemsInUse.take(key: oldUrl); |
1367 | Q_ASSERT(dir); |
1368 | |
1369 | DirectoryDataHash::iterator dit = directoryData.find(key: oldUrl); |
1370 | Q_ASSERT(dit != directoryData.end()); |
1371 | KCoreDirListerCacheDirectoryData oldDirData = *dit; |
1372 | directoryData.erase(it: dit); |
1373 | Q_ASSERT(!oldDirData.listersCurrentlyListing.isEmpty()); |
1374 | const QList<KCoreDirLister *> listers = oldDirData.listersCurrentlyListing; |
1375 | Q_ASSERT(!listers.isEmpty()); |
1376 | |
1377 | for (KCoreDirLister *kdl : listers) { |
1378 | kdl->d->redirect(oldUrl, newUrl, keepItems: false /*clear items*/); |
1379 | } |
1380 | |
1381 | // when a lister was stopped before the job emits the redirection signal, the old url will |
1382 | // also be in listersCurrentlyHolding |
1383 | const QList<KCoreDirLister *> holders = oldDirData.listersCurrentlyHolding; |
1384 | for (KCoreDirLister *kdl : holders) { |
1385 | kdl->jobStarted(job); |
1386 | // do it like when starting a new list-job that will redirect later |
1387 | // TODO: maybe don't emit started if there's an update running for newUrl already? |
1388 | Q_EMIT kdl->started(dirUrl: oldUrl); |
1389 | |
1390 | kdl->d->redirect(oldUrl, newUrl, keepItems: false /*clear items*/); |
1391 | } |
1392 | |
1393 | const QList<KCoreDirLister *> allListers = listers + holders; |
1394 | |
1395 | DirItem *newDir = itemsInUse.value(key: newUrl); |
1396 | if (newDir) { |
1397 | qCDebug(KIO_CORE_DIRLISTER) << newUrl << "already in use" ; |
1398 | |
1399 | // only in this case there can newUrl already be in listersCurrentlyListing or listersCurrentlyHolding |
1400 | delete dir; |
1401 | |
1402 | // get the job if one's running for newUrl already (can be a list-job or an update-job), but |
1403 | // do not return this 'job', which would happen because of the use of redirectionURL() |
1404 | KIO::ListJob *oldJob = jobForUrl(url: newUrl, not_job: job); |
1405 | |
1406 | // listers of newUrl with oldJob: forget about the oldJob and use the already running one |
1407 | // which will be converted to an updateJob |
1408 | KCoreDirListerCacheDirectoryData &newDirData = directoryData[newUrl]; |
1409 | |
1410 | QList<KCoreDirLister *> &curListers = newDirData.listersCurrentlyListing; |
1411 | if (!curListers.isEmpty()) { |
1412 | qCDebug(KIO_CORE_DIRLISTER) << "and it is currently listed" ; |
1413 | |
1414 | Q_ASSERT(oldJob); // ?! |
1415 | |
1416 | for (KCoreDirLister *kdl : std::as_const(t&: curListers)) { // listers of newUrl |
1417 | kdl->d->jobDone(oldJob); |
1418 | |
1419 | kdl->jobStarted(job); |
1420 | kdl->d->connectJob(job); |
1421 | } |
1422 | |
1423 | // append listers of oldUrl with newJob to listers of newUrl with oldJob |
1424 | for (KCoreDirLister *kdl : listers) { |
1425 | curListers.append(t: kdl); |
1426 | } |
1427 | } else { |
1428 | curListers = listers; |
1429 | } |
1430 | |
1431 | if (oldJob) { // kill the old job, be it a list-job or an update-job |
1432 | killJob(job: oldJob); |
1433 | } |
1434 | |
1435 | // holders of newUrl: use the already running job which will be converted to an updateJob |
1436 | QList<KCoreDirLister *> &curHolders = newDirData.listersCurrentlyHolding; |
1437 | if (!curHolders.isEmpty()) { |
1438 | qCDebug(KIO_CORE_DIRLISTER) << "and it is currently held." ; |
1439 | |
1440 | for (KCoreDirLister *kdl : std::as_const(t&: curHolders)) { // holders of newUrl |
1441 | kdl->jobStarted(job); |
1442 | Q_EMIT kdl->started(dirUrl: newUrl); |
1443 | } |
1444 | |
1445 | // append holders of oldUrl to holders of newUrl |
1446 | for (KCoreDirLister *kdl : holders) { |
1447 | curHolders.append(t: kdl); |
1448 | } |
1449 | } else { |
1450 | curHolders = holders; |
1451 | } |
1452 | |
1453 | // emit old items: listers, holders. NOT: newUrlListers/newUrlHolders, they already have them listed |
1454 | // TODO: make this a separate method? |
1455 | for (KCoreDirLister *kdl : allListers) { |
1456 | if (kdl->d->rootFileItem.isNull() && kdl->d->url == newUrl) { |
1457 | kdl->d->rootFileItem = newDir->rootItem; |
1458 | } |
1459 | |
1460 | kdl->d->addNewItems(directoryUrl: newUrl, items: newDir->lstItems); |
1461 | kdl->d->emitItems(); |
1462 | } |
1463 | } else if ((newDir = itemsCached.take(key: newUrl))) { |
1464 | qCDebug(KIO_CORE_DIRLISTER) << newUrl << "is unused, but already in the cache." ; |
1465 | |
1466 | delete dir; |
1467 | itemsInUse.insert(key: newUrl, value: newDir); |
1468 | KCoreDirListerCacheDirectoryData &newDirData = directoryData[newUrl]; |
1469 | newDirData.listersCurrentlyListing = listers; |
1470 | newDirData.listersCurrentlyHolding = holders; |
1471 | |
1472 | // emit old items: listers, holders |
1473 | for (KCoreDirLister *kdl : allListers) { |
1474 | if (kdl->d->rootFileItem.isNull() && kdl->d->url == newUrl) { |
1475 | kdl->d->rootFileItem = newDir->rootItem; |
1476 | } |
1477 | |
1478 | kdl->d->addNewItems(directoryUrl: newUrl, items: newDir->lstItems); |
1479 | kdl->d->emitItems(); |
1480 | } |
1481 | } else { |
1482 | qCDebug(KIO_CORE_DIRLISTER) << newUrl << "has not been listed yet." ; |
1483 | |
1484 | dir->rootItem = KFileItem(); |
1485 | dir->lstItems.clear(); |
1486 | dir->redirect(newUrl); |
1487 | itemsInUse.insert(key: newUrl, value: dir); |
1488 | KCoreDirListerCacheDirectoryData &newDirData = directoryData[newUrl]; |
1489 | newDirData.listersCurrentlyListing = listers; |
1490 | newDirData.listersCurrentlyHolding = holders; |
1491 | |
1492 | if (holders.isEmpty()) { |
1493 | #ifdef DEBUG_CACHE |
1494 | printDebug(); |
1495 | #endif |
1496 | return; // only in this case the job doesn't need to be converted, |
1497 | } |
1498 | } |
1499 | |
1500 | // make the job an update job |
1501 | job->disconnect(receiver: this); |
1502 | |
1503 | connect(sender: job, signal: &KIO::ListJob::entries, context: this, slot: &KCoreDirListerCache::slotUpdateEntries); |
1504 | connect(sender: job, signal: &KJob::result, context: this, slot: &KCoreDirListerCache::slotUpdateResult); |
1505 | |
1506 | // FIXME: autoUpdate-Counts!! |
1507 | |
1508 | #ifdef DEBUG_CACHE |
1509 | printDebug(); |
1510 | #endif |
1511 | } |
1512 | |
1513 | struct KCoreDirListerCache::ItemInUseChange { |
1514 | ItemInUseChange(const QUrl &old, const QUrl &newU, DirItem *di) |
1515 | : oldUrl(old) |
1516 | , newUrl(newU) |
1517 | , dirItem(di) |
1518 | { |
1519 | } |
1520 | QUrl oldUrl; |
1521 | QUrl newUrl; |
1522 | DirItem *dirItem; |
1523 | }; |
1524 | |
1525 | void KCoreDirListerCache::renameDir(const QUrl &oldUrl, const QUrl &newUrl) |
1526 | { |
1527 | qCDebug(KIO_CORE_DIRLISTER) << oldUrl << "->" << newUrl; |
1528 | |
1529 | std::vector<ItemInUseChange> itemsToChange; |
1530 | std::set<KCoreDirLister *> listers; |
1531 | |
1532 | // Look at all dirs being listed/shown |
1533 | for (auto itu = itemsInUse.begin(), ituend = itemsInUse.end(); itu != ituend; ++itu) { |
1534 | DirItem *dir = itu.value(); |
1535 | const QUrl &oldDirUrl = itu.key(); |
1536 | qCDebug(KIO_CORE_DIRLISTER) << "itemInUse:" << oldDirUrl; |
1537 | // Check if this dir is oldUrl, or a subfolder of it |
1538 | if (oldDirUrl == oldUrl || oldUrl.isParentOf(url: oldDirUrl)) { |
1539 | // TODO should use KUrl::cleanpath like isParentOf does |
1540 | QString relPath = oldDirUrl.path().mid(position: oldUrl.path().length() + 1); |
1541 | |
1542 | QUrl newDirUrl(newUrl); // take new base |
1543 | if (!relPath.isEmpty()) { |
1544 | newDirUrl.setPath(path: Utils::concatPaths(path1: newDirUrl.path(), path2: relPath)); // add unchanged relative path |
1545 | } |
1546 | qCDebug(KIO_CORE_DIRLISTER) << "new url=" << newDirUrl; |
1547 | |
1548 | // Update URL in dir item and in itemsInUse |
1549 | dir->redirect(newUrl: newDirUrl); |
1550 | |
1551 | itemsToChange.emplace_back(args: oldDirUrl.adjusted(options: QUrl::StripTrailingSlash), args: newDirUrl.adjusted(options: QUrl::StripTrailingSlash), args&: dir); |
1552 | // Rename all items under that dir |
1553 | // If all items of the directory change the same part of their url, the order is not |
1554 | // changed, therefore just change it in the list. |
1555 | for (KFileItem &item : dir->lstItems) { |
1556 | const KFileItem oldItem = item; |
1557 | KFileItem newItem = oldItem; |
1558 | const QUrl &oldItemUrl = oldItem.url(); |
1559 | QUrl newItemUrl(oldItemUrl); |
1560 | newItemUrl.setPath(path: Utils::concatPaths(path1: newDirUrl.path(), path2: oldItemUrl.fileName())); |
1561 | qCDebug(KIO_CORE_DIRLISTER) << "renaming" << oldItemUrl << "to" << newItemUrl; |
1562 | newItem.setUrl(newItemUrl); |
1563 | |
1564 | listers.merge(source: emitRefreshItem(oldItem, fileitem: newItem)); |
1565 | // Change the item |
1566 | item.setUrl(newItemUrl); |
1567 | } |
1568 | } |
1569 | } |
1570 | |
1571 | for (KCoreDirLister *kdl : listers) { |
1572 | kdl->d->emitItems(); |
1573 | } |
1574 | |
1575 | // Do the changes to itemsInUse out of the loop to avoid messing up iterators, |
1576 | // and so that emitRefreshItem can find the stuff in the hash. |
1577 | for (const ItemInUseChange &i : itemsToChange) { |
1578 | itemsInUse.remove(key: i.oldUrl); |
1579 | itemsInUse.insert(key: i.newUrl, value: i.dirItem); |
1580 | } |
1581 | // Now that all the caches are updated and consistent, emit the redirection. |
1582 | for (const ItemInUseChange &i : itemsToChange) { |
1583 | emitRedirections(oldUrl: QUrl(i.oldUrl), url: QUrl(i.newUrl)); |
1584 | } |
1585 | // Is oldUrl a directory in the cache? |
1586 | // Remove any child of oldUrl from the cache - even if the renamed dir itself isn't in it! |
1587 | removeDirFromCache(dir: oldUrl); |
1588 | // TODO rename, instead. |
1589 | } |
1590 | |
1591 | // helper for renameDir, not used for redirections from KIO::listDir(). |
1592 | void KCoreDirListerCache::emitRedirections(const QUrl &_oldUrl, const QUrl &_newUrl) |
1593 | { |
1594 | qCDebug(KIO_CORE_DIRLISTER) << _oldUrl << "->" << _newUrl; |
1595 | const QUrl oldUrl = _oldUrl.adjusted(options: QUrl::StripTrailingSlash); |
1596 | const QUrl newUrl = _newUrl.adjusted(options: QUrl::StripTrailingSlash); |
1597 | |
1598 | KIO::ListJob *job = jobForUrl(url: oldUrl); |
1599 | if (job) { |
1600 | killJob(job); |
1601 | } |
1602 | |
1603 | // Check if we were listing this dir. Need to abort and restart with new name in that case. |
1604 | DirectoryDataHash::iterator dit = directoryData.find(key: oldUrl); |
1605 | if (dit == directoryData.end()) { |
1606 | return; |
1607 | } |
1608 | const QList<KCoreDirLister *> listers = (*dit).listersCurrentlyListing; |
1609 | const QList<KCoreDirLister *> holders = (*dit).listersCurrentlyHolding; |
1610 | |
1611 | KCoreDirListerCacheDirectoryData &newDirData = directoryData[newUrl]; |
1612 | |
1613 | // Tell the world that the job listing the old url is dead. |
1614 | for (KCoreDirLister *kdl : listers) { |
1615 | if (job) { |
1616 | kdl->d->jobDone(job); |
1617 | } |
1618 | Q_EMIT kdl->listingDirCanceled(dirUrl: oldUrl); |
1619 | } |
1620 | newDirData.listersCurrentlyListing += listers; |
1621 | |
1622 | // Check if we are currently displaying this directory (odds opposite wrt above) |
1623 | for (KCoreDirLister *kdl : holders) { |
1624 | if (job) { |
1625 | kdl->d->jobDone(job); |
1626 | } |
1627 | } |
1628 | newDirData.listersCurrentlyHolding += holders; |
1629 | directoryData.erase(it: dit); |
1630 | |
1631 | if (!listers.isEmpty()) { |
1632 | updateDirectory(dir: newUrl); |
1633 | |
1634 | // Tell the world about the new url |
1635 | for (KCoreDirLister *kdl : listers) { |
1636 | Q_EMIT kdl->started(dirUrl: newUrl); |
1637 | } |
1638 | } |
1639 | |
1640 | // And notify the dirlisters of the redirection |
1641 | for (KCoreDirLister *kdl : holders) { |
1642 | kdl->d->redirect(oldUrl, newUrl, keepItems: true /*keep items*/); |
1643 | } |
1644 | } |
1645 | |
1646 | void KCoreDirListerCache::removeDirFromCache(const QUrl &dir) |
1647 | { |
1648 | qCDebug(KIO_CORE_DIRLISTER) << dir; |
1649 | const QList<QUrl> cachedDirs = itemsCached.keys(); // seems slow, but there's no qcache iterator... |
1650 | for (const QUrl &cachedDir : cachedDirs) { |
1651 | if (dir == cachedDir || dir.isParentOf(url: cachedDir)) { |
1652 | itemsCached.remove(key: cachedDir); |
1653 | } |
1654 | } |
1655 | } |
1656 | |
1657 | void KCoreDirListerCache::slotUpdateEntries(KIO::Job *job, const KIO::UDSEntryList &list) |
1658 | { |
1659 | runningListJobs[static_cast<KIO::ListJob *>(job)] += list; |
1660 | } |
1661 | |
1662 | void KCoreDirListerCache::slotUpdateResult(KJob *j) |
1663 | { |
1664 | Q_ASSERT(j); |
1665 | KIO::ListJob *job = static_cast<KIO::ListJob *>(j); |
1666 | |
1667 | QUrl jobUrl(joburl(job)); |
1668 | jobUrl = jobUrl.adjusted(options: QUrl::StripTrailingSlash); // need remove trailing slashes again, in case of redirections |
1669 | |
1670 | qCDebug(KIO_CORE_DIRLISTER) << "finished update" << jobUrl; |
1671 | |
1672 | KCoreDirListerCacheDirectoryData &dirData = directoryData[jobUrl]; |
1673 | // Collect the dirlisters which were listing the URL using that ListJob |
1674 | // plus those that were already holding that URL - they all get updated. |
1675 | dirData.moveListersWithoutCachedItemsJob(url: jobUrl); |
1676 | const QList<KCoreDirLister *> listers = dirData.listersCurrentlyHolding + dirData.listersCurrentlyListing; |
1677 | |
1678 | // once we are updating dirs that are only in the cache this will fail! |
1679 | Q_ASSERT(!listers.isEmpty()); |
1680 | |
1681 | if (job->error()) { |
1682 | for (KCoreDirLister *kdl : listers) { |
1683 | kdl->d->jobDone(job); |
1684 | |
1685 | // don't bother the user: no jobError signal emitted |
1686 | |
1687 | const bool silent = job->property(name: "_kdlc_silent" ).toBool(); |
1688 | if (!silent) { |
1689 | Q_EMIT kdl->listingDirCanceled(dirUrl: jobUrl); |
1690 | } |
1691 | if (kdl->d->numJobs() == 0) { |
1692 | kdl->d->complete = true; |
1693 | if (!silent) { |
1694 | Q_EMIT kdl->canceled(); |
1695 | } |
1696 | } |
1697 | } |
1698 | |
1699 | runningListJobs.remove(key: job); |
1700 | |
1701 | // TODO: if job is a parent of one or more |
1702 | // of the pending urls we should cancel them |
1703 | processPendingUpdates(); |
1704 | return; |
1705 | } |
1706 | |
1707 | DirItem *dir = itemsInUse.value(key: jobUrl, defaultValue: nullptr); |
1708 | if (!dir) { |
1709 | qCWarning(KIO_CORE) << "Internal error: itemsInUse did not contain" << jobUrl; |
1710 | #ifndef NDEBUG |
1711 | printDebug(); |
1712 | #endif |
1713 | Q_ASSERT(dir); |
1714 | } else { |
1715 | dir->complete = true; |
1716 | } |
1717 | |
1718 | // check if anyone wants the MIME types immediately |
1719 | bool delayedMimeTypes = true; |
1720 | for (const KCoreDirLister *kdl : listers) { |
1721 | delayedMimeTypes &= kdl->d->delayedMimeTypes; |
1722 | } |
1723 | |
1724 | typedef QHash<QString, KFileItem> FileItemHash; // fileName -> KFileItem |
1725 | FileItemHash fileItems; |
1726 | |
1727 | // Fill the hash from the old list of items. We'll remove entries as we see them |
1728 | // in the new listing, and the resulting hash entries will be the deleted items. |
1729 | for (const KFileItem &item : std::as_const(t&: dir->lstItems)) { |
1730 | fileItems.insert(key: item.name(), value: item); |
1731 | } |
1732 | |
1733 | CacheHiddenFile *cachedHidden = nullptr; |
1734 | bool dotHiddenChecked = false; |
1735 | const KIO::UDSEntryList &buf = runningListJobs.value(key: job); |
1736 | KFileItemList newItems; |
1737 | for (const auto &entry : buf) { |
1738 | // Form the complete url |
1739 | KFileItem item(entry, jobUrl, delayedMimeTypes, true); |
1740 | |
1741 | const QString name = item.name(); |
1742 | Q_ASSERT(!name.isEmpty()); // A KIO worker setting an empty UDS_NAME is utterly broken, fix the KIO worker! |
1743 | |
1744 | // we duplicate the check for dotdot here, to avoid iterating over |
1745 | // all items again and checking in matchesFilter() that way. |
1746 | if (name.isEmpty() || name == QLatin1String(".." )) { |
1747 | continue; |
1748 | } |
1749 | |
1750 | if (name == QLatin1Char('.')) { |
1751 | // if the update was started before finishing the original listing |
1752 | // there is no root item yet |
1753 | if (dir->rootItem.isNull()) { |
1754 | dir->rootItem = item; |
1755 | |
1756 | for (KCoreDirLister *kdl : listers) { |
1757 | if (kdl->d->rootFileItem.isNull() && kdl->d->url == jobUrl) { |
1758 | kdl->d->rootFileItem = dir->rootItem; |
1759 | } |
1760 | } |
1761 | } |
1762 | continue; |
1763 | } else { |
1764 | // get the names of the files listed in ".hidden", if it exists and is a local file |
1765 | if (!dotHiddenChecked) { |
1766 | const QString localPath = item.localPath(); |
1767 | if (!localPath.isEmpty()) { |
1768 | const QString rootItemPath = QFileInfo(localPath).absolutePath(); |
1769 | cachedHidden = cachedDotHiddenForDir(dir: rootItemPath); |
1770 | } |
1771 | dotHiddenChecked = true; |
1772 | } |
1773 | } |
1774 | |
1775 | // hide file if listed in ".hidden" |
1776 | if (cachedHidden && cachedHidden->listedFiles.find(x: name) != cachedHidden->listedFiles.cend()) { |
1777 | item.setHidden(); |
1778 | } |
1779 | |
1780 | // Find this item |
1781 | FileItemHash::iterator fiit = fileItems.find(key: item.name()); |
1782 | if (fiit != fileItems.end()) { |
1783 | const KFileItem tmp = fiit.value(); |
1784 | |
1785 | bool inPendingUpdates = false; |
1786 | bool inPendingRemoteUpdates = false; |
1787 | |
1788 | std::set<QString>::iterator pu_it; |
1789 | std::set<KFileItem>::iterator pru_it; |
1790 | if (tmp.url().isLocalFile()) { |
1791 | pu_it = pendingUpdates.find(x: tmp.url().toLocalFile()); |
1792 | inPendingUpdates = pu_it != pendingUpdates.end(); |
1793 | } else { |
1794 | pru_it = pendingRemoteUpdates.find(x: tmp); |
1795 | inPendingRemoteUpdates = pru_it != pendingRemoteUpdates.end(); |
1796 | } |
1797 | |
1798 | // check if something changed for this file, using KFileItem::cmp() |
1799 | if (inPendingRemoteUpdates || inPendingUpdates || !tmp.cmp(item)) { |
1800 | if (inPendingRemoteUpdates) { |
1801 | pendingRemoteUpdates.erase(position: pru_it); |
1802 | } |
1803 | if (inPendingUpdates) { |
1804 | pendingUpdates.erase(position: pu_it); |
1805 | } |
1806 | |
1807 | qCDebug(KIO_CORE_DIRLISTER) << "file changed:" << tmp.name(); |
1808 | |
1809 | reinsert(item, oldUrl: tmp.url()); |
1810 | for (KCoreDirLister *kdl : listers) { |
1811 | kdl->d->addRefreshItem(directoryUrl: jobUrl, oldItem: tmp, item); |
1812 | } |
1813 | } |
1814 | // Seen, remove |
1815 | fileItems.erase(it: fiit); |
1816 | } else { // this is a new file |
1817 | qCDebug(KIO_CORE_DIRLISTER) << "new file:" << name; |
1818 | newItems.append(t: item); |
1819 | } |
1820 | } |
1821 | |
1822 | // sort by url using KFileItem::operator< |
1823 | std::sort(first: newItems.begin(), last: newItems.end()); |
1824 | |
1825 | // Add the items sorted by url, needed by findByUrl |
1826 | dir->insertSortedItems(items: newItems); |
1827 | |
1828 | for (KCoreDirLister *kdl : listers) { |
1829 | kdl->d->addNewItems(directoryUrl: jobUrl, items: newItems); |
1830 | } |
1831 | |
1832 | runningListJobs.remove(key: job); |
1833 | |
1834 | if (!fileItems.isEmpty()) { |
1835 | deleteUnmarkedItems(listers, lstItems&: dir->lstItems, itemsToDelete: fileItems); |
1836 | } |
1837 | |
1838 | for (KCoreDirLister *kdl : listers) { |
1839 | kdl->d->emitItems(); |
1840 | |
1841 | kdl->d->jobDone(job); |
1842 | Q_EMIT kdl->listingDirCompleted(dirUrl: jobUrl); |
1843 | if (kdl->d->numJobs() == 0) { |
1844 | kdl->d->complete = true; |
1845 | Q_EMIT kdl->completed(); |
1846 | } |
1847 | } |
1848 | |
1849 | // TODO: hmm, if there was an error and job is a parent of one or more |
1850 | // of the pending urls we should cancel it/them as well |
1851 | processPendingUpdates(); |
1852 | |
1853 | if (job->property(name: "need_another_update" ).toBool()) { |
1854 | updateDirectory(dir: jobUrl); |
1855 | } |
1856 | } |
1857 | |
1858 | // private |
1859 | |
1860 | KIO::ListJob *KCoreDirListerCache::jobForUrl(const QUrl &url, KIO::ListJob *not_job) |
1861 | { |
1862 | for (auto it = runningListJobs.cbegin(); it != runningListJobs.cend(); ++it) { |
1863 | KIO::ListJob *job = it.key(); |
1864 | const QUrl jobUrl = joburl(job).adjusted(options: QUrl::StripTrailingSlash); |
1865 | |
1866 | if (jobUrl == url && job != not_job) { |
1867 | return job; |
1868 | } |
1869 | } |
1870 | return nullptr; |
1871 | } |
1872 | |
1873 | const QUrl &KCoreDirListerCache::joburl(KIO::ListJob *job) |
1874 | { |
1875 | if (job->redirectionUrl().isValid()) { |
1876 | return job->redirectionUrl(); |
1877 | } else { |
1878 | return job->url(); |
1879 | } |
1880 | } |
1881 | |
1882 | void KCoreDirListerCache::killJob(KIO::ListJob *job) |
1883 | { |
1884 | runningListJobs.remove(key: job); |
1885 | job->disconnect(receiver: this); |
1886 | job->kill(); |
1887 | } |
1888 | |
1889 | void KCoreDirListerCache::deleteUnmarkedItems(const QList<KCoreDirLister *> &listers, |
1890 | QList<KFileItem> &lstItems, |
1891 | const QHash<QString, KFileItem> &itemsToDelete) |
1892 | { |
1893 | // Make list of deleted items (for emitting) |
1894 | KFileItemList deletedItems; |
1895 | deletedItems.reserve(asize: itemsToDelete.size()); |
1896 | for (auto kit = itemsToDelete.cbegin(), endIt = itemsToDelete.cend(); kit != endIt; ++kit) { |
1897 | const KFileItem item = kit.value(); |
1898 | deletedItems.append(t: item); |
1899 | qCDebug(KIO_CORE_DIRLISTER) << "deleted:" << item.name() << item; |
1900 | } |
1901 | |
1902 | // Delete all remaining items |
1903 | auto it = std::remove_if(first: lstItems.begin(), last: lstItems.end(), pred: [&itemsToDelete](const KFileItem &item) { |
1904 | return itemsToDelete.contains(key: item.name()); |
1905 | }); |
1906 | lstItems.erase(abegin: it, aend: lstItems.end()); |
1907 | |
1908 | itemsDeleted(listers, deletedItems); |
1909 | } |
1910 | |
1911 | void KCoreDirListerCache::itemsDeleted(const QList<KCoreDirLister *> &listers, const KFileItemList &deletedItems) |
1912 | { |
1913 | for (KCoreDirLister *kdl : listers) { |
1914 | kdl->d->emitItemsDeleted(items: deletedItems); |
1915 | } |
1916 | |
1917 | for (const KFileItem &item : deletedItems) { |
1918 | if (item.isDir()) { |
1919 | deleteDir(dirUrl: item.url()); |
1920 | } |
1921 | } |
1922 | } |
1923 | |
1924 | void KCoreDirListerCache::deleteDir(const QUrl &_dirUrl) |
1925 | { |
1926 | qCDebug(KIO_CORE_DIRLISTER) << _dirUrl; |
1927 | // unregister and remove the children of the deleted item. |
1928 | // Idea: tell all the KCoreDirListers that they should forget the dir |
1929 | // and then remove it from the cache. |
1930 | |
1931 | QUrl dirUrl(_dirUrl.adjusted(options: QUrl::StripTrailingSlash)); |
1932 | |
1933 | // Separate itemsInUse iteration and calls to forgetDirs (which modify itemsInUse) |
1934 | QList<QUrl> affectedItems; |
1935 | |
1936 | auto itu = itemsInUse.cbegin(); |
1937 | const auto ituend = itemsInUse.cend(); |
1938 | for (; itu != ituend; ++itu) { |
1939 | const QUrl &deletedUrl = itu.key(); |
1940 | if (dirUrl == deletedUrl || dirUrl.isParentOf(url: deletedUrl)) { |
1941 | affectedItems.append(t: deletedUrl); |
1942 | } |
1943 | } |
1944 | |
1945 | for (const QUrl &deletedUrl : std::as_const(t&: affectedItems)) { |
1946 | // stop all jobs for deletedUrlStr |
1947 | auto dit = directoryData.constFind(key: deletedUrl); |
1948 | if (dit != directoryData.cend()) { |
1949 | // we need a copy because stop modifies the list |
1950 | const QList<KCoreDirLister *> listers = (*dit).listersCurrentlyListing; |
1951 | for (KCoreDirLister *kdl : listers) { |
1952 | stopListingUrl(lister: kdl, u: deletedUrl); |
1953 | } |
1954 | // tell listers holding deletedUrl to forget about it |
1955 | // this will stop running updates for deletedUrl as well |
1956 | |
1957 | // we need a copy because forgetDirs modifies the list |
1958 | const QList<KCoreDirLister *> holders = (*dit).listersCurrentlyHolding; |
1959 | for (KCoreDirLister *kdl : holders) { |
1960 | // lister's root is the deleted item |
1961 | if (kdl->d->url == deletedUrl) { |
1962 | // tell the view first. It might need the subdirs' items (which forgetDirs will delete) |
1963 | if (!kdl->d->rootFileItem.isNull()) { |
1964 | Q_EMIT kdl->itemsDeleted(items: KFileItemList{kdl->d->rootFileItem}); |
1965 | } |
1966 | forgetDirs(lister: kdl); |
1967 | kdl->d->rootFileItem = KFileItem(); |
1968 | } else { |
1969 | const bool treeview = kdl->d->lstDirs.count() > 1; |
1970 | if (!treeview) { |
1971 | Q_EMIT kdl->clear(); |
1972 | kdl->d->lstDirs.clear(); |
1973 | } else { |
1974 | kdl->d->lstDirs.removeAll(t: deletedUrl); |
1975 | } |
1976 | |
1977 | forgetDirs(lister: kdl, url: deletedUrl, notify: treeview); |
1978 | } |
1979 | } |
1980 | } |
1981 | |
1982 | // delete the entry for deletedUrl - should not be needed, it's in |
1983 | // items cached now |
1984 | int count = itemsInUse.remove(key: deletedUrl); |
1985 | Q_ASSERT(count == 0); |
1986 | Q_UNUSED(count); // keep gcc "unused variable" complaining quiet when in release mode |
1987 | } |
1988 | |
1989 | // remove the children from the cache |
1990 | removeDirFromCache(dir: dirUrl); |
1991 | } |
1992 | |
1993 | // delayed updating of files, FAM is flooding us with events |
1994 | void KCoreDirListerCache::processPendingUpdates() |
1995 | { |
1996 | std::set<KCoreDirLister *> listers; |
1997 | QList<QUrl> removedUrls; |
1998 | for (const QString &file : pendingUpdates) { // always a local path |
1999 | qCDebug(KIO_CORE_DIRLISTER) << file; |
2000 | QUrl u = QUrl::fromLocalFile(localfile: file); |
2001 | KFileItem item = findByUrl(lister: nullptr, u: u); // search all items |
2002 | if (!item.isNull()) { |
2003 | // we need to refresh the item, because e.g. the permissions can have changed. |
2004 | KFileItem oldItem = item; |
2005 | item.refresh(); |
2006 | |
2007 | if (!oldItem.cmp(item)) { |
2008 | if (!item.exists()) { |
2009 | removedUrls.append(t: oldItem.url()); |
2010 | } else { |
2011 | reinsert(item, oldUrl: oldItem.url()); |
2012 | } |
2013 | listers.merge(source: emitRefreshItem(oldItem, fileitem: item)); |
2014 | } |
2015 | } |
2016 | } |
2017 | pendingUpdates.clear(); |
2018 | for (KCoreDirLister *kdl : listers) { |
2019 | kdl->d->emitItems(); |
2020 | } |
2021 | |
2022 | // clean orphan KFileItem, after events were emitted |
2023 | for (const auto &removedUrl : removedUrls) { |
2024 | remove(oldUrl: removedUrl); |
2025 | } |
2026 | |
2027 | // Directories in need of updating |
2028 | for (const QString &dir : pendingDirectoryUpdates) { |
2029 | updateDirectory(dir: QUrl::fromLocalFile(localfile: dir)); |
2030 | } |
2031 | pendingDirectoryUpdates.clear(); |
2032 | } |
2033 | |
2034 | #ifndef NDEBUG |
2035 | void KCoreDirListerCache::printDebug() |
2036 | { |
2037 | qCDebug(KIO_CORE_DIRLISTER) << "Items in use:" ; |
2038 | auto itu = itemsInUse.constBegin(); |
2039 | const auto ituend = itemsInUse.constEnd(); |
2040 | for (; itu != ituend; ++itu) { |
2041 | qCDebug(KIO_CORE_DIRLISTER) << " " << itu.key() << "URL:" << itu.value()->url |
2042 | << "rootItem:" << (!itu.value()->rootItem.isNull() ? itu.value()->rootItem.url() : QUrl()) |
2043 | << "autoUpdates refcount:" << itu.value()->autoUpdates << "complete:" << itu.value()->complete |
2044 | << QStringLiteral("with %1 items." ).arg(a: itu.value()->lstItems.count()); |
2045 | } |
2046 | |
2047 | QList<KCoreDirLister *> listersWithoutJob; |
2048 | qCDebug(KIO_CORE_DIRLISTER) << "Directory data:" ; |
2049 | auto dit = directoryData.constBegin(); |
2050 | for (; dit != directoryData.constEnd(); ++dit) { |
2051 | QString list; |
2052 | const QList<KCoreDirLister *> listers = (*dit).listersCurrentlyListing; |
2053 | for (KCoreDirLister *listit : listers) { |
2054 | list += QLatin1String(" 0x" ) + QString::number(reinterpret_cast<qlonglong>(listit), base: 16); |
2055 | } |
2056 | qCDebug(KIO_CORE_DIRLISTER) << " " << dit.key() << listers.count() << "listers:" << list; |
2057 | for (KCoreDirLister *listit : listers) { |
2058 | if (!listit->d->m_cachedItemsJobs.isEmpty()) { |
2059 | qCDebug(KIO_CORE_DIRLISTER) << " Lister" << listit << "has CachedItemsJobs" << listit->d->m_cachedItemsJobs; |
2060 | } else if (KIO::ListJob *listJob = jobForUrl(url: dit.key())) { |
2061 | qCDebug(KIO_CORE_DIRLISTER) << " Lister" << listit << "has ListJob" << listJob; |
2062 | } else { |
2063 | listersWithoutJob.append(t: listit); |
2064 | } |
2065 | } |
2066 | |
2067 | list.clear(); |
2068 | const QList<KCoreDirLister *> holders = (*dit).listersCurrentlyHolding; |
2069 | for (KCoreDirLister *listit : holders) { |
2070 | list += QLatin1String(" 0x" ) + QString::number(reinterpret_cast<qlonglong>(listit), base: 16); |
2071 | } |
2072 | qCDebug(KIO_CORE_DIRLISTER) << " " << dit.key() << holders.count() << "holders:" << list; |
2073 | } |
2074 | |
2075 | QMap<KIO::ListJob *, KIO::UDSEntryList>::Iterator jit = runningListJobs.begin(); |
2076 | qCDebug(KIO_CORE_DIRLISTER) << "Jobs:" ; |
2077 | for (; jit != runningListJobs.end(); ++jit) { |
2078 | qCDebug(KIO_CORE_DIRLISTER) << " " << jit.key() << "listing" << joburl(job: jit.key()) << ":" << (*jit).count() << "entries." ; |
2079 | } |
2080 | |
2081 | qCDebug(KIO_CORE_DIRLISTER) << "Items in cache:" ; |
2082 | const QList<QUrl> cachedDirs = itemsCached.keys(); |
2083 | for (const QUrl &cachedDir : cachedDirs) { |
2084 | DirItem *dirItem = itemsCached.object(key: cachedDir); |
2085 | qCDebug(KIO_CORE_DIRLISTER) << " " << cachedDir |
2086 | << "rootItem:" << (!dirItem->rootItem.isNull() ? dirItem->rootItem.url().toString() : QStringLiteral("NULL" )) << "with" |
2087 | << dirItem->lstItems.count() << "items." ; |
2088 | } |
2089 | |
2090 | // Abort on listers without jobs -after- showing the full dump. Easier debugging. |
2091 | for (KCoreDirLister *listit : std::as_const(t&: listersWithoutJob)) { |
2092 | qCWarning(KIO_CORE) << "Fatal Error: HUH? Lister" << listit << "is supposed to be listing, but has no job!" ; |
2093 | abort(); |
2094 | } |
2095 | } |
2096 | #endif |
2097 | |
2098 | KCoreDirLister::KCoreDirLister(QObject *parent) |
2099 | : QObject(parent) |
2100 | , d(new KCoreDirListerPrivate(this)) |
2101 | { |
2102 | qCDebug(KIO_CORE_DIRLISTER) << "+KCoreDirLister" ; |
2103 | |
2104 | d->complete = true; |
2105 | |
2106 | setAutoUpdate(true); |
2107 | setDirOnlyMode(false); |
2108 | setShowHiddenFiles(false); |
2109 | } |
2110 | |
2111 | KCoreDirLister::~KCoreDirLister() |
2112 | { |
2113 | qCDebug(KIO_CORE_DIRLISTER) << "~KCoreDirLister" << this; |
2114 | |
2115 | // Stop all running jobs, remove lister from lists |
2116 | if (!qApp->closingDown()) { |
2117 | stop(); |
2118 | s_kDirListerCache.localData().forgetDirs(lister: this); |
2119 | } |
2120 | } |
2121 | |
2122 | // TODO KF6: remove bool ret val, it's always true |
2123 | bool KCoreDirLister::openUrl(const QUrl &_url, OpenUrlFlags _flags) |
2124 | { |
2125 | // emit the current changes made to avoid an inconsistent treeview |
2126 | if (d->hasPendingChanges && (_flags & Keep)) { |
2127 | emitChanges(); |
2128 | } |
2129 | |
2130 | d->hasPendingChanges = false; |
2131 | |
2132 | return s_kDirListerCache.localData().listDir(lister: this, dirUrl: _url, keep: _flags & Keep, reload: _flags & Reload); |
2133 | } |
2134 | |
2135 | void KCoreDirLister::stop() |
2136 | { |
2137 | s_kDirListerCache.localData().stop(lister: this); |
2138 | } |
2139 | |
2140 | void KCoreDirLister::stop(const QUrl &_url) |
2141 | { |
2142 | s_kDirListerCache.localData().stopListingUrl(lister: this, u: _url); |
2143 | } |
2144 | |
2145 | void KCoreDirLister::forgetDirs(const QUrl &_url) |
2146 | { |
2147 | s_kDirListerCache.localData().forgetDirs(lister: this, _url, notify: true); |
2148 | } |
2149 | |
2150 | bool KCoreDirLister::autoUpdate() const |
2151 | { |
2152 | return d->autoUpdate; |
2153 | } |
2154 | |
2155 | void KCoreDirLister::setAutoUpdate(bool enable) |
2156 | { |
2157 | if (d->autoUpdate == enable) { |
2158 | return; |
2159 | } |
2160 | |
2161 | d->autoUpdate = enable; |
2162 | s_kDirListerCache.localData().setAutoUpdate(lister: this, enable); |
2163 | } |
2164 | |
2165 | bool KCoreDirLister::showHiddenFiles() const |
2166 | { |
2167 | return d->settings.isShowingDotFiles; |
2168 | } |
2169 | |
2170 | void KCoreDirLister::setShowHiddenFiles(bool setShowHiddenFiles) |
2171 | { |
2172 | if (d->settings.isShowingDotFiles == setShowHiddenFiles) { |
2173 | return; |
2174 | } |
2175 | |
2176 | d->prepareForSettingsChange(); |
2177 | d->settings.isShowingDotFiles = setShowHiddenFiles; |
2178 | } |
2179 | |
2180 | bool KCoreDirLister::dirOnlyMode() const |
2181 | { |
2182 | return d->settings.dirOnlyMode; |
2183 | } |
2184 | |
2185 | void KCoreDirLister::setDirOnlyMode(bool dirsOnly) |
2186 | { |
2187 | if (d->settings.dirOnlyMode == dirsOnly) { |
2188 | return; |
2189 | } |
2190 | |
2191 | d->prepareForSettingsChange(); |
2192 | d->settings.dirOnlyMode = dirsOnly; |
2193 | } |
2194 | |
2195 | bool KCoreDirLister::requestMimeTypeWhileListing() const |
2196 | { |
2197 | return d->requestMimeTypeWhileListing; |
2198 | } |
2199 | |
2200 | void KCoreDirLister::setRequestMimeTypeWhileListing(bool request) |
2201 | { |
2202 | if (d->requestMimeTypeWhileListing == request) { |
2203 | return; |
2204 | } |
2205 | |
2206 | d->requestMimeTypeWhileListing = request; |
2207 | if (d->requestMimeTypeWhileListing) { |
2208 | // Changing from request off to on, clear any cached items associated |
2209 | // with this lister so we re-request them and get the mimetype as well. |
2210 | // If we do not, we risk caching items that have no mime type. |
2211 | s_kDirListerCache.localData().forgetDirs(lister: this); |
2212 | } |
2213 | } |
2214 | |
2215 | QUrl KCoreDirLister::url() const |
2216 | { |
2217 | return d->url; |
2218 | } |
2219 | |
2220 | QList<QUrl> KCoreDirLister::directories() const |
2221 | { |
2222 | return d->lstDirs; |
2223 | } |
2224 | |
2225 | void KCoreDirLister::emitChanges() |
2226 | { |
2227 | d->emitChanges(); |
2228 | } |
2229 | |
2230 | void KCoreDirListerPrivate::emitChanges() |
2231 | { |
2232 | if (!hasPendingChanges) { |
2233 | return; |
2234 | } |
2235 | |
2236 | // reset 'hasPendingChanges' now, in case of recursion |
2237 | // (testcase: enabling recursive scan in ktorrent, #174920) |
2238 | hasPendingChanges = false; |
2239 | |
2240 | const KCoreDirListerPrivate::FilterSettings newSettings = settings; |
2241 | settings = oldSettings; // temporarily |
2242 | |
2243 | // Fill hash with all items that are currently visible |
2244 | std::set<QString> oldVisibleItems; |
2245 | for (const QUrl &dir : std::as_const(t&: lstDirs)) { |
2246 | const QList<KFileItem> *itemList = s_kDirListerCache.localData().itemsForDir(dir); |
2247 | if (!itemList) { |
2248 | continue; |
2249 | } |
2250 | |
2251 | for (const KFileItem &item : *itemList) { |
2252 | if (isItemVisible(item) && matchesMimeFilter(item)) { |
2253 | oldVisibleItems.insert(x: item.name()); |
2254 | } |
2255 | } |
2256 | } |
2257 | |
2258 | settings = newSettings; |
2259 | |
2260 | const QList<QUrl> dirs = lstDirs; |
2261 | for (const QUrl &dir : dirs) { |
2262 | KFileItemList deletedItems; |
2263 | |
2264 | const QList<KFileItem> *itemList = s_kDirListerCache.localData().itemsForDir(dir); |
2265 | if (!itemList) { |
2266 | continue; |
2267 | } |
2268 | |
2269 | for (const auto &item : *itemList) { |
2270 | const QString text = item.text(); |
2271 | if (text == QLatin1Char('.') || text == QLatin1String(".." )) { |
2272 | continue; |
2273 | } |
2274 | const bool wasVisible = oldVisibleItems.find(x: item.name()) != oldVisibleItems.cend(); |
2275 | const bool mimeFiltered = matchesMimeFilter(item); |
2276 | const bool nowVisible = isItemVisible(item) && mimeFiltered; |
2277 | if (nowVisible && !wasVisible) { |
2278 | addNewItem(directoryUrl: dir, item); // takes care of emitting newItem or itemsFilteredByMime |
2279 | } else if (!nowVisible && wasVisible) { |
2280 | if (!mimeFiltered) { |
2281 | lstMimeFilteredItems.append(t: item); |
2282 | } |
2283 | deletedItems.append(t: item); |
2284 | } |
2285 | } |
2286 | if (!deletedItems.isEmpty()) { |
2287 | Q_EMIT q->itemsDeleted(items: deletedItems); |
2288 | } |
2289 | emitItems(); |
2290 | } |
2291 | oldSettings = settings; |
2292 | } |
2293 | |
2294 | void KCoreDirLister::updateDirectory(const QUrl &dirUrl) |
2295 | { |
2296 | s_kDirListerCache.localData().updateDirectory(dir: dirUrl); |
2297 | } |
2298 | |
2299 | bool KCoreDirLister::isFinished() const |
2300 | { |
2301 | return d->complete; |
2302 | } |
2303 | |
2304 | KFileItem KCoreDirLister::rootItem() const |
2305 | { |
2306 | return d->rootFileItem; |
2307 | } |
2308 | |
2309 | KFileItem KCoreDirLister::findByUrl(const QUrl &url) const |
2310 | { |
2311 | return s_kDirListerCache.localData().findByUrl(lister: this, u: url); |
2312 | } |
2313 | |
2314 | KFileItem KCoreDirLister::findByName(const QString &name) const |
2315 | { |
2316 | return s_kDirListerCache.localData().findByName(lister: this, name: name); |
2317 | } |
2318 | |
2319 | // ================ public filter methods ================ // |
2320 | |
2321 | void KCoreDirLister::setNameFilter(const QString &nameFilter) |
2322 | { |
2323 | if (d->nameFilter == nameFilter) { |
2324 | return; |
2325 | } |
2326 | |
2327 | d->prepareForSettingsChange(); |
2328 | |
2329 | d->settings.lstFilters.clear(); |
2330 | d->nameFilter = nameFilter; |
2331 | // Split on white space |
2332 | const QList<QStringView> list = QStringView(nameFilter).split(sep: QLatin1Char(' '), behavior: Qt::SkipEmptyParts); |
2333 | for (const QStringView filter : list) { |
2334 | d->settings.lstFilters.append(t: QRegularExpression(QRegularExpression::wildcardToRegularExpression(str: filter), QRegularExpression::CaseInsensitiveOption)); |
2335 | } |
2336 | } |
2337 | |
2338 | QString KCoreDirLister::nameFilter() const |
2339 | { |
2340 | return d->nameFilter; |
2341 | } |
2342 | |
2343 | void KCoreDirLister::setMimeFilter(const QStringList &mimeFilter) |
2344 | { |
2345 | if (d->settings.mimeFilter == mimeFilter) { |
2346 | return; |
2347 | } |
2348 | |
2349 | d->prepareForSettingsChange(); |
2350 | if (mimeFilter.contains(str: QLatin1String("application/octet-stream" )) || mimeFilter.contains(str: QLatin1String("all/allfiles" ))) { // all files |
2351 | d->settings.mimeFilter.clear(); |
2352 | } else { |
2353 | d->settings.mimeFilter = mimeFilter; |
2354 | } |
2355 | } |
2356 | |
2357 | void KCoreDirLister::setMimeExcludeFilter(const QStringList &mimeExcludeFilter) |
2358 | { |
2359 | if (d->settings.mimeExcludeFilter == mimeExcludeFilter) { |
2360 | return; |
2361 | } |
2362 | |
2363 | d->prepareForSettingsChange(); |
2364 | d->settings.mimeExcludeFilter = mimeExcludeFilter; |
2365 | } |
2366 | |
2367 | void KCoreDirLister::clearMimeFilter() |
2368 | { |
2369 | d->prepareForSettingsChange(); |
2370 | d->settings.mimeFilter.clear(); |
2371 | d->settings.mimeExcludeFilter.clear(); |
2372 | } |
2373 | |
2374 | QStringList KCoreDirLister::mimeFilters() const |
2375 | { |
2376 | return d->settings.mimeFilter; |
2377 | } |
2378 | |
2379 | // ================ protected methods ================ // |
2380 | |
2381 | bool KCoreDirListerPrivate::matchesFilter(const KFileItem &item) const |
2382 | { |
2383 | Q_ASSERT(!item.isNull()); |
2384 | |
2385 | if (item.text() == QLatin1String(".." )) { |
2386 | return false; |
2387 | } |
2388 | |
2389 | if (!settings.isShowingDotFiles && item.isHidden()) { |
2390 | return false; |
2391 | } |
2392 | |
2393 | if (item.isDir() || settings.lstFilters.isEmpty()) { |
2394 | return true; |
2395 | } |
2396 | |
2397 | return std::any_of(first: settings.lstFilters.cbegin(), last: settings.lstFilters.cend(), pred: [&item](const QRegularExpression &filter) { |
2398 | return filter.match(subject: item.text()).hasMatch(); |
2399 | }); |
2400 | } |
2401 | |
2402 | bool KCoreDirListerPrivate::matchesMimeFilter(const KFileItem &item) const |
2403 | { |
2404 | Q_ASSERT(!item.isNull()); |
2405 | // Don't lose time determining the MIME type if there is no filter |
2406 | if (settings.mimeFilter.isEmpty() && settings.mimeExcludeFilter.isEmpty()) { |
2407 | return true; |
2408 | } |
2409 | return doMimeFilter(mimeType: item.mimetype(), filters: settings.mimeFilter) && doMimeExcludeFilter(mimeExclude: item.mimetype(), filters: settings.mimeExcludeFilter); |
2410 | } |
2411 | |
2412 | bool KCoreDirListerPrivate::doMimeFilter(const QString &mime, const QStringList &filters) const |
2413 | { |
2414 | if (filters.isEmpty()) { |
2415 | return true; |
2416 | } |
2417 | |
2418 | QMimeDatabase db; |
2419 | const QMimeType mimeptr = db.mimeTypeForName(nameOrAlias: mime); |
2420 | if (!mimeptr.isValid()) { |
2421 | return false; |
2422 | } |
2423 | |
2424 | qCDebug(KIO_CORE_DIRLISTER) << "doMimeFilter: investigating:" << mimeptr.name(); |
2425 | return std::any_of(first: filters.cbegin(), last: filters.cend(), pred: [&mimeptr](const QString &filter) { |
2426 | return mimeptr.inherits(mimeTypeName: filter); |
2427 | }); |
2428 | } |
2429 | |
2430 | bool KCoreDirListerPrivate::doMimeExcludeFilter(const QString &mime, const QStringList &filters) const |
2431 | { |
2432 | return !std::any_of(first: filters.cbegin(), last: filters.cend(), pred: [&mime](const QString &filter) { |
2433 | return mime == filter; |
2434 | }); |
2435 | } |
2436 | |
2437 | // ================= private methods ================= // |
2438 | |
2439 | void KCoreDirListerPrivate::addNewItem(const QUrl &directoryUrl, const KFileItem &item) |
2440 | { |
2441 | if (!isItemVisible(item)) { |
2442 | return; // No reason to continue... bailing out here prevents a MIME type scan. |
2443 | } |
2444 | |
2445 | qCDebug(KIO_CORE_DIRLISTER) << "in" << directoryUrl << "item:" << item.url(); |
2446 | |
2447 | if (matchesMimeFilter(item)) { |
2448 | Q_ASSERT(!item.isNull()); |
2449 | lstNewItems[directoryUrl].append(t: item); // items not filtered |
2450 | } else { |
2451 | Q_ASSERT(!item.isNull()); |
2452 | lstMimeFilteredItems.append(t: item); // only filtered by MIME type |
2453 | } |
2454 | } |
2455 | |
2456 | void KCoreDirListerPrivate::addNewItems(const QUrl &directoryUrl, const QList<KFileItem> &items) |
2457 | { |
2458 | // TODO: make this faster - test if we have a filter at all first |
2459 | // DF: was this profiled? The matchesFoo() functions should be fast, w/o filters... |
2460 | // Of course if there is no filter and we can do a range-insertion instead of a loop, that might be good. |
2461 | for (const auto &item : items) { |
2462 | addNewItem(directoryUrl, item); |
2463 | } |
2464 | } |
2465 | |
2466 | void KCoreDirListerPrivate::addRefreshItem(const QUrl &directoryUrl, const KFileItem &oldItem, const KFileItem &item) |
2467 | { |
2468 | // Refreshing the root item "." of a dirlister |
2469 | if (directoryUrl == item.url()) { |
2470 | lstRefreshItems.append(t: {oldItem, item}); |
2471 | return; |
2472 | } |
2473 | |
2474 | const bool refreshItemWasFiltered = !isItemVisible(item: oldItem) || !matchesMimeFilter(item: oldItem); |
2475 | if (item.exists() && isItemVisible(item) && matchesMimeFilter(item)) { |
2476 | if (refreshItemWasFiltered) { |
2477 | Q_ASSERT(!item.isNull()); |
2478 | lstNewItems[directoryUrl].append(t: item); |
2479 | } else { |
2480 | Q_ASSERT(!item.isNull()); |
2481 | lstRefreshItems.append(t: qMakePair(value1: oldItem, value2: item)); |
2482 | } |
2483 | } else if (!refreshItemWasFiltered) { |
2484 | // notify the user that the MIME type of a file changed that doesn't match |
2485 | // a filter or does match an exclude filter |
2486 | // This also happens when renaming foo to .foo and dot files are hidden (#174721) |
2487 | Q_ASSERT(!oldItem.isNull()); |
2488 | lstRemoveItems.append(t: oldItem); |
2489 | } |
2490 | } |
2491 | |
2492 | void KCoreDirListerPrivate::emitItems() |
2493 | { |
2494 | if (!lstNewItems.empty()) { |
2495 | for (auto it = lstNewItems.cbegin(); it != lstNewItems.cend(); ++it) { |
2496 | const auto &val = it.value(); |
2497 | Q_EMIT q->itemsAdded(directoryUrl: it.key(), items: val); |
2498 | Q_EMIT q->newItems(items: val); // compat |
2499 | } |
2500 | lstNewItems.clear(); |
2501 | } |
2502 | |
2503 | if (!lstMimeFilteredItems.empty()) { |
2504 | Q_EMIT q->itemsFilteredByMime(items: lstMimeFilteredItems); |
2505 | lstMimeFilteredItems.clear(); |
2506 | } |
2507 | |
2508 | if (!lstRefreshItems.empty()) { |
2509 | Q_EMIT q->refreshItems(items: lstRefreshItems); |
2510 | lstRefreshItems.clear(); |
2511 | } |
2512 | |
2513 | if (!lstRemoveItems.empty()) { |
2514 | Q_EMIT q->itemsDeleted(items: lstRemoveItems); |
2515 | lstRemoveItems.clear(); |
2516 | } |
2517 | } |
2518 | |
2519 | bool KCoreDirListerPrivate::isItemVisible(const KFileItem &item) const |
2520 | { |
2521 | // Note that this doesn't include MIME type filters, because |
2522 | // of the itemsFilteredByMime signal. Filtered-by-MIME-type items are |
2523 | // considered "visible", they are just visible via a different signal... |
2524 | return (!settings.dirOnlyMode || item.isDir()) && matchesFilter(item); |
2525 | } |
2526 | |
2527 | void KCoreDirListerPrivate::emitItemsDeleted(const KFileItemList &itemsList) |
2528 | { |
2529 | KFileItemList items; |
2530 | std::copy_if(first: itemsList.cbegin(), last: itemsList.cend(), result: std::back_inserter(x&: items), pred: [this](const KFileItem &item) { |
2531 | return isItemVisible(item) || matchesMimeFilter(item); |
2532 | }); |
2533 | if (!items.isEmpty()) { |
2534 | Q_EMIT q->itemsDeleted(items); |
2535 | } |
2536 | } |
2537 | |
2538 | KCoreDirListerPrivate::KCoreDirListerPrivate(KCoreDirLister *qq) |
2539 | : q(qq) |
2540 | { |
2541 | } |
2542 | |
2543 | // ================ private slots ================ // |
2544 | |
2545 | void KCoreDirListerPrivate::slotInfoMessage(KJob *, const QString &message) |
2546 | { |
2547 | Q_EMIT q->infoMessage(msg: message); |
2548 | } |
2549 | |
2550 | void KCoreDirListerPrivate::slotPercent(KJob *job, unsigned long pcnt) |
2551 | { |
2552 | jobData[static_cast<KIO::ListJob *>(job)].percent = pcnt; |
2553 | |
2554 | int result = 0; |
2555 | |
2556 | KIO::filesize_t size = 0; |
2557 | |
2558 | for (auto dataIt = jobData.cbegin(); dataIt != jobData.cend(); ++dataIt) { |
2559 | const JobData &data = dataIt.value(); |
2560 | result += data.percent * data.totalSize; |
2561 | size += data.totalSize; |
2562 | } |
2563 | |
2564 | if (size != 0) { |
2565 | result /= size; |
2566 | } else { |
2567 | result = 100; |
2568 | } |
2569 | Q_EMIT q->percent(percent: result); |
2570 | } |
2571 | |
2572 | void KCoreDirListerPrivate::slotTotalSize(KJob *job, qulonglong size) |
2573 | { |
2574 | jobData[static_cast<KIO::ListJob *>(job)].totalSize = size; |
2575 | |
2576 | KIO::filesize_t result = 0; |
2577 | for (auto dataIt = jobData.cbegin(); dataIt != jobData.cend(); ++dataIt) { |
2578 | result += dataIt.value().totalSize; |
2579 | } |
2580 | |
2581 | Q_EMIT q->totalSize(size: result); |
2582 | } |
2583 | |
2584 | void KCoreDirListerPrivate::slotProcessedSize(KJob *job, qulonglong size) |
2585 | { |
2586 | jobData[static_cast<KIO::ListJob *>(job)].processedSize = size; |
2587 | |
2588 | KIO::filesize_t result = 0; |
2589 | for (auto dataIt = jobData.cbegin(); dataIt != jobData.cend(); ++dataIt) { |
2590 | result += dataIt.value().processedSize; |
2591 | } |
2592 | |
2593 | Q_EMIT q->processedSize(size: result); |
2594 | } |
2595 | |
2596 | void KCoreDirListerPrivate::slotSpeed(KJob *job, unsigned long spd) |
2597 | { |
2598 | jobData[static_cast<KIO::ListJob *>(job)].speed = spd; |
2599 | |
2600 | int result = 0; |
2601 | for (auto dataIt = jobData.cbegin(); dataIt != jobData.cend(); ++dataIt) { |
2602 | result += dataIt.value().speed; |
2603 | } |
2604 | |
2605 | Q_EMIT q->speed(bytes_per_second: result); |
2606 | } |
2607 | |
2608 | uint KCoreDirListerPrivate::numJobs() |
2609 | { |
2610 | #ifdef DEBUG_CACHE |
2611 | // This code helps detecting stale entries in the jobData map. |
2612 | qCDebug(KIO_CORE_DIRLISTER) << q << "numJobs:" << jobData.count(); |
2613 | for (auto it = jobData.cbegin(); it != jobData.cend(); ++it) { |
2614 | qCDebug(KIO_CORE_DIRLISTER) << (void *)it.key(); |
2615 | qCDebug(KIO_CORE_DIRLISTER) << it.key(); |
2616 | } |
2617 | #endif |
2618 | |
2619 | return jobData.count(); |
2620 | } |
2621 | |
2622 | void KCoreDirListerPrivate::jobDone(KIO::ListJob *job) |
2623 | { |
2624 | jobData.remove(key: job); |
2625 | } |
2626 | |
2627 | void KCoreDirLister::jobStarted(KIO::ListJob *job) |
2628 | { |
2629 | KCoreDirListerPrivate::JobData data; |
2630 | data.speed = 0; |
2631 | data.percent = 0; |
2632 | data.processedSize = 0; |
2633 | data.totalSize = 0; |
2634 | |
2635 | d->jobData.insert(key: job, value: data); |
2636 | d->complete = false; |
2637 | } |
2638 | |
2639 | void KCoreDirListerPrivate::connectJob(KIO::ListJob *job) |
2640 | { |
2641 | q->connect(sender: job, signal: &KJob::infoMessage, context: q, slot: [this](KJob *job, const QString &plain) { |
2642 | slotInfoMessage(job, message: plain); |
2643 | }); |
2644 | |
2645 | q->connect(sender: job, signal: &KJob::percentChanged, context: q, slot: [this](KJob *job, ulong _percent) { |
2646 | slotPercent(job, pcnt: _percent); |
2647 | }); |
2648 | |
2649 | q->connect(sender: job, signal: &KJob::totalSize, context: q, slot: [this](KJob *job, qulonglong _size) { |
2650 | slotTotalSize(job, size: _size); |
2651 | }); |
2652 | q->connect(sender: job, signal: &KJob::processedSize, context: q, slot: [this](KJob *job, qulonglong _psize) { |
2653 | slotProcessedSize(job, size: _psize); |
2654 | }); |
2655 | q->connect(sender: job, signal: &KJob::speed, context: q, slot: [this](KJob *job, qulonglong _speed) { |
2656 | slotSpeed(job, spd: _speed); |
2657 | }); |
2658 | } |
2659 | |
2660 | KFileItemList KCoreDirLister::items(WhichItems which) const |
2661 | { |
2662 | return itemsForDir(dirUrl: url(), which); |
2663 | } |
2664 | |
2665 | KFileItemList KCoreDirLister::itemsForDir(const QUrl &dir, WhichItems which) const |
2666 | { |
2667 | QList<KFileItem> *allItems = s_kDirListerCache.localData().itemsForDir(dir); |
2668 | KFileItemList result; |
2669 | if (!allItems) { |
2670 | return result; |
2671 | } |
2672 | |
2673 | if (which == AllItems) { |
2674 | return KFileItemList(*allItems); |
2675 | } else { // only items passing the filters |
2676 | std::copy_if(first: allItems->cbegin(), last: allItems->cend(), result: std::back_inserter(x&: result), pred: [this](const KFileItem &item) { |
2677 | return d->isItemVisible(item) && d->matchesMimeFilter(item); |
2678 | }); |
2679 | } |
2680 | return result; |
2681 | } |
2682 | |
2683 | bool KCoreDirLister::delayedMimeTypes() const |
2684 | { |
2685 | return d->delayedMimeTypes; |
2686 | } |
2687 | |
2688 | void KCoreDirLister::setDelayedMimeTypes(bool delayedMimeTypes) |
2689 | { |
2690 | d->delayedMimeTypes = delayedMimeTypes; |
2691 | } |
2692 | |
2693 | // called by KCoreDirListerCache::slotRedirection |
2694 | void KCoreDirListerPrivate::redirect(const QUrl &oldUrl, const QUrl &newUrl, bool keepItems) |
2695 | { |
2696 | if (url.matches(url: oldUrl, options: QUrl::StripTrailingSlash)) { |
2697 | if (!keepItems) { |
2698 | rootFileItem = KFileItem(); |
2699 | } else { |
2700 | rootFileItem.setUrl(newUrl); |
2701 | } |
2702 | url = newUrl; |
2703 | } |
2704 | |
2705 | const int idx = lstDirs.indexOf(t: oldUrl); |
2706 | if (idx == -1) { |
2707 | qCWarning(KIO_CORE) << "Unexpected redirection from" << oldUrl << "to" << newUrl << "but this dirlister is currently listing/holding" << lstDirs; |
2708 | } else { |
2709 | lstDirs[idx] = newUrl; |
2710 | } |
2711 | |
2712 | if (lstDirs.count() == 1) { |
2713 | if (!keepItems) { |
2714 | Q_EMIT q->clear(); |
2715 | } |
2716 | } else { |
2717 | if (!keepItems) { |
2718 | Q_EMIT q->clearDir(dirUrl: oldUrl); |
2719 | } |
2720 | } |
2721 | Q_EMIT q->redirection(oldUrl, newUrl); |
2722 | } |
2723 | |
2724 | void KCoreDirListerCacheDirectoryData::moveListersWithoutCachedItemsJob(const QUrl &url) |
2725 | { |
2726 | // Move dirlisters from listersCurrentlyListing to listersCurrentlyHolding, |
2727 | // but not those that are still waiting on a CachedItemsJob... |
2728 | // Unit-testing note: |
2729 | // Run kdirmodeltest in valgrind to hit the case where an update |
2730 | // is triggered while a lister has a CachedItemsJob (different timing...) |
2731 | QMutableListIterator<KCoreDirLister *> lister_it(listersCurrentlyListing); |
2732 | while (lister_it.hasNext()) { |
2733 | KCoreDirLister *kdl = lister_it.next(); |
2734 | if (!kdl->d->cachedItemsJobForUrl(url)) { |
2735 | // OK, move this lister from "currently listing" to "currently holding". |
2736 | |
2737 | // Huh? The KCoreDirLister was present twice in listersCurrentlyListing, or was in both lists? |
2738 | Q_ASSERT(!listersCurrentlyHolding.contains(kdl)); |
2739 | if (!listersCurrentlyHolding.contains(t: kdl)) { |
2740 | listersCurrentlyHolding.append(t: kdl); |
2741 | } |
2742 | lister_it.remove(); |
2743 | } else { |
2744 | qCDebug(KIO_CORE_DIRLISTER) << "Not moving" << kdl << "to listersCurrentlyHolding because it still has job" << kdl->d->m_cachedItemsJobs; |
2745 | } |
2746 | } |
2747 | } |
2748 | |
2749 | KFileItem KCoreDirLister::cachedItemForUrl(const QUrl &url) |
2750 | { |
2751 | if (s_kDirListerCache.hasLocalData()) { |
2752 | return s_kDirListerCache.localData().itemForUrl(url); |
2753 | } else { |
2754 | return {}; |
2755 | } |
2756 | } |
2757 | |
2758 | bool KCoreDirLister::autoErrorHandlingEnabled() const |
2759 | { |
2760 | return d->m_autoErrorHandling; |
2761 | } |
2762 | |
2763 | void KCoreDirLister::setAutoErrorHandlingEnabled(bool enable) |
2764 | { |
2765 | d->m_autoErrorHandling = enable; |
2766 | } |
2767 | |
2768 | KCoreDirListerCache::CacheHiddenFile *KCoreDirListerCache::cachedDotHiddenForDir(const QString &dir) |
2769 | { |
2770 | const QString path = dir + QLatin1String("/.hidden" ); |
2771 | QFile dotHiddenFile(path); |
2772 | |
2773 | if (dotHiddenFile.exists()) { |
2774 | const QDateTime mtime = QFileInfo(dotHiddenFile).lastModified(); |
2775 | CacheHiddenFile *cachedDotHiddenFile = m_cacheHiddenFiles.object(key: path); |
2776 | |
2777 | if (cachedDotHiddenFile && mtime <= cachedDotHiddenFile->mtime) { |
2778 | // ".hidden" is in cache and still valid (the file was not modified since then), |
2779 | // so return it |
2780 | return cachedDotHiddenFile; |
2781 | } else { |
2782 | // read the ".hidden" file, then cache it and return it |
2783 | if (dotHiddenFile.open(flags: QIODevice::ReadOnly | QIODevice::Text)) { |
2784 | std::set<QString> filesToHide; |
2785 | QTextStream stream(&dotHiddenFile); |
2786 | while (!stream.atEnd()) { |
2787 | QString name = stream.readLine(); |
2788 | if (!name.isEmpty()) { |
2789 | filesToHide.insert(x: name); |
2790 | } |
2791 | } |
2792 | |
2793 | m_cacheHiddenFiles.insert(key: path, object: new CacheHiddenFile(mtime, std::move(filesToHide))); |
2794 | |
2795 | return m_cacheHiddenFiles.object(key: path); |
2796 | } |
2797 | } |
2798 | } |
2799 | |
2800 | return {}; |
2801 | } |
2802 | |
2803 | #include "moc_kcoredirlister.cpp" |
2804 | #include "moc_kcoredirlister_p.cpp" |
2805 | |