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

Skip menu "kdelibs-4.10.2 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