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

KIO

  • kio
  • kio
deletejob.cpp
Go to the documentation of this file.
1 /* This file is part of the KDE libraries
2  Copyright 2000 Stephan Kulow <coolo@kde.org>
3  Copyright 2000-2009 David Faure <faure@kde.org>
4  Copyright 2000 Waldo Bastian <bastian@kde.org>
5 
6  This library is free software; you can redistribute it and/or
7  modify it under the terms of the GNU Library General Public
8  License as published by the Free Software Foundation; either
9  version 2 of the License, or (at your option) any later version.
10 
11  This library is distributed in the hope that it will be useful,
12  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14  Library General Public License for more details.
15 
16  You should have received a copy of the GNU Library General Public License
17  along with this library; see the file COPYING.LIB. If not, write to
18  the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19  Boston, MA 02110-1301, USA.
20 */
21 
22 #include "deletejob.h"
23 
24 #include "kdirlister.h"
25 #include "scheduler.h"
26 #include "kdirwatch.h"
27 #include "kprotocolmanager.h"
28 #include "jobuidelegate.h"
29 #include "clipboardupdater_p.h"
30 #include <kdirnotify.h>
31 
32 #include <klocale.h>
33 #include <kdebug.h>
34 #include <kde_file.h>
35 
36 #include <QtCore/QTimer>
37 #include <QtCore/QFile>
38 #include <QPointer>
39 
40 #include "job_p.h"
41 
42 extern bool kio_resolve_local_urls; // from copyjob.cpp, abused here to save a symbol.
43 
44 static bool isHttpProtocol(const QString& protocol)
45 {
46  return (protocol.startsWith(QLatin1String("webdav"), Qt::CaseInsensitive) ||
47  protocol.startsWith(QLatin1String("http"), Qt::CaseInsensitive));
48 }
49 
50 namespace KIO
51 {
52  enum DeleteJobState {
53  DELETEJOB_STATE_STATING,
54  DELETEJOB_STATE_DELETING_FILES,
55  DELETEJOB_STATE_DELETING_DIRS
56  };
57 
58  /*
59  static const char* const s_states[] = {
60  "DELETEJOB_STATE_STATING",
61  "DELETEJOB_STATE_DELETING_FILES",
62  "DELETEJOB_STATE_DELETING_DIRS"
63  };
64  */
65 
66  class DeleteJobPrivate: public KIO::JobPrivate
67  {
68  public:
69  DeleteJobPrivate(const KUrl::List& src)
70  : state( DELETEJOB_STATE_STATING )
71  , m_processedFiles( 0 )
72  , m_processedDirs( 0 )
73  , m_totalFilesDirs( 0 )
74  , m_srcList( src )
75  , m_currentStat( m_srcList.begin() )
76  , m_reportTimer( 0 )
77  {
78  }
79  DeleteJobState state;
80  int m_processedFiles;
81  int m_processedDirs;
82  int m_totalFilesDirs;
83  KUrl m_currentURL;
84  KUrl::List files;
85  KUrl::List symlinks;
86  KUrl::List dirs;
87  KUrl::List m_srcList;
88  KUrl::List::iterator m_currentStat;
89  QSet<QString> m_parentDirs;
90  QTimer *m_reportTimer;
91 
92  void statNextSrc();
93  void currentSourceStated(bool isDir, bool isLink);
94  void finishedStatPhase();
95  void deleteNextFile();
96  void deleteNextDir();
97  void slotReport();
98  void slotStart();
99  void slotEntries( KIO::Job*, const KIO::UDSEntryList& list );
100 
101  Q_DECLARE_PUBLIC(DeleteJob)
102 
103  static inline DeleteJob *newJob(const KUrl::List &src, JobFlags flags)
104  {
105  DeleteJob *job = new DeleteJob(*new DeleteJobPrivate(src));
106  job->setUiDelegate(new JobUiDelegate);
107  if (!(flags & HideProgressInfo))
108  KIO::getJobTracker()->registerJob(job);
109  return job;
110  }
111  };
112 
113 } // namespace KIO
114 
115 using namespace KIO;
116 
117 DeleteJob::DeleteJob(DeleteJobPrivate &dd)
118  : Job(dd)
119 {
120  d_func()->m_reportTimer = new QTimer(this);
121  connect(d_func()->m_reportTimer,SIGNAL(timeout()),this,SLOT(slotReport()));
122  //this will update the report dialog with 5 Hz, I think this is fast enough, aleXXX
123  d_func()->m_reportTimer->start( 200 );
124 
125  QTimer::singleShot(0, this, SLOT(slotStart()));
126 }
127 
128 DeleteJob::~DeleteJob()
129 {
130 }
131 
132 KUrl::List DeleteJob::urls() const
133 {
134  return d_func()->m_srcList;
135 }
136 
137 void DeleteJobPrivate::slotStart()
138 {
139  statNextSrc();
140 }
141 
142 void DeleteJobPrivate::slotReport()
143 {
144  Q_Q(DeleteJob);
145  emit q->deleting( q, m_currentURL );
146 
147  // TODO: maybe we could skip everything else when (flags & HideProgressInfo) ?
148  JobPrivate::emitDeleting( q, m_currentURL);
149 
150  switch( state ) {
151  case DELETEJOB_STATE_STATING:
152  q->setTotalAmount(KJob::Files, files.count());
153  q->setTotalAmount(KJob::Directories, dirs.count());
154  break;
155  case DELETEJOB_STATE_DELETING_DIRS:
156  q->setProcessedAmount(KJob::Directories, m_processedDirs);
157  q->emitPercent( m_processedFiles + m_processedDirs, m_totalFilesDirs );
158  break;
159  case DELETEJOB_STATE_DELETING_FILES:
160  q->setProcessedAmount(KJob::Files, m_processedFiles);
161  q->emitPercent( m_processedFiles, m_totalFilesDirs );
162  break;
163  }
164 }
165 
166 
167 void DeleteJobPrivate::slotEntries(KIO::Job* job, const UDSEntryList& list)
168 {
169  UDSEntryList::ConstIterator it = list.begin();
170  const UDSEntryList::ConstIterator end = list.end();
171  for (; it != end; ++it)
172  {
173  const UDSEntry& entry = *it;
174  const QString displayName = entry.stringValue( KIO::UDSEntry::UDS_NAME );
175 
176  Q_ASSERT(!displayName.isEmpty());
177  if (displayName != ".." && displayName != ".")
178  {
179  KUrl url;
180  const QString urlStr = entry.stringValue( KIO::UDSEntry::UDS_URL );
181  if ( !urlStr.isEmpty() )
182  url = urlStr;
183  else {
184  url = static_cast<SimpleJob *>(job)->url(); // assumed to be a dir
185  url.addPath( displayName );
186  }
187 
188  //kDebug(7007) << displayName << "(" << url << ")";
189  if ( entry.isLink() )
190  symlinks.append( url );
191  else if ( entry.isDir() )
192  dirs.append( url );
193  else
194  files.append( url );
195  }
196  }
197 }
198 
199 
200 void DeleteJobPrivate::statNextSrc()
201 {
202  Q_Q(DeleteJob);
203  //kDebug(7007);
204  if (m_currentStat != m_srcList.end()) {
205  m_currentURL = (*m_currentStat);
206 
207  // if the file system doesn't support deleting, we do not even stat
208  if (!KProtocolManager::supportsDeleting(m_currentURL)) {
209  QPointer<DeleteJob> that = q;
210  ++m_currentStat;
211  emit q->warning( q, buildErrorString(ERR_CANNOT_DELETE, m_currentURL.prettyUrl()) );
212  if (that)
213  statNextSrc();
214  return;
215  }
216  // Stat it
217  state = DELETEJOB_STATE_STATING;
218 
219  // Fast path for KFileItems in directory views
220  while(m_currentStat != m_srcList.end()) {
221  m_currentURL = (*m_currentStat);
222  const KFileItem cachedItem = KDirLister::cachedItemForUrl(m_currentURL);
223  if (cachedItem.isNull())
224  break;
225  //kDebug(7007) << "Found cached info about" << m_currentURL << "isDir=" << cachedItem.isDir() << "isLink=" << cachedItem.isLink();
226  currentSourceStated(cachedItem.isDir(), cachedItem.isLink());
227  ++m_currentStat;
228  }
229 
230  // Hook for unit test to disable the fast path.
231  if (!kio_resolve_local_urls) {
232 
233  // Fast path for local files
234  // (using a loop, instead of a huge recursion)
235  while(m_currentStat != m_srcList.end() && (*m_currentStat).isLocalFile()) {
236  m_currentURL = (*m_currentStat);
237  QFileInfo fileInfo(m_currentURL.toLocalFile());
238  currentSourceStated(fileInfo.isDir(), fileInfo.isSymLink());
239  ++m_currentStat;
240  }
241  }
242  if (m_currentStat == m_srcList.end()) {
243  // Done, jump to the last else of this method
244  statNextSrc();
245  } else {
246  KIO::SimpleJob * job = KIO::stat( m_currentURL, StatJob::SourceSide, 0, KIO::HideProgressInfo );
247  Scheduler::setJobPriority(job, 1);
248  //kDebug(7007) << "stat'ing" << m_currentURL;
249  q->addSubjob(job);
250  }
251  } else {
252  if (!q->hasSubjobs()) // don't go there yet if we're still listing some subdirs
253  finishedStatPhase();
254  }
255 }
256 
257 void DeleteJobPrivate::finishedStatPhase()
258 {
259  m_totalFilesDirs = files.count() + symlinks.count() + dirs.count();
260  slotReport();
261  // Now we know which dirs hold the files we're going to delete.
262  // To speed things up and prevent double-notification, we disable KDirWatch
263  // on those dirs temporarily (using KDirWatch::self, that's the instance
264  // used by e.g. kdirlister).
265  const QSet<QString>::const_iterator itEnd = m_parentDirs.constEnd();
266  for ( QSet<QString>::const_iterator it = m_parentDirs.constBegin() ; it != itEnd ; ++it )
267  KDirWatch::self()->stopDirScan( *it );
268  state = DELETEJOB_STATE_DELETING_FILES;
269  deleteNextFile();
270 }
271 
272 void DeleteJobPrivate::deleteNextFile()
273 {
274  Q_Q(DeleteJob);
275  //kDebug(7007);
276  if ( !files.isEmpty() || !symlinks.isEmpty() )
277  {
278  SimpleJob *job;
279  do {
280  // Take first file to delete out of list
281  KUrl::List::iterator it = files.begin();
282  bool isLink = false;
283  if ( it == files.end() ) // No more files
284  {
285  it = symlinks.begin(); // Pick up a symlink to delete
286  isLink = true;
287  }
288  // Normal deletion
289  // If local file, try do it directly
290 #ifdef Q_WS_WIN
291  if ( (*it).isLocalFile() && DeleteFileW( (LPCWSTR)(*it).toLocalFile().utf16() ) != 0 ) {
292 #else
293  if ( (*it).isLocalFile() && unlink( QFile::encodeName((*it).toLocalFile()) ) == 0 ) {
294 #endif
295  //kdDebug(7007) << "DeleteJob deleted" << (*it).toLocalFile();
296  job = 0;
297  m_processedFiles++;
298  if ( m_processedFiles % 300 == 1 || m_totalFilesDirs < 300) { // update progress info every 300 files
299  m_currentURL = *it;
300  slotReport();
301  }
302  } else
303  { // if remote - or if unlink() failed (we'll use the job's error handling in that case)
304  //kDebug(7007) << "calling file_delete on" << *it;
305  if (isHttpProtocol(it->protocol()))
306  job = KIO::http_delete( *it, KIO::HideProgressInfo );
307  else
308  job = KIO::file_delete( *it, KIO::HideProgressInfo );
309  Scheduler::setJobPriority(job, 1);
310  m_currentURL=(*it);
311  }
312  if ( isLink )
313  symlinks.erase(it);
314  else
315  files.erase(it);
316  if ( job ) {
317  q->addSubjob(job);
318  return;
319  }
320  // loop only if direct deletion worked (job=0) and there is something else to delete
321  } while (!job && (!files.isEmpty() || !symlinks.isEmpty()));
322  }
323  state = DELETEJOB_STATE_DELETING_DIRS;
324  deleteNextDir();
325 }
326 
327 void DeleteJobPrivate::deleteNextDir()
328 {
329  Q_Q(DeleteJob);
330  if ( !dirs.isEmpty() ) // some dirs to delete ?
331  {
332  do {
333  // Take first dir to delete out of list - last ones first !
334  KUrl::List::iterator it = --dirs.end();
335  // If local dir, try to rmdir it directly
336 #ifdef Q_WS_WIN
337  if ( (*it).isLocalFile() && RemoveDirectoryW( (LPCWSTR)(*it).toLocalFile().utf16() ) != 0 ) {
338 #else
339  if ( (*it).isLocalFile() && ::rmdir( QFile::encodeName((*it).toLocalFile()) ) == 0 ) {
340 #endif
341  m_processedDirs++;
342  if ( m_processedDirs % 100 == 1 ) { // update progress info every 100 dirs
343  m_currentURL = *it;
344  slotReport();
345  }
346  } else {
347  // Call rmdir - works for kioslaves with canDeleteRecursive too,
348  // CMD_DEL will trigger the recursive deletion in the slave.
349  SimpleJob* job = KIO::rmdir( *it );
350  job->addMetaData(QString::fromLatin1("recurse"), "true");
351  Scheduler::setJobPriority(job, 1);
352  dirs.erase(it);
353  q->addSubjob( job );
354  return;
355  }
356  dirs.erase(it);
357  } while ( !dirs.isEmpty() );
358  }
359 
360  // Re-enable watching on the dirs that held the deleted files
361  const QSet<QString>::const_iterator itEnd = m_parentDirs.constEnd();
362  for (QSet<QString>::const_iterator it = m_parentDirs.constBegin() ; it != itEnd ; ++it) {
363  KDirWatch::self()->restartDirScan( *it );
364  }
365 
366  // Finished - tell the world
367  if ( !m_srcList.isEmpty() )
368  {
369  //kDebug(7007) << "KDirNotify'ing FilesRemoved " << m_srcList.toStringList();
370  org::kde::KDirNotify::emitFilesRemoved( m_srcList.toStringList() );
371  }
372  if (m_reportTimer!=0)
373  m_reportTimer->stop();
374  q->emitResult();
375 }
376 
377 void DeleteJobPrivate::currentSourceStated(bool isDir, bool isLink)
378 {
379  Q_Q(DeleteJob);
380  const KUrl url = (*m_currentStat);
381  if (isDir && !isLink) {
382  // Add toplevel dir in list of dirs
383  dirs.append( url );
384  if (url.isLocalFile()) {
385  // We are about to delete this dir, no need to watch it
386  // Maybe we should ask kdirwatch to remove all watches recursively?
387  // But then there would be no feedback (things disappearing progressively) during huge deletions
388  KDirWatch::self()->stopDirScan(url.toLocalFile(KUrl::RemoveTrailingSlash));
389  }
390  if (!KProtocolManager::canDeleteRecursive(url)) {
391  //kDebug(7007) << url << "is a directory, let's list it";
392  ListJob *newjob = KIO::listRecursive(url, KIO::HideProgressInfo);
393  newjob->addMetaData("details", "0");
394  newjob->setUnrestricted(true); // No KIOSK restrictions
395  Scheduler::setJobPriority(newjob, 1);
396  QObject::connect(newjob, SIGNAL(entries(KIO::Job*,KIO::UDSEntryList)),
397  q, SLOT(slotEntries(KIO::Job*,KIO::UDSEntryList)));
398  q->addSubjob(newjob);
399  // Note that this listing job will happen in parallel with other stat jobs.
400  }
401  } else {
402  if (isLink) {
403  //kDebug(7007) << "Target is a symlink";
404  symlinks.append(url);
405  } else {
406  //kDebug(7007) << "Target is a file";
407  files.append(url);
408  }
409  }
410  if (url.isLocalFile()) {
411  const QString parentDir = url.directory(KUrl::IgnoreTrailingSlash);
412  m_parentDirs.insert(parentDir);
413  }
414 }
415 
416 void DeleteJob::slotResult( KJob *job )
417 {
418  Q_D(DeleteJob);
419  switch ( d->state )
420  {
421  case DELETEJOB_STATE_STATING:
422  removeSubjob( job );
423 
424  // Was this a stat job or a list job? We do both in parallel.
425  if (StatJob* statJob = qobject_cast<StatJob*>(job)) {
426  // Was there an error while stating ?
427  if (job->error()) {
428  // Probably : doesn't exist
429  Job::slotResult(job); // will set the error and emit result(this)
430  return;
431  }
432 
433  const UDSEntry entry = statJob->statResult();
434  // Is it a file or a dir ?
435  const bool isLink = entry.isLink();
436  const bool isDir = entry.isDir();
437  d->currentSourceStated(isDir, isLink);
438 
439  ++d->m_currentStat;
440  d->statNextSrc();
441  } else {
442  if (job->error()) {
443  // Try deleting nonetheless, it may be empty (and non-listable)
444  }
445  if (!hasSubjobs())
446  d->finishedStatPhase();
447  }
448  break;
449  case DELETEJOB_STATE_DELETING_FILES:
450  // Propagate the subjob's metadata (a SimpleJob) to the real DeleteJob
451  // FIXME: setMetaData() in the KIO API only allows access to outgoing metadata,
452  // but we need to alter the incoming one
453  d->m_incomingMetaData = dynamic_cast<KIO::Job*>(job)->metaData();
454 
455  if ( job->error() )
456  {
457  Job::slotResult( job ); // will set the error and emit result(this)
458  return;
459  }
460  removeSubjob( job );
461  Q_ASSERT( !hasSubjobs() );
462  d->m_processedFiles++;
463 
464  d->deleteNextFile();
465  break;
466  case DELETEJOB_STATE_DELETING_DIRS:
467  if ( job->error() )
468  {
469  Job::slotResult( job ); // will set the error and emit result(this)
470  return;
471  }
472  removeSubjob( job );
473  Q_ASSERT( !hasSubjobs() );
474  d->m_processedDirs++;
475  //emit processedAmount( this, KJob::Directories, d->m_processedDirs );
476  //emitPercent( d->m_processedFiles + d->m_processedDirs, d->m_totalFilesDirs );
477 
478  d->deleteNextDir();
479  break;
480  default:
481  Q_ASSERT(0);
482  }
483 }
484 
485 DeleteJob *KIO::del( const KUrl& src, JobFlags flags )
486 {
487  KUrl::List srcList;
488  srcList.append( src );
489  DeleteJob* job = DeleteJobPrivate::newJob(srcList, flags);
490  ClipboardUpdater::create(job, ClipboardUpdater::RemoveContent);
491  return job;
492 }
493 
494 DeleteJob *KIO::del( const KUrl::List& src, JobFlags flags )
495 {
496  DeleteJob* job = DeleteJobPrivate::newJob(src, flags);
497  ClipboardUpdater::create(job, ClipboardUpdater::RemoveContent);
498  return job;
499 }
500 
501 #include "deletejob.moc"
This file is part of the KDE documentation.
Documentation copyright © 1996-2014 The KDE developers.
Generated on Tue Sep 23 2014 09:58:50 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.11.5 API Reference

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

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