KIO
deletejob.cpp
Go to the documentation of this file.
00001 /* This file is part of the KDE libraries 00002 Copyright 2000 Stephan Kulow <coolo@kde.org> 00003 Copyright 2000-2009 David Faure <faure@kde.org> 00004 Copyright 2000 Waldo Bastian <bastian@kde.org> 00005 00006 This library is free software; you can redistribute it and/or 00007 modify it under the terms of the GNU Library General Public 00008 License as published by the Free Software Foundation; either 00009 version 2 of the License, or (at your option) any later version. 00010 00011 This library is distributed in the hope that it will be useful, 00012 but WITHOUT ANY WARRANTY; without even the implied warranty of 00013 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00014 Library General Public License for more details. 00015 00016 You should have received a copy of the GNU Library General Public License 00017 along with this library; see the file COPYING.LIB. If not, write to 00018 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 00019 Boston, MA 02110-1301, USA. 00020 */ 00021 00022 #include "deletejob.h" 00023 00024 #include "kdirlister.h" 00025 #include "scheduler.h" 00026 #include "kdirwatch.h" 00027 #include "kprotocolmanager.h" 00028 #include "jobuidelegate.h" 00029 #include <kdirnotify.h> 00030 00031 #include <klocale.h> 00032 #include <kdebug.h> 00033 #include <kde_file.h> 00034 00035 #include <QtCore/QTimer> 00036 #include <QtCore/QFile> 00037 #include <QPointer> 00038 00039 #include "job_p.h" 00040 00041 extern bool kio_resolve_local_urls; // from copyjob.cpp, abused here to save a symbol. 00042 00043 static bool isHttpProtocol(const QString& protocol) 00044 { 00045 return (protocol.startsWith(QLatin1String("webdav"), Qt::CaseInsensitive) || 00046 protocol.startsWith(QLatin1String("http"), Qt::CaseInsensitive)); 00047 } 00048 00049 namespace KIO 00050 { 00051 enum DeleteJobState { 00052 DELETEJOB_STATE_STATING, 00053 DELETEJOB_STATE_DELETING_FILES, 00054 DELETEJOB_STATE_DELETING_DIRS 00055 }; 00056 00057 /* 00058 static const char* const s_states[] = { 00059 "DELETEJOB_STATE_STATING", 00060 "DELETEJOB_STATE_DELETING_FILES", 00061 "DELETEJOB_STATE_DELETING_DIRS" 00062 }; 00063 */ 00064 00065 class DeleteJobPrivate: public KIO::JobPrivate 00066 { 00067 public: 00068 DeleteJobPrivate(const KUrl::List& src) 00069 : state( DELETEJOB_STATE_STATING ) 00070 , m_processedFiles( 0 ) 00071 , m_processedDirs( 0 ) 00072 , m_totalFilesDirs( 0 ) 00073 , m_srcList( src ) 00074 , m_currentStat( m_srcList.begin() ) 00075 , m_reportTimer( 0 ) 00076 { 00077 } 00078 DeleteJobState state; 00079 int m_processedFiles; 00080 int m_processedDirs; 00081 int m_totalFilesDirs; 00082 KUrl m_currentURL; 00083 KUrl::List files; 00084 KUrl::List symlinks; 00085 KUrl::List dirs; 00086 KUrl::List m_srcList; 00087 KUrl::List::iterator m_currentStat; 00088 QSet<QString> m_parentDirs; 00089 QTimer *m_reportTimer; 00090 00091 void statNextSrc(); 00092 void currentSourceStated(bool isDir, bool isLink); 00093 void finishedStatPhase(); 00094 void deleteNextFile(); 00095 void deleteNextDir(); 00096 void slotReport(); 00097 void slotStart(); 00098 void slotEntries( KIO::Job*, const KIO::UDSEntryList& list ); 00099 00100 Q_DECLARE_PUBLIC(DeleteJob) 00101 00102 static inline DeleteJob *newJob(const KUrl::List &src, JobFlags flags) 00103 { 00104 DeleteJob *job = new DeleteJob(*new DeleteJobPrivate(src)); 00105 job->setUiDelegate(new JobUiDelegate); 00106 if (!(flags & HideProgressInfo)) 00107 KIO::getJobTracker()->registerJob(job); 00108 return job; 00109 } 00110 }; 00111 00112 } // namespace KIO 00113 00114 using namespace KIO; 00115 00116 DeleteJob::DeleteJob(DeleteJobPrivate &dd) 00117 : Job(dd) 00118 { 00119 d_func()->m_reportTimer = new QTimer(this); 00120 connect(d_func()->m_reportTimer,SIGNAL(timeout()),this,SLOT(slotReport())); 00121 //this will update the report dialog with 5 Hz, I think this is fast enough, aleXXX 00122 d_func()->m_reportTimer->start( 200 ); 00123 00124 QTimer::singleShot(0, this, SLOT(slotStart())); 00125 } 00126 00127 DeleteJob::~DeleteJob() 00128 { 00129 } 00130 00131 KUrl::List DeleteJob::urls() const 00132 { 00133 return d_func()->m_srcList; 00134 } 00135 00136 void DeleteJobPrivate::slotStart() 00137 { 00138 statNextSrc(); 00139 } 00140 00141 void DeleteJobPrivate::slotReport() 00142 { 00143 Q_Q(DeleteJob); 00144 emit q->deleting( q, m_currentURL ); 00145 00146 // TODO: maybe we could skip everything else when (flags & HideProgressInfo) ? 00147 JobPrivate::emitDeleting( q, m_currentURL); 00148 00149 switch( state ) { 00150 case DELETEJOB_STATE_STATING: 00151 q->setTotalAmount(KJob::Files, files.count()); 00152 q->setTotalAmount(KJob::Directories, dirs.count()); 00153 break; 00154 case DELETEJOB_STATE_DELETING_DIRS: 00155 q->setProcessedAmount(KJob::Directories, m_processedDirs); 00156 q->emitPercent( m_processedFiles + m_processedDirs, m_totalFilesDirs ); 00157 break; 00158 case DELETEJOB_STATE_DELETING_FILES: 00159 q->setProcessedAmount(KJob::Files, m_processedFiles); 00160 q->emitPercent( m_processedFiles, m_totalFilesDirs ); 00161 break; 00162 } 00163 } 00164 00165 00166 void DeleteJobPrivate::slotEntries(KIO::Job* job, const UDSEntryList& list) 00167 { 00168 UDSEntryList::ConstIterator it = list.begin(); 00169 const UDSEntryList::ConstIterator end = list.end(); 00170 for (; it != end; ++it) 00171 { 00172 const UDSEntry& entry = *it; 00173 const QString displayName = entry.stringValue( KIO::UDSEntry::UDS_NAME ); 00174 00175 Q_ASSERT(!displayName.isEmpty()); 00176 if (displayName != ".." && displayName != ".") 00177 { 00178 KUrl url; 00179 const QString urlStr = entry.stringValue( KIO::UDSEntry::UDS_URL ); 00180 if ( !urlStr.isEmpty() ) 00181 url = urlStr; 00182 else { 00183 url = static_cast<SimpleJob *>(job)->url(); // assumed to be a dir 00184 url.addPath( displayName ); 00185 } 00186 00187 //kDebug(7007) << displayName << "(" << url << ")"; 00188 if ( entry.isLink() ) 00189 symlinks.append( url ); 00190 else if ( entry.isDir() ) 00191 dirs.append( url ); 00192 else 00193 files.append( url ); 00194 } 00195 } 00196 } 00197 00198 00199 void DeleteJobPrivate::statNextSrc() 00200 { 00201 Q_Q(DeleteJob); 00202 //kDebug(7007); 00203 if (m_currentStat != m_srcList.end()) { 00204 m_currentURL = (*m_currentStat); 00205 00206 // if the file system doesn't support deleting, we do not even stat 00207 if (!KProtocolManager::supportsDeleting(m_currentURL)) { 00208 QPointer<DeleteJob> that = q; 00209 ++m_currentStat; 00210 emit q->warning( q, buildErrorString(ERR_CANNOT_DELETE, m_currentURL.prettyUrl()) ); 00211 if (that) 00212 statNextSrc(); 00213 return; 00214 } 00215 // Stat it 00216 state = DELETEJOB_STATE_STATING; 00217 00218 // Fast path for KFileItems in directory views 00219 while(m_currentStat != m_srcList.end()) { 00220 m_currentURL = (*m_currentStat); 00221 const KFileItem cachedItem = KDirLister::cachedItemForUrl(m_currentURL); 00222 if (cachedItem.isNull()) 00223 break; 00224 //kDebug(7007) << "Found cached info about" << m_currentURL << "isDir=" << cachedItem.isDir() << "isLink=" << cachedItem.isLink(); 00225 currentSourceStated(cachedItem.isDir(), cachedItem.isLink()); 00226 ++m_currentStat; 00227 } 00228 00229 // Hook for unit test to disable the fast path. 00230 if (!kio_resolve_local_urls) { 00231 00232 // Fast path for local files 00233 // (using a loop, instead of a huge recursion) 00234 while(m_currentStat != m_srcList.end() && (*m_currentStat).isLocalFile()) { 00235 m_currentURL = (*m_currentStat); 00236 QFileInfo fileInfo(m_currentURL.toLocalFile()); 00237 currentSourceStated(fileInfo.isDir(), fileInfo.isSymLink()); 00238 ++m_currentStat; 00239 } 00240 } 00241 if (m_currentStat == m_srcList.end()) { 00242 // Done, jump to the last else of this method 00243 statNextSrc(); 00244 } else { 00245 KIO::SimpleJob * job = KIO::stat( m_currentURL, StatJob::SourceSide, 0, KIO::HideProgressInfo ); 00246 Scheduler::setJobPriority(job, 1); 00247 //kDebug(7007) << "stat'ing" << m_currentURL; 00248 q->addSubjob(job); 00249 } 00250 } else { 00251 if (!q->hasSubjobs()) // don't go there yet if we're still listing some subdirs 00252 finishedStatPhase(); 00253 } 00254 } 00255 00256 void DeleteJobPrivate::finishedStatPhase() 00257 { 00258 m_totalFilesDirs = files.count() + symlinks.count() + dirs.count(); 00259 slotReport(); 00260 // Now we know which dirs hold the files we're going to delete. 00261 // To speed things up and prevent double-notification, we disable KDirWatch 00262 // on those dirs temporarily (using KDirWatch::self, that's the instance 00263 // used by e.g. kdirlister). 00264 const QSet<QString>::const_iterator itEnd = m_parentDirs.constEnd(); 00265 for ( QSet<QString>::const_iterator it = m_parentDirs.constBegin() ; it != itEnd ; ++it ) 00266 KDirWatch::self()->stopDirScan( *it ); 00267 state = DELETEJOB_STATE_DELETING_FILES; 00268 deleteNextFile(); 00269 } 00270 00271 void DeleteJobPrivate::deleteNextFile() 00272 { 00273 Q_Q(DeleteJob); 00274 //kDebug(7007); 00275 if ( !files.isEmpty() || !symlinks.isEmpty() ) 00276 { 00277 SimpleJob *job; 00278 do { 00279 // Take first file to delete out of list 00280 KUrl::List::iterator it = files.begin(); 00281 bool isLink = false; 00282 if ( it == files.end() ) // No more files 00283 { 00284 it = symlinks.begin(); // Pick up a symlink to delete 00285 isLink = true; 00286 } 00287 // Normal deletion 00288 // If local file, try do it directly 00289 #ifdef Q_WS_WIN 00290 if ( (*it).isLocalFile() && DeleteFileW( (LPCWSTR)(*it).toLocalFile().utf16() ) != 0 ) { 00291 #else 00292 if ( (*it).isLocalFile() && unlink( QFile::encodeName((*it).toLocalFile()) ) == 0 ) { 00293 #endif 00294 //kdDebug(7007) << "DeleteJob deleted" << (*it).toLocalFile(); 00295 job = 0; 00296 m_processedFiles++; 00297 if ( m_processedFiles % 300 == 1 || m_totalFilesDirs < 300) { // update progress info every 300 files 00298 m_currentURL = *it; 00299 slotReport(); 00300 } 00301 } else 00302 { // if remote - or if unlink() failed (we'll use the job's error handling in that case) 00303 //kDebug(7007) << "calling file_delete on" << *it; 00304 if (isHttpProtocol(it->protocol())) 00305 job = KIO::http_delete( *it, KIO::HideProgressInfo ); 00306 else 00307 job = KIO::file_delete( *it, KIO::HideProgressInfo ); 00308 Scheduler::setJobPriority(job, 1); 00309 m_currentURL=(*it); 00310 } 00311 if ( isLink ) 00312 symlinks.erase(it); 00313 else 00314 files.erase(it); 00315 if ( job ) { 00316 q->addSubjob(job); 00317 return; 00318 } 00319 // loop only if direct deletion worked (job=0) and there is something else to delete 00320 } while (!job && (!files.isEmpty() || !symlinks.isEmpty())); 00321 } 00322 state = DELETEJOB_STATE_DELETING_DIRS; 00323 deleteNextDir(); 00324 } 00325 00326 void DeleteJobPrivate::deleteNextDir() 00327 { 00328 Q_Q(DeleteJob); 00329 if ( !dirs.isEmpty() ) // some dirs to delete ? 00330 { 00331 do { 00332 // Take first dir to delete out of list - last ones first ! 00333 KUrl::List::iterator it = --dirs.end(); 00334 // If local dir, try to rmdir it directly 00335 #ifdef Q_WS_WIN 00336 if ( (*it).isLocalFile() && RemoveDirectoryW( (LPCWSTR)(*it).toLocalFile().utf16() ) != 0 ) { 00337 #else 00338 if ( (*it).isLocalFile() && ::rmdir( QFile::encodeName((*it).toLocalFile()) ) == 0 ) { 00339 #endif 00340 m_processedDirs++; 00341 if ( m_processedDirs % 100 == 1 ) { // update progress info every 100 dirs 00342 m_currentURL = *it; 00343 slotReport(); 00344 } 00345 } else { 00346 // Call rmdir - works for kioslaves with canDeleteRecursive too, 00347 // CMD_DEL will trigger the recursive deletion in the slave. 00348 SimpleJob* job = KIO::rmdir( *it ); 00349 job->addMetaData(QString::fromLatin1("recurse"), "true"); 00350 Scheduler::setJobPriority(job, 1); 00351 dirs.erase(it); 00352 q->addSubjob( job ); 00353 return; 00354 } 00355 dirs.erase(it); 00356 } while ( !dirs.isEmpty() ); 00357 } 00358 00359 // Re-enable watching on the dirs that held the deleted files 00360 const QSet<QString>::const_iterator itEnd = m_parentDirs.constEnd(); 00361 for (QSet<QString>::const_iterator it = m_parentDirs.constBegin() ; it != itEnd ; ++it) { 00362 KDirWatch::self()->restartDirScan( *it ); 00363 } 00364 00365 // Finished - tell the world 00366 if ( !m_srcList.isEmpty() ) 00367 { 00368 //kDebug(7007) << "KDirNotify'ing FilesRemoved " << m_srcList.toStringList(); 00369 org::kde::KDirNotify::emitFilesRemoved( m_srcList.toStringList() ); 00370 } 00371 if (m_reportTimer!=0) 00372 m_reportTimer->stop(); 00373 q->emitResult(); 00374 } 00375 00376 void DeleteJobPrivate::currentSourceStated(bool isDir, bool isLink) 00377 { 00378 Q_Q(DeleteJob); 00379 const KUrl url = (*m_currentStat); 00380 if (isDir && !isLink) { 00381 // Add toplevel dir in list of dirs 00382 dirs.append( url ); 00383 if (url.isLocalFile()) { 00384 // We are about to delete this dir, no need to watch it 00385 // Maybe we should ask kdirwatch to remove all watches recursively? 00386 // But then there would be no feedback (things disappearing progressively) during huge deletions 00387 KDirWatch::self()->stopDirScan(url.toLocalFile(KUrl::RemoveTrailingSlash)); 00388 } 00389 if (!KProtocolManager::canDeleteRecursive(url)) { 00390 //kDebug(7007) << url << "is a directory, let's list it"; 00391 ListJob *newjob = KIO::listRecursive(url, KIO::HideProgressInfo); 00392 newjob->addMetaData("details", "0"); 00393 newjob->setUnrestricted(true); // No KIOSK restrictions 00394 Scheduler::setJobPriority(newjob, 1); 00395 QObject::connect(newjob, SIGNAL(entries(KIO::Job*,KIO::UDSEntryList)), 00396 q, SLOT(slotEntries(KIO::Job*,KIO::UDSEntryList))); 00397 q->addSubjob(newjob); 00398 // Note that this listing job will happen in parallel with other stat jobs. 00399 } 00400 } else { 00401 if (isLink) { 00402 //kDebug(7007) << "Target is a symlink"; 00403 symlinks.append(url); 00404 } else { 00405 //kDebug(7007) << "Target is a file"; 00406 files.append(url); 00407 } 00408 } 00409 if (url.isLocalFile()) { 00410 const QString parentDir = url.directory(KUrl::IgnoreTrailingSlash); 00411 m_parentDirs.insert(parentDir); 00412 } 00413 } 00414 00415 void DeleteJob::slotResult( KJob *job ) 00416 { 00417 Q_D(DeleteJob); 00418 switch ( d->state ) 00419 { 00420 case DELETEJOB_STATE_STATING: 00421 removeSubjob( job ); 00422 00423 // Was this a stat job or a list job? We do both in parallel. 00424 if (StatJob* statJob = qobject_cast<StatJob*>(job)) { 00425 // Was there an error while stating ? 00426 if (job->error()) { 00427 // Probably : doesn't exist 00428 Job::slotResult(job); // will set the error and emit result(this) 00429 return; 00430 } 00431 00432 const UDSEntry entry = statJob->statResult(); 00433 // Is it a file or a dir ? 00434 const bool isLink = entry.isLink(); 00435 const bool isDir = entry.isDir(); 00436 d->currentSourceStated(isDir, isLink); 00437 00438 ++d->m_currentStat; 00439 d->statNextSrc(); 00440 } else { 00441 if (job->error()) { 00442 // Try deleting nonetheless, it may be empty (and non-listable) 00443 } 00444 if (!hasSubjobs()) 00445 d->finishedStatPhase(); 00446 } 00447 break; 00448 case DELETEJOB_STATE_DELETING_FILES: 00449 // Propagate the subjob's metadata (a SimpleJob) to the real DeleteJob 00450 // FIXME: setMetaData() in the KIO API only allows access to outgoing metadata, 00451 // but we need to alter the incoming one 00452 d->m_incomingMetaData = dynamic_cast<KIO::Job*>(job)->metaData(); 00453 00454 if ( job->error() ) 00455 { 00456 Job::slotResult( job ); // will set the error and emit result(this) 00457 return; 00458 } 00459 removeSubjob( job ); 00460 Q_ASSERT( !hasSubjobs() ); 00461 d->m_processedFiles++; 00462 00463 d->deleteNextFile(); 00464 break; 00465 case DELETEJOB_STATE_DELETING_DIRS: 00466 if ( job->error() ) 00467 { 00468 Job::slotResult( job ); // will set the error and emit result(this) 00469 return; 00470 } 00471 removeSubjob( job ); 00472 Q_ASSERT( !hasSubjobs() ); 00473 d->m_processedDirs++; 00474 //emit processedAmount( this, KJob::Directories, d->m_processedDirs ); 00475 //emitPercent( d->m_processedFiles + d->m_processedDirs, d->m_totalFilesDirs ); 00476 00477 d->deleteNextDir(); 00478 break; 00479 default: 00480 Q_ASSERT(0); 00481 } 00482 } 00483 00484 DeleteJob *KIO::del( const KUrl& src, JobFlags flags ) 00485 { 00486 KUrl::List srcList; 00487 srcList.append( src ); 00488 return DeleteJobPrivate::newJob(srcList, flags); 00489 } 00490 00491 DeleteJob *KIO::del( const KUrl::List& src, JobFlags flags ) 00492 { 00493 return DeleteJobPrivate::newJob(src, flags); 00494 } 00495 00496 #include "deletejob.moc"
This file is part of the KDE documentation.
Documentation copyright © 1996-2012 The KDE developers.
Generated on Wed May 2 2012 18:20:55 by doxygen 1.8.0 written by Dimitri van Heesch, © 1997-2006
Documentation copyright © 1996-2012 The KDE developers.
Generated on Wed May 2 2012 18:20:55 by doxygen 1.8.0 written by Dimitri van Heesch, © 1997-2006
KDE's Doxygen guidelines are available online.