job.cpp
00001 /* This file is part of the KDE libraries 00002 Copyright (C) 2000 Stephan Kulow <coolo@kde.org> 00003 David Faure <faure@kde.org> 00004 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 "kio/job.h" 00023 00024 #include <config.h> 00025 00026 #include <sys/types.h> 00027 #include <sys/wait.h> 00028 #include <sys/stat.h> 00029 00030 #include <assert.h> 00031 00032 #include <signal.h> 00033 #include <stdlib.h> 00034 #include <stdio.h> 00035 #include <time.h> 00036 #include <unistd.h> 00037 extern "C" { 00038 #include <pwd.h> 00039 #include <grp.h> 00040 } 00041 #include <qtimer.h> 00042 #include <qfile.h> 00043 00044 #include <kapplication.h> 00045 #include <kglobal.h> 00046 #include <klocale.h> 00047 #include <ksimpleconfig.h> 00048 #include <kdebug.h> 00049 #include <kdialog.h> 00050 #include <kmessagebox.h> 00051 #include <kdatastream.h> 00052 #include <kmainwindow.h> 00053 #include <kde_file.h> 00054 00055 #include <errno.h> 00056 00057 #include "kmimetype.h" 00058 #include "slave.h" 00059 #include "scheduler.h" 00060 #include "kdirwatch.h" 00061 #include "kmimemagic.h" 00062 #include "kprotocolinfo.h" 00063 #include "kprotocolmanager.h" 00064 00065 #include "kio/observer.h" 00066 00067 #include "kssl/ksslcsessioncache.h" 00068 00069 #include <kdirnotify_stub.h> 00070 #include <ktempfile.h> 00071 #include <dcopclient.h> 00072 00073 #ifdef Q_OS_UNIX 00074 #include <utime.h> 00075 #endif 00076 #if defined Q_WS_X11 00077 #include <netwm.h> 00078 #include <fixx11h.h> 00079 #endif 00080 00081 using namespace KIO; 00082 template class QPtrList<KIO::Job>; 00083 00084 //this will update the report dialog with 5 Hz, I think this is fast enough, aleXXX 00085 #define REPORT_TIMEOUT 200 00086 00087 #define KIO_ARGS QByteArray packedArgs; QDataStream stream( packedArgs, IO_WriteOnly ); stream 00088 00089 class Job::JobPrivate 00090 { 00091 public: 00092 JobPrivate() : m_autoErrorHandling( false ), m_autoWarningHandling( true ), 00093 m_interactive( true ), m_parentJob( 0L ), m_extraFlags(0), 00094 m_processedSize(0), m_userTimestamp(0) 00095 {} 00096 00097 bool m_autoErrorHandling; 00098 bool m_autoWarningHandling; 00099 bool m_interactive; 00100 QGuardedPtr<QWidget> m_errorParentWidget; 00101 // Maybe we could use the QObject parent/child mechanism instead 00102 // (requires a new ctor, and moving the ctor code to some init()). 00103 Job* m_parentJob; 00104 int m_extraFlags; 00105 KIO::filesize_t m_processedSize; 00106 unsigned long m_userTimestamp; 00107 }; 00108 00109 Job::Job(bool showProgressInfo) : QObject(0, "job"), m_error(0), m_percent(0) 00110 , m_progressId(0), m_speedTimer(0), d( new JobPrivate ) 00111 { 00112 // All jobs delete themselves after emiting 'result'. 00113 00114 // Notify the UI Server and get a progress id 00115 if ( showProgressInfo ) 00116 { 00117 m_progressId = Observer::self()->newJob( this, true ); 00118 addMetaData("progress-id", QString::number(m_progressId)); 00119 //kdDebug(7007) << "Created job " << this << " with progress info -- m_progressId=" << m_progressId << endl; 00120 // Connect global progress info signals 00121 connect( this, SIGNAL( percent( KIO::Job*, unsigned long ) ), 00122 Observer::self(), SLOT( slotPercent( KIO::Job*, unsigned long ) ) ); 00123 connect( this, SIGNAL( infoMessage( KIO::Job*, const QString & ) ), 00124 Observer::self(), SLOT( slotInfoMessage( KIO::Job*, const QString & ) ) ); 00125 connect( this, SIGNAL( totalSize( KIO::Job*, KIO::filesize_t ) ), 00126 Observer::self(), SLOT( slotTotalSize( KIO::Job*, KIO::filesize_t ) ) ); 00127 connect( this, SIGNAL( processedSize( KIO::Job*, KIO::filesize_t ) ), 00128 Observer::self(), SLOT( slotProcessedSize( KIO::Job*, KIO::filesize_t ) ) ); 00129 connect( this, SIGNAL( speed( KIO::Job*, unsigned long ) ), 00130 Observer::self(), SLOT( slotSpeed( KIO::Job*, unsigned long ) ) ); 00131 } 00132 // Don't exit while this job is running 00133 if (kapp) 00134 kapp->ref(); 00135 if (kapp) 00136 updateUserTimestamp( kapp->userTimestamp()); 00137 } 00138 00139 Job::~Job() 00140 { 00141 delete m_speedTimer; 00142 delete d; 00143 if (kapp) 00144 kapp->deref(); 00145 } 00146 00147 int& Job::extraFlags() 00148 { 00149 return d->m_extraFlags; 00150 } 00151 00152 void Job::setProcessedSize(KIO::filesize_t size) 00153 { 00154 d->m_processedSize = size; 00155 } 00156 00157 KIO::filesize_t Job::getProcessedSize() 00158 { 00159 return d->m_processedSize; 00160 } 00161 00162 void Job::addSubjob(Job *job, bool inheritMetaData) 00163 { 00164 //kdDebug(7007) << "addSubjob(" << job << ") this = " << this << endl; 00165 subjobs.append(job); 00166 00167 connect( job, SIGNAL(result(KIO::Job*)), 00168 SLOT(slotResult(KIO::Job*)) ); 00169 00170 // Forward information from that subjob. 00171 connect( job, SIGNAL(speed( KIO::Job*, unsigned long )), 00172 SLOT(slotSpeed(KIO::Job*, unsigned long)) ); 00173 00174 connect( job, SIGNAL(infoMessage( KIO::Job*, const QString & )), 00175 SLOT(slotInfoMessage(KIO::Job*, const QString &)) ); 00176 00177 if (inheritMetaData) 00178 job->mergeMetaData(m_outgoingMetaData); 00179 00180 job->setWindow( m_window ); 00181 job->updateUserTimestamp( d->m_userTimestamp ); 00182 } 00183 00184 void Job::removeSubjob( Job *job ) 00185 { 00186 removeSubjob( job, false, true ); 00187 } 00188 00189 void Job::removeSubjob( Job *job, bool mergeMetaData, bool emitResultIfLast ) 00190 { 00191 //kdDebug(7007) << "removeSubjob(" << job << ") this = " << this << " subjobs = " << subjobs.count() << endl; 00192 // Merge metadata from subjob 00193 if ( mergeMetaData ) 00194 m_incomingMetaData += job->metaData(); 00195 subjobs.remove(job); 00196 if ( subjobs.isEmpty() && emitResultIfLast ) 00197 emitResult(); 00198 } 00199 00200 void Job::emitPercent( KIO::filesize_t processedSize, KIO::filesize_t totalSize ) 00201 { 00202 // calculate percents 00203 unsigned long ipercent = m_percent; 00204 00205 if ( totalSize == 0 ) 00206 m_percent = 100; 00207 else 00208 m_percent = (unsigned long)(( (float)(processedSize) / (float)(totalSize) ) * 100.0); 00209 00210 if ( m_percent != ipercent || m_percent == 100 /* for those buggy total sizes that grow */ ) { 00211 emit percent( this, m_percent ); 00212 //kdDebug(7007) << "Job::emitPercent - percent = " << (unsigned int) m_percent << endl; 00213 } 00214 } 00215 00216 void Job::emitSpeed( unsigned long bytes_per_second ) 00217 { 00218 //kdDebug(7007) << "Job " << this << " emitSpeed " << bytes_per_second << endl; 00219 if ( !m_speedTimer ) 00220 { 00221 m_speedTimer = new QTimer(); 00222 connect( m_speedTimer, SIGNAL( timeout() ), SLOT( slotSpeedTimeout() ) ); 00223 } 00224 emit speed( this, bytes_per_second ); 00225 m_speedTimer->start( 5000 ); // 5 seconds interval should be enough 00226 } 00227 00228 void Job::emitResult() 00229 { 00230 // If we are displaying a progress dialog, remove it first. 00231 if ( m_progressId ) // Did we get an ID from the observer ? 00232 Observer::self()->jobFinished( m_progressId ); 00233 if ( m_error && d->m_interactive && d->m_autoErrorHandling ) 00234 showErrorDialog( d->m_errorParentWidget ); 00235 emit result(this); 00236 deleteLater(); 00237 } 00238 00239 void Job::kill( bool quietly ) 00240 { 00241 kdDebug(7007) << "Job::kill this=" << this << " " << className() << " m_progressId=" << m_progressId << " quietly=" << quietly << endl; 00242 // kill all subjobs, without triggering their result slot 00243 QPtrListIterator<Job> it( subjobs ); 00244 for ( ; it.current() ; ++it ) 00245 (*it)->kill( true ); 00246 subjobs.clear(); 00247 00248 if ( ! quietly ) { 00249 m_error = ERR_USER_CANCELED; 00250 emit canceled( this ); // Not very useful (deprecated) 00251 emitResult(); 00252 } else 00253 { 00254 if ( m_progressId ) // in both cases we want to hide the progress window 00255 Observer::self()->jobFinished( m_progressId ); 00256 deleteLater(); 00257 } 00258 } 00259 00260 void Job::slotResult( Job *job ) 00261 { 00262 // Did job have an error ? 00263 if ( job->error() && !m_error ) 00264 { 00265 // Store it in the parent only if first error 00266 m_error = job->error(); 00267 m_errorText = job->errorText(); 00268 } 00269 removeSubjob(job); 00270 } 00271 00272 void Job::slotSpeed( KIO::Job*, unsigned long speed ) 00273 { 00274 //kdDebug(7007) << "Job::slotSpeed " << speed << endl; 00275 emitSpeed( speed ); 00276 } 00277 00278 void Job::slotInfoMessage( KIO::Job*, const QString & msg ) 00279 { 00280 emit infoMessage( this, msg ); 00281 } 00282 00283 void Job::slotSpeedTimeout() 00284 { 00285 //kdDebug(7007) << "slotSpeedTimeout()" << endl; 00286 // send 0 and stop the timer 00287 // timer will be restarted only when we receive another speed event 00288 emit speed( this, 0 ); 00289 m_speedTimer->stop(); 00290 } 00291 00292 //Job::errorString is implemented in global.cpp 00293 00294 void Job::showErrorDialog( QWidget * parent ) 00295 { 00296 //kdDebug(7007) << "Job::showErrorDialog parent=" << parent << endl; 00297 kapp->enableStyles(); 00298 // Show a message box, except for "user canceled" or "no content" 00299 if ( (m_error != ERR_USER_CANCELED) && (m_error != ERR_NO_CONTENT) ) { 00300 //old plain error message 00301 //kdDebug(7007) << "Default language: " << KGlobal::locale()->defaultLanguage() << endl; 00302 if ( 1 ) 00303 KMessageBox::queuedMessageBox( parent, KMessageBox::Error, errorString() ); 00304 #if 0 00305 } else { 00306 QStringList errors = detailedErrorStrings(); 00307 QString caption, err, detail; 00308 QStringList::const_iterator it = errors.begin(); 00309 if ( it != errors.end() ) 00310 caption = *(it++); 00311 if ( it != errors.end() ) 00312 err = *(it++); 00313 if ( it != errors.end() ) 00314 detail = *it; 00315 KMessageBox::queuedDetailedError( parent, err, detail, caption ); 00316 } 00317 #endif 00318 } 00319 } 00320 00321 void Job::setAutoErrorHandlingEnabled( bool enable, QWidget *parentWidget ) 00322 { 00323 d->m_autoErrorHandling = enable; 00324 d->m_errorParentWidget = parentWidget; 00325 } 00326 00327 bool Job::isAutoErrorHandlingEnabled() const 00328 { 00329 return d->m_autoErrorHandling; 00330 } 00331 00332 void Job::setAutoWarningHandlingEnabled( bool enable ) 00333 { 00334 d->m_autoWarningHandling = enable; 00335 } 00336 00337 bool Job::isAutoWarningHandlingEnabled() const 00338 { 00339 return d->m_autoWarningHandling; 00340 } 00341 00342 void Job::setInteractive(bool enable) 00343 { 00344 d->m_interactive = enable; 00345 } 00346 00347 bool Job::isInteractive() const 00348 { 00349 return d->m_interactive; 00350 } 00351 00352 void Job::setWindow(QWidget *window) 00353 { 00354 m_window = window; 00355 KIO::Scheduler::registerWindow(window); 00356 } 00357 00358 QWidget *Job::window() const 00359 { 00360 return m_window; 00361 } 00362 00363 void Job::updateUserTimestamp( unsigned long time ) 00364 { 00365 #if defined Q_WS_X11 00366 if( d->m_userTimestamp == 0 || NET::timestampCompare( time, d->m_userTimestamp ) > 0 ) 00367 d->m_userTimestamp = time; 00368 #endif 00369 } 00370 00371 unsigned long Job::userTimestamp() const 00372 { 00373 return d->m_userTimestamp; 00374 } 00375 00376 void Job::setParentJob(Job* job) 00377 { 00378 Q_ASSERT(d->m_parentJob == 0L); 00379 Q_ASSERT(job); 00380 d->m_parentJob = job; 00381 } 00382 00383 Job* Job::parentJob() const 00384 { 00385 return d->m_parentJob; 00386 } 00387 00388 MetaData Job::metaData() const 00389 { 00390 return m_incomingMetaData; 00391 } 00392 00393 QString Job::queryMetaData(const QString &key) 00394 { 00395 if (!m_incomingMetaData.contains(key)) 00396 return QString::null; 00397 return m_incomingMetaData[key]; 00398 } 00399 00400 void Job::setMetaData( const KIO::MetaData &_metaData) 00401 { 00402 m_outgoingMetaData = _metaData; 00403 } 00404 00405 void Job::addMetaData( const QString &key, const QString &value) 00406 { 00407 m_outgoingMetaData.insert(key, value); 00408 } 00409 00410 void Job::addMetaData( const QMap<QString,QString> &values) 00411 { 00412 QMapConstIterator<QString,QString> it = values.begin(); 00413 for(;it != values.end(); ++it) 00414 m_outgoingMetaData.insert(it.key(), it.data()); 00415 } 00416 00417 void Job::mergeMetaData( const QMap<QString,QString> &values) 00418 { 00419 QMapConstIterator<QString,QString> it = values.begin(); 00420 for(;it != values.end(); ++it) 00421 m_outgoingMetaData.insert(it.key(), it.data(), false); 00422 } 00423 00424 MetaData Job::outgoingMetaData() const 00425 { 00426 return m_outgoingMetaData; 00427 } 00428 00429 00430 SimpleJob::SimpleJob(const KURL& url, int command, const QByteArray &packedArgs, 00431 bool showProgressInfo ) 00432 : Job(showProgressInfo), m_slave(0), m_packedArgs(packedArgs), 00433 m_url(url), m_command(command), m_totalSize(0) 00434 { 00435 if (m_url.hasSubURL()) 00436 { 00437 KURL::List list = KURL::split(m_url); 00438 KURL::List::Iterator it = list.fromLast(); 00439 list.remove(it); 00440 m_subUrl = KURL::join(list); 00441 //kdDebug(7007) << "New URL = " << m_url.url() << endl; 00442 //kdDebug(7007) << "Sub URL = " << m_subUrl.url() << endl; 00443 } 00444 00445 Scheduler::doJob(this); 00446 00447 if (!m_url.isValid()) 00448 { 00449 kdDebug() << "ERR_MALFORMED_URL" << endl; 00450 m_error = ERR_MALFORMED_URL; 00451 m_errorText = m_url.url(); 00452 QTimer::singleShot(0, this, SLOT(slotFinished()) ); 00453 return; 00454 } 00455 } 00456 00457 void SimpleJob::kill( bool quietly ) 00458 { 00459 Scheduler::cancelJob( this ); // deletes the slave if not 0 00460 m_slave = 0; // -> set to 0 00461 Job::kill( quietly ); 00462 } 00463 00464 void SimpleJob::putOnHold() 00465 { 00466 Q_ASSERT( m_slave ); 00467 if ( m_slave ) 00468 { 00469 Scheduler::putSlaveOnHold(this, m_url); 00470 m_slave = 0; 00471 } 00472 kill(true); 00473 } 00474 00475 void SimpleJob::removeOnHold() 00476 { 00477 Scheduler::removeSlaveOnHold(); 00478 } 00479 00480 SimpleJob::~SimpleJob() 00481 { 00482 if (m_slave) // was running 00483 { 00484 kdDebug(7007) << "SimpleJob::~SimpleJob: Killing running job in destructor!" << endl; 00485 #if 0 00486 m_slave->kill(); 00487 Scheduler::jobFinished( this, m_slave ); // deletes the slave 00488 #endif 00489 Scheduler::cancelJob( this ); 00490 m_slave = 0; // -> set to 0 00491 } 00492 } 00493 00494 void SimpleJob::start(Slave *slave) 00495 { 00496 m_slave = slave; 00497 00498 connect( m_slave, SIGNAL( error( int , const QString & ) ), 00499 SLOT( slotError( int , const QString & ) ) ); 00500 00501 connect( m_slave, SIGNAL( warning( const QString & ) ), 00502 SLOT( slotWarning( const QString & ) ) ); 00503 00504 connect( m_slave, SIGNAL( infoMessage( const QString & ) ), 00505 SLOT( slotInfoMessage( const QString & ) ) ); 00506 00507 connect( m_slave, SIGNAL( connected() ), 00508 SLOT( slotConnected() ) ); 00509 00510 connect( m_slave, SIGNAL( finished() ), 00511 SLOT( slotFinished() ) ); 00512 00513 if ((extraFlags() & EF_TransferJobDataSent) == 0) 00514 { 00515 connect( m_slave, SIGNAL( totalSize( KIO::filesize_t ) ), 00516 SLOT( slotTotalSize( KIO::filesize_t ) ) ); 00517 00518 connect( m_slave, SIGNAL( processedSize( KIO::filesize_t ) ), 00519 SLOT( slotProcessedSize( KIO::filesize_t ) ) ); 00520 00521 connect( m_slave, SIGNAL( speed( unsigned long ) ), 00522 SLOT( slotSpeed( unsigned long ) ) ); 00523 } 00524 00525 connect( slave, SIGNAL( needProgressId() ), 00526 SLOT( slotNeedProgressId() ) ); 00527 00528 connect( slave, SIGNAL(metaData( const KIO::MetaData& ) ), 00529 SLOT( slotMetaData( const KIO::MetaData& ) ) ); 00530 00531 if (m_window) 00532 { 00533 QString id; 00534 addMetaData("window-id", id.setNum((ulong)m_window->winId())); 00535 } 00536 if (userTimestamp()) 00537 { 00538 QString id; 00539 addMetaData("user-timestamp", id.setNum(userTimestamp())); 00540 } 00541 00542 QString sslSession = KSSLCSessionCache::getSessionForURL(m_url); 00543 if ( !sslSession.isNull() ) 00544 { 00545 addMetaData("ssl_session_id", sslSession); 00546 } 00547 00548 if (!isInteractive()) 00549 { 00550 addMetaData("no-auth-prompt", "true"); 00551 } 00552 00553 if (!m_outgoingMetaData.isEmpty()) 00554 { 00555 KIO_ARGS << m_outgoingMetaData; 00556 slave->send( CMD_META_DATA, packedArgs ); 00557 } 00558 00559 if (!m_subUrl.isEmpty()) 00560 { 00561 KIO_ARGS << m_subUrl; 00562 m_slave->send( CMD_SUBURL, packedArgs ); 00563 } 00564 00565 m_slave->send( m_command, m_packedArgs ); 00566 } 00567 00568 void SimpleJob::slaveDone() 00569 { 00570 if (!m_slave) return; 00571 disconnect(m_slave); // Remove all signals between slave and job 00572 Scheduler::jobFinished( this, m_slave ); 00573 m_slave = 0; 00574 } 00575 00576 void SimpleJob::slotFinished( ) 00577 { 00578 // Return slave to the scheduler 00579 slaveDone(); 00580 00581 if (subjobs.isEmpty()) 00582 { 00583 if ( !m_error && (m_command == CMD_MKDIR || m_command == CMD_RENAME ) ) 00584 { 00585 KDirNotify_stub allDirNotify( "*", "KDirNotify*" ); 00586 if ( m_command == CMD_MKDIR ) 00587 { 00588 KURL urlDir( url() ); 00589 urlDir.setPath( urlDir.directory() ); 00590 allDirNotify.FilesAdded( urlDir ); 00591 } 00592 else /*if ( m_command == CMD_RENAME )*/ 00593 { 00594 KURL src, dst; 00595 QDataStream str( m_packedArgs, IO_ReadOnly ); 00596 str >> src >> dst; 00597 if ( src.directory() == dst.directory() ) // For the user, moving isn't renaming. Only renaming is. 00598 allDirNotify.FileRenamed( src, dst ); 00599 } 00600 } 00601 emitResult(); 00602 } 00603 } 00604 00605 void SimpleJob::slotError( int error, const QString & errorText ) 00606 { 00607 m_error = error; 00608 m_errorText = errorText; 00609 if ((m_error == ERR_UNKNOWN_HOST) && m_url.host().isEmpty()) 00610 m_errorText = QString::null; 00611 // error terminates the job 00612 slotFinished(); 00613 } 00614 00615 void SimpleJob::slotWarning( const QString & errorText ) 00616 { 00617 QGuardedPtr<SimpleJob> guard( this ); 00618 if (isInteractive() && isAutoWarningHandlingEnabled()) 00619 { 00620 static uint msgBoxDisplayed = 0; 00621 if ( msgBoxDisplayed == 0 ) // don't bomb the user with message boxes, only one at a time 00622 { 00623 msgBoxDisplayed++; 00624 KMessageBox::information( 0L, errorText ); 00625 msgBoxDisplayed--; 00626 } 00627 // otherwise just discard it. 00628 } 00629 00630 if ( !guard.isNull() ) 00631 emit warning( this, errorText ); 00632 } 00633 00634 void SimpleJob::slotInfoMessage( const QString & msg ) 00635 { 00636 emit infoMessage( this, msg ); 00637 } 00638 00639 void SimpleJob::slotConnected() 00640 { 00641 emit connected( this ); 00642 } 00643 00644 void SimpleJob::slotNeedProgressId() 00645 { 00646 if ( !m_progressId ) 00647 m_progressId = Observer::self()->newJob( this, false ); 00648 m_slave->setProgressId( m_progressId ); 00649 } 00650 00651 void SimpleJob::slotTotalSize( KIO::filesize_t size ) 00652 { 00653 if (size > m_totalSize) 00654 { 00655 m_totalSize = size; 00656 emit totalSize( this, size ); 00657 } 00658 } 00659 00660 void SimpleJob::slotProcessedSize( KIO::filesize_t size ) 00661 { 00662 //kdDebug(7007) << "SimpleJob::slotProcessedSize " << KIO::number(size) << endl; 00663 setProcessedSize(size); 00664 emit processedSize( this, size ); 00665 if ( size > m_totalSize ) { 00666 slotTotalSize(size); // safety 00667 } 00668 emitPercent( size, m_totalSize ); 00669 } 00670 00671 void SimpleJob::slotSpeed( unsigned long speed ) 00672 { 00673 //kdDebug(7007) << "SimpleJob::slotSpeed( " << speed << " )" << endl; 00674 emitSpeed( speed ); 00675 } 00676 00677 void SimpleJob::slotMetaData( const KIO::MetaData &_metaData) 00678 { 00679 m_incomingMetaData += _metaData; 00680 } 00681 00682 void SimpleJob::storeSSLSessionFromJob(const KURL &m_redirectionURL) { 00683 QString sslSession = queryMetaData("ssl_session_id"); 00684 00685 if ( !sslSession.isNull() ) { 00686 const KURL &queryURL = m_redirectionURL.isEmpty()?m_url:m_redirectionURL; 00687 KSSLCSessionCache::putSessionForURL(queryURL, sslSession); 00688 } 00689 } 00690 00692 MkdirJob::MkdirJob( const KURL& url, int command, 00693 const QByteArray &packedArgs, bool showProgressInfo ) 00694 : SimpleJob(url, command, packedArgs, showProgressInfo) 00695 { 00696 } 00697 00698 void MkdirJob::start(Slave *slave) 00699 { 00700 connect( slave, SIGNAL( redirection(const KURL &) ), 00701 SLOT( slotRedirection(const KURL &) ) ); 00702 00703 SimpleJob::start(slave); 00704 } 00705 00706 // Slave got a redirection request 00707 void MkdirJob::slotRedirection( const KURL &url) 00708 { 00709 kdDebug(7007) << "MkdirJob::slotRedirection(" << url << ")" << endl; 00710 if (!kapp->authorizeURLAction("redirect", m_url, url)) 00711 { 00712 kdWarning(7007) << "MkdirJob: Redirection from " << m_url << " to " << url << " REJECTED!" << endl; 00713 m_error = ERR_ACCESS_DENIED; 00714 m_errorText = url.prettyURL(); 00715 return; 00716 } 00717 m_redirectionURL = url; // We'll remember that when the job finishes 00718 if (m_url.hasUser() && !url.hasUser() && (m_url.host().lower() == url.host().lower())) 00719 m_redirectionURL.setUser(m_url.user()); // Preserve user 00720 // Tell the user that we haven't finished yet 00721 emit redirection(this, m_redirectionURL); 00722 } 00723 00724 void MkdirJob::slotFinished() 00725 { 00726 if ( m_redirectionURL.isEmpty() || !m_redirectionURL.isValid()) 00727 { 00728 // Return slave to the scheduler 00729 SimpleJob::slotFinished(); 00730 } else { 00731 //kdDebug(7007) << "MkdirJob: Redirection to " << m_redirectionURL << endl; 00732 if (queryMetaData("permanent-redirect")=="true") 00733 emit permanentRedirection(this, m_url, m_redirectionURL); 00734 KURL dummyUrl; 00735 int permissions; 00736 QDataStream istream( m_packedArgs, IO_ReadOnly ); 00737 istream >> dummyUrl >> permissions; 00738 00739 m_url = m_redirectionURL; 00740 m_redirectionURL = KURL(); 00741 m_packedArgs.truncate(0); 00742 QDataStream stream( m_packedArgs, IO_WriteOnly ); 00743 stream << m_url << permissions; 00744 00745 // Return slave to the scheduler 00746 slaveDone(); 00747 Scheduler::doJob(this); 00748 } 00749 } 00750 00751 SimpleJob *KIO::mkdir( const KURL& url, int permissions ) 00752 { 00753 //kdDebug(7007) << "mkdir " << url << endl; 00754 KIO_ARGS << url << permissions; 00755 return new MkdirJob(url, CMD_MKDIR, packedArgs, false); 00756 } 00757 00758 SimpleJob *KIO::rmdir( const KURL& url ) 00759 { 00760 //kdDebug(7007) << "rmdir " << url << endl; 00761 KIO_ARGS << url << Q_INT8(false); // isFile is false 00762 return new SimpleJob(url, CMD_DEL, packedArgs, false); 00763 } 00764 00765 SimpleJob *KIO::chmod( const KURL& url, int permissions ) 00766 { 00767 //kdDebug(7007) << "chmod " << url << endl; 00768 KIO_ARGS << url << permissions; 00769 return new SimpleJob(url, CMD_CHMOD, packedArgs, false); 00770 } 00771 00772 SimpleJob *KIO::rename( const KURL& src, const KURL & dest, bool overwrite ) 00773 { 00774 //kdDebug(7007) << "rename " << src << " " << dest << endl; 00775 KIO_ARGS << src << dest << (Q_INT8) overwrite; 00776 return new SimpleJob(src, CMD_RENAME, packedArgs, false); 00777 } 00778 00779 SimpleJob *KIO::symlink( const QString& target, const KURL & dest, bool overwrite, bool showProgressInfo ) 00780 { 00781 //kdDebug(7007) << "symlink target=" << target << " " << dest << endl; 00782 KIO_ARGS << target << dest << (Q_INT8) overwrite; 00783 return new SimpleJob(dest, CMD_SYMLINK, packedArgs, showProgressInfo); 00784 } 00785 00786 SimpleJob *KIO::special(const KURL& url, const QByteArray & data, bool showProgressInfo) 00787 { 00788 //kdDebug(7007) << "special " << url << endl; 00789 return new SimpleJob(url, CMD_SPECIAL, data, showProgressInfo); 00790 } 00791 00792 SimpleJob *KIO::mount( bool ro, const char *fstype, const QString& dev, const QString& point, bool showProgressInfo ) 00793 { 00794 KIO_ARGS << int(1) << Q_INT8( ro ? 1 : 0 ) 00795 << QString::fromLatin1(fstype) << dev << point; 00796 SimpleJob *job = special( KURL("file:/"), packedArgs, showProgressInfo ); 00797 if ( showProgressInfo ) 00798 Observer::self()->mounting( job, dev, point ); 00799 return job; 00800 } 00801 00802 SimpleJob *KIO::unmount( const QString& point, bool showProgressInfo ) 00803 { 00804 KIO_ARGS << int(2) << point; 00805 SimpleJob *job = special( KURL("file:/"), packedArgs, showProgressInfo ); 00806 if ( showProgressInfo ) 00807 Observer::self()->unmounting( job, point ); 00808 return job; 00809 } 00810 00811 00812 00814 00815 StatJob::StatJob( const KURL& url, int command, 00816 const QByteArray &packedArgs, bool showProgressInfo ) 00817 : SimpleJob(url, command, packedArgs, showProgressInfo), 00818 m_bSource(true), m_details(2) 00819 { 00820 } 00821 00822 void StatJob::start(Slave *slave) 00823 { 00824 m_outgoingMetaData.replace( "statSide", m_bSource ? "source" : "dest" ); 00825 m_outgoingMetaData.replace( "details", QString::number(m_details) ); 00826 00827 connect( slave, SIGNAL( statEntry( const KIO::UDSEntry& ) ), 00828 SLOT( slotStatEntry( const KIO::UDSEntry & ) ) ); 00829 connect( slave, SIGNAL( redirection(const KURL &) ), 00830 SLOT( slotRedirection(const KURL &) ) ); 00831 00832 SimpleJob::start(slave); 00833 } 00834 00835 void StatJob::slotStatEntry( const KIO::UDSEntry & entry ) 00836 { 00837 //kdDebug(7007) << "StatJob::slotStatEntry" << endl; 00838 m_statResult = entry; 00839 } 00840 00841 // Slave got a redirection request 00842 void StatJob::slotRedirection( const KURL &url) 00843 { 00844 kdDebug(7007) << "StatJob::slotRedirection(" << url << ")" << endl; 00845 if (!kapp->authorizeURLAction("redirect", m_url, url)) 00846 { 00847 kdWarning(7007) << "StatJob: Redirection from " << m_url << " to " << url << " REJECTED!" << endl; 00848 m_error = ERR_ACCESS_DENIED; 00849 m_errorText = url.prettyURL(); 00850 return; 00851 } 00852 m_redirectionURL = url; // We'll remember that when the job finishes 00853 if (m_url.hasUser() && !url.hasUser() && (m_url.host().lower() == url.host().lower())) 00854 m_redirectionURL.setUser(m_url.user()); // Preserve user 00855 // Tell the user that we haven't finished yet 00856 emit redirection(this, m_redirectionURL); 00857 } 00858 00859 void StatJob::slotFinished() 00860 { 00861 if ( m_redirectionURL.isEmpty() || !m_redirectionURL.isValid()) 00862 { 00863 // Return slave to the scheduler 00864 SimpleJob::slotFinished(); 00865 } else { 00866 //kdDebug(7007) << "StatJob: Redirection to " << m_redirectionURL << endl; 00867 if (queryMetaData("permanent-redirect")=="true") 00868 emit permanentRedirection(this, m_url, m_redirectionURL); 00869 m_url = m_redirectionURL; 00870 m_redirectionURL = KURL(); 00871 m_packedArgs.truncate(0); 00872 QDataStream stream( m_packedArgs, IO_WriteOnly ); 00873 stream << m_url; 00874 00875 // Return slave to the scheduler 00876 slaveDone(); 00877 Scheduler::doJob(this); 00878 } 00879 } 00880 00881 void StatJob::slotMetaData( const KIO::MetaData &_metaData) { 00882 SimpleJob::slotMetaData(_metaData); 00883 storeSSLSessionFromJob(m_redirectionURL); 00884 } 00885 00886 StatJob *KIO::stat(const KURL& url, bool showProgressInfo) 00887 { 00888 // Assume sideIsSource. Gets are more common than puts. 00889 return stat( url, true, 2, showProgressInfo ); 00890 } 00891 00892 StatJob *KIO::stat(const KURL& url, bool sideIsSource, short int details, bool showProgressInfo) 00893 { 00894 kdDebug(7007) << "stat " << url << endl; 00895 KIO_ARGS << url; 00896 StatJob * job = new StatJob(url, CMD_STAT, packedArgs, showProgressInfo ); 00897 job->setSide( sideIsSource ); 00898 job->setDetails( details ); 00899 if ( showProgressInfo ) 00900 Observer::self()->stating( job, url ); 00901 return job; 00902 } 00903 00904 SimpleJob *KIO::http_update_cache( const KURL& url, bool no_cache, time_t expireDate) 00905 { 00906 assert( (url.protocol() == "http") || (url.protocol() == "https") ); 00907 // Send http update_cache command (2) 00908 KIO_ARGS << (int)2 << url << no_cache << expireDate; 00909 SimpleJob * job = new SimpleJob( url, CMD_SPECIAL, packedArgs, false ); 00910 Scheduler::scheduleJob(job); 00911 return job; 00912 } 00913 00915 00916 TransferJob::TransferJob( const KURL& url, int command, 00917 const QByteArray &packedArgs, 00918 const QByteArray &_staticData, 00919 bool showProgressInfo) 00920 : SimpleJob(url, command, packedArgs, showProgressInfo), staticData( _staticData) 00921 { 00922 m_suspended = false; 00923 m_errorPage = false; 00924 m_subJob = 0L; 00925 if ( showProgressInfo ) 00926 Observer::self()->slotTransferring( this, url ); 00927 } 00928 00929 // Slave sends data 00930 void TransferJob::slotData( const QByteArray &_data) 00931 { 00932 if(m_redirectionURL.isEmpty() || !m_redirectionURL.isValid() || m_error) 00933 emit data( this, _data); 00934 } 00935 00936 // Slave got a redirection request 00937 void TransferJob::slotRedirection( const KURL &url) 00938 { 00939 kdDebug(7007) << "TransferJob::slotRedirection(" << url << ")" << endl; 00940 if (!kapp->authorizeURLAction("redirect", m_url, url)) 00941 { 00942 kdWarning(7007) << "TransferJob: Redirection from " << m_url << " to " << url << " REJECTED!" << endl; 00943 return; 00944 } 00945 00946 // Some websites keep redirecting to themselves where each redirection 00947 // acts as the stage in a state-machine. We define "endless redirections" 00948 // as 5 redirections to the same URL. 00949 if (m_redirectionList.contains(url) > 5) 00950 { 00951 kdDebug(7007) << "TransferJob::slotRedirection: CYCLIC REDIRECTION!" << endl; 00952 m_error = ERR_CYCLIC_LINK; 00953 m_errorText = m_url.prettyURL(); 00954 } 00955 else 00956 { 00957 m_redirectionURL = url; // We'll remember that when the job finishes 00958 if (m_url.hasUser() && !url.hasUser() && (m_url.host().lower() == url.host().lower())) 00959 m_redirectionURL.setUser(m_url.user()); // Preserve user 00960 m_redirectionList.append(url); 00961 m_outgoingMetaData["ssl_was_in_use"] = m_incomingMetaData["ssl_in_use"]; 00962 // Tell the user that we haven't finished yet 00963 emit redirection(this, m_redirectionURL); 00964 } 00965 } 00966 00967 void TransferJob::slotFinished() 00968 { 00969 //kdDebug(7007) << "TransferJob::slotFinished(" << this << ", " << m_url << ")" << endl; 00970 if (m_redirectionURL.isEmpty() || !m_redirectionURL.isValid()) 00971 SimpleJob::slotFinished(); 00972 else { 00973 //kdDebug(7007) << "TransferJob: Redirection to " << m_redirectionURL << endl; 00974 if (queryMetaData("permanent-redirect")=="true") 00975 emit permanentRedirection(this, m_url, m_redirectionURL); 00976 // Honour the redirection 00977 // We take the approach of "redirecting this same job" 00978 // Another solution would be to create a subjob, but the same problem 00979 // happens (unpacking+repacking) 00980 staticData.truncate(0); 00981 m_incomingMetaData.clear(); 00982 if (queryMetaData("cache") != "reload") 00983 addMetaData("cache","refresh"); 00984 m_suspended = false; 00985 m_url = m_redirectionURL; 00986 m_redirectionURL = KURL(); 00987 // The very tricky part is the packed arguments business 00988 QString dummyStr; 00989 KURL dummyUrl; 00990 QDataStream istream( m_packedArgs, IO_ReadOnly ); 00991 switch( m_command ) { 00992 case CMD_GET: { 00993 m_packedArgs.truncate(0); 00994 QDataStream stream( m_packedArgs, IO_WriteOnly ); 00995 stream << m_url; 00996 break; 00997 } 00998 case CMD_PUT: { 00999 int permissions; 01000 Q_INT8 iOverwrite, iResume; 01001 istream >> dummyUrl >> iOverwrite >> iResume >> permissions; 01002 m_packedArgs.truncate(0); 01003 QDataStream stream( m_packedArgs, IO_WriteOnly ); 01004 stream << m_url << iOverwrite << iResume << permissions; 01005 break; 01006 } 01007 case CMD_SPECIAL: { 01008 int specialcmd; 01009 istream >> specialcmd; 01010 if (specialcmd == 1) // HTTP POST 01011 { 01012 addMetaData("cache","reload"); 01013 m_packedArgs.truncate(0); 01014 QDataStream stream( m_packedArgs, IO_WriteOnly ); 01015 stream << m_url; 01016 m_command = CMD_GET; 01017 } 01018 break; 01019 } 01020 } 01021 01022 // Return slave to the scheduler 01023 slaveDone(); 01024 Scheduler::doJob(this); 01025 } 01026 } 01027 01028 void TransferJob::setAsyncDataEnabled(bool enabled) 01029 { 01030 if (enabled) 01031 extraFlags() |= EF_TransferJobAsync; 01032 else 01033 extraFlags() &= ~EF_TransferJobAsync; 01034 } 01035 01036 void TransferJob::sendAsyncData(const QByteArray &dataForSlave) 01037 { 01038 if (extraFlags() & EF_TransferJobNeedData) 01039 { 01040 m_slave->send( MSG_DATA, dataForSlave ); 01041 if (extraFlags() & EF_TransferJobDataSent) 01042 { 01043 KIO::filesize_t size = getProcessedSize()+dataForSlave.size(); 01044 setProcessedSize(size); 01045 emit processedSize( this, size ); 01046 if ( size > m_totalSize ) { 01047 slotTotalSize(size); // safety 01048 } 01049 emitPercent( size, m_totalSize ); 01050 } 01051 } 01052 01053 extraFlags() &= ~EF_TransferJobNeedData; 01054 } 01055 01056 void TransferJob::setReportDataSent(bool enabled) 01057 { 01058 if (enabled) 01059 extraFlags() |= EF_TransferJobDataSent; 01060 else 01061 extraFlags() &= ~EF_TransferJobDataSent; 01062 } 01063 01064 bool TransferJob::reportDataSent() 01065 { 01066 return (extraFlags() & EF_TransferJobDataSent); 01067 } 01068 01069 01070 // Slave requests data 01071 void TransferJob::slotDataReq() 01072 { 01073 QByteArray dataForSlave; 01074 01075 extraFlags() |= EF_TransferJobNeedData; 01076 01077 if (!staticData.isEmpty()) 01078 { 01079 dataForSlave = staticData; 01080 staticData = QByteArray(); 01081 } 01082 else 01083 { 01084 emit dataReq( this, dataForSlave); 01085 01086 if (extraFlags() & EF_TransferJobAsync) 01087 return; 01088 } 01089 01090 static const size_t max_size = 14 * 1024 * 1024; 01091 if (dataForSlave.size() > max_size) 01092 { 01093 kdDebug(7007) << "send " << dataForSlave.size() / 1024 / 1024 << "MB of data in TransferJob::dataReq. This needs to be splitted, which requires a copy. Fix the application.\n"; 01094 staticData.duplicate(dataForSlave.data() + max_size , dataForSlave.size() - max_size); 01095 dataForSlave.truncate(max_size); 01096 } 01097 01098 sendAsyncData(dataForSlave); 01099 01100 if (m_subJob) 01101 { 01102 // Bitburger protocol in action 01103 suspend(); // Wait for more data from subJob. 01104 m_subJob->resume(); // Ask for more! 01105 } 01106 } 01107 01108 void TransferJob::slotMimetype( const QString& type ) 01109 { 01110 m_mimetype = type; 01111 emit mimetype( this, m_mimetype); 01112 } 01113 01114 01115 void TransferJob::suspend() 01116 { 01117 m_suspended = true; 01118 if (m_slave) 01119 m_slave->suspend(); 01120 } 01121 01122 void TransferJob::resume() 01123 { 01124 m_suspended = false; 01125 if (m_slave) 01126 m_slave->resume(); 01127 } 01128 01129 void TransferJob::start(Slave *slave) 01130 { 01131 assert(slave); 01132 connect( slave, SIGNAL( data( const QByteArray & ) ), 01133 SLOT( slotData( const QByteArray & ) ) ); 01134 01135 connect( slave, SIGNAL( dataReq() ), 01136 SLOT( slotDataReq() ) ); 01137 01138 connect( slave, SIGNAL( redirection(const KURL &) ), 01139 SLOT( slotRedirection(const KURL &) ) ); 01140 01141 connect( slave, SIGNAL(mimeType( const QString& ) ), 01142 SLOT( slotMimetype( const QString& ) ) ); 01143 01144 connect( slave, SIGNAL(errorPage() ), 01145 SLOT( slotErrorPage() ) ); 01146 01147 connect( slave, SIGNAL( needSubURLData() ), 01148 SLOT( slotNeedSubURLData() ) ); 01149 01150 connect( slave, SIGNAL(canResume( KIO::filesize_t ) ), 01151 SLOT( slotCanResume( KIO::filesize_t ) ) ); 01152 01153 if (slave->suspended()) 01154 { 01155 m_mimetype = "unknown"; 01156 // WABA: The slave was put on hold. Resume operation. 01157 slave->resume(); 01158 } 01159 01160 SimpleJob::start(slave); 01161 if (m_suspended) 01162 slave->suspend(); 01163 } 01164 01165 void TransferJob::slotNeedSubURLData() 01166 { 01167 // Job needs data from subURL. 01168 m_subJob = KIO::get( m_subUrl, false, false); 01169 suspend(); // Put job on hold until we have some data. 01170 connect(m_subJob, SIGNAL( data(KIO::Job*,const QByteArray &)), 01171 SLOT( slotSubURLData(KIO::Job*,const QByteArray &))); 01172 addSubjob(m_subJob); 01173 } 01174 01175 void TransferJob::slotSubURLData(KIO::Job*, const QByteArray &data) 01176 { 01177 // The Alternating Bitburg protocol in action again. 01178 staticData = data; 01179 m_subJob->suspend(); // Put job on hold until we have delivered the data. 01180 resume(); // Activate ourselves again. 01181 } 01182 01183 void TransferJob::slotMetaData( const KIO::MetaData &_metaData) { 01184 SimpleJob::slotMetaData(_metaData); 01185 storeSSLSessionFromJob(m_redirectionURL); 01186 } 01187 01188 void TransferJob::slotErrorPage() 01189 { 01190 m_errorPage = true; 01191 } 01192 01193 void TransferJob::slotCanResume( KIO::filesize_t offset ) 01194 { 01195 emit canResume(this, offset); 01196 } 01197 01198 void TransferJob::slotResult( KIO::Job *job) 01199 { 01200 // This can only be our suburl. 01201 assert(job == m_subJob); 01202 // Did job have an error ? 01203 if ( job->error() ) 01204 { 01205 m_error = job->error(); 01206 m_errorText = job->errorText(); 01207 01208 emitResult(); 01209 return; 01210 } 01211 01212 if (job == m_subJob) 01213 { 01214 m_subJob = 0; // No action required 01215 resume(); // Make sure we get the remaining data. 01216 } 01217 removeSubjob( job, false, false ); // Remove job, but don't kill this job. 01218 } 01219 01220 TransferJob *KIO::get( const KURL& url, bool reload, bool showProgressInfo ) 01221 { 01222 // Send decoded path and encoded query 01223 KIO_ARGS << url; 01224 TransferJob * job = new TransferJob( url, CMD_GET, packedArgs, QByteArray(), showProgressInfo ); 01225 if (reload) 01226 job->addMetaData("cache", "reload"); 01227 return job; 01228 } 01229 01230 class PostErrorJob : public TransferJob 01231 { 01232 public: 01233 01234 PostErrorJob(int _error, const QString& url, const QByteArray &packedArgs, const QByteArray &postData, bool showProgressInfo) 01235 : TransferJob(KURL(), CMD_SPECIAL, packedArgs, postData, showProgressInfo) 01236 { 01237 m_error = _error; 01238 m_errorText = url; 01239 } 01240 01241 }; 01242 01243 TransferJob *KIO::http_post( const KURL& url, const QByteArray &postData, bool showProgressInfo ) 01244 { 01245 int _error = 0; 01246 01247 // filter out some malicious ports 01248 static const int bad_ports[] = { 01249 1, // tcpmux 01250 7, // echo 01251 9, // discard 01252 11, // systat 01253 13, // daytime 01254 15, // netstat 01255 17, // qotd 01256 19, // chargen 01257 20, // ftp-data 01258 21, // ftp-cntl 01259 22, // ssh 01260 23, // telnet 01261 25, // smtp 01262 37, // time 01263 42, // name 01264 43, // nicname 01265 53, // domain 01266 77, // priv-rjs 01267 79, // finger 01268 87, // ttylink 01269 95, // supdup 01270 101, // hostriame 01271 102, // iso-tsap 01272 103, // gppitnp 01273 104, // acr-nema 01274 109, // pop2 01275 110, // pop3 01276 111, // sunrpc 01277 113, // auth 01278 115, // sftp 01279 117, // uucp-path 01280 119, // nntp 01281 123, // NTP 01282 135, // loc-srv / epmap 01283 139, // netbios 01284 143, // imap2 01285 179, // BGP 01286 389, // ldap 01287 512, // print / exec 01288 513, // login 01289 514, // shell 01290 515, // printer 01291 526, // tempo 01292 530, // courier 01293 531, // Chat 01294 532, // netnews 01295 540, // uucp 01296 556, // remotefs 01297 587, // sendmail 01298 601, // 01299 989, // ftps data 01300 990, // ftps 01301 992, // telnets 01302 993, // imap/SSL 01303 995, // pop3/SSL 01304 1080, // SOCKS 01305 2049, // nfs 01306 4045, // lockd 01307 6000, // x11 01308 6667, // irc 01309 0}; 01310 for (int cnt=0; bad_ports[cnt]; ++cnt) 01311 if (url.port() == bad_ports[cnt]) 01312 { 01313 _error = KIO::ERR_POST_DENIED; 01314 break; 01315 } 01316 01317 if( _error ) 01318 { 01319 static bool override_loaded = false; 01320 static QValueList< int >* overriden_ports = NULL; 01321 if( !override_loaded ) 01322 { 01323 KConfig cfg( "kio_httprc", true ); 01324 overriden_ports = new QValueList< int >; 01325 *overriden_ports = cfg.readIntListEntry( "OverriddenPorts" ); 01326 override_loaded = true; 01327 } 01328 for( QValueList< int >::ConstIterator it = overriden_ports->begin(); 01329 it != overriden_ports->end(); 01330 ++it ) 01331 if( overriden_ports->contains( url.port())) 01332 _error = 0; 01333 } 01334 01335 // filter out non https? protocols 01336 if ((url.protocol() != "http") && (url.protocol() != "https" )) 01337 _error = KIO::ERR_POST_DENIED; 01338 01339 bool redirection = false; 01340 KURL _url(url); 01341 if (_url.path().isEmpty()) 01342 { 01343 redirection = true; 01344 _url.setPath("/"); 01345 } 01346 01347 if (!_error && !kapp->authorizeURLAction("open", KURL(), _url)) 01348 _error = KIO::ERR_ACCESS_DENIED; 01349 01350 // if request is not valid, return an invalid transfer job 01351 if (_error) 01352 { 01353 KIO_ARGS << (int)1 << url; 01354 TransferJob * job = new PostErrorJob(_error, url.prettyURL(), packedArgs, postData, showProgressInfo); 01355 return job; 01356 } 01357 01358 // Send http post command (1), decoded path and encoded query 01359 KIO_ARGS << (int)1 << _url; 01360 TransferJob * job = new TransferJob( _url, CMD_SPECIAL, 01361 packedArgs, postData, showProgressInfo ); 01362 01363 if (redirection) 01364 QTimer::singleShot(0, job, SLOT(slotPostRedirection()) ); 01365 01366 return job; 01367 } 01368 01369 // http post got redirected from http://host to http://host/ by TransferJob 01370 // We must do this redirection ourselves because redirections by the 01371 // slave change post jobs into get jobs. 01372 void TransferJob::slotPostRedirection() 01373 { 01374 kdDebug(7007) << "TransferJob::slotPostRedirection(" << m_url << ")" << endl; 01375 // Tell the user about the new url. 01376 emit redirection(this, m_url); 01377 } 01378 01379 01380 TransferJob *KIO::put( const KURL& url, int permissions, 01381 bool overwrite, bool resume, bool showProgressInfo ) 01382 { 01383 KIO_ARGS << url << Q_INT8( overwrite ? 1 : 0 ) << Q_INT8( resume ? 1 : 0 ) << permissions; 01384 TransferJob * job = new TransferJob( url, CMD_PUT, packedArgs, QByteArray(), showProgressInfo ); 01385 return job; 01386 } 01387 01389 01390 StoredTransferJob::StoredTransferJob(const KURL& url, int command, 01391 const QByteArray &packedArgs, 01392 const QByteArray &_staticData, 01393 bool showProgressInfo) 01394 : TransferJob( url, command, packedArgs, _staticData, showProgressInfo ), 01395 m_uploadOffset( 0 ) 01396 { 01397 connect( this, SIGNAL( data( KIO::Job *, const QByteArray & ) ), 01398 SLOT( slotStoredData( KIO::Job *, const QByteArray & ) ) ); 01399 connect( this, SIGNAL( dataReq( KIO::Job *, QByteArray & ) ), 01400 SLOT( slotStoredDataReq( KIO::Job *, QByteArray & ) ) ); 01401 } 01402 01403 void StoredTransferJob::setData( const QByteArray& arr ) 01404 { 01405 Q_ASSERT( m_data.isNull() ); // check that we're only called once 01406 Q_ASSERT( m_uploadOffset == 0 ); // no upload started yet 01407 m_data = arr; 01408 } 01409 01410 void StoredTransferJob::slotStoredData( KIO::Job *, const QByteArray &data ) 01411 { 01412 // check for end-of-data marker: 01413 if ( data.size() == 0 ) 01414 return; 01415 unsigned int oldSize = m_data.size(); 01416 m_data.resize( oldSize + data.size(), QGArray::SpeedOptim ); 01417 memcpy( m_data.data() + oldSize, data.data(), data.size() ); 01418 } 01419 01420 void StoredTransferJob::slotStoredDataReq( KIO::Job *, QByteArray &data ) 01421 { 01422 // Inspired from kmail's KMKernel::byteArrayToRemoteFile 01423 // send the data in 64 KB chunks 01424 const int MAX_CHUNK_SIZE = 64*1024; 01425 int remainingBytes = m_data.size() - m_uploadOffset; 01426 if( remainingBytes > MAX_CHUNK_SIZE ) { 01427 // send MAX_CHUNK_SIZE bytes to the receiver (deep copy) 01428 data.duplicate( m_data.data() + m_uploadOffset, MAX_CHUNK_SIZE ); 01429 m_uploadOffset += MAX_CHUNK_SIZE; 01430 //kdDebug() << "Sending " << MAX_CHUNK_SIZE << " bytes (" 01431 // << remainingBytes - MAX_CHUNK_SIZE << " bytes remain)\n"; 01432 } else { 01433 // send the remaining bytes to the receiver (deep copy) 01434 data.duplicate( m_data.data() + m_uploadOffset, remainingBytes ); 01435 m_data = QByteArray(); 01436 m_uploadOffset = 0; 01437 //kdDebug() << "Sending " << remainingBytes << " bytes\n"; 01438 } 01439 } 01440 01441 StoredTransferJob *KIO::storedGet( const KURL& url, bool reload, bool showProgressInfo ) 01442 { 01443 // Send decoded path and encoded query 01444 KIO_ARGS << url; 01445 StoredTransferJob * job = new StoredTransferJob( url, CMD_GET, packedArgs, QByteArray(), showProgressInfo ); 01446 if (reload) 01447 job->addMetaData("cache", "reload"); 01448 return job; 01449 } 01450 01451 StoredTransferJob *KIO::storedPut( const QByteArray& arr, const KURL& url, int permissions, 01452 bool overwrite, bool resume, bool showProgressInfo ) 01453 { 01454 KIO_ARGS << url << Q_INT8( overwrite ? 1 : 0 ) << Q_INT8( resume ? 1 : 0 ) << permissions; 01455 StoredTransferJob * job = new StoredTransferJob( url, CMD_PUT, packedArgs, QByteArray(), showProgressInfo ); 01456 job->setData( arr ); 01457 return job; 01458 } 01459 01461 01462 MimetypeJob::MimetypeJob( const KURL& url, int command, 01463 const QByteArray &packedArgs, bool showProgressInfo ) 01464 : TransferJob(url, command, packedArgs, QByteArray(), showProgressInfo) 01465 { 01466 } 01467 01468 void MimetypeJob::start(Slave *slave) 01469 { 01470 TransferJob::start(slave); 01471 } 01472 01473 01474 void MimetypeJob::slotFinished( ) 01475 { 01476 //kdDebug(7007) << "MimetypeJob::slotFinished()" << endl; 01477 if ( m_error == KIO::ERR_IS_DIRECTORY ) 01478 { 01479 // It is in fact a directory. This happens when HTTP redirects to FTP. 01480 // Due to the "protocol doesn't support listing" code in KRun, we 01481 // assumed it was a file. 01482 kdDebug(7007) << "It is in fact a directory!" << endl; 01483 m_mimetype = QString::fromLatin1("inode/directory"); 01484 emit TransferJob::mimetype( this, m_mimetype ); 01485 m_error = 0; 01486 } 01487 if ( m_redirectionURL.isEmpty() || !m_redirectionURL.isValid() || m_error ) 01488 { 01489 // Return slave to the scheduler 01490 TransferJob::slotFinished(); 01491 } else { 01492 //kdDebug(7007) << "MimetypeJob: Redirection to " << m_redirectionURL << endl; 01493 if (queryMetaData("permanent-redirect")=="true") 01494 emit permanentRedirection(this, m_url, m_redirectionURL); 01495 staticData.truncate(0); 01496 m_suspended = false; 01497 m_url = m_redirectionURL; 01498 m_redirectionURL = KURL(); 01499 m_packedArgs.truncate(0); 01500 QDataStream stream( m_packedArgs, IO_WriteOnly ); 01501 stream << m_url; 01502 01503 // Return slave to the scheduler 01504 slaveDone(); 01505 Scheduler::doJob(this); 01506 } 01507 } 01508 01509 MimetypeJob *KIO::mimetype(const KURL& url, bool showProgressInfo ) 01510 { 01511 KIO_ARGS << url; 01512 MimetypeJob * job = new MimetypeJob(url, CMD_MIMETYPE, packedArgs, showProgressInfo); 01513 if ( showProgressInfo ) 01514 Observer::self()->stating( job, url ); 01515 return job; 01516 } 01517 01519 01520 DirectCopyJob::DirectCopyJob( const KURL& url, int command, 01521 const QByteArray &packedArgs, bool showProgressInfo ) 01522 : SimpleJob(url, command, packedArgs, showProgressInfo) 01523 { 01524 } 01525 01526 void DirectCopyJob::start( Slave* slave ) 01527 { 01528 connect( slave, SIGNAL(canResume( KIO::filesize_t ) ), 01529 SLOT( slotCanResume( KIO::filesize_t ) ) ); 01530 SimpleJob::start(slave); 01531 } 01532 01533 void DirectCopyJob::slotCanResume( KIO::filesize_t offset ) 01534 { 01535 emit canResume(this, offset); 01536 } 01537 01539 01540 01541 class FileCopyJob::FileCopyJobPrivate 01542 { 01543 public: 01544 KIO::filesize_t m_sourceSize; 01545 time_t m_modificationTime; 01546 SimpleJob *m_delJob; 01547 }; 01548 01549 /* 01550 * The FileCopyJob works according to the famous Bayern 01551 * 'Alternating Bitburger Protocol': we either drink a beer or we 01552 * we order a beer, but never both at the same time. 01553 * Tranlated to io-slaves: We alternate between receiving a block of data 01554 * and sending it away. 01555 */ 01556 FileCopyJob::FileCopyJob( const KURL& src, const KURL& dest, int permissions, 01557 bool move, bool overwrite, bool resume, bool showProgressInfo) 01558 : Job(showProgressInfo), m_src(src), m_dest(dest), 01559 m_permissions(permissions), m_move(move), m_overwrite(overwrite), m_resume(resume), 01560 m_totalSize(0) 01561 { 01562 if (showProgressInfo && !move) 01563 Observer::self()->slotCopying( this, src, dest ); 01564 else if (showProgressInfo && move) 01565 Observer::self()->slotMoving( this, src, dest ); 01566 01567 //kdDebug(7007) << "FileCopyJob::FileCopyJob()" << endl; 01568 m_moveJob = 0; 01569 m_copyJob = 0; 01570 m_getJob = 0; 01571 m_putJob = 0; 01572 d = new FileCopyJobPrivate; 01573 d->m_delJob = 0; 01574 d->m_sourceSize = (KIO::filesize_t) -1; 01575 d->m_modificationTime = static_cast<time_t>( -1 ); 01576 QTimer::singleShot(0, this, SLOT(slotStart())); 01577 } 01578 01579 void FileCopyJob::slotStart() 01580 { 01581 if ( m_move ) 01582 { 01583 // The if() below must be the same as the one in startBestCopyMethod 01584 if ((m_src.protocol() == m_dest.protocol()) && 01585 (m_src.host() == m_dest.host()) && 01586 (m_src.port() == m_dest.port()) && 01587 (m_src.user() == m_dest.user()) && 01588 (m_src.pass() == m_dest.pass()) && 01589 !m_src.hasSubURL() && !m_dest.hasSubURL()) 01590 { 01591 startRenameJob(m_src); 01592 return; 01593 } 01594 else if (m_src.isLocalFile() && KProtocolInfo::canRenameFromFile(m_dest)) 01595 { 01596 startRenameJob(m_dest); 01597 return; 01598 } 01599 else if (m_dest.isLocalFile() && KProtocolInfo::canRenameToFile(m_src)) 01600 { 01601 startRenameJob(m_src); 01602 return; 01603 } 01604 // No fast-move available, use copy + del. 01605 } 01606 startBestCopyMethod(); 01607 } 01608 01609 void FileCopyJob::startBestCopyMethod() 01610 { 01611 if ((m_src.protocol() == m_dest.protocol()) && 01612 (m_src.host() == m_dest.host()) && 01613 (m_src.port() == m_dest.port()) && 01614 (m_src.user() == m_dest.user()) && 01615 (m_src.pass() == m_dest.pass()) && 01616 !m_src.hasSubURL() && !m_dest.hasSubURL()) 01617 { 01618 startCopyJob(); 01619 } 01620 else if (m_src.isLocalFile() && KProtocolInfo::canCopyFromFile(m_dest)) 01621 { 01622 startCopyJob(m_dest); 01623 } 01624 else if (m_dest.isLocalFile() && KProtocolInfo::canCopyToFile(m_src)) 01625 { 01626 startCopyJob(m_src); 01627 } 01628 else 01629 { 01630 startDataPump(); 01631 } 01632 } 01633 01634 FileCopyJob::~FileCopyJob() 01635 { 01636 delete d; 01637 } 01638 01639 void FileCopyJob::setSourceSize( off_t size ) 01640 { 01641 d->m_sourceSize = size; 01642 if (size != (off_t) -1) 01643 m_totalSize = size; 01644 } 01645 01646 void FileCopyJob::setSourceSize64( KIO::filesize_t size ) 01647 { 01648 d->m_sourceSize = size; 01649 if (size != (KIO::filesize_t) -1) 01650 m_totalSize = size; 01651 } 01652 01653 void FileCopyJob::setModificationTime( time_t mtime ) 01654 { 01655 d->m_modificationTime = mtime; 01656 } 01657 01658 void FileCopyJob::startCopyJob() 01659 { 01660 startCopyJob(m_src); 01661 } 01662 01663 void FileCopyJob::startCopyJob(const KURL &slave_url) 01664 { 01665 //kdDebug(7007) << "FileCopyJob::startCopyJob()" << endl; 01666 KIO_ARGS << m_src << m_dest << m_permissions << (Q_INT8) m_overwrite; 01667 m_copyJob = new DirectCopyJob(slave_url, CMD_COPY, packedArgs, false); 01668 addSubjob( m_copyJob ); 01669 connectSubjob( m_copyJob ); 01670 connect( m_copyJob, SIGNAL(canResume(KIO::Job *, KIO::filesize_t)), 01671 SLOT( slotCanResume(KIO::Job *, KIO::filesize_t))); 01672 } 01673 01674 void FileCopyJob::startRenameJob(const KURL &slave_url) 01675 { 01676 KIO_ARGS << m_src << m_dest << (Q_INT8) m_overwrite; 01677 m_moveJob = new SimpleJob(slave_url, CMD_RENAME, packedArgs, false); 01678 addSubjob( m_moveJob ); 01679 connectSubjob( m_moveJob ); 01680 } 01681 01682 void FileCopyJob::connectSubjob( SimpleJob * job ) 01683 { 01684 connect( job, SIGNAL(totalSize( KIO::Job*, KIO::filesize_t )), 01685 this, SLOT( slotTotalSize(KIO::Job*, KIO::filesize_t)) ); 01686 01687 connect( job, SIGNAL(processedSize( KIO::Job*, KIO::filesize_t )), 01688 this, SLOT( slotProcessedSize(KIO::Job*, KIO::filesize_t)) ); 01689 01690 connect( job, SIGNAL(percent( KIO::Job*, unsigned long )), 01691 this, SLOT( slotPercent(KIO::Job*, unsigned long)) ); 01692 01693 } 01694 01695 void FileCopyJob::slotProcessedSize( KIO::Job *, KIO::filesize_t size ) 01696 { 01697 setProcessedSize(size); 01698 emit processedSize( this, size ); 01699 if ( size > m_totalSize ) { 01700 slotTotalSize( this, size ); // safety 01701 } 01702 emitPercent( size, m_totalSize ); 01703 } 01704 01705 void FileCopyJob::slotTotalSize( KIO::Job*, KIO::filesize_t size ) 01706 { 01707 if (size > m_totalSize) 01708 { 01709 m_totalSize = size; 01710 emit totalSize( this, m_totalSize ); 01711 } 01712 } 01713 01714 void FileCopyJob::slotPercent( KIO::Job*, unsigned long pct ) 01715 { 01716 if ( pct > m_percent ) 01717 { 01718 m_percent = pct; 01719 emit percent( this, m_percent ); 01720 } 01721 } 01722 01723 void FileCopyJob::startDataPump() 01724 { 01725 //kdDebug(7007) << "FileCopyJob::startDataPump()" << endl; 01726 01727 m_canResume = false; 01728 m_resumeAnswerSent = false; 01729 m_getJob = 0L; // for now 01730 m_putJob = put( m_dest, m_permissions, m_overwrite, m_resume, false /* no GUI */); 01731 if ( d->m_modificationTime != static_cast<time_t>( -1 ) ) { 01732 QDateTime dt; dt.setTime_t( d->m_modificationTime ); 01733 m_putJob->addMetaData( "modified", dt.toString( Qt::ISODate ) ); 01734 } 01735 //kdDebug(7007) << "FileCopyJob: m_putJob = " << m_putJob << " m_dest=" << m_dest << endl; 01736 01737 // The first thing the put job will tell us is whether we can 01738 // resume or not (this is always emitted) 01739 connect( m_putJob, SIGNAL(canResume(KIO::Job *, KIO::filesize_t)), 01740 SLOT( slotCanResume(KIO::Job *, KIO::filesize_t))); 01741 connect( m_putJob, SIGNAL(dataReq(KIO::Job *, QByteArray&)), 01742 SLOT( slotDataReq(KIO::Job *, QByteArray&))); 01743 addSubjob( m_putJob ); 01744 } 01745 01746 void FileCopyJob::slotCanResume( KIO::Job* job, KIO::filesize_t offset ) 01747 { 01748 if ( job == m_putJob || job == m_copyJob ) 01749 { 01750 //kdDebug(7007) << "FileCopyJob::slotCanResume from PUT job. offset=" << KIO::number(offset) << endl; 01751 if (offset) 01752 { 01753 RenameDlg_Result res = R_RESUME; 01754 01755 if (!KProtocolManager::autoResume() && !m_overwrite) 01756 { 01757 QString newPath; 01758 KIO::Job* job = ( !m_progressId && parentJob() ) ? parentJob() : this; 01759 // Ask confirmation about resuming previous transfer 01760 res = Observer::self()->open_RenameDlg( 01761 job, i18n("File Already Exists"), 01762 m_src.url(), 01763 m_dest.url(), 01764 (RenameDlg_Mode) (M_OVERWRITE | M_RESUME | M_NORENAME), newPath, 01765 d->m_sourceSize, offset ); 01766 } 01767 01768 if ( res == R_OVERWRITE || m_overwrite ) 01769 offset = 0; 01770 else if ( res == R_CANCEL ) 01771 { 01772 if ( job == m_putJob ) 01773 m_putJob->kill(true); 01774 else 01775 m_copyJob->kill(true); 01776 m_error = ERR_USER_CANCELED; 01777 emitResult(); 01778 return; 01779 } 01780 } 01781 else 01782 m_resumeAnswerSent = true; // No need for an answer 01783 01784 if ( job == m_putJob ) 01785 { 01786 m_getJob = get( m_src, false, false /* no GUI */ ); 01787 //kdDebug(7007) << "FileCopyJob: m_getJob = " << m_getJob << endl; 01788 m_getJob->addMetaData( "errorPage", "false" ); 01789 m_getJob->addMetaData( "AllowCompressedPage", "false" ); 01790 // Set size in subjob. This helps if the slave doesn't emit totalSize. 01791 if ( d->m_sourceSize != (KIO::filesize_t)-1 ) 01792 m_getJob->slotTotalSize( d->m_sourceSize ); 01793 if (offset) 01794 { 01795 //kdDebug(7007) << "Setting metadata for resume to " << (unsigned long) offset << endl; 01796 // TODO KDE4: rename to seek or offset and document it 01797 // This isn't used only for resuming, but potentially also for extracting (#72302). 01798 m_getJob->addMetaData( "resume", KIO::number(offset) ); 01799 01800 // Might or might not get emitted 01801 connect( m_getJob, SIGNAL(canResume(KIO::Job *, KIO::filesize_t)), 01802 SLOT( slotCanResume(KIO::Job *, KIO::filesize_t))); 01803 } 01804 m_putJob->slave()->setOffset( offset ); 01805 01806 m_putJob->suspend(); 01807 addSubjob( m_getJob ); 01808 connectSubjob( m_getJob ); // Progress info depends on get 01809 m_getJob->resume(); // Order a beer 01810 01811 connect( m_getJob, SIGNAL(data(KIO::Job*,const QByteArray&)), 01812 SLOT( slotData(KIO::Job*,const QByteArray&)) ); 01813 connect( m_getJob, SIGNAL(mimetype(KIO::Job*,const QString&) ), 01814 SLOT(slotMimetype(KIO::Job*,const QString&)) ); 01815 } 01816 else // copyjob 01817 { 01818 m_copyJob->slave()->sendResumeAnswer( offset != 0 ); 01819 } 01820 } 01821 else if ( job == m_getJob ) 01822 { 01823 // Cool, the get job said ok, we can resume 01824 m_canResume = true; 01825 //kdDebug(7007) << "FileCopyJob::slotCanResume from the GET job -> we can resume" << endl; 01826 01827 m_getJob->slave()->setOffset( m_putJob->slave()->offset() ); 01828 } 01829 else 01830 kdWarning(7007) << "FileCopyJob::slotCanResume from unknown job=" << job 01831 << " m_getJob=" << m_getJob << " m_putJob=" << m_putJob << endl; 01832 } 01833 01834 void FileCopyJob::slotData( KIO::Job * , const QByteArray &data) 01835 { 01836 //kdDebug(7007) << "FileCopyJob::slotData" << endl; 01837 //kdDebug(7007) << " data size : " << data.size() << endl; 01838 assert(m_putJob); 01839 if (!m_putJob) return; // Don't crash 01840 m_getJob->suspend(); 01841 m_putJob->resume(); // Drink the beer 01842 m_buffer = data; 01843 01844 // On the first set of data incoming, we tell the "put" slave about our 01845 // decision about resuming 01846 if (!m_resumeAnswerSent) 01847 { 01848 m_resumeAnswerSent = true; 01849 //kdDebug(7007) << "FileCopyJob::slotData (first time) -> send resume answer " << m_canResume << endl; 01850 m_putJob->slave()->sendResumeAnswer( m_canResume ); 01851 } 01852 } 01853 01854 void FileCopyJob::slotDataReq( KIO::Job * , QByteArray &data) 01855 { 01856 //kdDebug(7007) << "FileCopyJob::slotDataReq" << endl; 01857 if (!m_resumeAnswerSent && !m_getJob) 01858 { 01859 // This can't happen (except as a migration bug on 12/10/2000) 01860 m_error = ERR_INTERNAL; 01861 m_errorText = "'Put' job didn't send canResume or 'Get' job didn't send data!"; 01862 m_putJob->kill(true); 01863 emitResult(); 01864 return; 01865 } 01866 if (m_getJob) 01867 { 01868 m_getJob->resume(); // Order more beer 01869 m_putJob->suspend(); 01870 } 01871 data = m_buffer; 01872 m_buffer = QByteArray(); 01873 } 01874 01875 void FileCopyJob::slotMimetype( KIO::Job*, const QString& type ) 01876 { 01877 emit mimetype( this, type ); 01878 } 01879 01880 void FileCopyJob::slotResult( KIO::Job *job) 01881 { 01882 //kdDebug(7007) << "FileCopyJob this=" << this << " ::slotResult(" << job << ")" << endl; 01883 // Did job have an error ? 01884 if ( job->error() ) 01885 { 01886 if ((job == m_moveJob) && (job->error() == ERR_UNSUPPORTED_ACTION)) 01887 { 01888 m_moveJob = 0; 01889 startBestCopyMethod(); 01890 removeSubjob(job); 01891 return; 01892 } 01893 else if ((job == m_copyJob) && (job->error() == ERR_UNSUPPORTED_ACTION)) 01894 { 01895 m_copyJob = 0; 01896 startDataPump(); 01897 removeSubjob(job); 01898 return; 01899 } 01900 else if (job == m_getJob) 01901 { 01902 m_getJob = 0L; 01903 if (m_putJob) 01904 m_putJob->kill(true); 01905 } 01906 else if (job == m_putJob) 01907 { 01908 m_putJob = 0L; 01909 if (m_getJob) 01910 m_getJob->kill(true); 01911 } 01912 m_error = job->error(); 01913 m_errorText = job->errorText(); 01914 emitResult(); 01915 return; 01916 } 01917 01918 if (job == m_moveJob) 01919 { 01920 m_moveJob = 0; // Finished 01921 } 01922 01923 if (job == m_copyJob) 01924 { 01925 m_copyJob = 0; 01926 if (m_move) 01927 { 01928 d->m_delJob = file_delete( m_src, false/*no GUI*/ ); // Delete source 01929 addSubjob(d->m_delJob); 01930 } 01931 } 01932 01933 if (job == m_getJob) 01934 { 01935 m_getJob = 0; // No action required 01936 if (m_putJob) 01937 m_putJob->resume(); 01938 } 01939 01940 if (job == m_putJob) 01941 { 01942 //kdDebug(7007) << "FileCopyJob: m_putJob finished " << endl; 01943 m_putJob = 0; 01944 if (m_getJob) 01945 { 01946 kdWarning(7007) << "WARNING ! Get still going on..." << endl; 01947 m_getJob->resume(); 01948 } 01949 if (m_move) 01950 { 01951 d->m_delJob = file_delete( m_src, false/*no GUI*/ ); // Delete source 01952 addSubjob(d->m_delJob); 01953 } 01954 } 01955 01956 if (job == d->m_delJob) 01957 { 01958 d->m_delJob = 0; // Finished 01959 } 01960 removeSubjob(job); 01961 } 01962 01963 FileCopyJob *KIO::file_copy( const KURL& src, const KURL& dest, int permissions, 01964 bool overwrite, bool resume, bool showProgressInfo) 01965 { 01966 return new FileCopyJob( src, dest, permissions, false, overwrite, resume, showProgressInfo ); 01967 } 01968 01969 FileCopyJob *KIO::file_move( const KURL& src, const KURL& dest, int permissions, 01970 bool overwrite, bool resume, bool showProgressInfo) 01971 { 01972 return new FileCopyJob( src, dest, permissions, true, overwrite, resume, showProgressInfo ); 01973 } 01974 01975 SimpleJob *KIO::file_delete( const KURL& src, bool showProgressInfo) 01976 { 01977 KIO_ARGS << src << Q_INT8(true); // isFile 01978 return new SimpleJob(src, CMD_DEL, packedArgs, showProgressInfo ); 01979 } 01980 01982 01983 // KDE 4: Make it const QString & _prefix 01984 ListJob::ListJob(const KURL& u, bool showProgressInfo, bool _recursive, QString _prefix, bool _includeHidden) : 01985 SimpleJob(u, CMD_LISTDIR, QByteArray(), showProgressInfo), 01986 recursive(_recursive), includeHidden(_includeHidden), prefix(_prefix), m_processedEntries(0) 01987 { 01988 // We couldn't set the args when calling the parent constructor, 01989 // so do it now. 01990 QDataStream stream( m_packedArgs, IO_WriteOnly ); 01991 stream << u; 01992 } 01993 01994 void ListJob::slotListEntries( const KIO::UDSEntryList& list ) 01995 { 01996 // Emit progress info (takes care of emit processedSize and percent) 01997 m_processedEntries += list.count(); 01998 slotProcessedSize( m_processedEntries ); 01999 02000 if (recursive) { 02001 UDSEntryListConstIterator it = list.begin(); 02002 UDSEntryListConstIterator end = list.end(); 02003 02004 for (; it != end; ++it) { 02005 bool isDir = false; 02006 bool isLink = false; 02007 KURL itemURL; 02008 02009 UDSEntry::ConstIterator it2 = (*it).begin(); 02010 UDSEntry::ConstIterator end2 = (*it).end(); 02011 for( ; it2 != end2; it2++ ) { 02012 switch( (*it2).m_uds ) { 02013 case UDS_FILE_TYPE: 02014 isDir = S_ISDIR((*it2).m_long); 02015 break; 02016 case UDS_NAME: 02017 if( itemURL.isEmpty() ) { 02018 itemURL = url(); 02019 itemURL.addPath( (*it2).m_str ); 02020 } 02021 break; 02022 case UDS_URL: 02023 itemURL = (*it2).m_str; 02024 break; 02025 case UDS_LINK_DEST: 02026 // This is a link !!! Don't follow ! 02027 isLink = !(*it2).m_str.isEmpty(); 02028 break; 02029 default: 02030 break; 02031 } 02032 } 02033 if (isDir && !isLink) { 02034 const QString filename = itemURL.fileName(); 02035 // skip hidden dirs when listing if requested 02036 if (filename != ".." && filename != "." && (includeHidden || filename[0] != '.')) { 02037 ListJob *job = new ListJob(itemURL, 02038 false /*no progress info!*/, 02039 true /*recursive*/, 02040 prefix + filename + "/", 02041 includeHidden); 02042 Scheduler::scheduleJob(job); 02043 connect(job, SIGNAL(entries( KIO::Job *, 02044 const KIO::UDSEntryList& )), 02045 SLOT( gotEntries( KIO::Job*, 02046 const KIO::UDSEntryList& ))); 02047 addSubjob(job); 02048 } 02049 } 02050 } 02051 } 02052 02053 // Not recursive, or top-level of recursive listing : return now (send . and .. as well) 02054 // exclusion of hidden files also requires the full sweep, but the case for full-listing 02055 // a single dir is probably common enough to justify the shortcut 02056 if (prefix.isNull() && includeHidden) { 02057 emit entries(this, list); 02058 } else { 02059 // cull the unwanted hidden dirs and/or parent dir references from the listing, then emit that 02060 UDSEntryList newlist; 02061 02062 UDSEntryListConstIterator it = list.begin(); 02063 UDSEntryListConstIterator end = list.end(); 02064 for (; it != end; ++it) { 02065 02066 UDSEntry newone = *it; 02067 UDSEntry::Iterator it2 = newone.begin(); 02068 QString filename; 02069 for( ; it2 != newone.end(); it2++ ) { 02070 if ((*it2).m_uds == UDS_NAME) { 02071 filename = (*it2).m_str; 02072 (*it2).m_str = prefix + filename; 02073 } 02074 } 02075 // Avoid returning entries like subdir/. and subdir/.., but include . and .. for 02076 // the toplevel dir, and skip hidden files/dirs if that was requested 02077 if ( (prefix.isNull() || (filename != ".." && filename != ".") ) 02078 && (includeHidden || (filename[0] != '.') ) ) 02079 newlist.append(newone); 02080 } 02081 02082 emit entries(this, newlist); 02083 } 02084 } 02085 02086 void ListJob::gotEntries(KIO::Job *, const KIO::UDSEntryList& list ) 02087 { 02088 // Forward entries received by subjob - faking we received them ourselves 02089 emit entries(this, list); 02090 } 02091 02092 void ListJob::slotResult( KIO::Job * job ) 02093 { 02094 // If we can't list a subdir, the result is still ok 02095 // This is why we override Job::slotResult() - to skip error checking 02096 removeSubjob( job ); 02097 } 02098 02099 void ListJob::slotRedirection( const KURL & url ) 02100 { 02101 if (!kapp->authorizeURLAction("redirect", m_url, url)) 02102 { 02103 kdWarning(7007) << "ListJob: Redirection from " << m_url << " to " << url << " REJECTED!" << endl; 02104 return; 02105 } 02106 m_redirectionURL = url; // We'll remember that when the job finishes 02107 if (m_url.hasUser() && !url.hasUser() && (m_url.host().lower() == url.host().lower())) 02108 m_redirectionURL.setUser(m_url.user()); // Preserve user 02109 emit redirection( this, m_redirectionURL ); 02110 } 02111 02112 void ListJob::slotFinished() 02113 { 02114 // Support for listing archives as directories 02115 if ( m_error == KIO::ERR_IS_FILE && m_url.isLocalFile() ) { 02116 KMimeType::Ptr ptr = KMimeType::findByURL( m_url, 0, true, true ); 02117 if ( ptr ) { 02118 QString proto = ptr->property("X-KDE-LocalProtocol").toString(); 02119 if ( !proto.isEmpty() && KProtocolInfo::isKnownProtocol(proto) ) { 02120 m_redirectionURL = m_url; 02121 m_redirectionURL.setProtocol( proto ); 02122 m_error = 0; 02123 emit redirection(this,m_redirectionURL); 02124 } 02125 } 02126 } 02127 if ( m_redirectionURL.isEmpty() || !m_redirectionURL.isValid() || m_error ) { 02128 // Return slave to the scheduler 02129 SimpleJob::slotFinished(); 02130 } else { 02131 02132 //kdDebug(7007) << "ListJob: Redirection to " << m_redirectionURL << endl; 02133 if (queryMetaData("permanent-redirect")=="true") 02134 emit permanentRedirection(this, m_url, m_redirectionURL); 02135 m_url = m_redirectionURL; 02136 m_redirectionURL = KURL(); 02137 m_packedArgs.truncate(0); 02138 QDataStream stream( m_packedArgs, IO_WriteOnly ); 02139 stream << m_url; 02140 02141 // Return slave to the scheduler 02142 slaveDone(); 02143 Scheduler::doJob(this); 02144 } 02145 } 02146 02147 void ListJob::slotMetaData( const KIO::MetaData &_metaData) { 02148 SimpleJob::slotMetaData(_metaData); 02149 storeSSLSessionFromJob(m_redirectionURL); 02150 } 02151 02152 ListJob *KIO::listDir( const KURL& url, bool showProgressInfo, bool includeHidden ) 02153 { 02154 ListJob * job = new ListJob(url, showProgressInfo,false,QString::null,includeHidden); 02155 return job; 02156 } 02157 02158 ListJob *KIO::listRecursive( const KURL& url, bool showProgressInfo, bool includeHidden ) 02159 { 02160 ListJob * job = new ListJob(url, showProgressInfo, true,QString::null,includeHidden); 02161 return job; 02162 } 02163 02164 void ListJob::setUnrestricted(bool unrestricted) 02165 { 02166 if (unrestricted) 02167 extraFlags() |= EF_ListJobUnrestricted; 02168 else 02169 extraFlags() &= ~EF_ListJobUnrestricted; 02170 } 02171 02172 void ListJob::start(Slave *slave) 02173 { 02174 if (kapp && !kapp->authorizeURLAction("list", m_url, m_url) && !(extraFlags() & EF_ListJobUnrestricted)) 02175 { 02176 m_error = ERR_ACCESS_DENIED; 02177 m_errorText = m_url.url(); 02178 QTimer::singleShot(0, this, SLOT(slotFinished()) ); 02179 return; 02180 } 02181 connect( slave, SIGNAL( listEntries( const KIO::UDSEntryList& )), 02182 SLOT( slotListEntries( const KIO::UDSEntryList& ))); 02183 connect( slave, SIGNAL( totalSize( KIO::filesize_t ) ), 02184 SLOT( slotTotalSize( KIO::filesize_t ) ) ); 02185 connect( slave, SIGNAL( redirection(const KURL &) ), 02186 SLOT( slotRedirection(const KURL &) ) ); 02187 02188 SimpleJob::start(slave); 02189 } 02190 02191 class CopyJob::CopyJobPrivate 02192 { 02193 public: 02194 CopyJobPrivate() { 02195 m_defaultPermissions = false; 02196 m_bURLDirty = false; 02197 } 02198 // This is the dest URL that was initially given to CopyJob 02199 // It is copied into m_dest, which can be changed for a given src URL 02200 // (when using the RENAME dialog in slotResult), 02201 // and which will be reset for the next src URL. 02202 KURL m_globalDest; 02203 // The state info about that global dest 02204 CopyJob::DestinationState m_globalDestinationState; 02205 // See setDefaultPermissions 02206 bool m_defaultPermissions; 02207 // Whether URLs changed (and need to be emitted by the next slotReport call) 02208 bool m_bURLDirty; 02209 // Used after copying all the files into the dirs, to set mtime (TODO: and permissions?) 02210 // after the copy is done 02211 QValueList<CopyInfo> m_directoriesCopied; 02212 }; 02213 02214 CopyJob::CopyJob( const KURL::List& src, const KURL& dest, CopyMode mode, bool asMethod, bool showProgressInfo ) 02215 : Job(showProgressInfo), m_mode(mode), m_asMethod(asMethod), 02216 destinationState(DEST_NOT_STATED), state(STATE_STATING), 02217 m_totalSize(0), m_processedSize(0), m_fileProcessedSize(0), 02218 m_processedFiles(0), m_processedDirs(0), 02219 m_srcList(src), m_currentStatSrc(m_srcList.begin()), 02220 m_bCurrentOperationIsLink(false), m_bSingleFileCopy(false), m_bOnlyRenames(mode==Move), 02221 m_dest(dest), m_bAutoSkip( false ), m_bOverwriteAll( false ), 02222 m_conflictError(0), m_reportTimer(0) 02223 { 02224 d = new CopyJobPrivate; 02225 d->m_globalDest = dest; 02226 d->m_globalDestinationState = destinationState; 02227 02228 if ( showProgressInfo ) { 02229 connect( this, SIGNAL( totalFiles( KIO::Job*, unsigned long ) ), 02230 Observer::self(), SLOT( slotTotalFiles( KIO::Job*, unsigned long ) ) ); 02231 02232 connect( this, SIGNAL( totalDirs( KIO::Job*, unsigned long ) ), 02233 Observer::self(), SLOT( slotTotalDirs( KIO::Job*, unsigned long ) ) ); 02234 } 02235 QTimer::singleShot(0, this, SLOT(slotStart())); 02250 } 02251 02252 CopyJob::~CopyJob() 02253 { 02254 delete d; 02255 } 02256 02257 void CopyJob::slotStart() 02258 { 02264 m_reportTimer = new QTimer(this); 02265 02266 connect(m_reportTimer,SIGNAL(timeout()),this,SLOT(slotReport())); 02267 m_reportTimer->start(REPORT_TIMEOUT,false); 02268 02269 // Stat the dest 02270 KIO::Job * job = KIO::stat( m_dest, false, 2, false ); 02271 //kdDebug(7007) << "CopyJob:stating the dest " << m_dest << endl; 02272 addSubjob(job); 02273 } 02274 02275 // For unit test purposes 02276 KIO_EXPORT bool kio_resolve_local_urls = true; 02277 02278 void CopyJob::slotResultStating( Job *job ) 02279 { 02280 //kdDebug(7007) << "CopyJob::slotResultStating" << endl; 02281 // Was there an error while stating the src ? 02282 if (job->error() && destinationState != DEST_NOT_STATED ) 02283 { 02284 KURL srcurl = ((SimpleJob*)job)->url(); 02285 if ( !srcurl.isLocalFile() ) 02286 { 02287 // Probably : src doesn't exist. Well, over some protocols (e.g. FTP) 02288 // this info isn't really reliable (thanks to MS FTP servers). 02289 // We'll assume a file, and try to download anyway. 02290 kdDebug(7007) << "Error while stating source. Activating hack" << endl; 02291 subjobs.remove( job ); 02292 assert ( subjobs.isEmpty() ); // We should have only one job at a time ... 02293 struct CopyInfo info; 02294 info.permissions = (mode_t) -1; 02295 info.mtime = (time_t) -1; 02296 info.ctime = (time_t) -1; 02297 info.size = (KIO::filesize_t)-1; 02298 info.uSource = srcurl; 02299 info.uDest = m_dest; 02300 // Append filename or dirname to destination URL, if allowed 02301 if ( destinationState == DEST_IS_DIR && !m_asMethod ) 02302 info.uDest.addPath( srcurl.fileName() ); 02303 02304 files.append( info ); 02305 statNextSrc(); 02306 return; 02307 } 02308 // Local file. If stat fails, the file definitely doesn't exist. 02309 Job::slotResult( job ); // will set the error and emit result(this) 02310 return; 02311 } 02312 02313 // Is it a file or a dir ? Does it have a local path? 02314 UDSEntry entry = ((StatJob*)job)->statResult(); 02315 bool bDir = false; 02316 bool bLink = false; 02317 QString sName; 02318 QString sLocalPath; 02319 UDSEntry::ConstIterator it2 = entry.begin(); 02320 for( ; it2 != entry.end(); it2++ ) { 02321 if ( ((*it2).m_uds) == UDS_FILE_TYPE ) 02322 bDir = S_ISDIR( (mode_t)(*it2).m_long ); 02323 else if ( ((*it2).m_uds) == UDS_LINK_DEST ) 02324 bLink = !((*it2).m_str.isEmpty()); 02325 else if ( ((*it2).m_uds) == UDS_NAME ) 02326 sName = (*it2).m_str; 02327 else if ( ((*it2).m_uds) == UDS_LOCAL_PATH ) 02328 sLocalPath = (*it2).m_str; 02329 } 02330 02331 if ( destinationState == DEST_NOT_STATED ) 02332 // we were stating the dest 02333 { 02334 if (job->error()) 02335 destinationState = DEST_DOESNT_EXIST; 02336 else { 02337 // Treat symlinks to dirs as dirs here, so no test on bLink 02338 destinationState = bDir ? DEST_IS_DIR : DEST_IS_FILE; 02339 //kdDebug(7007) << "CopyJob::slotResultStating dest is dir:" << bDir << endl; 02340 } 02341 const bool isGlobalDest = m_dest == d->m_globalDest; 02342 if ( isGlobalDest ) 02343 d->m_globalDestinationState = destinationState; 02344 02345 if ( !sLocalPath.isEmpty() && kio_resolve_local_urls ) { 02346 m_dest = KURL(); 02347 m_dest.setPath(sLocalPath); 02348 if ( isGlobalDest ) 02349 d->m_globalDest = m_dest; 02350 } 02351 02352 subjobs.remove( job ); 02353 assert ( subjobs.isEmpty() ); 02354 02355 // After knowing what the dest is, we can start stat'ing the first src. 02356 statCurrentSrc(); 02357 return; 02358 } 02359 // We were stating the current source URL 02360 m_currentDest = m_dest; // used by slotEntries 02361 // Create a dummy list with it, for slotEntries 02362 UDSEntryList lst; 02363 lst.append(entry); 02364 02365 // There 6 cases, and all end up calling slotEntries(job, lst) first : 02366 // 1 - src is a dir, destination is a directory, 02367 // slotEntries will append the source-dir-name to the destination 02368 // 2 - src is a dir, destination is a file, ERROR (done later on) 02369 // 3 - src is a dir, destination doesn't exist, then it's the destination dirname, 02370 // so slotEntries will use it as destination. 02371 02372 // 4 - src is a file, destination is a directory, 02373 // slotEntries will append the filename to the destination. 02374 // 5 - src is a file, destination is a file, m_dest is the exact destination name 02375 // 6 - src is a file, destination doesn't exist, m_dest is the exact destination name 02376 // Tell slotEntries not to alter the src url 02377 m_bCurrentSrcIsDir = false; 02378 slotEntries(job, lst); 02379 02380 KURL srcurl; 02381 if (!sLocalPath.isEmpty()) 02382 srcurl.setPath(sLocalPath); 02383 else 02384 srcurl = ((SimpleJob*)job)->url(); 02385 02386 subjobs.remove( job ); 02387 assert ( subjobs.isEmpty() ); // We should have only one job at a time ... 02388 02389 if ( bDir 02390 && !bLink // treat symlinks as files (no recursion) 02391 && m_mode != Link ) // No recursion in Link mode either. 02392 { 02393 //kdDebug(7007) << " Source is a directory " << endl; 02394 02395 m_bCurrentSrcIsDir = true; // used by slotEntries 02396 if ( destinationState == DEST_IS_DIR ) // (case 1) 02397 { 02398 if ( !m_asMethod ) 02399 { 02400 // Use <desturl>/<directory_copied> as destination, from now on 02401 QString directory = srcurl.fileName(); 02402 if ( !sName.isEmpty() && KProtocolInfo::fileNameUsedForCopying( srcurl ) == KProtocolInfo::Name ) 02403 { 02404 directory = sName; 02405 } 02406 m_currentDest.addPath( directory ); 02407 } 02408 } 02409 else if ( destinationState == DEST_IS_FILE ) // (case 2) 02410 { 02411 m_error = ERR_IS_FILE; 02412 m_errorText = m_dest.prettyURL(); 02413 emitResult(); 02414 return; 02415 } 02416 else // (case 3) 02417 { 02418 // otherwise dest is new name for toplevel dir 02419 // so the destination exists, in fact, from now on. 02420 // (This even works with other src urls in the list, since the 02421 // dir has effectively been created) 02422 destinationState = DEST_IS_DIR; 02423 if ( m_dest == d->m_globalDest ) 02424 d->m_globalDestinationState = destinationState; 02425 } 02426 02427 startListing( srcurl ); 02428 } 02429 else 02430 { 02431 //kdDebug(7007) << " Source is a file (or a symlink), or we are linking -> no recursive listing " << endl; 02432 statNextSrc(); 02433 } 02434 } 02435 02436 void CopyJob::slotReport() 02437 { 02438 // If showProgressInfo was set, m_progressId is > 0. 02439 Observer * observer = m_progressId ? Observer::self() : 0L; 02440 switch (state) { 02441 case STATE_COPYING_FILES: 02442 emit processedFiles( this, m_processedFiles ); 02443 if (observer) observer->slotProcessedFiles(this, m_processedFiles); 02444 if (d->m_bURLDirty) 02445 { 02446 // Only emit urls when they changed. This saves time, and fixes #66281 02447 d->m_bURLDirty = false; 02448 if (m_mode==Move) 02449 { 02450 if (observer) observer->slotMoving( this, m_currentSrcURL, m_currentDestURL); 02451 emit moving( this, m_currentSrcURL, m_currentDestURL); 02452 } 02453 else if (m_mode==Link) 02454 { 02455 if (observer) observer->slotCopying( this, m_currentSrcURL, m_currentDestURL ); // we don't have a slotLinking 02456 emit linking( this, m_currentSrcURL.path(), m_currentDestURL ); 02457 } 02458 else 02459 { 02460 if (observer) observer->slotCopying( this, m_currentSrcURL, m_currentDestURL ); 02461 emit copying( this, m_currentSrcURL, m_currentDestURL ); 02462 } 02463 } 02464 break; 02465 02466 case STATE_CREATING_DIRS: 02467 if (observer) observer->slotProcessedDirs( this, m_processedDirs ); 02468 emit processedDirs( this, m_processedDirs ); 02469 if (d->m_bURLDirty) 02470 { 02471 d->m_bURLDirty = false; 02472 emit creatingDir( this, m_currentDestURL ); 02473 if (observer) observer->slotCreatingDir( this, m_currentDestURL); 02474 } 02475 break; 02476 02477 case STATE_STATING: 02478 case STATE_LISTING: 02479 if (d->m_bURLDirty) 02480 { 02481 d->m_bURLDirty = false; 02482 if (observer) observer->slotCopying( this, m_currentSrcURL, m_currentDestURL ); 02483 } 02484 emit totalSize( this, m_totalSize ); 02485 emit totalFiles( this, files.count() ); 02486 emit totalDirs( this, dirs.count() ); 02487 break; 02488 02489 default: 02490 break; 02491 } 02492 } 02493 02494 void CopyJob::slotEntries(KIO::Job* job, const UDSEntryList& list) 02495 { 02496 UDSEntryListConstIterator it = list.begin(); 02497 UDSEntryListConstIterator end = list.end(); 02498 for (; it != end; ++it) { 02499 UDSEntry::ConstIterator it2 = (*it).begin(); 02500 struct CopyInfo info; 02501 info.permissions = -1; 02502 info.mtime = (time_t) -1; 02503 info.ctime = (time_t) -1; 02504 info.size = (KIO::filesize_t)-1; 02505 QString displayName; 02506 KURL url; 02507 QString localPath; 02508 bool isDir = false; 02509 for( ; it2 != (*it).end(); it2++ ) { 02510 switch ((*it2).m_uds) { 02511 case UDS_FILE_TYPE: 02512 //info.type = (mode_t)((*it2).m_long); 02513 isDir = S_ISDIR( (mode_t)((*it2).m_long) ); 02514 break; 02515 case UDS_NAME: // recursive listing, displayName can be a/b/c/d 02516 displayName = (*it2).m_str; 02517 break; 02518 case UDS_URL: // optional 02519 url = KURL((*it2).m_str); 02520 break; 02521 case UDS_LOCAL_PATH: 02522 localPath = (*it2).m_str; 02523 break; 02524 case UDS_LINK_DEST: 02525 info.linkDest = (*it2).m_str; 02526 break; 02527 case UDS_ACCESS: 02528 info.permissions = ((*it2).m_long); 02529 break; 02530 case UDS_SIZE: 02531 info.size = (KIO::filesize_t)((*it2).m_long); 02532 m_totalSize += info.size; 02533 break; 02534 case UDS_MODIFICATION_TIME: 02535 info.mtime = (time_t)((*it2).m_long); 02536 break; 02537 case UDS_CREATION_TIME: 02538 info.ctime = (time_t)((*it2).m_long); 02539 default: 02540 break; 02541 } 02542 } 02543 if (displayName != ".." && displayName != ".") 02544 { 02545 bool hasCustomURL = !url.isEmpty() || !localPath.isEmpty(); 02546 if( !hasCustomURL ) { 02547 // Make URL from displayName 02548 url = ((SimpleJob *)job)->url(); 02549 if ( m_bCurrentSrcIsDir ) { // Only if src is a directory. Otherwise uSource is fine as is 02550 //kdDebug(7007) << "adding path " << displayName << endl; 02551 url.addPath( displayName ); 02552 } 02553 } 02554 //kdDebug(7007) << "displayName=" << displayName << " url=" << url << endl; 02555 if (!localPath.isEmpty() && kio_resolve_local_urls) { 02556 url = KURL(); 02557 url.setPath(localPath); 02558 } 02559 02560 info.uSource = url; 02561 info.uDest = m_currentDest; 02562 //kdDebug(7007) << " uSource=" << info.uSource << " uDest(1)=" << info.uDest << endl; 02563 // Append filename or dirname to destination URL, if allowed 02564 if ( destinationState == DEST_IS_DIR && 02565 // "copy/move as <foo>" means 'foo' is the dest for the base srcurl 02566 // (passed here during stating) but not its children (during listing) 02567 ( ! ( m_asMethod && state == STATE_STATING ) ) ) 02568 { 02569 QString destFileName; 02570 if ( hasCustomURL && 02571 KProtocolInfo::fileNameUsedForCopying( url ) == KProtocolInfo::FromURL ) { 02572 //destFileName = url.fileName(); // Doesn't work for recursive listing 02573 // Count the number of prefixes used by the recursive listjob 02574 int numberOfSlashes = displayName.contains( '/' ); // don't make this a find()! 02575 QString path = url.path(); 02576 int pos = 0; 02577 for ( int n = 0; n < numberOfSlashes + 1; ++n ) { 02578 pos = path.findRev( '/', pos - 1 ); 02579 if ( pos == -1 ) { // error 02580 kdWarning(7007) << "kioslave bug: not enough slashes in UDS_URL " << path << " - looking for " << numberOfSlashes << " slashes" << endl; 02581 break; 02582 } 02583 } 02584 if ( pos >= 0 ) { 02585 destFileName = path.mid( pos + 1 ); 02586 } 02587 02588 } else { // destination filename taken from UDS_NAME 02589 destFileName = displayName; 02590 } 02591 02592 // Here we _really_ have to add some filename to the dest. 02593 // Otherwise, we end up with e.g. dest=..../Desktop/ itself. 02594 // (This can happen when dropping a link to a webpage with no path) 02595 if ( destFileName.isEmpty() ) 02596 destFileName = KIO::encodeFileName( info.uSource.prettyURL() ); 02597 02598 //kdDebug(7007) << " adding destFileName=" << destFileName << endl; 02599 info.uDest.addPath( destFileName ); 02600 } 02601 //kdDebug(7007) << " uDest(2)=" << info.uDest << endl; 02602 //kdDebug(7007) << " " << info.uSource << " -> " << info.uDest << endl; 02603 if ( info.linkDest.isEmpty() && isDir && m_mode != Link ) // Dir 02604 { 02605 dirs.append( info ); // Directories 02606 if (m_mode == Move) 02607 dirsToRemove.append( info.uSource ); 02608 } 02609 else { 02610 files.append( info ); // Files and any symlinks 02611 } 02612 } 02613 } 02614 } 02615 02616 void CopyJob::skipSrc() 02617 { 02618 m_dest = d->m_globalDest; 02619 destinationState = d->m_globalDestinationState; 02620 ++m_currentStatSrc; 02621 skip( m_currentSrcURL ); 02622 statCurrentSrc(); 02623 } 02624 02625 void CopyJob::statNextSrc() 02626 { 02627 /* Revert to the global destination, the one that applies to all source urls. 02628 * Imagine you copy the items a b and c into /d, but /d/b exists so the user uses "Rename" to put it in /foo/b instead. 02629 * m_dest is /foo/b for b, but we have to revert to /d for item c and following. 02630 */ 02631 m_dest = d->m_globalDest; 02632 destinationState = d->m_globalDestinationState; 02633 ++m_currentStatSrc; 02634 statCurrentSrc(); 02635 } 02636 02637 void CopyJob::statCurrentSrc() 02638 { 02639 if ( m_currentStatSrc != m_srcList.end() ) 02640 { 02641 m_currentSrcURL = (*m_currentStatSrc); 02642 d->m_bURLDirty = true; 02643 if ( m_mode == Link ) 02644 { 02645 // Skip the "stating the source" stage, we don't need it for linking 02646 m_currentDest = m_dest; 02647 struct CopyInfo info; 02648 info.permissions = -1; 02649 info.mtime = (time_t) -1; 02650 info.ctime = (time_t) -1; 02651 info.size = (KIO::filesize_t)-1; 02652 info.uSource = m_currentSrcURL; 02653 info.uDest = m_currentDest; 02654 // Append filename or dirname to destination URL, if allowed 02655 if ( destinationState == DEST_IS_DIR && !m_asMethod ) 02656 { 02657 if ( 02658 (m_currentSrcURL.protocol() == info.uDest.protocol()) && 02659 (m_currentSrcURL.host() == info.uDest.host()) && 02660 (m_currentSrcURL.port() == info.uDest.port()) && 02661 (m_currentSrcURL.user() == info.uDest.user()) && 02662 (m_currentSrcURL.pass() == info.uDest.pass()) ) 02663 { 02664 // This is the case of creating a real symlink 02665 info.uDest.addPath( m_currentSrcURL.fileName() ); 02666 } 02667 else 02668 { 02669 // Different protocols, we'll create a .desktop file 02670 // We have to change the extension anyway, so while we're at it, 02671 // name the file like the URL 02672 info.uDest.addPath( KIO::encodeFileName( m_currentSrcURL.prettyURL() )+".desktop" ); 02673 } 02674 } 02675 files.append( info ); // Files and any symlinks 02676 statNextSrc(); // we could use a loop instead of a recursive call :) 02677 return; 02678 } 02679 else if ( m_mode == Move && ( 02680 // Don't go renaming right away if we need a stat() to find out the destination filename 02681 KProtocolInfo::fileNameUsedForCopying( m_currentSrcURL ) == KProtocolInfo::FromURL || 02682 destinationState != DEST_IS_DIR || m_asMethod ) 02683 ) 02684 { 02685 // If moving, before going for the full stat+[list+]copy+del thing, try to rename 02686 // The logic is pretty similar to FileCopyJob::slotStart() 02687 if ( (m_currentSrcURL.protocol() == m_dest.protocol()) && 02688 (m_currentSrcURL.host() == m_dest.host()) && 02689 (m_currentSrcURL.port() == m_dest.port()) && 02690 (m_currentSrcURL.user() == m_dest.user()) && 02691 (m_currentSrcURL.pass() == m_dest.pass()) ) 02692 { 02693 startRenameJob( m_currentSrcURL ); 02694 return; 02695 } 02696 else if ( m_currentSrcURL.isLocalFile() && KProtocolInfo::canRenameFromFile( m_dest ) ) 02697 { 02698 startRenameJob( m_dest ); 02699 return; 02700 } 02701 else if ( m_dest.isLocalFile() && KProtocolInfo::canRenameToFile( m_currentSrcURL ) ) 02702 { 02703 startRenameJob( m_currentSrcURL ); 02704 return; 02705 } 02706 } 02707 02708 // if the file system doesn't support deleting, we do not even stat 02709 if (m_mode == Move && !KProtocolInfo::supportsDeleting(m_currentSrcURL)) { 02710 QGuardedPtr<CopyJob> that = this; 02711 if (isInteractive()) 02712 KMessageBox::information( 0, buildErrorString(ERR_CANNOT_DELETE, m_currentSrcURL.prettyURL())); 02713 if (that) 02714 statNextSrc(); // we could use a loop instead of a recursive call :) 02715 return; 02716 } 02717 02718 // Stat the next src url 02719 Job * job = KIO::stat( m_currentSrcURL, true, 2, false ); 02720 //kdDebug(7007) << "KIO::stat on " << m_currentSrcURL << endl; 02721 state = STATE_STATING; 02722 addSubjob(job); 02723 m_currentDestURL=m_dest; 02724 m_bOnlyRenames = false; 02725 d->m_bURLDirty = true; 02726 } 02727 else 02728 { 02729 // Finished the stat'ing phase 02730 // First make sure that the totals were correctly emitted 02731 state = STATE_STATING; 02732 d->m_bURLDirty = true; 02733 slotReport(); 02734 if (!dirs.isEmpty()) 02735 emit aboutToCreate( this, dirs ); 02736 if (!files.isEmpty()) 02737 emit aboutToCreate( this, files ); 02738 // Check if we are copying a single file 02739 m_bSingleFileCopy = ( files.count() == 1 && dirs.isEmpty() ); 02740 // Then start copying things 02741 state = STATE_CREATING_DIRS; 02742 createNextDir(); 02743 } 02744 } 02745 02746 void CopyJob::startRenameJob( const KURL& slave_url ) 02747 { 02748 KURL dest = m_dest; 02749 // Append filename or dirname to destination URL, if allowed 02750 if ( destinationState == DEST_IS_DIR && !m_asMethod ) 02751 dest.addPath( m_currentSrcURL.fileName() ); 02752 kdDebug(7007) << "This seems to be a suitable case for trying to rename before stat+[list+]copy+del" << endl; 02753 state = STATE_RENAMING; 02754 02755 struct CopyInfo info; 02756 info.permissions = -1; 02757 info.mtime = (time_t) -1; 02758 info.ctime = (time_t) -1; 02759 info.size = (KIO::filesize_t)-1; 02760 info.uSource = m_currentSrcURL; 02761 info.uDest = dest; 02762 QValueList<CopyInfo> files; 02763 files.append(info); 02764 emit aboutToCreate( this, files ); 02765 02766 KIO_ARGS << m_currentSrcURL << dest << (Q_INT8) false /*no overwrite*/; 02767 SimpleJob * newJob = new SimpleJob(slave_url, CMD_RENAME, packedArgs, false); 02768 Scheduler::scheduleJob(newJob); 02769 addSubjob( newJob ); 02770 if ( m_currentSrcURL.directory() != dest.directory() ) // For the user, moving isn't renaming. Only renaming is. 02771 m_bOnlyRenames = false; 02772 } 02773 02774 void CopyJob::startListing( const KURL & src ) 02775 { 02776 state = STATE_LISTING; 02777 d->m_bURLDirty = true; 02778 ListJob * newjob = listRecursive( src, false ); 02779 newjob->setUnrestricted(true); 02780 connect(newjob, SIGNAL(entries( KIO::Job *, 02781 const KIO::UDSEntryList& )), 02782 SLOT( slotEntries( KIO::Job*, 02783 const KIO::UDSEntryList& ))); 02784 addSubjob( newjob ); 02785 } 02786 02787 void CopyJob::skip( const KURL & sourceUrl ) 02788 { 02789 // Check if this is one if toplevel sources 02790 // If yes, remove it from m_srcList, for a correct FilesRemoved() signal 02791 //kdDebug(7007) << "CopyJob::skip: looking for " << sourceUrl << endl; 02792 KURL::List::Iterator sit = m_srcList.find( sourceUrl ); 02793 if ( sit != m_srcList.end() ) 02794 { 02795 //kdDebug(7007) << "CopyJob::skip: removing " << sourceUrl << " from list" << endl; 02796 m_srcList.remove( sit ); 02797 } 02798 dirsToRemove.remove( sourceUrl ); 02799 } 02800 02801 bool CopyJob::shouldOverwrite( const QString& path ) const 02802 { 02803 if ( m_bOverwriteAll ) 02804 return true; 02805 QStringList::ConstIterator sit = m_overwriteList.begin(); 02806 for( ; sit != m_overwriteList.end(); ++sit ) 02807 if ( path.startsWith( *sit ) ) 02808 return true; 02809 return false; 02810 } 02811 02812 bool CopyJob::shouldSkip( const QString& path ) const 02813 { 02814 QStringList::ConstIterator sit = m_skipList.begin(); 02815 for( ; sit != m_skipList.end(); ++sit ) 02816 if ( path.startsWith( *sit ) ) 02817 return true; 02818 return false; 02819 } 02820 02821 void CopyJob::slotResultCreatingDirs( Job * job ) 02822 { 02823 // The dir we are trying to create: 02824 QValueList<CopyInfo>::Iterator it = dirs.begin(); 02825 // Was there an error creating a dir ? 02826 if ( job->error() ) 02827 { 02828 m_conflictError = job->error(); 02829 if ( (m_conflictError == ERR_DIR_ALREADY_EXIST) 02830 || (m_conflictError == ERR_FILE_ALREADY_EXIST) ) // can't happen? 02831 { 02832 KURL oldURL = ((SimpleJob*)job)->url(); 02833 // Should we skip automatically ? 02834 if ( m_bAutoSkip ) { 02835 // We don't want to copy files in this directory, so we put it on the skip list 02836 m_skipList.append( oldURL.path( 1 ) ); 02837 skip( oldURL ); 02838 dirs.remove( it ); // Move on to next dir 02839 } else { 02840 // Did the user choose to overwrite already? 02841 const QString destFile = (*it).uDest.path(); 02842 if ( shouldOverwrite( destFile ) ) { // overwrite => just skip 02843 emit copyingDone( this, ( *it ).uSource, ( *it ).uDest, true /* directory */, false /* renamed */ ); 02844 dirs.remove( it ); // Move on to next dir 02845 } else { 02846 if ( !isInteractive() ) { 02847 Job::slotResult( job ); // will set the error and emit result(this) 02848 return; 02849 } 02850 02851 assert( ((SimpleJob*)job)->url().url() == (*it).uDest.url() ); 02852 subjobs.remove( job ); 02853 assert ( subjobs.isEmpty() ); // We should have only one job at a time ... 02854 02855 // We need to stat the existing dir, to get its last-modification time 02856 KURL existingDest( (*it).uDest ); 02857 SimpleJob * newJob = KIO::stat( existingDest, false, 2, false ); 02858 Scheduler::scheduleJob(newJob); 02859 kdDebug(7007) << "KIO::stat for resolving conflict on " << existingDest << endl; 02860 state = STATE_CONFLICT_CREATING_DIRS; 02861 addSubjob(newJob); 02862 return; // Don't move to next dir yet ! 02863 } 02864 } 02865 } 02866 else 02867 { 02868 // Severe error, abort 02869 Job::slotResult( job ); // will set the error and emit result(this) 02870 return; 02871 } 02872 } 02873 else // no error : remove from list, to move on to next dir 02874 { 02875 //this is required for the undo feature 02876 emit copyingDone( this, (*it).uSource, (*it).uDest, true, false ); 02877 d->m_directoriesCopied.append( *it ); 02878 dirs.remove( it ); 02879 } 02880 02881 m_processedDirs++; 02882 //emit processedDirs( this, m_processedDirs ); 02883 subjobs.remove( job ); 02884 assert( subjobs.isEmpty() ); // We should have only one job at a time ... 02885 createNextDir(); 02886 } 02887 02888 void CopyJob::slotResultConflictCreatingDirs( KIO::Job * job ) 02889 { 02890 // We come here after a conflict has been detected and we've stated the existing dir 02891 02892 // The dir we were trying to create: 02893 QValueList<CopyInfo>::Iterator it = dirs.begin(); 02894 // Its modification time: 02895 time_t destmtime = (time_t)-1; 02896 time_t destctime = (time_t)-1; 02897 KIO::filesize_t destsize = 0; 02898 QString linkDest; 02899 02900 UDSEntry entry = ((KIO::StatJob*)job)->statResult(); 02901 KIO::UDSEntry::ConstIterator it2 = entry.begin(); 02902 for( ; it2 != entry.end(); it2++ ) { 02903 switch ((*it2).m_uds) { 02904 case UDS_MODIFICATION_TIME: 02905 destmtime = (time_t)((*it2).m_long); 02906 break; 02907 case UDS_CREATION_TIME: 02908 destctime = (time_t)((*it2).m_long); 02909 break; 02910 case UDS_SIZE: 02911 destsize = (*it2).m_long; 02912 break; 02913 case UDS_LINK_DEST: 02914 linkDest = (*it2).m_str; 02915 break; 02916 } 02917 } 02918 subjobs.remove( job ); 02919 assert ( subjobs.isEmpty() ); // We should have only one job at a time ... 02920 02921 // Always multi and skip (since there are files after that) 02922 RenameDlg_Mode mode = (RenameDlg_Mode)( M_MULTI | M_SKIP ); 02923 // Overwrite only if the existing thing is a dir (no chance with a file) 02924 if ( m_conflictError == ERR_DIR_ALREADY_EXIST ) 02925 { 02926 if( (*it).uSource == (*it).uDest || 02927 ((*it).uSource.protocol() == (*it).uDest.protocol() && 02928 (*it).uSource.path(-1) == linkDest) ) 02929 mode = (RenameDlg_Mode)( mode | M_OVERWRITE_ITSELF); 02930 else 02931 mode = (RenameDlg_Mode)( mode | M_OVERWRITE ); 02932 } 02933 02934 QString existingDest = (*it).uDest.path(); 02935 QString newPath; 02936 if (m_reportTimer) 02937 m_reportTimer->stop(); 02938 RenameDlg_Result r = Observer::self()->open_RenameDlg( this, i18n("Folder Already Exists"), 02939 (*it).uSource.url(), 02940 (*it).uDest.url(), 02941 mode, newPath, 02942 (*it).size, destsize, 02943 (*it).ctime, destctime, 02944 (*it).mtime, destmtime ); 02945 if (m_reportTimer) 02946 m_reportTimer->start(REPORT_TIMEOUT,false); 02947 switch ( r ) { 02948 case R_CANCEL: 02949 m_error = ERR_USER_CANCELED; 02950 emitResult(); 02951 return; 02952 case R_RENAME: 02953 { 02954 QString oldPath = (*it).uDest.path( 1 ); 02955 KURL newUrl( (*it).uDest ); 02956 newUrl.setPath( newPath ); 02957 emit renamed( this, (*it).uDest, newUrl ); // for e.g. kpropsdlg 02958 02959 // Change the current one and strip the trailing '/' 02960 (*it).uDest.setPath( newUrl.path( -1 ) ); 02961 newPath = newUrl.path( 1 ); // With trailing slash 02962 QValueList<CopyInfo>::Iterator renamedirit = it; 02963 ++renamedirit; 02964 // Change the name of subdirectories inside the directory 02965 for( ; renamedirit != dirs.end() ; ++renamedirit ) 02966 { 02967 QString path = (*renamedirit).uDest.path(); 02968 if ( path.left(oldPath.length()) == oldPath ) { 02969 QString n = path; 02970 n.replace( 0, oldPath.length(), newPath ); 02971 kdDebug(7007) << "dirs list: " << (*renamedirit).uSource.path() 02972 << " was going to be " << path 02973 << ", changed into " << n << endl; 02974 (*renamedirit).uDest.setPath( n ); 02975 } 02976 } 02977 // Change filenames inside the directory 02978 QValueList<CopyInfo>::Iterator renamefileit = files.begin(); 02979 for( ; renamefileit != files.end() ; ++renamefileit ) 02980 { 02981 QString path = (*renamefileit).uDest.path(); 02982 if ( path.left(oldPath.length()) == oldPath ) { 02983 QString n = path; 02984 n.replace( 0, oldPath.length(), newPath ); 02985 kdDebug(7007) << "files list: " << (*renamefileit).uSource.path() 02986 << " was going to be " << path 02987 << ", changed into " << n << endl; 02988 (*renamefileit).uDest.setPath( n ); 02989 } 02990 } 02991 if (!dirs.isEmpty()) 02992 emit aboutToCreate( this, dirs ); 02993 if (!files.isEmpty()) 02994 emit aboutToCreate( this, files ); 02995 } 02996 break; 02997 case R_AUTO_SKIP: 02998 m_bAutoSkip = true; 02999 // fall through 03000 case R_SKIP: 03001 m_skipList.append( existingDest ); 03002 skip( (*it).uSource ); 03003 // Move on to next dir 03004 dirs.remove( it ); 03005 m_processedDirs++; 03006 break; 03007 case R_OVERWRITE: 03008 m_overwriteList.append( existingDest ); 03009 emit copyingDone( this, ( *it ).uSource, ( *it ).uDest, true /* directory */, false /* renamed */ ); 03010 // Move on to next dir 03011 dirs.remove( it ); 03012 m_processedDirs++; 03013 break; 03014 case R_OVERWRITE_ALL: 03015 m_bOverwriteAll = true; 03016 emit copyingDone( this, ( *it ).uSource, ( *it ).uDest, true /* directory */, false /* renamed */ ); 03017 // Move on to next dir 03018 dirs.remove( it ); 03019 m_processedDirs++; 03020 break; 03021 default: 03022 assert( 0 ); 03023 } 03024 state = STATE_CREATING_DIRS; 03025 //emit processedDirs( this, m_processedDirs ); 03026 createNextDir(); 03027 } 03028 03029 void CopyJob::createNextDir() 03030 { 03031 KURL udir; 03032 if ( !dirs.isEmpty() ) 03033 { 03034 // Take first dir to create out of list 03035 QValueList<CopyInfo>::Iterator it = dirs.begin(); 03036 // Is this URL on the skip list or the overwrite list ? 03037 while( it != dirs.end() && udir.isEmpty() ) 03038 { 03039 const QString dir = (*it).uDest.path(); 03040 if ( shouldSkip( dir ) ) { 03041 dirs.remove( it ); 03042 it = dirs.begin(); 03043 } else 03044 udir = (*it).uDest; 03045 } 03046 } 03047 if ( !udir.isEmpty() ) // any dir to create, finally ? 03048 { 03049 // Create the directory - with default permissions so that we can put files into it 03050 // TODO : change permissions once all is finished; but for stuff coming from CDROM it sucks... 03051 KIO::SimpleJob *newjob = KIO::mkdir( udir, -1 ); 03052 Scheduler::scheduleJob(newjob); 03053 03054 m_currentDestURL = udir; 03055 d->m_bURLDirty = true; 03056 03057 addSubjob(newjob); 03058 return; 03059 } 03060 else // we have finished creating dirs 03061 { 03062 emit processedDirs( this, m_processedDirs ); // make sure final number appears 03063 if (m_progressId) Observer::self()->slotProcessedDirs( this, m_processedDirs ); 03064 03065 state = STATE_COPYING_FILES; 03066 m_processedFiles++; // Ralf wants it to start at 1, not 0 03067 copyNextFile(); 03068 } 03069 } 03070 03071 void CopyJob::slotResultCopyingFiles( Job * job ) 03072 { 03073 // The file we were trying to copy: 03074 QValueList<CopyInfo>::Iterator it = files.begin(); 03075 if ( job->error() ) 03076 { 03077 // Should we skip automatically ? 03078 if ( m_bAutoSkip ) 03079 { 03080 skip( (*it).uSource ); 03081 m_fileProcessedSize = (*it).size; 03082 files.remove( it ); // Move on to next file 03083 } 03084 else 03085 { 03086 if ( !isInteractive() ) { 03087 Job::slotResult( job ); // will set the error and emit result(this) 03088 return; 03089 } 03090 03091 m_conflictError = job->error(); // save for later 03092 // Existing dest ? 03093 if ( ( m_conflictError == ERR_FILE_ALREADY_EXIST ) 03094 || ( m_conflictError == ERR_DIR_ALREADY_EXIST ) 03095 || ( m_conflictError == ERR_IDENTICAL_FILES ) ) 03096 { 03097 subjobs.remove( job ); 03098 assert ( subjobs.isEmpty() ); 03099 // We need to stat the existing file, to get its last-modification time 03100 KURL existingFile( (*it).uDest ); 03101 SimpleJob * newJob = KIO::stat( existingFile, false, 2, false ); 03102 Scheduler::scheduleJob(newJob); 03103 kdDebug(7007) << "KIO::stat for resolving conflict on " << existingFile << endl; 03104 state = STATE_CONFLICT_COPYING_FILES; 03105 addSubjob(newJob); 03106 return; // Don't move to next file yet ! 03107 } 03108 else 03109 { 03110 if ( m_bCurrentOperationIsLink && ::qt_cast<KIO::DeleteJob*>( job ) ) 03111 { 03112 // Very special case, see a few lines below 03113 // We are deleting the source of a symlink we successfully moved... ignore error 03114 m_fileProcessedSize = (*it).size; 03115 files.remove( it ); 03116 } else { 03117 // Go directly to the conflict resolution, there is nothing to stat 03118 slotResultConflictCopyingFiles( job ); 03119 return; 03120 } 03121 } 03122 } 03123 } else // no error 03124 { 03125 // Special case for moving links. That operation needs two jobs, unlike others. 03126 if ( m_bCurrentOperationIsLink && m_mode == Move 03127 && !::qt_cast<KIO::DeleteJob *>( job ) // Deleting source not already done 03128 ) 03129 { 03130 subjobs.remove( job ); 03131 assert ( subjobs.isEmpty() ); 03132 // The only problem with this trick is that the error handling for this del operation 03133 // is not going to be right... see 'Very special case' above. 03134 KIO::Job * newjob = KIO::del( (*it).uSource, false /*don't shred*/, false /*no GUI*/ ); 03135 addSubjob( newjob ); 03136 return; // Don't move to next file yet ! 03137 } 03138 03139 if ( m_bCurrentOperationIsLink ) 03140 { 03141 QString target = ( m_mode == Link ? (*it).uSource.path() : (*it).linkDest ); 03142 //required for the undo feature 03143 emit copyingLinkDone( this, (*it).uSource, target, (*it).uDest ); 03144 } 03145 else 03146 //required for the undo feature 03147 emit copyingDone( this, (*it).uSource, (*it).uDest, false, false ); 03148 // remove from list, to move on to next file 03149 files.remove( it ); 03150 } 03151 m_processedFiles++; 03152 03153 // clear processed size for last file and add it to overall processed size 03154 m_processedSize += m_fileProcessedSize; 03155 m_fileProcessedSize = 0; 03156 03157 //kdDebug(7007) << files.count() << " files remaining" << endl; 03158 03159 removeSubjob( job, true, false ); // merge metadata 03160 assert ( subjobs.isEmpty() ); // We should have only one job at a time ... 03161 copyNextFile(); 03162 } 03163 03164 void CopyJob::slotResultConflictCopyingFiles( KIO::Job * job ) 03165 { 03166 // We come here after a conflict has been detected and we've stated the existing file 03167 // The file we were trying to create: 03168 QValueList<CopyInfo>::Iterator it = files.begin(); 03169 03170 RenameDlg_Result res; 03171 QString newPath; 03172 03173 if (m_reportTimer) 03174 m_reportTimer->stop(); 03175 03176 if ( ( m_conflictError == ERR_FILE_ALREADY_EXIST ) 03177 || ( m_conflictError == ERR_DIR_ALREADY_EXIST ) 03178 || ( m_conflictError == ERR_IDENTICAL_FILES ) ) 03179 { 03180 // Its modification time: 03181 time_t destmtime = (time_t)-1; 03182 time_t destctime = (time_t)-1; 03183 KIO::filesize_t destsize = 0; 03184 QString linkDest; 03185 UDSEntry entry = ((KIO::StatJob*)job)->statResult(); 03186 KIO::UDSEntry::ConstIterator it2 = entry.begin(); 03187 for( ; it2 != entry.end(); it2++ ) { 03188 switch ((*it2).m_uds) { 03189 case UDS_MODIFICATION_TIME: 03190 destmtime = (time_t)((*it2).m_long); 03191 break; 03192 case UDS_CREATION_TIME: 03193 destctime = (time_t)((*it2).m_long); 03194 break; 03195 case UDS_SIZE: 03196 destsize = (*it2).m_long; 03197 break; 03198 case UDS_LINK_DEST: 03199 linkDest = (*it2).m_str; 03200 break; 03201 } 03202 } 03203 03204 // Offer overwrite only if the existing thing is a file 03205 // If src==dest, use "overwrite-itself" 03206 RenameDlg_Mode mode; 03207 bool isDir = true; 03208 03209 if( m_conflictError == ERR_DIR_ALREADY_EXIST ) 03210 mode = (RenameDlg_Mode) 0; 03211 else 03212 { 03213 if ( (*it).uSource == (*it).uDest || 03214 ((*it).uSource.protocol() == (*it).uDest.protocol() && 03215 (*it).uSource.path(-1) == linkDest) ) 03216 mode = M_OVERWRITE_ITSELF; 03217 else 03218 mode = M_OVERWRITE; 03219 isDir = false; 03220 } 03221 03222 if ( m_bSingleFileCopy ) 03223 mode = (RenameDlg_Mode) ( mode | M_SINGLE ); 03224 else 03225 mode = (RenameDlg_Mode) ( mode | M_MULTI | M_SKIP ); 03226 03227 res = Observer::self()->open_RenameDlg( this, !isDir ? 03228 i18n("File Already Exists") : i18n("Already Exists as Folder"), 03229 (*it).uSource.url(), 03230 (*it).uDest.url(), 03231 mode, newPath, 03232 (*it).size, destsize, 03233 (*it).ctime, destctime, 03234 (*it).mtime, destmtime ); 03235 03236 } 03237 else 03238 { 03239 if ( job->error() == ERR_USER_CANCELED ) 03240 res = R_CANCEL; 03241 else if ( !isInteractive() ) { 03242 Job::slotResult( job ); // will set the error and emit result(this) 03243 return; 03244 } 03245 else 03246 { 03247 SkipDlg_Result skipResult = Observer::self()->open_SkipDlg( this, files.count() > 1, 03248 job->errorString() ); 03249 03250 // Convert the return code from SkipDlg into a RenameDlg code 03251 res = ( skipResult == S_SKIP ) ? R_SKIP : 03252 ( skipResult == S_AUTO_SKIP ) ? R_AUTO_SKIP : 03253 R_CANCEL; 03254 } 03255 } 03256 03257 if (m_reportTimer) 03258 m_reportTimer->start(REPORT_TIMEOUT,false); 03259 03260 subjobs.remove( job ); 03261 assert ( subjobs.isEmpty() ); 03262 switch ( res ) { 03263 case R_CANCEL: 03264 m_error = ERR_USER_CANCELED; 03265 emitResult(); 03266 return; 03267 case R_RENAME: 03268 { 03269 KURL newUrl( (*it).uDest ); 03270 newUrl.setPath( newPath ); 03271 emit renamed( this, (*it).uDest, newUrl ); // for e.g. kpropsdlg 03272 (*it).uDest = newUrl; 03273 03274 QValueList<CopyInfo> files; 03275 files.append(*it); 03276 emit aboutToCreate( this, files ); 03277 } 03278 break; 03279 case R_AUTO_SKIP: 03280 m_bAutoSkip = true; 03281 // fall through 03282 case R_SKIP: 03283 // Move on to next file 03284 skip( (*it).uSource ); 03285 m_processedSize += (*it).size; 03286 files.remove( it ); 03287 m_processedFiles++; 03288 break; 03289 case R_OVERWRITE_ALL: 03290 m_bOverwriteAll = true; 03291 break; 03292 case R_OVERWRITE: 03293 // Add to overwrite list, so that copyNextFile knows to overwrite 03294 m_overwriteList.append( (*it).uDest.path() ); 03295 break; 03296 default: 03297 assert( 0 ); 03298 } 03299 state = STATE_COPYING_FILES; 03300 //emit processedFiles( this, m_processedFiles ); 03301 copyNextFile(); 03302 } 03303 03304 void CopyJob::copyNextFile() 03305 { 03306 bool bCopyFile = false; 03307 //kdDebug(7007) << "CopyJob::copyNextFile()" << endl; 03308 // Take the first file in the list 03309 QValueList<CopyInfo>::Iterator it = files.begin(); 03310 // Is this URL on the skip list ? 03311 while (it != files.end() && !bCopyFile) 03312 { 03313 const QString destFile = (*it).uDest.path(); 03314 bCopyFile = !shouldSkip( destFile ); 03315 if ( !bCopyFile ) { 03316 files.remove( it ); 03317 it = files.begin(); 03318 } 03319 } 03320 03321 if (bCopyFile) // any file to create, finally ? 03322 { 03323 // Do we set overwrite ? 03324 bool bOverwrite; 03325 const QString destFile = (*it).uDest.path(); 03326 kdDebug(7007) << "copying " << destFile << endl; 03327 if ( (*it).uDest == (*it).uSource ) 03328 bOverwrite = false; 03329 else 03330 bOverwrite = shouldOverwrite( destFile ); 03331 03332 m_bCurrentOperationIsLink = false; 03333 KIO::Job * newjob = 0L; 03334 if ( m_mode == Link ) 03335 { 03336 //kdDebug(7007) << "Linking" << endl; 03337 if ( 03338 ((*it).uSource.protocol() == (*it).uDest.protocol()) && 03339 ((*it).uSource.host() == (*it).uDest.host()) && 03340 ((*it).uSource.port() == (*it).uDest.port()) && 03341 ((*it).uSource.user() == (*it).uDest.user()) && 03342 ((*it).uSource.pass() == (*it).uDest.pass()) ) 03343 { 03344 // This is the case of creating a real symlink 03345 KIO::SimpleJob *newJob = KIO::symlink( (*it).uSource.path(), (*it).uDest, bOverwrite, false /*no GUI*/ ); 03346 newjob = newJob; 03347 Scheduler::scheduleJob(newJob); 03348 //kdDebug(7007) << "CopyJob::copyNextFile : Linking target=" << (*it).uSource.path() << " link=" << (*it).uDest << endl; 03349 //emit linking( this, (*it).uSource.path(), (*it).uDest ); 03350 m_bCurrentOperationIsLink = true; 03351 m_currentSrcURL=(*it).uSource; 03352 m_currentDestURL=(*it).uDest; 03353 d->m_bURLDirty = true; 03354 //Observer::self()->slotCopying( this, (*it).uSource, (*it).uDest ); // should be slotLinking perhaps 03355 } else { 03356 //kdDebug(7007) << "CopyJob::copyNextFile : Linking URL=" << (*it).uSource << " link=" << (*it).uDest << endl; 03357 if ( (*it).uDest.isLocalFile() ) 03358 { 03359 bool devicesOk=false; 03360 03361 // if the source is a devices url, handle it a littlebit special 03362 if ((*it).uSource.protocol()==QString::fromLatin1("devices")) 03363 { 03364 QByteArray data; 03365 QByteArray param; 03366 QCString retType; 03367 QDataStream streamout(param,IO_WriteOnly); 03368 streamout<<(*it).uSource; 03369 streamout<<(*it).uDest; 03370 if ( kapp && kapp->dcopClient()->call( "kded", 03371 "mountwatcher", "createLink(KURL, KURL)", param,retType,data,false ) ) 03372 { 03373 QDataStream streamin(data,IO_ReadOnly); 03374 streamin>>devicesOk; 03375 } 03376 if (devicesOk) 03377 { 03378 files.remove( it ); 03379 m_processedFiles++; 03380 //emit processedFiles( this, m_processedFiles ); 03381 copyNextFile(); 03382 return; 03383 } 03384 } 03385 03386 if (!devicesOk) 03387 { 03388 QString path = (*it).uDest.path(); 03389 //kdDebug(7007) << "CopyJob::copyNextFile path=" << path << endl; 03390 QFile f( path ); 03391 if ( f.open( IO_ReadWrite ) ) 03392 { 03393 f.close(); 03394 KSimpleConfig config( path ); 03395 config.setDesktopGroup(); 03396 KURL url = (*it).uSource; 03397 url.setPass( "" ); 03398 config.writePathEntry( QString::fromLatin1("URL"), url.url() ); 03399 config.writeEntry( QString::fromLatin1("Name"), url.url() ); 03400 config.writeEntry( QString::fromLatin1("Type"), QString::fromLatin1("Link") ); 03401 QString protocol = (*it).uSource.protocol(); 03402 if ( protocol == QString::fromLatin1("ftp") ) 03403 config.writeEntry( QString::fromLatin1("Icon"), QString::fromLatin1("ftp") ); 03404 else if ( protocol == QString::fromLatin1("http") ) 03405 config.writeEntry( QString::fromLatin1("Icon"), QString::fromLatin1("www") ); 03406 else if ( protocol == QString::fromLatin1("info") ) 03407 config.writeEntry( QString::fromLatin1("Icon"), QString::fromLatin1("info") ); 03408 else if ( protocol == QString::fromLatin1("mailto") ) // sven: 03409 config.writeEntry( QString::fromLatin1("Icon"), QString::fromLatin1("kmail") ); // added mailto: support 03410 else 03411 config.writeEntry( QString::fromLatin1("Icon"), QString::fromLatin1("unknown") ); 03412 config.sync(); 03413 files.remove( it ); 03414 m_processedFiles++; 03415 //emit processedFiles( this, m_processedFiles ); 03416 copyNextFile(); 03417 return; 03418 } 03419 else 03420 { 03421 kdDebug(7007) << "CopyJob::copyNextFile ERR_CANNOT_OPEN_FOR_WRITING" << endl; 03422 m_error = ERR_CANNOT_OPEN_FOR_WRITING; 03423 m_errorText = (*it).uDest.path(); 03424 emitResult(); 03425 return; 03426 } 03427 } 03428 } else { 03429 // Todo: not show "link" on remote dirs if the src urls are not from the same protocol+host+... 03430 m_error = ERR_CANNOT_SYMLINK; 03431 m_errorText = (*it).uDest.prettyURL(); 03432 emitResult(); 03433 return; 03434 } 03435 } 03436 } 03437 else if ( !(*it).linkDest.isEmpty() && 03438 ((*it).uSource.protocol() == (*it).uDest.protocol()) && 03439 ((*it).uSource.host() == (*it).uDest.host()) && 03440 ((*it).uSource.port() == (*it).uDest.port()) && 03441 ((*it).uSource.user() == (*it).uDest.user()) && 03442 ((*it).uSource.pass() == (*it).uDest.pass())) 03443 // Copying a symlink - only on the same protocol/host/etc. (#5601, downloading an FTP file through its link), 03444 { 03445 KIO::SimpleJob *newJob = KIO::symlink( (*it).linkDest, (*it).uDest, bOverwrite, false /*no GUI*/ ); 03446 Scheduler::scheduleJob(newJob); 03447 newjob = newJob; 03448 //kdDebug(7007) << "CopyJob::copyNextFile : Linking target=" << (*it).linkDest << " link=" << (*it).uDest << endl; 03449 //emit linking( this, (*it).linkDest, (*it).uDest ); 03450 m_currentSrcURL=(*it).linkDest; 03451 m_currentDestURL=(*it).uDest; 03452 d->m_bURLDirty = true; 03453 //Observer::self()->slotCopying( this, (*it).linkDest, (*it).uDest ); // should be slotLinking perhaps 03454 m_bCurrentOperationIsLink = true; 03455 // NOTE: if we are moving stuff, the deletion of the source will be done in slotResultCopyingFiles 03456 } else if (m_mode == Move) // Moving a file 03457 { 03458 KIO::FileCopyJob * moveJob = KIO::file_move( (*it).uSource, (*it).uDest, (*it).permissions, bOverwrite, false, false/*no GUI*/ ); 03459 moveJob->setSourceSize64( (*it).size ); 03460 newjob = moveJob; 03461 //kdDebug(7007) << "CopyJob::copyNextFile : Moving " << (*it).uSource << " to " << (*it).uDest << endl; 03462 //emit moving( this, (*it).uSource, (*it).uDest ); 03463 m_currentSrcURL=(*it).uSource; 03464 m_currentDestURL=(*it).uDest; 03465 d->m_bURLDirty = true; 03466 //Observer::self()->slotMoving( this, (*it).uSource, (*it).uDest ); 03467 } 03468 else // Copying a file 03469 { 03470 // If source isn't local and target is local, we ignore the original permissions 03471 // Otherwise, files downloaded from HTTP end up with -r--r--r-- 03472 bool remoteSource = !KProtocolInfo::supportsListing((*it).uSource); 03473 int permissions = (*it).permissions; 03474 if ( d->m_defaultPermissions || ( remoteSource && (*it).uDest.isLocalFile() ) ) 03475 permissions = -1; 03476 KIO::FileCopyJob * copyJob = KIO::file_copy( (*it).uSource, (*it).uDest, permissions, bOverwrite, false, false/*no GUI*/ ); 03477 copyJob->setParentJob( this ); // in case of rename dialog 03478 copyJob->setSourceSize64( (*it).size ); 03479 copyJob->setModificationTime( (*it).mtime ); 03480 newjob = copyJob; 03481 //kdDebug(7007) << "CopyJob::copyNextFile : Copying " << (*it).uSource << " to " << (*it).uDest << endl; 03482 m_currentSrcURL=(*it).uSource; 03483 m_currentDestURL=(*it).uDest; 03484 d->m_bURLDirty = true; 03485 } 03486 addSubjob(newjob); 03487 connect( newjob, SIGNAL( processedSize( KIO::Job*, KIO::filesize_t ) ), 03488 this, SLOT( slotProcessedSize( KIO::Job*, KIO::filesize_t ) ) ); 03489 connect( newjob, SIGNAL( totalSize( KIO::Job*, KIO::filesize_t ) ), 03490 this, SLOT( slotTotalSize( KIO::Job*, KIO::filesize_t ) ) ); 03491 } 03492 else 03493 { 03494 // We're done 03495 //kdDebug(7007) << "copyNextFile finished" << endl; 03496 deleteNextDir(); 03497 } 03498 } 03499 03500 void CopyJob::deleteNextDir() 03501 { 03502 if ( m_mode == Move && !dirsToRemove.isEmpty() ) // some dirs to delete ? 03503 { 03504 state = STATE_DELETING_DIRS; 03505 d->m_bURLDirty = true; 03506 // Take first dir to delete out of list - last ones first ! 03507 KURL::List::Iterator it = dirsToRemove.fromLast(); 03508 SimpleJob *job = KIO::rmdir( *it ); 03509 Scheduler::scheduleJob(job); 03510 dirsToRemove.remove(it); 03511 addSubjob( job ); 03512 } 03513 else 03514 { 03515 // This step is done, move on 03516 setNextDirAttribute(); 03517 } 03518 } 03519 03520 void CopyJob::setNextDirAttribute() 03521 { 03522 if ( !d->m_directoriesCopied.isEmpty() ) 03523 { 03524 state = STATE_SETTING_DIR_ATTRIBUTES; 03525 #ifdef Q_OS_UNIX 03526 // TODO KDE4: this should use a SlaveBase method, but we have none yet in KDE3. 03527 QValueList<CopyInfo>::Iterator it = d->m_directoriesCopied.begin(); 03528 for ( ; it != d->m_directoriesCopied.end() ; ++it ) { 03529 const KURL& url = (*it).uDest; 03530 if ( url.isLocalFile() && (*it).mtime != (time_t)-1 ) { 03531 const QCString path = QFile::encodeName( url.path() ); 03532 KDE_struct_stat statbuf; 03533 if (KDE_lstat(path, &statbuf) == 0) { 03534 struct utimbuf utbuf; 03535 utbuf.actime = statbuf.st_atime; // access time, unchanged 03536 utbuf.modtime = (*it).mtime; // modification time 03537 utime( path, &utbuf ); 03538 } 03539 03540 } 03541 } 03542 #endif 03543 d->m_directoriesCopied.clear(); 03544 } 03545 03546 // No "else" here, since the above is a simple sync loop 03547 03548 { 03549 // Finished - tell the world 03550 if ( !m_bOnlyRenames ) 03551 { 03552 KDirNotify_stub allDirNotify("*", "KDirNotify*"); 03553 KURL url( d->m_globalDest ); 03554 if ( d->m_globalDestinationState != DEST_IS_DIR || m_asMethod ) 03555 url.setPath( url.directory() ); 03556 //kdDebug(7007) << "KDirNotify'ing FilesAdded " << url << endl; 03557 allDirNotify.FilesAdded( url ); 03558 03559 if ( m_mode == Move && !m_srcList.isEmpty() ) { 03560 //kdDebug(7007) << "KDirNotify'ing FilesRemoved " << m_srcList.toStringList() << endl; 03561 allDirNotify.FilesRemoved( m_srcList ); 03562 } 03563 } 03564 if (m_reportTimer) 03565 m_reportTimer->stop(); 03566 --m_processedFiles; // undo the "start at 1" hack 03567 slotReport(); // display final numbers, important if progress dialog stays up 03568 03569 emitResult(); 03570 } 03571 } 03572 03573 void CopyJob::slotProcessedSize( KIO::Job*, KIO::filesize_t data_size ) 03574 { 03575 //kdDebug(7007) << "CopyJob::slotProcessedSize " << data_size << endl; 03576 m_fileProcessedSize = data_size; 03577 setProcessedSize(m_processedSize + m_fileProcessedSize); 03578 03579 if ( m_processedSize + m_fileProcessedSize > m_totalSize ) 03580 { 03581 m_totalSize = m_processedSize + m_fileProcessedSize; 03582 //kdDebug(7007) << "Adjusting m_totalSize to " << m_totalSize << endl; 03583 emit totalSize( this, m_totalSize ); // safety 03584 } 03585 //kdDebug(7007) << "emit processedSize " << (unsigned long) (m_processedSize + m_fileProcessedSize) << endl; 03586 emit processedSize( this, m_processedSize + m_fileProcessedSize ); 03587 emitPercent( m_processedSize + m_fileProcessedSize, m_totalSize ); 03588 } 03589 03590 void CopyJob::slotTotalSize( KIO::Job*, KIO::filesize_t size ) 03591 { 03592 //kdDebug(7007) << "slotTotalSize: " << size << endl; 03593 // Special case for copying a single file 03594 // This is because some protocols don't implement stat properly 03595 // (e.g. HTTP), and don't give us a size in some cases (redirection) 03596 // so we'd rather rely on the size given for the transfer 03597 if ( m_bSingleFileCopy && size > m_totalSize) 03598 { 03599 //kdDebug(7007) << "slotTotalSize: updating totalsize to " << size << endl; 03600 m_totalSize = size; 03601 emit totalSize( this, size ); 03602 } 03603 } 03604 03605 void CopyJob::slotResultDeletingDirs( Job * job ) 03606 { 03607 if (job->error()) 03608 { 03609 // Couldn't remove directory. Well, perhaps it's not empty 03610 // because the user pressed Skip for a given file in it. 03611 // Let's not display "Could not remove dir ..." for each of those dir ! 03612 } 03613 subjobs.remove( job ); 03614 assert ( subjobs.isEmpty() ); 03615 deleteNextDir(); 03616 } 03617 03618 #if 0 // TODO KDE4 03619 void CopyJob::slotResultSettingDirAttributes( Job * job ) 03620 { 03621 if (job->error()) 03622 { 03623 // Couldn't set directory attributes. Ignore the error, it can happen 03624 // with inferior file systems like VFAT. 03625 // Let's not display warnings for each dir like "cp -a" does. 03626 } 03627 subjobs.remove( job ); 03628 assert ( subjobs.isEmpty() ); 03629 setNextDirAttribute(); 03630 } 03631 #endif 03632 03633 void CopyJob::slotResultRenaming( Job* job ) 03634 { 03635 int err = job->error(); 03636 const QString errText = job->errorText(); 03637 removeSubjob( job, true, false ); // merge metadata 03638 assert ( subjobs.isEmpty() ); 03639 // Determine dest again 03640 KURL dest = m_dest; 03641 if ( destinationState == DEST_IS_DIR && !m_asMethod ) 03642 dest.addPath( m_currentSrcURL.fileName() ); 03643 if ( err ) 03644 { 03645 // Direct renaming didn't work. Try renaming to a temp name, 03646 // this can help e.g. when renaming 'a' to 'A' on a VFAT partition. 03647 // In that case it's the _same_ dir, we don't want to copy+del (data loss!) 03648 if ( m_currentSrcURL.isLocalFile() && m_currentSrcURL.url(-1) != dest.url(-1) && 03649 m_currentSrcURL.url(-1).lower() == dest.url(-1).lower() && 03650 ( err == ERR_FILE_ALREADY_EXIST || 03651 err == ERR_DIR_ALREADY_EXIST || 03652 err == ERR_IDENTICAL_FILES ) ) 03653 { 03654 kdDebug(7007) << "Couldn't rename directly, dest already exists. Detected special case of lower/uppercase renaming in same dir, try with 2 rename calls" << endl; 03655 QCString _src( QFile::encodeName(m_currentSrcURL.path()) ); 03656 QCString _dest( QFile::encodeName(dest.path()) ); 03657 KTempFile tmpFile( m_currentSrcURL.directory(false) ); 03658 QCString _tmp( QFile::encodeName(tmpFile.name()) ); 03659 kdDebug(7007) << "CopyJob::slotResult KTempFile status:" << tmpFile.status() << " using " << _tmp << " as intermediary" << endl; 03660 tmpFile.unlink(); 03661 if ( ::rename( _src, _tmp ) == 0 ) 03662 { 03663 if ( !QFile::exists( _dest ) && ::rename( _tmp, _dest ) == 0 ) 03664 { 03665 kdDebug(7007) << "Success." << endl; 03666 err = 0; 03667 } 03668 else 03669 { 03670 // Revert back to original name! 03671 if ( ::rename( _tmp, _src ) != 0 ) { 03672 kdError(7007) << "Couldn't rename " << tmpFile.name() << " back to " << _src << " !" << endl; 03673 // Severe error, abort 03674 Job::slotResult( job ); // will set the error and emit result(this) 03675 return; 03676 } 03677 } 03678 } 03679 } 03680 } 03681 if ( err ) 03682 { 03683 // This code is similar to CopyJob::slotResultConflictCopyingFiles 03684 // but here it's about the base src url being moved/renamed 03685 // (*m_currentStatSrc) and its dest (m_dest), not about a single file. 03686 // It also means we already stated the dest, here. 03687 // On the other hand we haven't stated the src yet (we skipped doing it 03688 // to save time, since it's not necessary to rename directly!)... 03689 03690 Q_ASSERT( m_currentSrcURL == *m_currentStatSrc ); 03691 03692 // Existing dest? 03693 if ( ( err == ERR_DIR_ALREADY_EXIST || 03694 err == ERR_FILE_ALREADY_EXIST || 03695 err == ERR_IDENTICAL_FILES ) 03696 && isInteractive() ) 03697 { 03698 if (m_reportTimer) 03699 m_reportTimer->stop(); 03700 03701 // Should we skip automatically ? 03702 if ( m_bAutoSkip ) { 03703 // Move on to next file 03704 skipSrc(); 03705 return; 03706 } else if ( m_bOverwriteAll ) { 03707 ; // nothing to do, stat+copy+del will overwrite 03708 } else { 03709 QString newPath; 03710 // If src==dest, use "overwrite-itself" 03711 RenameDlg_Mode mode = (RenameDlg_Mode) 03712 ( ( m_currentSrcURL == dest ) ? M_OVERWRITE_ITSELF : M_OVERWRITE ); 03713 03714 if ( m_srcList.count() > 1 ) 03715 mode = (RenameDlg_Mode) ( mode | M_MULTI | M_SKIP ); 03716 else 03717 mode = (RenameDlg_Mode) ( mode | M_SINGLE ); 03718 03719 // we lack mtime info for both the src (not stated) 03720 // and the dest (stated but this info wasn't stored) 03721 // Let's do it for local files, at least 03722 KIO::filesize_t sizeSrc = (KIO::filesize_t) -1; 03723 KIO::filesize_t sizeDest = (KIO::filesize_t) -1; 03724 time_t ctimeSrc = (time_t) -1; 03725 time_t ctimeDest = (time_t) -1; 03726 time_t mtimeSrc = (time_t) -1; 03727 time_t mtimeDest = (time_t) -1; 03728 03729 KDE_struct_stat stat_buf; 03730 if ( m_currentSrcURL.isLocalFile() && 03731 KDE_stat(QFile::encodeName(m_currentSrcURL.path()), &stat_buf) == 0 ) { 03732 sizeSrc = stat_buf.st_size; 03733 ctimeSrc = stat_buf.st_ctime; 03734 mtimeSrc = stat_buf.st_mtime; 03735 } 03736 if ( dest.isLocalFile() && 03737 KDE_stat(QFile::encodeName(dest.path()), &stat_buf) == 0 ) { 03738 sizeDest = stat_buf.st_size; 03739 ctimeDest = stat_buf.st_ctime; 03740 mtimeDest = stat_buf.st_mtime; 03741 } 03742 03743 RenameDlg_Result r = Observer::self()->open_RenameDlg( 03744 this, 03745 err != ERR_DIR_ALREADY_EXIST ? i18n("File Already Exists") : i18n("Already Exists as Folder"), 03746 m_currentSrcURL.url(), 03747 dest.url(), 03748 mode, newPath, 03749 sizeSrc, sizeDest, 03750 ctimeSrc, ctimeDest, 03751 mtimeSrc, mtimeDest ); 03752 if (m_reportTimer) 03753 m_reportTimer->start(REPORT_TIMEOUT,false); 03754 03755 switch ( r ) 03756 { 03757 case R_CANCEL: 03758 { 03759 m_error = ERR_USER_CANCELED; 03760 emitResult(); 03761 return; 03762 } 03763 case R_RENAME: 03764 { 03765 // Set m_dest to the chosen destination 03766 // This is only for this src url; the next one will revert to d->m_globalDest 03767 m_dest.setPath( newPath ); 03768 KIO::Job* job = KIO::stat( m_dest, false, 2, false ); 03769 state = STATE_STATING; 03770 destinationState = DEST_NOT_STATED; 03771 addSubjob(job); 03772 return; 03773 } 03774 case R_AUTO_SKIP: 03775 m_bAutoSkip = true; 03776 // fall through 03777 case R_SKIP: 03778 // Move on to next file 03779 skipSrc(); 03780 return; 03781 case R_OVERWRITE_ALL: 03782 m_bOverwriteAll = true; 03783 break; 03784 case R_OVERWRITE: 03785 // Add to overwrite list 03786 // Note that we add dest, not m_dest. 03787 // This ensures that when moving several urls into a dir (m_dest), 03788 // we only overwrite for the current one, not for all. 03789 // When renaming a single file (m_asMethod), it makes no difference. 03790 kdDebug(7007) << "adding to overwrite list: " << dest.path() << endl; 03791 m_overwriteList.append( dest.path() ); 03792 break; 03793 default: 03794 //assert( 0 ); 03795 break; 03796 } 03797 } 03798 } else if ( err != KIO::ERR_UNSUPPORTED_ACTION ) { 03799 kdDebug(7007) << "Couldn't rename " << m_currentSrcURL << " to " << dest << ", aborting" << endl; 03800 m_error = err; 03801 m_errorText = errText; 03802 emitResult(); 03803 return; 03804 } 03805 kdDebug(7007) << "Couldn't rename " << m_currentSrcURL << " to " << dest << ", reverting to normal way, starting with stat" << endl; 03806 //kdDebug(7007) << "KIO::stat on " << m_currentSrcURL << endl; 03807 KIO::Job* job = KIO::stat( m_currentSrcURL, true, 2, false ); 03808 state = STATE_STATING; 03809 addSubjob(job); 03810 m_bOnlyRenames = false; 03811 } 03812 else 03813 { 03814 //kdDebug(7007) << "Renaming succeeded, move on" << endl; 03815 emit copyingDone( this, *m_currentStatSrc, dest, true, true ); 03816 statNextSrc(); 03817 } 03818 } 03819 03820 void CopyJob::slotResult( Job *job ) 03821 { 03822 //kdDebug(7007) << "CopyJob::slotResult() state=" << (int) state << endl; 03823 // In each case, what we have to do is : 03824 // 1 - check for errors and treat them 03825 // 2 - subjobs.remove(job); 03826 // 3 - decide what to do next 03827 03828 switch ( state ) { 03829 case STATE_STATING: // We were trying to stat a src url or the dest 03830 slotResultStating( job ); 03831 break; 03832 case STATE_RENAMING: // We were trying to do a direct renaming, before even stat'ing 03833 { 03834 slotResultRenaming( job ); 03835 break; 03836 } 03837 case STATE_LISTING: // recursive listing finished 03838 //kdDebug(7007) << "totalSize: " << (unsigned int) m_totalSize << " files: " << files.count() << " dirs: " << dirs.count() << endl; 03839 // Was there an error ? 03840 if (job->error()) 03841 { 03842 Job::slotResult( job ); // will set the error and emit result(this) 03843 return; 03844 } 03845 03846 subjobs.remove( job ); 03847 assert ( subjobs.isEmpty() ); 03848 03849 statNextSrc(); 03850 break; 03851 case STATE_CREATING_DIRS: 03852 slotResultCreatingDirs( job ); 03853 break; 03854 case STATE_CONFLICT_CREATING_DIRS: 03855 slotResultConflictCreatingDirs( job ); 03856 break; 03857 case STATE_COPYING_FILES: 03858 slotResultCopyingFiles( job ); 03859 break; 03860 case STATE_CONFLICT_COPYING_FILES: 03861 slotResultConflictCopyingFiles( job ); 03862 break; 03863 case STATE_DELETING_DIRS: 03864 slotResultDeletingDirs( job ); 03865 break; 03866 case STATE_SETTING_DIR_ATTRIBUTES: // TODO KDE4 03867 assert( 0 ); 03868 //slotResultSettingDirAttributes( job ); 03869 break; 03870 default: 03871 assert( 0 ); 03872 } 03873 } 03874 03875 void KIO::CopyJob::setDefaultPermissions( bool b ) 03876 { 03877 d->m_defaultPermissions = b; 03878 } 03879 03880 // KDE4: remove 03881 void KIO::CopyJob::setInteractive( bool b ) 03882 { 03883 Job::setInteractive( b ); 03884 } 03885 03886 CopyJob *KIO::copy(const KURL& src, const KURL& dest, bool showProgressInfo ) 03887 { 03888 //kdDebug(7007) << "KIO::copy src=" << src << " dest=" << dest << endl; 03889 KURL::List srcList; 03890 srcList.append( src ); 03891 return new CopyJob( srcList, dest, CopyJob::Copy, false, showProgressInfo ); 03892 } 03893 03894 CopyJob *KIO::copyAs(const KURL& src, const KURL& dest, bool showProgressInfo ) 03895 { 03896 //kdDebug(7007) << "KIO::copyAs src=" << src << " dest=" << dest << endl; 03897 KURL::List srcList; 03898 srcList.append( src ); 03899 return new CopyJob( srcList, dest, CopyJob::Copy, true, showProgressInfo ); 03900 } 03901 03902 CopyJob *KIO::copy( const KURL::List& src, const KURL& dest, bool showProgressInfo ) 03903 { 03904 //kdDebug(7007) << src << " " << dest << endl; 03905 return new CopyJob( src, dest, CopyJob::Copy, false, showProgressInfo ); 03906 } 03907 03908 CopyJob *KIO::move(const KURL& src, const KURL& dest, bool showProgressInfo ) 03909 { 03910 //kdDebug(7007) << src << " " << dest << endl; 03911 KURL::List srcList; 03912 srcList.append( src ); 03913 return new CopyJob( srcList, dest, CopyJob::Move, false, showProgressInfo ); 03914 } 03915 03916 CopyJob *KIO::moveAs(const KURL& src, const KURL& dest, bool showProgressInfo ) 03917 { 03918 //kdDebug(7007) << src << " " << dest << endl; 03919 KURL::List srcList; 03920 srcList.append( src ); 03921 return new CopyJob( srcList, dest, CopyJob::Move, true, showProgressInfo ); 03922 } 03923 03924 CopyJob *KIO::move( const KURL::List& src, const KURL& dest, bool showProgressInfo ) 03925 { 03926 //kdDebug(7007) << src << " " << dest << endl; 03927 return new CopyJob( src, dest, CopyJob::Move, false, showProgressInfo ); 03928 } 03929 03930 CopyJob *KIO::link(const KURL& src, const KURL& destDir, bool showProgressInfo ) 03931 { 03932 KURL::List srcList; 03933 srcList.append( src ); 03934 return new CopyJob( srcList, destDir, CopyJob::Link, false, showProgressInfo ); 03935 } 03936 03937 CopyJob *KIO::link(const KURL::List& srcList, const KURL& destDir, bool showProgressInfo ) 03938 { 03939 return new CopyJob( srcList, destDir, CopyJob::Link, false, showProgressInfo ); 03940 } 03941 03942 CopyJob *KIO::linkAs(const KURL& src, const KURL& destDir, bool showProgressInfo ) 03943 { 03944 KURL::List srcList; 03945 srcList.append( src ); 03946 return new CopyJob( srcList, destDir, CopyJob::Link, false, showProgressInfo ); 03947 } 03948 03949 CopyJob *KIO::trash(const KURL& src, bool showProgressInfo ) 03950 { 03951 KURL::List srcList; 03952 srcList.append( src ); 03953 return new CopyJob( srcList, KURL( "trash:/" ), CopyJob::Move, false, showProgressInfo ); 03954 } 03955 03956 CopyJob *KIO::trash(const KURL::List& srcList, bool showProgressInfo ) 03957 { 03958 return new CopyJob( srcList, KURL( "trash:/" ), CopyJob::Move, false, showProgressInfo ); 03959 } 03960 03962 03963 DeleteJob::DeleteJob( const KURL::List& src, bool /*shred*/, bool showProgressInfo ) 03964 : Job(showProgressInfo), m_totalSize( 0 ), m_processedSize( 0 ), m_fileProcessedSize( 0 ), 03965 m_processedFiles( 0 ), m_processedDirs( 0 ), m_totalFilesDirs( 0 ), 03966 m_srcList(src), m_currentStat(m_srcList.begin()), m_reportTimer(0) 03967 { 03968 if ( showProgressInfo ) { 03969 03970 connect( this, SIGNAL( totalFiles( KIO::Job*, unsigned long ) ), 03971 Observer::self(), SLOT( slotTotalFiles( KIO::Job*, unsigned long ) ) ); 03972 03973 connect( this, SIGNAL( totalDirs( KIO::Job*, unsigned long ) ), 03974 Observer::self(), SLOT( slotTotalDirs( KIO::Job*, unsigned long ) ) ); 03975 03976 // See slotReport 03977 /*connect( this, SIGNAL( processedFiles( KIO::Job*, unsigned long ) ), 03978 m_observer, SLOT( slotProcessedFiles( KIO::Job*, unsigned long ) ) ); 03979 03980 connect( this, SIGNAL( processedDirs( KIO::Job*, unsigned long ) ), 03981 m_observer, SLOT( slotProcessedDirs( KIO::Job*, unsigned long ) ) ); 03982 03983 connect( this, SIGNAL( deleting( KIO::Job*, const KURL& ) ), 03984 m_observer, SLOT( slotDeleting( KIO::Job*, const KURL& ) ) );*/ 03985 03986 m_reportTimer=new QTimer(this); 03987 connect(m_reportTimer,SIGNAL(timeout()),this,SLOT(slotReport())); 03988 //this will update the report dialog with 5 Hz, I think this is fast enough, aleXXX 03989 m_reportTimer->start(REPORT_TIMEOUT,false); 03990 } 03991 03992 QTimer::singleShot(0, this, SLOT(slotStart())); 03993 } 03994 03995 void DeleteJob::slotStart() 03996 { 03997 statNextSrc(); 03998 } 03999 04000 //this is called often, so calling the functions 04001 //from Observer here directly might improve the performance a little bit 04002 //aleXXX 04003 void DeleteJob::slotReport() 04004 { 04005 if (m_progressId==0) 04006 return; 04007 04008 Observer * observer = Observer::self(); 04009 04010 emit deleting( this, m_currentURL ); 04011 observer->slotDeleting(this,m_currentURL); 04012 04013 switch( state ) { 04014 case STATE_STATING: 04015 case STATE_LISTING: 04016 emit totalSize( this, m_totalSize ); 04017 emit totalFiles( this, files.count() ); 04018 emit totalDirs( this, dirs.count() ); 04019 break; 04020 case STATE_DELETING_DIRS: 04021 emit processedDirs( this, m_processedDirs ); 04022 observer->slotProcessedDirs(this,m_processedDirs); 04023 emitPercent( m_processedFiles + m_processedDirs, m_totalFilesDirs ); 04024 break; 04025 case STATE_DELETING_FILES: 04026 observer->slotProcessedFiles(this,m_processedFiles); 04027 emit processedFiles( this, m_processedFiles ); 04028 emitPercent( m_processedFiles, m_totalFilesDirs ); 04029 break; 04030 } 04031 } 04032 04033 04034 void DeleteJob::slotEntries(KIO::Job* job, const UDSEntryList& list) 04035 { 04036 UDSEntryListConstIterator it = list.begin(); 04037 UDSEntryListConstIterator end = list.end(); 04038 for (; it != end; ++it) 04039 { 04040 UDSEntry::ConstIterator it2 = (*it).begin(); 04041 bool bDir = false; 04042 bool bLink = false; 04043 QString displayName; 04044 KURL url; 04045 int atomsFound(0); 04046 for( ; it2 != (*it).end(); it2++ ) 04047 { 04048 switch ((*it2).m_uds) 04049 { 04050 case UDS_FILE_TYPE: 04051 bDir = S_ISDIR((*it2).m_long); 04052 atomsFound++; 04053 break; 04054 case UDS_NAME: 04055 displayName = (*it2).m_str; 04056 atomsFound++; 04057 break; 04058 case UDS_URL: 04059 url = KURL((*it2).m_str); 04060 atomsFound++; 04061 break; 04062 case UDS_LINK_DEST: 04063 bLink = !(*it2).m_str.isEmpty(); 04064 atomsFound++; 04065 break; 04066 case UDS_SIZE: 04067 m_totalSize += (KIO::filesize_t)((*it2).m_long); 04068 atomsFound++; 04069 break; 04070 default: 04071 break; 04072 } 04073 if (atomsFound==5) break; 04074 } 04075 assert(!displayName.isEmpty()); 04076 if (displayName != ".." && displayName != ".") 04077 { 04078 if( url.isEmpty() ) { 04079 url = ((SimpleJob *)job)->url(); // assumed to be a dir 04080 url.addPath( displayName ); 04081 } 04082 //kdDebug(7007) << "DeleteJob::slotEntries " << displayName << " (" << url << ")" << endl; 04083 if ( bLink ) 04084 symlinks.append( url ); 04085 else if ( bDir ) 04086 dirs.append( url ); 04087 else 04088 files.append( url ); 04089 } 04090 } 04091 } 04092 04093 04094 void DeleteJob::statNextSrc() 04095 { 04096 //kdDebug(7007) << "statNextSrc" << endl; 04097 if ( m_currentStat != m_srcList.end() ) 04098 { 04099 m_currentURL = (*m_currentStat); 04100 04101 // if the file system doesn't support deleting, we do not even stat 04102 if (!KProtocolInfo::supportsDeleting(m_currentURL)) { 04103 QGuardedPtr<DeleteJob> that = this; 04104 ++m_currentStat; 04105 if (isInteractive()) 04106 KMessageBox::information( 0, buildErrorString(ERR_CANNOT_DELETE, m_currentURL.prettyURL())); 04107 if (that) 04108 statNextSrc(); 04109 return; 04110 } 04111 // Stat it 04112 state = STATE_STATING; 04113 KIO::SimpleJob * job = KIO::stat( m_currentURL, true, 1, false ); 04114 Scheduler::scheduleJob(job); 04115 //kdDebug(7007) << "KIO::stat (DeleteJob) " << m_currentURL << endl; 04116 addSubjob(job); 04117 //if ( m_progressId ) // Did we get an ID from the observer ? 04118 // Observer::self()->slotDeleting( this, *it ); // show asap 04119 } else 04120 { 04121 m_totalFilesDirs = files.count()+symlinks.count() + dirs.count(); 04122 slotReport(); 04123 // Now we know which dirs hold the files we're going to delete. 04124 // To speed things up and prevent double-notification, we disable KDirWatch 04125 // on those dirs temporarily (using KDirWatch::self, that's the instanced 04126 // used by e.g. kdirlister). 04127 for ( QStringList::Iterator it = m_parentDirs.begin() ; it != m_parentDirs.end() ; ++it ) 04128 KDirWatch::self()->stopDirScan( *it ); 04129 state = STATE_DELETING_FILES; 04130 deleteNextFile(); 04131 } 04132 } 04133 04134 void DeleteJob::deleteNextFile() 04135 { 04136 //kdDebug(7007) << "deleteNextFile" << endl; 04137 if ( !files.isEmpty() || !symlinks.isEmpty() ) 04138 { 04139 SimpleJob *job; 04140 do { 04141 // Take first file to delete out of list 04142 KURL::List::Iterator it = files.begin(); 04143 bool isLink = false; 04144 if ( it == files.end() ) // No more files 04145 { 04146 it = symlinks.begin(); // Pick up a symlink to delete 04147 isLink = true; 04148 } 04149 // Normal deletion 04150 // If local file, try do it directly 04151 if ( (*it).isLocalFile() && unlink( QFile::encodeName((*it).path()) ) == 0 ) { 04152 //kdDebug(7007) << "DeleteJob deleted " << (*it).path() << endl; 04153 job = 0; 04154 m_processedFiles++; 04155 if ( m_processedFiles % 300 == 0 || m_totalFilesDirs < 300) { // update progress info every 300 files 04156 m_currentURL = *it; 04157 slotReport(); 04158 } 04159 } else 04160 { // if remote - or if unlink() failed (we'll use the job's error handling in that case) 04161 job = KIO::file_delete( *it, false /*no GUI*/); 04162 Scheduler::scheduleJob(job); 04163 m_currentURL=(*it); 04164 } 04165 if ( isLink ) 04166 symlinks.remove(it); 04167 else 04168 files.remove(it); 04169 if ( job ) { 04170 addSubjob(job); 04171 return; 04172 } 04173 // loop only if direct deletion worked (job=0) and there is something else to delete 04174 } while (!job && (!files.isEmpty() || !symlinks.isEmpty())); 04175 } 04176 state = STATE_DELETING_DIRS; 04177 deleteNextDir(); 04178 } 04179 04180 void DeleteJob::deleteNextDir() 04181 { 04182 if ( !dirs.isEmpty() ) // some dirs to delete ? 04183 { 04184 do { 04185 // Take first dir to delete out of list - last ones first ! 04186 KURL::List::Iterator it = dirs.fromLast(); 04187 // If local dir, try to rmdir it directly 04188 if ( (*it).isLocalFile() && ::rmdir( QFile::encodeName((*it).path()) ) == 0 ) { 04189 04190 m_processedDirs++; 04191 if ( m_processedDirs % 100 == 0 ) { // update progress info every 100 dirs 04192 m_currentURL = *it; 04193 slotReport(); 04194 } 04195 } else { 04196 SimpleJob* job; 04197 if ( KProtocolInfo::canDeleteRecursive( *it ) ) { 04198 // If the ioslave supports recursive deletion of a directory, then 04199 // we only need to send a single CMD_DEL command, so we use file_delete :) 04200 job = KIO::file_delete( *it, false /*no gui*/ ); 04201 } else { 04202 job = KIO::rmdir( *it ); 04203 } 04204 Scheduler::scheduleJob(job); 04205 dirs.remove(it); 04206 addSubjob( job ); 04207 return; 04208 } 04209 dirs.remove(it); 04210 } while ( !dirs.isEmpty() ); 04211 } 04212 04213 // Re-enable watching on the dirs that held the deleted files 04214 for ( QStringList::Iterator it = m_parentDirs.begin() ; it != m_parentDirs.end() ; ++it ) 04215 KDirWatch::self()->restartDirScan( *it ); 04216 04217 // Finished - tell the world 04218 if ( !m_srcList.isEmpty() ) 04219 { 04220 KDirNotify_stub allDirNotify("*", "KDirNotify*"); 04221 //kdDebug(7007) << "KDirNotify'ing FilesRemoved " << m_srcList.toStringList() << endl; 04222 allDirNotify.FilesRemoved( m_srcList ); 04223 } 04224 if (m_reportTimer!=0) 04225 m_reportTimer->stop(); 04226 emitResult(); 04227 } 04228 04229 void DeleteJob::slotProcessedSize( KIO::Job*, KIO::filesize_t data_size ) 04230 { 04231 // Note: this is the same implementation as CopyJob::slotProcessedSize but 04232 // it's different from FileCopyJob::slotProcessedSize - which is why this 04233 // is not in Job. 04234 04235 m_fileProcessedSize = data_size; 04236 setProcessedSize(m_processedSize + m_fileProcessedSize); 04237 04238 //kdDebug(7007) << "DeleteJob::slotProcessedSize " << (unsigned int) (m_processedSize + m_fileProcessedSize) << endl; 04239 04240 emit processedSize( this, m_processedSize + m_fileProcessedSize ); 04241 04242 // calculate percents 04243 unsigned long ipercent = m_percent; 04244 04245 if ( m_totalSize == 0 ) 04246 m_percent = 100; 04247 else 04248 m_percent = (unsigned long)(( (float)(m_processedSize + m_fileProcessedSize) / (float)m_totalSize ) * 100.0); 04249 04250 if ( m_percent > ipercent ) 04251 { 04252 emit percent( this, m_percent ); 04253 //kdDebug(7007) << "DeleteJob::slotProcessedSize - percent = " << (unsigned int) m_percent << endl; 04254 } 04255 04256 } 04257 04258 void DeleteJob::slotResult( Job *job ) 04259 { 04260 switch ( state ) 04261 { 04262 case STATE_STATING: 04263 { 04264 // Was there an error while stating ? 04265 if (job->error() ) 04266 { 04267 // Probably : doesn't exist 04268 Job::slotResult( job ); // will set the error and emit result(this) 04269 return; 04270 } 04271 04272 // Is it a file or a dir ? 04273 UDSEntry entry = ((StatJob*)job)->statResult(); 04274 bool bDir = false; 04275 bool bLink = false; 04276 KIO::filesize_t size = (KIO::filesize_t)-1; 04277 UDSEntry::ConstIterator it2 = entry.begin(); 04278 int atomsFound(0); 04279 for( ; it2 != entry.end(); it2++ ) 04280 { 04281 if ( ((*it2).m_uds) == UDS_FILE_TYPE ) 04282 { 04283 bDir = S_ISDIR( (mode_t)(*it2).m_long ); 04284 atomsFound++; 04285 } 04286 else if ( ((*it2).m_uds) == UDS_LINK_DEST ) 04287 { 04288 bLink = !((*it2).m_str.isEmpty()); 04289 atomsFound++; 04290 } 04291 else if ( ((*it2).m_uds) == UDS_SIZE ) 04292 { 04293 size = (*it2).m_long; 04294 atomsFound++; 04295 } 04296 if (atomsFound==3) break; 04297 } 04298 04299 KURL url = ((SimpleJob*)job)->url(); 04300 04301 subjobs.remove( job ); 04302 assert( subjobs.isEmpty() ); 04303 04304 if (bDir && !bLink) 04305 { 04306 // Add toplevel dir in list of dirs 04307 dirs.append( url ); 04308 if ( url.isLocalFile() && !m_parentDirs.contains( url.path(-1) ) ) 04309 m_parentDirs.append( url.path(-1) ); 04310 04311 if ( !KProtocolInfo::canDeleteRecursive( url ) ) { 04312 //kdDebug(7007) << " Target is a directory " << endl; 04313 // List it 04314 state = STATE_LISTING; 04315 ListJob *newjob = listRecursive( url, false ); 04316 newjob->setUnrestricted(true); // No KIOSK restrictions 04317 Scheduler::scheduleJob(newjob); 04318 connect(newjob, SIGNAL(entries( KIO::Job *, 04319 const KIO::UDSEntryList& )), 04320 SLOT( slotEntries( KIO::Job*, 04321 const KIO::UDSEntryList& ))); 04322 addSubjob(newjob); 04323 } else { 04324 ++m_currentStat; 04325 statNextSrc(); 04326 } 04327 } 04328 else 04329 { 04330 if ( bLink ) { 04331 //kdDebug(7007) << " Target is a symlink" << endl; 04332 symlinks.append( url ); 04333 } else { 04334 //kdDebug(7007) << " Target is a file" << endl; 04335 files.append( url ); 04336 } 04337 if ( url.isLocalFile() && !m_parentDirs.contains( url.directory(false) ) ) 04338 m_parentDirs.append( url.directory(false) ); 04339 ++m_currentStat; 04340 statNextSrc(); 04341 } 04342 } 04343 break; 04344 case STATE_LISTING: 04345 if ( job->error() ) 04346 { 04347 // Try deleting nonetheless, it may be empty (and non-listable) 04348 } 04349 subjobs.remove( job ); 04350 assert( subjobs.isEmpty() ); 04351 ++m_currentStat; 04352 statNextSrc(); 04353 break; 04354 case STATE_DELETING_FILES: 04355 if ( job->error() ) 04356 { 04357 Job::slotResult( job ); // will set the error and emit result(this) 04358 return; 04359 } 04360 subjobs.remove( job ); 04361 assert( subjobs.isEmpty() ); 04362 m_processedFiles++; 04363 04364 deleteNextFile(); 04365 break; 04366 case STATE_DELETING_DIRS: 04367 if ( job->error() ) 04368 { 04369 Job::slotResult( job ); // will set the error and emit result(this) 04370 return; 04371 } 04372 subjobs.remove( job ); 04373 assert( subjobs.isEmpty() ); 04374 m_processedDirs++; 04375 //emit processedDirs( this, m_processedDirs ); 04376 //if (!m_shred) 04377 //emitPercent( m_processedFiles + m_processedDirs, m_totalFilesDirs ); 04378 04379 deleteNextDir(); 04380 break; 04381 default: 04382 assert(0); 04383 } 04384 } 04385 04386 DeleteJob *KIO::del( const KURL& src, bool shred, bool showProgressInfo ) 04387 { 04388 KURL::List srcList; 04389 srcList.append( src ); 04390 DeleteJob *job = new DeleteJob( srcList, shred, showProgressInfo ); 04391 return job; 04392 } 04393 04394 DeleteJob *KIO::del( const KURL::List& src, bool shred, bool showProgressInfo ) 04395 { 04396 DeleteJob *job = new DeleteJob( src, shred, showProgressInfo ); 04397 return job; 04398 } 04399 04400 MultiGetJob::MultiGetJob(const KURL& url, 04401 bool showProgressInfo) 04402 : TransferJob(url, 0, QByteArray(), QByteArray(), showProgressInfo) 04403 { 04404 m_waitQueue.setAutoDelete(true); 04405 m_activeQueue.setAutoDelete(true); 04406 m_currentEntry = 0; 04407 } 04408 04409 void MultiGetJob::get(long id, const KURL &url, const MetaData &metaData) 04410 { 04411 GetRequest *entry = new GetRequest(id, url, metaData); 04412 entry->metaData["request-id"] = QString("%1").arg(id); 04413 m_waitQueue.append(entry); 04414 } 04415 04416 void MultiGetJob::flushQueue(QPtrList<GetRequest> &queue) 04417 { 04418 GetRequest *entry; 04419 // Use multi-get 04420 // Scan all jobs in m_waitQueue 04421 for(entry = m_waitQueue.first(); entry; ) 04422 { 04423 if ((m_url.protocol() == entry->url.protocol()) && 04424 (m_url.host() == entry->url.host()) && 04425 (m_url.port() == entry->url.port()) && 04426 (m_url.user() == entry->url.user())) 04427 { 04428 m_waitQueue.take(); 04429 queue.append(entry); 04430 entry = m_waitQueue.current(); 04431 } 04432 else 04433 { 04434 entry = m_waitQueue.next(); 04435 } 04436 } 04437 // Send number of URLs, (URL, metadata)* 04438 KIO_ARGS << (Q_INT32) queue.count(); 04439 for(entry = queue.first(); entry; entry = queue.next()) 04440 { 04441 stream << entry->url << entry->metaData; 04442 } 04443 m_packedArgs = packedArgs; 04444 m_command = CMD_MULTI_GET; 04445 m_outgoingMetaData.clear(); 04446 } 04447 04448 void MultiGetJob::start(Slave *slave) 04449 { 04450 // Add first job from m_waitQueue and add it to m_activeQueue 04451 GetRequest *entry = m_waitQueue.take(0); 04452 m_activeQueue.append(entry); 04453 04454 m_url = entry->url; 04455 04456 if (!entry->url.protocol().startsWith("http")) 04457 { 04458 // Use normal get 04459 KIO_ARGS << entry->url; 04460 m_packedArgs = packedArgs; 04461 m_outgoingMetaData = entry->metaData; 04462 m_command = CMD_GET; 04463 b_multiGetActive = false; 04464 } 04465 else 04466 { 04467 flushQueue(m_activeQueue); 04468 b_multiGetActive = true; 04469 } 04470 04471 TransferJob::start(slave); // Anything else to do?? 04472 } 04473 04474 bool MultiGetJob::findCurrentEntry() 04475 { 04476 if (b_multiGetActive) 04477 { 04478 long id = m_incomingMetaData["request-id"].toLong(); 04479 for(GetRequest *entry = m_activeQueue.first(); entry; entry = m_activeQueue.next()) 04480 { 04481 if (entry->id == id) 04482 { 04483 m_currentEntry = entry; 04484 return true; 04485 } 04486 } 04487 m_currentEntry = 0; 04488 return false; 04489 } 04490 else 04491 { 04492 m_currentEntry = m_activeQueue.first(); 04493 return (m_currentEntry != 0); 04494 } 04495 } 04496 04497 void MultiGetJob::slotRedirection( const KURL &url) 04498 { 04499 if (!findCurrentEntry()) return; // Error 04500 if (kapp && !kapp->authorizeURLAction("redirect", m_url, url)) 04501 { 04502 kdWarning(7007) << "MultiGetJob: Redirection from " << m_currentEntry->url << " to " << url << " REJECTED!" << endl; 04503 return; 04504 } 04505 m_redirectionURL = url; 04506 if (m_currentEntry->url.hasUser() && !url.hasUser() && (m_currentEntry->url.host().lower() == url.host().lower())) 04507 m_redirectionURL.setUser(m_currentEntry->url.user()); // Preserve user 04508 get(m_currentEntry->id, m_redirectionURL, m_currentEntry->metaData); // Try again 04509 } 04510 04511 04512 void MultiGetJob::slotFinished() 04513 { 04514 if (!findCurrentEntry()) return; 04515 if (m_redirectionURL.isEmpty()) 04516 { 04517 // No redirection, tell the world that we are finished. 04518 emit result(m_currentEntry->id); 04519 } 04520 m_redirectionURL = KURL(); 04521 m_error = 0; 04522 m_incomingMetaData.clear(); 04523 m_activeQueue.removeRef(m_currentEntry); 04524 if (m_activeQueue.count() == 0) 04525 { 04526 if (m_waitQueue.count() == 0) 04527 { 04528 // All done 04529 TransferJob::slotFinished(); 04530 } 04531 else 04532 { 04533 // return slave to pool 04534 // fetch new slave for first entry in m_waitQueue and call start 04535 // again. 04536 GetRequest *entry = m_waitQueue.at(0); 04537 m_url = entry->url; 04538 slaveDone(); 04539 Scheduler::doJob(this); 04540 } 04541 } 04542 } 04543 04544 void MultiGetJob::slotData( const QByteArray &_data) 04545 { 04546 if(!m_currentEntry) return;// Error, unknown request! 04547 if(m_redirectionURL.isEmpty() || !m_redirectionURL.isValid() || m_error) 04548 emit data(m_currentEntry->id, _data); 04549 } 04550 04551 void MultiGetJob::slotMimetype( const QString &_mimetype ) 04552 { 04553 if (b_multiGetActive) 04554 { 04555 QPtrList<GetRequest> newQueue; 04556 flushQueue(newQueue); 04557 if (!newQueue.isEmpty()) 04558 { 04559 while(!newQueue.isEmpty()) 04560 m_activeQueue.append(newQueue.take(0)); 04561 m_slave->send( m_command, m_packedArgs ); 04562 } 04563 } 04564 if (!findCurrentEntry()) return; // Error, unknown request! 04565 emit mimetype(m_currentEntry->id, _mimetype); 04566 } 04567 04568 MultiGetJob *KIO::multi_get(long id, const KURL &url, const MetaData &metaData) 04569 { 04570 MultiGetJob * job = new MultiGetJob( url, false ); 04571 job->get(id, url, metaData); 04572 return job; 04573 } 04574 04575 04576 #ifdef CACHE_INFO 04577 CacheInfo::CacheInfo(const KURL &url) 04578 { 04579 m_url = url; 04580 } 04581 04582 QString CacheInfo::cachedFileName() 04583 { 04584 const QChar separator = '_'; 04585 04586 QString CEF = m_url.path(); 04587 04588 int p = CEF.find('/'); 04589 04590 while(p != -1) 04591 { 04592 CEF[p] = separator; 04593 p = CEF.find('/', p); 04594 } 04595 04596 QString host = m_url.host().lower(); 04597 CEF = host + CEF + '_'; 04598 04599 QString dir = KProtocolManager::cacheDir(); 04600 if (dir[dir.length()-1] != '/') 04601 dir += "/"; 04602 04603 int l = m_url.host().length(); 04604 for(int i = 0; i < l; i++) 04605 { 04606 if (host[i].isLetter() && (host[i] != 'w')) 04607 { 04608 dir += host[i]; 04609 break; 04610 } 04611 } 04612 if (dir[dir.length()-1] == '/') 04613 dir += "0"; 04614 04615 unsigned long hash = 0x00000000; 04616 QCString u = m_url.url().latin1(); 04617 for(int i = u.length(); i--;) 04618 { 04619 hash = (hash * 12211 + u[i]) % 2147483563; 04620 } 04621 04622 QString hashString; 04623 hashString.sprintf("%08lx", hash); 04624 04625 CEF = CEF + hashString; 04626 04627 CEF = dir + "/" + CEF; 04628 04629 return CEF; 04630 } 04631 04632 QFile *CacheInfo::cachedFile() 04633 { 04634 #ifdef Q_WS_WIN 04635 const char *mode = (readWrite ? "rb+" : "rb"); 04636 #else 04637 const char *mode = (readWrite ? "r+" : "r"); 04638 #endif 04639 04640 FILE *fs = fopen(QFile::encodeName(CEF), mode); // Open for reading and writing 04641 if (!fs) 04642 return 0; 04643 04644 char buffer[401]; 04645 bool ok = true; 04646 04647 // CacheRevision 04648 if (ok && (!fgets(buffer, 400, fs))) 04649 ok = false; 04650 if (ok && (strcmp(buffer, CACHE_REVISION) != 0)) 04651 ok = false; 04652 04653 time_t date; 04654 time_t currentDate = time(0); 04655 04656 // URL 04657 if (ok && (!fgets(buffer, 400, fs))) 04658 ok = false; 04659 if (ok) 04660 { 04661 int l = strlen(buffer); 04662 if (l>0) 04663 buffer[l-1] = 0; // Strip newline 04664 if (m_.url.url() != buffer) 04665 { 04666 ok = false; // Hash collision 04667 } 04668 } 04669 04670 // Creation Date 04671 if (ok && (!fgets(buffer, 400, fs))) 04672 ok = false; 04673 if (ok) 04674 { 04675 date = (time_t) strtoul(buffer, 0, 10); 04676 if (m_maxCacheAge && (difftime(currentDate, date) > m_maxCacheAge)) 04677 { 04678 m_bMustRevalidate = true; 04679 m_expireDate = currentDate; 04680 } 04681 } 04682 04683 // Expiration Date 04684 m_cacheExpireDateOffset = ftell(fs); 04685 if (ok && (!fgets(buffer, 400, fs))) 04686 ok = false; 04687 if (ok) 04688 { 04689 if (m_request.cache == CC_Verify) 04690 { 04691 date = (time_t) strtoul(buffer, 0, 10); 04692 // After the expire date we need to revalidate. 04693 if (!date || difftime(currentDate, date) >= 0) 04694 m_bMustRevalidate = true; 04695 m_expireDate = date; 04696 } 04697 } 04698 04699 // ETag 04700 if (ok && (!fgets(buffer, 400, fs))) 04701 ok = false; 04702 if (ok) 04703 { 04704 m_etag = QString(buffer).stripWhiteSpace(); 04705 } 04706 04707 // Last-Modified 04708 if (ok && (!fgets(buffer, 400, fs))) 04709 ok = false; 04710 if (ok) 04711 { 04712 m_lastModified = QString(buffer).stripWhiteSpace(); 04713 } 04714 04715 fclose(fs); 04716 04717 if (ok) 04718 return fs; 04719 04720 unlink( QFile::encodeName(CEF) ); 04721 return 0; 04722 04723 } 04724 04725 void CacheInfo::flush() 04726 { 04727 cachedFile().remove(); 04728 } 04729 04730 void CacheInfo::touch() 04731 { 04732 04733 } 04734 void CacheInfo::setExpireDate(int); 04735 void CacheInfo::setExpireTimeout(int); 04736 04737 04738 int CacheInfo::creationDate(); 04739 int CacheInfo::expireDate(); 04740 int CacheInfo::expireTimeout(); 04741 #endif 04742 04743 void Job::virtual_hook( int, void* ) 04744 { /*BASE::virtual_hook( id, data );*/ } 04745 04746 void SimpleJob::virtual_hook( int id, void* data ) 04747 { KIO::Job::virtual_hook( id, data ); } 04748 04749 void MkdirJob::virtual_hook( int id, void* data ) 04750 { SimpleJob::virtual_hook( id, data ); } 04751 04752 void StatJob::virtual_hook( int id, void* data ) 04753 { SimpleJob::virtual_hook( id, data ); } 04754 04755 void TransferJob::virtual_hook( int id, void* data ) 04756 { SimpleJob::virtual_hook( id, data ); } 04757 04758 void MultiGetJob::virtual_hook( int id, void* data ) 04759 { TransferJob::virtual_hook( id, data ); } 04760 04761 void MimetypeJob::virtual_hook( int id, void* data ) 04762 { TransferJob::virtual_hook( id, data ); } 04763 04764 void FileCopyJob::virtual_hook( int id, void* data ) 04765 { Job::virtual_hook( id, data ); } 04766 04767 void ListJob::virtual_hook( int id, void* data ) 04768 { SimpleJob::virtual_hook( id, data ); } 04769 04770 void CopyJob::virtual_hook( int id, void* data ) 04771 { Job::virtual_hook( id, data ); } 04772 04773 void DeleteJob::virtual_hook( int id, void* data ) 04774 { Job::virtual_hook( id, data ); } 04775 04776 04777 #include "jobclasses.moc"