Make a branch to make krunner Good Enough For Aaron™.
[kdebase/uwolfer.git] / runtime / kioslave / trash / kio_trash.cpp
bloba3b60e75ba2708378b02fff5be77b113da5ef28f
1 /* This file is part of the KDE project
2 Copyright (C) 2004 David Faure <faure@kde.org>
4 This library is free software; you can redistribute it and/or
5 modify it under the terms of the GNU Library General Public
6 License as published by the Free Software Foundation; either
7 version 2 of the License, or (at your option) any later version.
9 This library is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 Library General Public License for more details.
14 You should have received a copy of the GNU Library General Public License
15 along with this library; see the file COPYING.LIB. If not, write to
16 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 Boston, MA 02110-1301, USA.
20 #include "kio_trash.h"
21 #include <kio/job.h>
23 #include <kapplication.h>
24 #include <kdebug.h>
25 #include <klocale.h>
26 #include <kde_file.h>
27 #include <kcmdlineargs.h>
28 #include <kmimetype.h>
30 #include <QDataStream>
31 #include <QTextStream>
32 #include <QFile>
33 #include <QEventLoop>
35 #include <time.h>
36 #include <pwd.h>
37 #include <grp.h>
38 #include <sys/types.h>
39 #include <sys/stat.h>
40 #include <stdlib.h>
42 extern "C" {
43 int KDE_EXPORT kdemain( int argc, char **argv )
45 //KComponentData componentData("kio_trash");
46 // KApplication is necessary to use kio_file
47 putenv(strdup("SESSION_MANAGER="));
48 //KApplication::disableAutoDcopRegistration();
49 KCmdLineArgs::init(argc, argv, "kio_trash", 0, KLocalizedString(), 0);
51 KCmdLineOptions options;
52 options.add("+protocol", ki18n( "Protocol name" ));
53 options.add("+pool", ki18n( "Socket name" ));
54 options.add("+app", ki18n( "Socket name" ));
55 KCmdLineArgs::addCmdLineOptions( options );
56 KApplication app( false );
58 KCmdLineArgs *args = KCmdLineArgs::parsedArgs();
59 TrashProtocol slave( QFile::encodeName( args->arg(0) ),
60 QFile::encodeName( args->arg(1) ),
61 QFile::encodeName( args->arg(2) ) );
62 slave.dispatchLoop();
63 return 0;
67 #define INIT_IMPL \
68 if ( !impl.init() ) { \
69 error( impl.lastErrorCode(), impl.lastErrorMessage() ); \
70 return; \
73 TrashProtocol::TrashProtocol( const QByteArray& protocol, const QByteArray &pool, const QByteArray &app)
74 : SlaveBase(protocol, pool, app )
76 struct passwd *user = getpwuid( getuid() );
77 if ( user )
78 m_userName = QString::fromLatin1(user->pw_name);
79 struct group *grp = getgrgid( getgid() );
80 if ( grp )
81 m_groupName = QString::fromLatin1(grp->gr_name);
84 TrashProtocol::~TrashProtocol()
88 void TrashProtocol::enterLoop()
90 QEventLoop eventLoop;
91 connect(this, SIGNAL(leaveModality()),
92 &eventLoop, SLOT(quit()));
93 eventLoop.exec(QEventLoop::ExcludeUserInputEvents);
96 void TrashProtocol::restore( const KUrl& trashURL )
98 int trashId;
99 QString fileId, relativePath;
100 bool ok = TrashImpl::parseURL( trashURL, trashId, fileId, relativePath );
101 if ( !ok ) {
102 error( KIO::ERR_SLAVE_DEFINED, i18n( "Malformed URL %1", trashURL.prettyUrl() ) );
103 return;
105 TrashedFileInfo info;
106 ok = impl.infoForFile( trashId, fileId, info );
107 if ( !ok ) {
108 error( impl.lastErrorCode(), impl.lastErrorMessage() );
109 return;
111 KUrl dest;
112 dest.setPath( info.origPath );
113 if ( !relativePath.isEmpty() )
114 dest.addPath( relativePath );
116 // Check that the destination directory exists, to improve the error code in case it doesn't.
117 const QString destDir = dest.directory();
118 KDE_struct_stat buff;
119 if ( KDE_lstat( QFile::encodeName( destDir ), &buff ) == -1 ) {
120 error( KIO::ERR_SLAVE_DEFINED,
121 i18n( "The directory %1 does not exist anymore, so it is not possible to restore this item to its original location. "
122 "You can either recreate that directory and use the restore operation again, or drag the item anywhere else to restore it.", destDir ) );
123 return;
126 copyOrMove( trashURL, dest, false /*overwrite*/, Move );
129 void TrashProtocol::rename( const KUrl &oldURL, const KUrl &newURL, KIO::JobFlags flags )
131 INIT_IMPL;
133 kDebug()<<"TrashProtocol::rename(): old="<<oldURL<<" new="<<newURL<<" overwrite=" << (flags & KIO::Overwrite);
135 if ( oldURL.protocol() == "trash" && newURL.protocol() == "trash" ) {
136 error( KIO::ERR_CANNOT_RENAME, oldURL.prettyUrl() );
137 return;
140 copyOrMove( oldURL, newURL, (flags & KIO::Overwrite), Move );
143 void TrashProtocol::copy( const KUrl &src, const KUrl &dest, int /*permissions*/, KIO::JobFlags flags )
145 INIT_IMPL;
147 kDebug()<<"TrashProtocol::copy(): " << src << " " << dest;
149 if ( src.protocol() == "trash" && dest.protocol() == "trash" ) {
150 error( KIO::ERR_UNSUPPORTED_ACTION, i18n( "This file is already in the trash bin." ) );
151 return;
154 copyOrMove( src, dest, (flags & KIO::Overwrite), Copy );
157 void TrashProtocol::copyOrMove( const KUrl &src, const KUrl &dest, bool overwrite, CopyOrMove action )
159 if ( src.protocol() == "trash" && dest.isLocalFile() ) {
160 // Extracting (e.g. via dnd). Ignore original location stored in info file.
161 int trashId;
162 QString fileId, relativePath;
163 bool ok = TrashImpl::parseURL( src, trashId, fileId, relativePath );
164 if ( !ok ) {
165 error( KIO::ERR_SLAVE_DEFINED, i18n( "Malformed URL %1", src.prettyUrl() ) );
166 return;
168 const QString destPath = dest.path();
169 if ( QFile::exists( destPath ) ) {
170 if ( overwrite ) {
171 ok = QFile::remove( destPath );
172 Q_ASSERT( ok ); // ### TODO
173 } else {
174 error( KIO::ERR_FILE_ALREADY_EXIST, destPath );
175 return;
179 if ( action == Move ) {
180 kDebug() << "calling moveFromTrash(" << destPath << " " << trashId << " " << fileId << ")";
181 ok = impl.moveFromTrash( destPath, trashId, fileId, relativePath );
182 } else { // Copy
183 kDebug() << "calling copyFromTrash(" << destPath << " " << trashId << " " << fileId << ")";
184 ok = impl.copyFromTrash( destPath, trashId, fileId, relativePath );
186 if ( !ok ) {
187 error( impl.lastErrorCode(), impl.lastErrorMessage() );
188 } else {
189 if ( action == Move && relativePath.isEmpty() )
190 (void)impl.deleteInfo( trashId, fileId );
191 finished();
193 return;
194 } else if ( src.isLocalFile() && dest.protocol() == "trash" ) {
195 QString dir = dest.directory();
196 //kDebug() << "trashing a file to " << dir;
197 // Trashing a file
198 // We detect the case where this isn't normal trashing, but
199 // e.g. if kwrite tries to save (moving tempfile over destination)
200 if ( dir.length() <= 1 && src.fileName() == dest.fileName() ) // new toplevel entry
202 const QString srcPath = src.path();
203 // In theory we should use TrashImpl::parseURL to give the right filename to createInfo,
204 // in case the trash URL didn't contain the same filename as srcPath.
205 // But this can only happen with copyAs/moveAs, not available in the GUI
206 // for the trash (New/... or Rename from iconview/listview).
207 int trashId;
208 QString fileId;
209 if ( !impl.createInfo( srcPath, trashId, fileId ) ) {
210 error( impl.lastErrorCode(), impl.lastErrorMessage() );
211 } else {
212 bool ok;
213 if ( action == Move ) {
214 kDebug() << "calling moveToTrash(" << srcPath << " " << trashId << " " << fileId << ")";
215 ok = impl.moveToTrash( srcPath, trashId, fileId );
216 } else { // Copy
217 kDebug() << "calling copyToTrash(" << srcPath << " " << trashId << " " << fileId << ")";
218 ok = impl.copyToTrash( srcPath, trashId, fileId );
220 if ( !ok ) {
221 (void)impl.deleteInfo( trashId, fileId );
222 error( impl.lastErrorCode(), impl.lastErrorMessage() );
223 } else {
224 // Inform caller of the final URL. Used by konq_undo.
225 const KUrl url = impl.makeURL( trashId, fileId, QString() );
226 setMetaData( "trashURL-" + srcPath, url.url() );
227 finished();
230 return;
231 } else {
232 kDebug() << "returning KIO::ERR_ACCESS_DENIED, it's not allowed to add a file to an existing trash directory";
233 // It's not allowed to add a file to an existing trash directory.
234 error( KIO::ERR_ACCESS_DENIED, dest.prettyUrl() );
235 return;
237 } else
238 error( KIO::ERR_UNSUPPORTED_ACTION, "should never happen" );
241 void TrashProtocol::createTopLevelDirEntry(KIO::UDSEntry& entry)
243 entry.clear();
244 entry.insert( KIO::UDSEntry::UDS_NAME, QString::fromLatin1("."));
245 entry.insert( KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR);
246 entry.insert( KIO::UDSEntry::UDS_ACCESS, 0700);
247 entry.insert( KIO::UDSEntry::UDS_MIME_TYPE, QString::fromLatin1("inode/directory"));
248 entry.insert( KIO::UDSEntry::UDS_USER, m_userName);
249 entry.insert( KIO::UDSEntry::UDS_GROUP, m_groupName);
252 void TrashProtocol::stat(const KUrl& url)
254 INIT_IMPL;
255 const QString path = url.path();
256 if( path.isEmpty() || path == "/" ) {
257 // The root is "virtual" - it's not a single physical directory
258 KIO::UDSEntry entry;
259 createTopLevelDirEntry( entry );
260 statEntry( entry );
261 finished();
262 } else {
263 int trashId;
264 QString fileId, relativePath;
266 bool ok = TrashImpl::parseURL( url, trashId, fileId, relativePath );
268 if ( !ok ) {
269 // ######## do we still need this?
270 kDebug() << url << " looks fishy, returning does-not-exist";
271 // A URL like trash:/file simply means that CopyJob is trying to see if
272 // the destination exists already (it made up the URL by itself).
273 error( KIO::ERR_DOES_NOT_EXIST, url.prettyUrl() );
274 //error( KIO::ERR_SLAVE_DEFINED, i18n( "Malformed URL %1" ).arg( url.prettyUrl() ) );
275 return;
278 const QString filePath = impl.physicalPath( trashId, fileId, relativePath );
279 if ( filePath.isEmpty() ) {
280 error( impl.lastErrorCode(), impl.lastErrorMessage() );
281 return;
284 QString fileName = filePath.section('/', -1, -1, QString::SectionSkipEmpty);
286 QString fileURL = QString();
287 if ( url.path().length() > 1 ) {
288 fileURL = url.url();
291 KIO::UDSEntry entry;
292 TrashedFileInfo info;
293 ok = impl.infoForFile( trashId, fileId, info );
294 if ( ok )
295 ok = createUDSEntry( filePath, fileName, fileURL, entry, info );
297 if ( !ok ) {
298 error( KIO::ERR_COULD_NOT_STAT, url.prettyUrl() );
301 statEntry( entry );
302 finished();
306 void TrashProtocol::del( const KUrl &url, bool /*isfile*/ )
308 int trashId;
309 QString fileId, relativePath;
311 bool ok = TrashImpl::parseURL( url, trashId, fileId, relativePath );
312 if ( !ok ) {
313 error( KIO::ERR_SLAVE_DEFINED, i18n( "Malformed URL %1", url.prettyUrl() ) );
314 return;
317 ok = relativePath.isEmpty();
318 if ( !ok ) {
319 error( KIO::ERR_ACCESS_DENIED, url.prettyUrl() );
320 return;
323 ok = impl.del(trashId, fileId);
324 if ( !ok ) {
325 error( impl.lastErrorCode(), impl.lastErrorMessage() );
326 return;
329 finished();
332 void TrashProtocol::listDir(const KUrl& url)
334 INIT_IMPL;
335 kDebug() << "listdir: " << url;
336 if ( url.path().length() <= 1 ) {
337 listRoot();
338 return;
340 int trashId;
341 QString fileId;
342 QString relativePath;
343 bool ok = TrashImpl::parseURL( url, trashId, fileId, relativePath );
344 if ( !ok ) {
345 error( KIO::ERR_SLAVE_DEFINED, i18n( "Malformed URL %1", url.prettyUrl() ) );
346 return;
348 //was: const QString physicalPath = impl.physicalPath( trashId, fileId, relativePath );
350 // Get info for deleted directory - the date of deletion and orig path will be used
351 // for all the items in it, and we need the physicalPath.
352 TrashedFileInfo info;
353 ok = impl.infoForFile( trashId, fileId, info );
354 if ( !ok || info.physicalPath.isEmpty() ) {
355 error( impl.lastErrorCode(), impl.lastErrorMessage() );
356 return;
358 if ( !relativePath.isEmpty() ) {
359 info.physicalPath += '/';
360 info.physicalPath += relativePath;
363 // List subdir. Can't use kio_file here since we provide our own info...
364 kDebug() << "listing " << info.physicalPath;
365 const QStringList entryNames = impl.listDir( info.physicalPath );
366 totalSize( entryNames.count() );
367 KIO::UDSEntry entry;
368 for ( QStringList::const_iterator entryIt = entryNames.begin(), entryEnd = entryNames.end();
369 entryIt != entryEnd ; ++entryIt )
371 const QString fileName = *entryIt;
372 if ( fileName == ".." )
373 continue;
374 const QString filePath = info.physicalPath + '/' + fileName;
375 // shouldn't be necessary
376 //const QString url = TrashImpl::makeURL( trashId, fileId, relativePath + '/' + fileName );
377 entry.clear();
378 TrashedFileInfo infoForItem( info );
379 infoForItem.origPath += '/';
380 infoForItem.origPath += fileName;
381 if ( ok && createUDSEntry( filePath, fileName, QString() /*url*/, entry, infoForItem ) ) {
382 listEntry( entry, false );
385 entry.clear();
386 listEntry( entry, true );
387 finished();
390 bool TrashProtocol::createUDSEntry( const QString& physicalPath, const QString& fileName, const QString& url, KIO::UDSEntry& entry, const TrashedFileInfo& info )
392 QByteArray physicalPath_c = QFile::encodeName( physicalPath );
393 KDE_struct_stat buff;
394 if ( KDE_lstat( physicalPath_c, &buff ) == -1 ) {
395 kWarning() << "couldn't stat " << physicalPath ;
396 return false;
398 if (S_ISLNK(buff.st_mode)) {
399 char buffer2[ 1000 ];
400 int n = readlink( physicalPath_c, buffer2, 1000 );
401 if ( n != -1 ) {
402 buffer2[ n ] = 0;
405 entry.insert( KIO::UDSEntry::UDS_LINK_DEST, QFile::decodeName( buffer2 ) );
406 // Follow symlink
407 // That makes sense in kio_file, but not in the trash, especially for the size
408 // #136876
409 #if 0
410 if ( KDE_stat( physicalPath_c, &buff ) == -1 ) {
411 // It is a link pointing to nowhere
412 buff.st_mode = S_IFLNK | S_IRWXU | S_IRWXG | S_IRWXO;
413 buff.st_mtime = 0;
414 buff.st_atime = 0;
415 buff.st_size = 0;
417 #endif
419 mode_t type = buff.st_mode & S_IFMT; // extract file type
420 mode_t access = buff.st_mode & 07777; // extract permissions
421 access &= 07555; // make it readonly, since it's in the trashcan
422 entry.insert( KIO::UDSEntry::UDS_NAME, fileName );
423 entry.insert( KIO::UDSEntry::UDS_FILE_TYPE, type );
424 if ( !url.isEmpty() )
425 entry.insert( KIO::UDSEntry::UDS_URL, url );
427 KMimeType::Ptr mt = KMimeType::findByPath( physicalPath, buff.st_mode );
428 if ( mt )
429 entry.insert( KIO::UDSEntry::UDS_MIME_TYPE, mt->name() );
430 entry.insert( KIO::UDSEntry::UDS_ACCESS, access );
431 entry.insert( KIO::UDSEntry::UDS_SIZE, buff.st_size );
432 entry.insert( KIO::UDSEntry::UDS_USER, m_userName ); // assumption
433 entry.insert( KIO::UDSEntry::UDS_GROUP, m_groupName ); // assumption
434 entry.insert( KIO::UDSEntry::UDS_MODIFICATION_TIME, buff.st_mtime );
435 entry.insert( KIO::UDSEntry::UDS_ACCESS_TIME, buff.st_atime ); // ## or use it for deletion time?
436 entry.insert( KIO::UDSEntry::UDS_EXTRA, info.origPath );
437 entry.insert( KIO::UDSEntry::UDS_EXTRA, info.deletionDate.toString( Qt::ISODate ) );
438 return true;
441 void TrashProtocol::listRoot()
443 INIT_IMPL;
444 const TrashedFileInfoList lst = impl.list();
445 totalSize( lst.count() );
446 KIO::UDSEntry entry;
447 createTopLevelDirEntry( entry );
448 listEntry( entry, false );
449 for ( TrashedFileInfoList::ConstIterator it = lst.begin(); it != lst.end(); ++it ) {
450 const KUrl url = TrashImpl::makeURL( (*it).trashId, (*it).fileId, QString() );
451 KUrl origURL;
452 origURL.setPath( (*it).origPath );
453 entry.clear();
454 if ( createUDSEntry( (*it).physicalPath, origURL.fileName(), url.url(), entry, *it ) )
455 listEntry( entry, false );
457 entry.clear();
458 listEntry( entry, true );
459 finished();
462 void TrashProtocol::special( const QByteArray & data )
464 INIT_IMPL;
465 QDataStream stream( data );
466 int cmd;
467 stream >> cmd;
469 switch (cmd) {
470 case 1:
471 if ( impl.emptyTrash() )
472 finished();
473 else
474 error( impl.lastErrorCode(), impl.lastErrorMessage() );
475 break;
476 case 2:
477 impl.migrateOldTrash();
478 finished();
479 break;
480 case 3:
482 KUrl url;
483 stream >> url;
484 restore( url );
485 break;
487 default:
488 kWarning(7116) << "Unknown command in special(): " << cmd ;
489 error( KIO::ERR_UNSUPPORTED_ACTION, QString::number(cmd) );
490 break;
494 void TrashProtocol::put( const KUrl& url, int /*permissions*/, KIO::JobFlags )
496 INIT_IMPL;
497 kDebug() << "put: " << url;
498 // create deleted file. We need to get the mtime and original location from metadata...
499 // Maybe we can find the info file for url.fileName(), in case ::rename() was called first, and failed...
500 error( KIO::ERR_ACCESS_DENIED, url.prettyUrl() );
503 void TrashProtocol::get( const KUrl& url )
505 INIT_IMPL;
506 kDebug() << "get() : " << url;
507 if ( !url.isValid() ) {
508 kDebug() << kBacktrace();
509 error( KIO::ERR_SLAVE_DEFINED, i18n( "Malformed URL %1", url.url() ) );
510 return;
512 if ( url.path().length() <= 1 ) {
513 error( KIO::ERR_IS_DIRECTORY, url.prettyUrl() );
514 return;
516 int trashId;
517 QString fileId;
518 QString relativePath;
519 bool ok = TrashImpl::parseURL( url, trashId, fileId, relativePath );
520 if ( !ok ) {
521 error( KIO::ERR_SLAVE_DEFINED, i18n( "Malformed URL %1", url.prettyUrl() ) );
522 return;
524 const QString physicalPath = impl.physicalPath( trashId, fileId, relativePath );
525 if ( physicalPath.isEmpty() ) {
526 error( impl.lastErrorCode(), impl.lastErrorMessage() );
527 return;
530 // Usually we run jobs in TrashImpl (for e.g. future kdedmodule)
531 // But for this one we wouldn't use DCOP for every bit of data...
532 KUrl fileURL;
533 fileURL.setPath( physicalPath );
534 KIO::Job* job = KIO::get( fileURL );
535 connect( job, SIGNAL( data( KIO::Job*, const QByteArray& ) ),
536 this, SLOT( slotData( KIO::Job*, const QByteArray& ) ) );
537 connect( job, SIGNAL( mimetype( KIO::Job*, const QString& ) ),
538 this, SLOT( slotMimetype( KIO::Job*, const QString& ) ) );
539 connect( job, SIGNAL( result(KJob*) ),
540 this, SLOT( jobFinished(KJob*) ) );
541 enterLoop();
544 void TrashProtocol::slotData( KIO::Job*, const QByteArray&arr )
546 data( arr );
549 void TrashProtocol::slotMimetype( KIO::Job*, const QString& mt )
551 mimeType( mt );
554 void TrashProtocol::jobFinished( KJob* job )
556 if ( job->error() )
557 error( job->error(), job->errorText() );
558 else
559 finished();
560 emit leaveModality();
563 #if 0
564 void TrashProtocol::mkdir( const KUrl& url, int /*permissions*/ )
566 INIT_IMPL;
567 // create info about deleted dir
568 // ############ Problem: we don't know the original path.
569 // Let's try to avoid this case (we should get to copy() instead, for local files)
570 kDebug() << "mkdir: " << url;
571 QString dir = url.directory();
573 if ( dir.length() <= 1 ) // new toplevel entry
575 // ## we should use TrashImpl::parseURL to give the right filename to createInfo
576 int trashId;
577 QString fileId;
578 if ( !impl.createInfo( url.path(), trashId, fileId ) ) {
579 error( impl.lastErrorCode(), impl.lastErrorMessage() );
580 } else {
581 if ( !impl.mkdir( trashId, fileId, permissions ) ) {
582 (void)impl.deleteInfo( trashId, fileId );
583 error( impl.lastErrorCode(), impl.lastErrorMessage() );
584 } else
585 finished();
587 } else {
588 // Well it's not allowed to add a directory to an existing deleted directory.
589 error( KIO::ERR_ACCESS_DENIED, url.prettyUrl() );
592 #endif
594 #include "kio_trash.moc"