/* This file is part of the KDE project Copyright (C) 2004 David Faure This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #define QT_NO_CAST_FROM_ASCII #include "kio_trash.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include extern "C" { int KDE_EXPORT kdemain( int argc, char **argv ) { // necessary to use other kio slaves KComponentData componentData("kio_trash" ); QCoreApplication app(argc, argv); // start the slave TrashProtocol slave( argv[1], argv[2], argv[3] ); slave.dispatchLoop(); return 0; } } #define INIT_IMPL \ if ( !impl.init() ) { \ error( impl.lastErrorCode(), impl.lastErrorMessage() ); \ return; \ } TrashProtocol::TrashProtocol( const QByteArray& protocol, const QByteArray &pool, const QByteArray &app) : SlaveBase(protocol, pool, app ) { struct passwd *user = getpwuid( getuid() ); if ( user ) m_userName = QString::fromLatin1(user->pw_name); struct group *grp = getgrgid( getgid() ); if ( grp ) m_groupName = QString::fromLatin1(grp->gr_name); } TrashProtocol::~TrashProtocol() { } void TrashProtocol::enterLoop() { QEventLoop eventLoop; connect(this, SIGNAL(leaveModality()), &eventLoop, SLOT(quit())); eventLoop.exec(QEventLoop::ExcludeUserInputEvents); } void TrashProtocol::restore( const KUrl& trashURL ) { int trashId; QString fileId, relativePath; bool ok = TrashImpl::parseURL( trashURL, trashId, fileId, relativePath ); if ( !ok ) { error( KIO::ERR_SLAVE_DEFINED, i18n( "Malformed URL %1", trashURL.prettyUrl() ) ); return; } TrashedFileInfo info; ok = impl.infoForFile( trashId, fileId, info ); if ( !ok ) { error( impl.lastErrorCode(), impl.lastErrorMessage() ); return; } KUrl dest; dest.setPath( info.origPath ); if ( !relativePath.isEmpty() ) dest.addPath( relativePath ); // Check that the destination directory exists, to improve the error code in case it doesn't. const QString destDir = dest.directory(); KDE_struct_stat buff; if ( KDE_lstat( QFile::encodeName( destDir ), &buff ) == -1 ) { error( KIO::ERR_SLAVE_DEFINED, i18n( "The directory %1 does not exist anymore, so it is not possible to restore this item to its original location. " "You can either recreate that directory and use the restore operation again, or drag the item anywhere else to restore it.", destDir ) ); return; } copyOrMove( trashURL, dest, false /*overwrite*/, Move ); } void TrashProtocol::rename( const KUrl &oldURL, const KUrl &newURL, KIO::JobFlags flags ) { INIT_IMPL; kDebug()<<"TrashProtocol::rename(): old="< 1 ) { fileURL = url; } KIO::UDSEntry entry; TrashedFileInfo info; ok = impl.infoForFile( trashId, fileId, info ); if ( ok ) ok = createUDSEntry( filePath, fileDisplayName, fileURL.fileName(), entry, info ); if ( !ok ) { error( KIO::ERR_COULD_NOT_STAT, url.prettyUrl() ); return; } statEntry( entry ); finished(); } } void TrashProtocol::del( const KUrl &url, bool /*isfile*/ ) { INIT_IMPL; int trashId; QString fileId, relativePath; bool ok = TrashImpl::parseURL( url, trashId, fileId, relativePath ); if ( !ok ) { error( KIO::ERR_SLAVE_DEFINED, i18n( "Malformed URL %1", url.prettyUrl() ) ); return; } ok = relativePath.isEmpty(); if ( !ok ) { error( KIO::ERR_ACCESS_DENIED, url.prettyUrl() ); return; } ok = impl.del(trashId, fileId); if ( !ok ) { error( impl.lastErrorCode(), impl.lastErrorMessage() ); return; } finished(); } void TrashProtocol::listDir(const KUrl& url) { INIT_IMPL; kDebug() << "listdir: " << url; if ( url.path(KUrl::AddTrailingSlash) == QLatin1String("/") ) { listRoot(); return; } int trashId; QString fileId; QString relativePath; bool ok = TrashImpl::parseURL( url, trashId, fileId, relativePath ); if ( !ok ) { error( KIO::ERR_SLAVE_DEFINED, i18n( "Malformed URL %1", url.prettyUrl() ) ); return; } //was: const QString physicalPath = impl.physicalPath( trashId, fileId, relativePath ); // Get info for deleted directory - the date of deletion and orig path will be used // for all the items in it, and we need the physicalPath. TrashedFileInfo info; ok = impl.infoForFile( trashId, fileId, info ); if ( !ok || info.physicalPath.isEmpty() ) { error( impl.lastErrorCode(), impl.lastErrorMessage() ); return; } if ( !relativePath.isEmpty() ) { info.physicalPath += QLatin1Char('/'); info.physicalPath += relativePath; } // List subdir. Can't use kio_file here since we provide our own info... kDebug() << "listing " << info.physicalPath; const QStringList entryNames = impl.listDir( info.physicalPath ); totalSize( entryNames.count() ); KIO::UDSEntry entry; for ( QStringList::const_iterator entryIt = entryNames.begin(), entryEnd = entryNames.end(); entryIt != entryEnd ; ++entryIt ) { const QString fileName = *entryIt; if (fileName == QLatin1String("..")) continue; const QString filePath = info.physicalPath + QLatin1Char('/') + fileName; // shouldn't be necessary //const QString url = TrashImpl::makeURL( trashId, fileId, relativePath + '/' + fileName ); entry.clear(); TrashedFileInfo infoForItem( info ); infoForItem.origPath += QLatin1Char('/'); infoForItem.origPath += fileName; if (createUDSEntry(filePath, fileName, fileName, entry, infoForItem)) { listEntry( entry, false ); } } entry.clear(); listEntry( entry, true ); finished(); } bool TrashProtocol::createUDSEntry( const QString& physicalPath, const QString& displayFileName, const QString& internalFileName, KIO::UDSEntry& entry, const TrashedFileInfo& info ) { QByteArray physicalPath_c = QFile::encodeName( physicalPath ); KDE_struct_stat buff; if ( KDE_lstat( physicalPath_c, &buff ) == -1 ) { kWarning() << "couldn't stat " << physicalPath ; return false; } if (S_ISLNK(buff.st_mode)) { char buffer2[ 1000 ]; int n = readlink( physicalPath_c, buffer2, 999 ); if ( n != -1 ) { buffer2[ n ] = 0; } entry.insert( KIO::UDSEntry::UDS_LINK_DEST, QFile::decodeName( buffer2 ) ); // Follow symlink // That makes sense in kio_file, but not in the trash, especially for the size // #136876 #if 0 if ( KDE_stat( physicalPath_c, &buff ) == -1 ) { // It is a link pointing to nowhere buff.st_mode = S_IFLNK | S_IRWXU | S_IRWXG | S_IRWXO; buff.st_mtime = 0; buff.st_atime = 0; buff.st_size = 0; } #endif } mode_t type = buff.st_mode & S_IFMT; // extract file type mode_t access = buff.st_mode & 07777; // extract permissions access &= 07555; // make it readonly, since it's in the trashcan Q_ASSERT(!internalFileName.isEmpty()); entry.insert( KIO::UDSEntry::UDS_NAME, internalFileName ); // internal filename, like "0-foo" entry.insert( KIO::UDSEntry::UDS_DISPLAY_NAME, displayFileName ); // user-visible filename, like "foo" entry.insert( KIO::UDSEntry::UDS_FILE_TYPE, type ); //if ( !url.isEmpty() ) // entry.insert( KIO::UDSEntry::UDS_URL, url ); KMimeType::Ptr mt = KMimeType::findByPath( physicalPath, buff.st_mode ); if ( mt ) entry.insert( KIO::UDSEntry::UDS_MIME_TYPE, mt->name() ); entry.insert( KIO::UDSEntry::UDS_ACCESS, access ); entry.insert( KIO::UDSEntry::UDS_SIZE, buff.st_size ); entry.insert( KIO::UDSEntry::UDS_USER, m_userName ); // assumption entry.insert( KIO::UDSEntry::UDS_GROUP, m_groupName ); // assumption entry.insert( KIO::UDSEntry::UDS_MODIFICATION_TIME, buff.st_mtime ); entry.insert( KIO::UDSEntry::UDS_ACCESS_TIME, buff.st_atime ); // ## or use it for deletion time? entry.insert( KIO::UDSEntry::UDS_EXTRA, info.origPath ); entry.insert( KIO::UDSEntry::UDS_EXTRA + 1, info.deletionDate.toString( Qt::ISODate ) ); return true; } void TrashProtocol::listRoot() { INIT_IMPL; const TrashedFileInfoList lst = impl.list(); totalSize( lst.count() ); KIO::UDSEntry entry; createTopLevelDirEntry( entry ); listEntry( entry, false ); for ( TrashedFileInfoList::ConstIterator it = lst.begin(); it != lst.end(); ++it ) { const KUrl url = TrashImpl::makeURL( (*it).trashId, (*it).fileId, QString() ); KUrl origURL; origURL.setPath( (*it).origPath ); entry.clear(); const QString fileDisplayName = (*it).fileId; if ( createUDSEntry( (*it).physicalPath, fileDisplayName, url.fileName(), entry, *it ) ) listEntry( entry, false ); } entry.clear(); listEntry( entry, true ); finished(); } void TrashProtocol::special( const QByteArray & data ) { INIT_IMPL; QDataStream stream( data ); int cmd; stream >> cmd; switch (cmd) { case 1: if ( impl.emptyTrash() ) finished(); else error( impl.lastErrorCode(), impl.lastErrorMessage() ); break; case 2: impl.migrateOldTrash(); finished(); break; case 3: { KUrl url; stream >> url; restore( url ); break; } default: kWarning(7116) << "Unknown command in special(): " << cmd ; error( KIO::ERR_UNSUPPORTED_ACTION, QString::number(cmd) ); break; } } void TrashProtocol::put( const KUrl& url, int /*permissions*/, KIO::JobFlags ) { INIT_IMPL; kDebug() << "put: " << url; // create deleted file. We need to get the mtime and original location from metadata... // Maybe we can find the info file for url.fileName(), in case ::rename() was called first, and failed... error( KIO::ERR_ACCESS_DENIED, url.prettyUrl() ); } void TrashProtocol::get( const KUrl& url ) { INIT_IMPL; kDebug() << "get() : " << url; if ( !url.isValid() ) { kDebug() << kBacktrace(); error( KIO::ERR_SLAVE_DEFINED, i18n( "Malformed URL %1", url.url() ) ); return; } if ( url.path().length() <= 1 ) { error( KIO::ERR_IS_DIRECTORY, url.prettyUrl() ); return; } int trashId; QString fileId; QString relativePath; bool ok = TrashImpl::parseURL( url, trashId, fileId, relativePath ); if ( !ok ) { error( KIO::ERR_SLAVE_DEFINED, i18n( "Malformed URL %1", url.prettyUrl() ) ); return; } const QString physicalPath = impl.physicalPath( trashId, fileId, relativePath ); if ( physicalPath.isEmpty() ) { error( impl.lastErrorCode(), impl.lastErrorMessage() ); return; } // Usually we run jobs in TrashImpl (for e.g. future kdedmodule) // But for this one we wouldn't use DCOP for every bit of data... KUrl fileURL; fileURL.setPath( physicalPath ); KIO::Job* job = KIO::get( fileURL, KIO::NoReload, KIO::HideProgressInfo ); connect( job, SIGNAL( data( KIO::Job*, const QByteArray& ) ), this, SLOT( slotData( KIO::Job*, const QByteArray& ) ) ); connect( job, SIGNAL( mimetype( KIO::Job*, const QString& ) ), this, SLOT( slotMimetype( KIO::Job*, const QString& ) ) ); connect( job, SIGNAL( result(KJob*) ), this, SLOT( jobFinished(KJob*) ) ); enterLoop(); } void TrashProtocol::slotData( KIO::Job*, const QByteArray&arr ) { data( arr ); } void TrashProtocol::slotMimetype( KIO::Job*, const QString& mt ) { mimeType( mt ); } void TrashProtocol::jobFinished( KJob* job ) { if ( job->error() ) error( job->error(), job->errorText() ); else finished(); emit leaveModality(); } #if 0 void TrashProtocol::mkdir( const KUrl& url, int /*permissions*/ ) { INIT_IMPL; // create info about deleted dir // ############ Problem: we don't know the original path. // Let's try to avoid this case (we should get to copy() instead, for local files) kDebug() << "mkdir: " << url; QString dir = url.directory(); if ( dir.length() <= 1 ) // new toplevel entry { // ## we should use TrashImpl::parseURL to give the right filename to createInfo int trashId; QString fileId; if ( !impl.createInfo( url.path(), trashId, fileId ) ) { error( impl.lastErrorCode(), impl.lastErrorMessage() ); } else { if ( !impl.mkdir( trashId, fileId, permissions ) ) { (void)impl.deleteInfo( trashId, fileId ); error( impl.lastErrorCode(), impl.lastErrorMessage() ); } else finished(); } } else { // Well it's not allowed to add a directory to an existing deleted directory. error( KIO::ERR_ACCESS_DENIED, url.prettyUrl() ); } } #endif #include "kio_trash.moc"