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