• Skip to content
  • Skip to link menu
  • KDE API Reference
  • kdelibs-4.8.5 API Reference
  • KDE Home
  • Contact Us
 

KIO

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

KDE's Doxygen guidelines are available online.

KIO

Skip menu "KIO"
  • Main Page
  • Namespace List
  • Namespace Members
  • Alphabetical List
  • Class List
  • Class Hierarchy
  • Class Members
  • File List
  • File Members
  • Related Pages

kdelibs-4.8.5 API Reference

Skip menu "kdelibs-4.8.5 API Reference"
  • DNSSD
  • Interfaces
  •   KHexEdit
  •   KMediaPlayer
  •   KSpeech
  •   KTextEditor
  • kconf_update
  • KDE3Support
  •   KUnitTest
  • KDECore
  • KDED
  • KDEsu
  • KDEUI
  • KDEWebKit
  • KDocTools
  • KFile
  • KHTML
  • KImgIO
  • KInit
  • kio
  • KIOSlave
  • KJS
  •   KJS-API
  •   WTF
  • kjsembed
  • KNewStuff
  • KParts
  • KPty
  • Kross
  • KUnitConversion
  • KUtils
  • Nepomuk
  • Plasma
  • Solid
  • Sonnet
  • ThreadWeaver
Report problems with this website to our bug tracking system.
Contact the specific authors with questions and comments about the page contents.

KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal