summaryrefslogtreecommitdiffstats
path: root/kioslave/trash/kio_trash.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'kioslave/trash/kio_trash.cpp')
-rw-r--r--kioslave/trash/kio_trash.cpp596
1 files changed, 596 insertions, 0 deletions
diff --git a/kioslave/trash/kio_trash.cpp b/kioslave/trash/kio_trash.cpp
new file mode 100644
index 000000000..7912cbb7c
--- /dev/null
+++ b/kioslave/trash/kio_trash.cpp
@@ -0,0 +1,596 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 David Faure <[email protected]>
+
+ 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.
+*/
+
+#include "kio_trash.h"
+#include <kio/job.h>
+
+#include <kapplication.h>
+#include <kdebug.h>
+#include <klocale.h>
+#include <klargefile.h>
+#include <kcmdlineargs.h>
+#include <kmimetype.h>
+#include <kprocess.h>
+
+#include <dcopclient.h>
+#include <qdatastream.h>
+#include <qtextstream.h>
+#include <qfile.h>
+#include <qeventloop.h>
+
+#include <time.h>
+#include <pwd.h>
+#include <grp.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <stdlib.h>
+
+static const KCmdLineOptions options[] =
+{
+ { "+protocol", I18N_NOOP( "Protocol name" ), 0 },
+ { "+pool", I18N_NOOP( "Socket name" ), 0 },
+ { "+app", I18N_NOOP( "Socket name" ), 0 },
+ KCmdLineLastOption
+};
+
+extern "C" {
+ int KDE_EXPORT kdemain( int argc, char **argv )
+ {
+ //KInstance instance( "kio_trash" );
+ // KApplication is necessary to use kio_file
+ putenv(strdup("SESSION_MANAGER="));
+ KApplication::disableAutoDcopRegistration();
+ KCmdLineArgs::init(argc, argv, "kio_trash", 0, 0, 0, 0);
+ KCmdLineArgs::addCmdLineOptions( options );
+ KApplication app( false, false );
+
+ KCmdLineArgs *args = KCmdLineArgs::parsedArgs();
+ TrashProtocol slave( args->arg(0), args->arg(1), args->arg(2) );
+ slave.dispatchLoop();
+ return 0;
+ }
+}
+
+#define INIT_IMPL \
+ if ( !impl.init() ) { \
+ error( impl.lastErrorCode(), impl.lastErrorMessage() ); \
+ return; \
+ }
+
+TrashProtocol::TrashProtocol( const QCString& protocol, const QCString &pool, const QCString &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::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" ).arg( 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." ).arg( destDir ) );
+ return;
+ }
+
+ copyOrMove( trashURL, dest, false /*overwrite*/, Move );
+}
+
+void TrashProtocol::rename( const KURL &oldURL, const KURL &newURL, bool overwrite )
+{
+ INIT_IMPL;
+
+ kdDebug()<<"TrashProtocol::rename(): old="<<oldURL<<" new="<<newURL<<" overwrite=" << overwrite<<endl;
+
+ if ( oldURL.protocol() == "trash" && newURL.protocol() == "trash" ) {
+ error( KIO::ERR_CANNOT_RENAME, oldURL.prettyURL() );
+ return;
+ }
+
+ copyOrMove( oldURL, newURL, overwrite, Move );
+}
+
+void TrashProtocol::copy( const KURL &src, const KURL &dest, int /*permissions*/, bool overwrite )
+{
+ INIT_IMPL;
+
+ kdDebug()<<"TrashProtocol::copy(): " << src << " " << dest << endl;
+
+ if ( src.protocol() == "trash" && dest.protocol() == "trash" ) {
+ error( KIO::ERR_UNSUPPORTED_ACTION, i18n( "This file is already in the trash bin." ) );
+ return;
+ }
+
+ copyOrMove( src, dest, overwrite, Copy );
+}
+
+void TrashProtocol::copyOrMove( const KURL &src, const KURL &dest, bool overwrite, CopyOrMove action )
+{
+ if ( src.protocol() == "trash" && dest.isLocalFile() ) {
+ // Extracting (e.g. via dnd). Ignore original location stored in info file.
+ int trashId;
+ QString fileId, relativePath;
+ bool ok = TrashImpl::parseURL( src, trashId, fileId, relativePath );
+ if ( !ok ) {
+ error( KIO::ERR_SLAVE_DEFINED, i18n( "Malformed URL %1" ).arg( src.prettyURL() ) );
+ return;
+ }
+ const QString destPath = dest.path();
+ if ( QFile::exists( destPath ) ) {
+ if ( overwrite ) {
+ ok = QFile::remove( destPath );
+ Q_ASSERT( ok ); // ### TODO
+ } else {
+ error( KIO::ERR_FILE_ALREADY_EXIST, destPath );
+ return;
+ }
+ }
+
+ if ( action == Move ) {
+ kdDebug() << "calling moveFromTrash(" << destPath << " " << trashId << " " << fileId << ")" << endl;
+ ok = impl.moveFromTrash( destPath, trashId, fileId, relativePath );
+ } else { // Copy
+ kdDebug() << "calling copyFromTrash(" << destPath << " " << trashId << " " << fileId << ")" << endl;
+ ok = impl.copyFromTrash( destPath, trashId, fileId, relativePath );
+ }
+ if ( !ok ) {
+ error( impl.lastErrorCode(), impl.lastErrorMessage() );
+ } else {
+ if ( action == Move && relativePath.isEmpty() )
+ (void)impl.deleteInfo( trashId, fileId );
+ finished();
+ }
+ return;
+ } else if ( src.isLocalFile() && dest.protocol() == "trash" ) {
+ QString dir = dest.directory();
+ //kdDebug() << "trashing a file to " << dir << endl;
+ // Trashing a file
+ // We detect the case where this isn't normal trashing, but
+ // e.g. if kwrite tries to save (moving tempfile over destination)
+ if ( dir.length() <= 1 && src.fileName() == dest.fileName() ) // new toplevel entry
+ {
+ const QString srcPath = src.path();
+ // In theory we should use TrashImpl::parseURL to give the right filename to createInfo,
+ // in case the trash URL didn't contain the same filename as srcPath.
+ // But this can only happen with copyAs/moveAs, not available in the GUI
+ // for the trash (New/... or Rename from iconview/listview).
+ int trashId;
+ QString fileId;
+ if ( !impl.createInfo( srcPath, trashId, fileId ) ) {
+ error( impl.lastErrorCode(), impl.lastErrorMessage() );
+ } else {
+ bool ok;
+ if ( action == Move ) {
+ kdDebug() << "calling moveToTrash(" << srcPath << " " << trashId << " " << fileId << ")" << endl;
+ ok = impl.moveToTrash( srcPath, trashId, fileId );
+ } else { // Copy
+ kdDebug() << "calling copyToTrash(" << srcPath << " " << trashId << " " << fileId << ")" << endl;
+ ok = impl.copyToTrash( srcPath, trashId, fileId );
+ }
+ if ( !ok ) {
+ (void)impl.deleteInfo( trashId, fileId );
+ error( impl.lastErrorCode(), impl.lastErrorMessage() );
+ } else {
+ // Inform caller of the final URL. Used by konq_undo.
+ const KURL url = impl.makeURL( trashId, fileId, QString::null );
+ setMetaData( "trashURL-" + srcPath, url.url() );
+ finished();
+ }
+ }
+ return;
+ } else {
+ kdDebug() << "returning KIO::ERR_ACCESS_DENIED, it's not allowed to add a file to an existing trash directory" << endl;
+ // It's not allowed to add a file to an existing trash directory.
+ error( KIO::ERR_ACCESS_DENIED, dest.prettyURL() );
+ return;
+ }
+ } else
+ error( KIO::ERR_UNSUPPORTED_ACTION, "should never happen" );
+}
+
+static void addAtom(KIO::UDSEntry& entry, unsigned int ID, long long l, const QString& s = QString::null)
+{
+ KIO::UDSAtom atom;
+ atom.m_uds = ID;
+ atom.m_long = l;
+ atom.m_str = s;
+ entry.append(atom);
+}
+
+void TrashProtocol::createTopLevelDirEntry(KIO::UDSEntry& entry)
+{
+ entry.clear();
+ addAtom(entry, KIO::UDS_NAME, 0, ".");
+ addAtom(entry, KIO::UDS_FILE_TYPE, S_IFDIR);
+ addAtom(entry, KIO::UDS_ACCESS, 0700);
+ addAtom(entry, KIO::UDS_MIME_TYPE, 0, "inode/directory");
+ addAtom(entry, KIO::UDS_USER, 0, m_userName);
+ addAtom(entry, KIO::UDS_GROUP, 0, m_groupName);
+}
+
+void TrashProtocol::stat(const KURL& url)
+{
+ INIT_IMPL;
+ const QString path = url.path();
+ if( path.isEmpty() || path == "/" ) {
+ // The root is "virtual" - it's not a single physical directory
+ KIO::UDSEntry entry;
+ createTopLevelDirEntry( entry );
+ statEntry( entry );
+ finished();
+ } else {
+ int trashId;
+ QString fileId, relativePath;
+
+ bool ok = TrashImpl::parseURL( url, trashId, fileId, relativePath );
+
+ if ( !ok ) {
+ // ######## do we still need this?
+ kdDebug() << k_funcinfo << url << " looks fishy, returning does-not-exist" << endl;
+ // A URL like trash:/file simply means that CopyJob is trying to see if
+ // the destination exists already (it made up the URL by itself).
+ error( KIO::ERR_DOES_NOT_EXIST, url.prettyURL() );
+ //error( KIO::ERR_SLAVE_DEFINED, i18n( "Malformed URL %1" ).arg( url.prettyURL() ) );
+ return;
+ }
+
+ const QString filePath = impl.physicalPath( trashId, fileId, relativePath );
+ if ( filePath.isEmpty() ) {
+ error( impl.lastErrorCode(), impl.lastErrorMessage() );
+ return;
+ }
+
+ QString fileName = filePath.section('/', -1, -1, QString::SectionSkipEmpty);
+
+ QString fileURL = QString::null;
+ if ( url.path().length() > 1 ) {
+ fileURL = url.url();
+ }
+
+ KIO::UDSEntry entry;
+ TrashedFileInfo info;
+ ok = impl.infoForFile( trashId, fileId, info );
+ if ( ok )
+ ok = createUDSEntry( filePath, fileName, fileURL, entry, info );
+
+ if ( !ok ) {
+ error( KIO::ERR_COULD_NOT_STAT, url.prettyURL() );
+ }
+
+ statEntry( entry );
+ finished();
+ }
+}
+
+void TrashProtocol::del( const KURL &url, bool /*isfile*/ )
+{
+ int trashId;
+ QString fileId, relativePath;
+
+ bool ok = TrashImpl::parseURL( url, trashId, fileId, relativePath );
+ if ( !ok ) {
+ error( KIO::ERR_SLAVE_DEFINED, i18n( "Malformed URL %1" ).arg( 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;
+ kdDebug() << "listdir: " << url << endl;
+ if ( url.path().length() <= 1 ) {
+ 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" ).arg( 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 += "/";
+ info.physicalPath += relativePath;
+ }
+
+ // List subdir. Can't use kio_file here since we provide our own info...
+ kdDebug() << k_funcinfo << "listing " << info.physicalPath << endl;
+ QStrList entryNames = impl.listDir( info.physicalPath );
+ totalSize( entryNames.count() );
+ KIO::UDSEntry entry;
+ QStrListIterator entryIt( entryNames );
+ for (; entryIt.current(); ++entryIt) {
+ QString fileName = QFile::decodeName( entryIt.current() );
+ if ( fileName == ".." )
+ continue;
+ const QString filePath = info.physicalPath + "/" + fileName;
+ // shouldn't be necessary
+ //const QString url = TrashImpl::makeURL( trashId, fileId, relativePath + "/" + fileName );
+ entry.clear();
+ TrashedFileInfo infoForItem( info );
+ infoForItem.origPath += '/';
+ infoForItem.origPath += fileName;
+ if ( ok && createUDSEntry( filePath, fileName, QString::null /*url*/, entry, infoForItem ) ) {
+ listEntry( entry, false );
+ }
+ }
+ entry.clear();
+ listEntry( entry, true );
+ finished();
+}
+
+bool TrashProtocol::createUDSEntry( const QString& physicalPath, const QString& fileName, const QString& url, KIO::UDSEntry& entry, const TrashedFileInfo& info )
+{
+ QCString physicalPath_c = QFile::encodeName( physicalPath );
+ KDE_struct_stat buff;
+ if ( KDE_lstat( physicalPath_c, &buff ) == -1 ) {
+ kdWarning() << "couldn't stat " << physicalPath << endl;
+ return false;
+ }
+ if (S_ISLNK(buff.st_mode)) {
+ char buffer2[ 1000 ];
+ int n = readlink( physicalPath_c, buffer2, 1000 );
+ if ( n != -1 ) {
+ buffer2[ n ] = 0;
+ }
+
+ addAtom( entry, KIO::UDS_LINK_DEST, 0, 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
+ addAtom( entry, KIO::UDS_NAME, 0, fileName );
+ addAtom( entry, KIO::UDS_FILE_TYPE, type );
+ if ( !url.isEmpty() )
+ addAtom( entry, KIO::UDS_URL, 0, url );
+
+ KMimeType::Ptr mt = KMimeType::findByPath( physicalPath, buff.st_mode );
+ addAtom( entry, KIO::UDS_MIME_TYPE, 0, mt->name() );
+ addAtom( entry, KIO::UDS_ACCESS, access );
+ addAtom( entry, KIO::UDS_SIZE, buff.st_size );
+ addAtom( entry, KIO::UDS_USER, 0, m_userName ); // assumption
+ addAtom( entry, KIO::UDS_GROUP, 0, m_groupName ); // assumption
+ addAtom( entry, KIO::UDS_MODIFICATION_TIME, buff.st_mtime );
+ addAtom( entry, KIO::UDS_ACCESS_TIME, buff.st_atime ); // ## or use it for deletion time?
+ addAtom( entry, KIO::UDS_EXTRA, 0, info.origPath );
+ addAtom( entry, KIO::UDS_EXTRA, 0, 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::null );
+ KURL origURL;
+ origURL.setPath( (*it).origPath );
+ entry.clear();
+ if ( createUDSEntry( (*it).physicalPath, origURL.fileName(), url.url(), entry, *it ) )
+ listEntry( entry, false );
+ }
+ entry.clear();
+ listEntry( entry, true );
+ finished();
+}
+
+void TrashProtocol::special( const QByteArray & data )
+{
+ INIT_IMPL;
+ QDataStream stream( data, IO_ReadOnly );
+ 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:
+ kdWarning(7116) << "Unknown command in special(): " << cmd << endl;
+ error( KIO::ERR_UNSUPPORTED_ACTION, QString::number(cmd) );
+ break;
+ }
+}
+
+void TrashProtocol::put( const KURL& url, int /*permissions*/, bool /*overwrite*/, bool /*resume*/ )
+{
+ INIT_IMPL;
+ kdDebug() << "put: " << url << endl;
+ // 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;
+ kdDebug() << "get() : " << url << endl;
+ if ( !url.isValid() ) {
+ kdDebug() << kdBacktrace() << endl;
+ error( KIO::ERR_SLAVE_DEFINED, i18n( "Malformed URL %1" ).arg( 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" ).arg( 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 );
+ 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(KIO::Job *) ),
+ this, SLOT( jobFinished(KIO::Job *) ) );
+ qApp->eventLoop()->enterLoop();
+}
+
+void TrashProtocol::slotData( KIO::Job*, const QByteArray&arr )
+{
+ data( arr );
+}
+
+void TrashProtocol::slotMimetype( KIO::Job*, const QString& mt )
+{
+ mimeType( mt );
+}
+
+void TrashProtocol::jobFinished( KIO::Job* job )
+{
+ if ( job->error() )
+ error( job->error(), job->errorText() );
+ else
+ finished();
+ qApp->eventLoop()->exitLoop();
+}
+
+#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)
+ kdDebug() << "mkdir: " << url << endl;
+ 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"