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 "kmimetype.h" 00026 #include "scheduler.h" 00027 #include "kdirwatch.h" 00028 #include "kprotocolmanager.h" 00029 #include "jobuidelegate.h" 00030 #include <kdirnotify.h> 00031 00032 #include <kauthorized.h> 00033 #include <klocale.h> 00034 #include <kdebug.h> 00035 #include <kde_file.h> 00036 00037 #include <assert.h> 00038 #include <stdlib.h> 00039 #include <time.h> 00040 00041 #include <QtCore/QTimer> 00042 #include <QtCore/QFile> 00043 #include <QPointer> 00044 00045 #include "job_p.h" 00046 00047 extern bool kio_resolve_local_urls; // from copyjob.cpp, abused here to save a symbol. 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 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 for ( QSet<QString>::const_iterator it = m_parentDirs.constBegin() ; it != m_parentDirs.constEnd() ; ++it ) 00265 KDirWatch::self()->stopDirScan( *it ); 00266 state = DELETEJOB_STATE_DELETING_FILES; 00267 deleteNextFile(); 00268 } 00269 00270 void DeleteJobPrivate::deleteNextFile() 00271 { 00272 Q_Q(DeleteJob); 00273 //kDebug(7007); 00274 if ( !files.isEmpty() || !symlinks.isEmpty() ) 00275 { 00276 SimpleJob *job; 00277 do { 00278 // Take first file to delete out of list 00279 KUrl::List::iterator it = files.begin(); 00280 bool isLink = false; 00281 if ( it == files.end() ) // No more files 00282 { 00283 it = symlinks.begin(); // Pick up a symlink to delete 00284 isLink = true; 00285 } 00286 // Normal deletion 00287 // If local file, try do it directly 00288 #ifdef Q_WS_WIN 00289 if ( (*it).isLocalFile() && DeleteFileW( (LPCWSTR)(*it).toLocalFile().utf16() ) == 0 ) { 00290 #else 00291 if ( (*it).isLocalFile() && unlink( QFile::encodeName((*it).toLocalFile()) ) == 0 ) { 00292 #endif 00293 //kdDebug(7007) << "DeleteJob deleted" << (*it).toLocalFile(); 00294 job = 0; 00295 m_processedFiles++; 00296 if ( m_processedFiles % 300 == 1 || m_totalFilesDirs < 300) { // update progress info every 300 files 00297 m_currentURL = *it; 00298 slotReport(); 00299 } 00300 } else 00301 { // if remote - or if unlink() failed (we'll use the job's error handling in that case) 00302 //kDebug(7007) << "calling file_delete on" << *it; 00303 job = KIO::file_delete( *it, KIO::HideProgressInfo ); 00304 Scheduler::setJobPriority(job, 1); 00305 m_currentURL=(*it); 00306 } 00307 if ( isLink ) 00308 symlinks.erase(it); 00309 else 00310 files.erase(it); 00311 if ( job ) { 00312 q->addSubjob(job); 00313 return; 00314 } 00315 // loop only if direct deletion worked (job=0) and there is something else to delete 00316 } while (!job && (!files.isEmpty() || !symlinks.isEmpty())); 00317 } 00318 state = DELETEJOB_STATE_DELETING_DIRS; 00319 deleteNextDir(); 00320 } 00321 00322 void DeleteJobPrivate::deleteNextDir() 00323 { 00324 Q_Q(DeleteJob); 00325 if ( !dirs.isEmpty() ) // some dirs to delete ? 00326 { 00327 do { 00328 // Take first dir to delete out of list - last ones first ! 00329 KUrl::List::iterator it = --dirs.end(); 00330 // If local dir, try to rmdir it directly 00331 #ifdef Q_WS_WIN 00332 if ( (*it).isLocalFile() && RemoveDirectoryW( (LPCWSTR)(*it).toLocalFile().utf16() ) != 0 ) { 00333 #else 00334 if ( (*it).isLocalFile() && ::rmdir( QFile::encodeName((*it).toLocalFile()) ) == 0 ) { 00335 #endif 00336 m_processedDirs++; 00337 if ( m_processedDirs % 100 == 1 ) { // update progress info every 100 dirs 00338 m_currentURL = *it; 00339 slotReport(); 00340 } 00341 } else { 00342 // Call rmdir - works for kioslaves with canDeleteRecursive too, 00343 // CMD_DEL will trigger the recursive deletion in the slave. 00344 SimpleJob* job = KIO::rmdir( *it ); 00345 job->addMetaData(QString::fromLatin1("recurse"), "true"); 00346 Scheduler::setJobPriority(job, 1); 00347 dirs.erase(it); 00348 q->addSubjob( job ); 00349 return; 00350 } 00351 dirs.erase(it); 00352 } while ( !dirs.isEmpty() ); 00353 } 00354 00355 // Re-enable watching on the dirs that held the deleted files 00356 for (QSet<QString>::const_iterator it = m_parentDirs.constBegin() ; it != m_parentDirs.constEnd() ; ++it) { 00357 KDirWatch::self()->restartDirScan( *it ); 00358 } 00359 00360 // Finished - tell the world 00361 if ( !m_srcList.isEmpty() ) 00362 { 00363 //kDebug(7007) << "KDirNotify'ing FilesRemoved " << m_srcList.toStringList(); 00364 org::kde::KDirNotify::emitFilesRemoved( m_srcList.toStringList() ); 00365 } 00366 if (m_reportTimer!=0) 00367 m_reportTimer->stop(); 00368 q->emitResult(); 00369 } 00370 00371 void DeleteJobPrivate::currentSourceStated(bool isDir, bool isLink) 00372 { 00373 Q_Q(DeleteJob); 00374 const KUrl url = (*m_currentStat); 00375 if (isDir && !isLink) { 00376 // Add toplevel dir in list of dirs 00377 dirs.append( url ); 00378 if (url.isLocalFile()) { 00379 // We are about to delete this dir, no need to watch it 00380 // Maybe we should ask kdirwatch to remove all watches recursively? 00381 // But then there would be no feedback (things disappearing progressively) during huge deletions 00382 KDirWatch::self()->stopDirScan(url.toLocalFile(KUrl::RemoveTrailingSlash)); 00383 } 00384 if (!KProtocolManager::canDeleteRecursive(url)) { 00385 //kDebug(7007) << url << "is a directory, let's list it"; 00386 ListJob *newjob = KIO::listRecursive(url, KIO::HideProgressInfo); 00387 newjob->addMetaData("details", "0"); 00388 newjob->setUnrestricted(true); // No KIOSK restrictions 00389 Scheduler::setJobPriority(newjob, 1); 00390 QObject::connect(newjob, SIGNAL(entries(KIO::Job*, const KIO::UDSEntryList&)), 00391 q, SLOT(slotEntries(KIO::Job*,const KIO::UDSEntryList&))); 00392 q->addSubjob(newjob); 00393 // Note that this listing job will happen in parallel with other stat jobs. 00394 } 00395 } else { 00396 if (isLink) { 00397 //kDebug(7007) << "Target is a symlink"; 00398 symlinks.append(url); 00399 } else { 00400 //kDebug(7007) << "Target is a file"; 00401 files.append(url); 00402 } 00403 } 00404 if (url.isLocalFile()) { 00405 const QString parentDir = url.directory(KUrl::IgnoreTrailingSlash); 00406 m_parentDirs.insert(parentDir); 00407 } 00408 } 00409 00410 void DeleteJob::slotResult( KJob *job ) 00411 { 00412 Q_D(DeleteJob); 00413 switch ( d->state ) 00414 { 00415 case DELETEJOB_STATE_STATING: 00416 removeSubjob( job ); 00417 00418 // Was this a stat job or a list job? We do both in parallel. 00419 if (StatJob* statJob = qobject_cast<StatJob*>(job)) { 00420 // Was there an error while stating ? 00421 if (job->error()) { 00422 // Probably : doesn't exist 00423 Job::slotResult(job); // will set the error and emit result(this) 00424 return; 00425 } 00426 00427 const UDSEntry entry = statJob->statResult(); 00428 // Is it a file or a dir ? 00429 const bool isLink = entry.isLink(); 00430 const bool isDir = entry.isDir(); 00431 d->currentSourceStated(isDir, isLink); 00432 00433 ++d->m_currentStat; 00434 d->statNextSrc(); 00435 } else { 00436 if (job->error()) { 00437 // Try deleting nonetheless, it may be empty (and non-listable) 00438 } 00439 if (!hasSubjobs()) 00440 d->finishedStatPhase(); 00441 } 00442 break; 00443 case DELETEJOB_STATE_DELETING_FILES: 00444 // Propagate the subjob's metadata (a SimpleJob) to the real DeleteJob 00445 // FIXME: setMetaData() in the KIO API only allows access to outgoing metadata, 00446 // but we need to alter the incoming one 00447 d->m_incomingMetaData = dynamic_cast<KIO::Job*>(job)->metaData(); 00448 00449 if ( job->error() ) 00450 { 00451 Job::slotResult( job ); // will set the error and emit result(this) 00452 return; 00453 } 00454 removeSubjob( job ); 00455 assert( !hasSubjobs() ); 00456 d->m_processedFiles++; 00457 00458 d->deleteNextFile(); 00459 break; 00460 case DELETEJOB_STATE_DELETING_DIRS: 00461 if ( job->error() ) 00462 { 00463 Job::slotResult( job ); // will set the error and emit result(this) 00464 return; 00465 } 00466 removeSubjob( job ); 00467 assert( !hasSubjobs() ); 00468 d->m_processedDirs++; 00469 //emit processedAmount( this, KJob::Directories, d->m_processedDirs ); 00470 //emitPercent( d->m_processedFiles + d->m_processedDirs, d->m_totalFilesDirs ); 00471 00472 d->deleteNextDir(); 00473 break; 00474 default: 00475 assert(0); 00476 } 00477 } 00478 00479 DeleteJob *KIO::del( const KUrl& src, JobFlags flags ) 00480 { 00481 KUrl::List srcList; 00482 srcList.append( src ); 00483 return DeleteJobPrivate::newJob(srcList, flags); 00484 } 00485 00486 DeleteJob *KIO::del( const KUrl::List& src, JobFlags flags ) 00487 { 00488 return DeleteJobPrivate::newJob(src, flags); 00489 } 00490 00491 #include "deletejob.moc"
KDE 4.6 API Reference