delay a few things on startup, such as setting the visibility mode, which ensures...
[personal-kdebase.git] / runtime / kioslave / trash / kio_trash.cpp
blob12b2e0b1855e04d74923dc67fa59051403d06265
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 <kdebug.h>
24 #include <klocale.h>
25 #include <kde_file.h>
26 #include <kcomponentdata.h>
27 #include <kmimetype.h>
29 #include <QCoreApplication>
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 // necessary to use other kio slaves
46 KComponentData componentData("kio_trash" );
47 QCoreApplication app(argc, argv);
49 // start the slave
50 TrashProtocol slave( argv[1], argv[2], argv[3] );
51 slave.dispatchLoop();
52 return 0;
56 #define INIT_IMPL \
57 if ( !impl.init() ) { \
58 error( impl.lastErrorCode(), impl.lastErrorMessage() ); \
59 return; \
62 TrashProtocol::TrashProtocol( const QByteArray& protocol, const QByteArray &pool, const QByteArray &app)
63 : SlaveBase(protocol, pool, app )
65 struct passwd *user = getpwuid( getuid() );
66 if ( user )
67 m_userName = QString::fromLatin1(user->pw_name);
68 struct group *grp = getgrgid( getgid() );
69 if ( grp )
70 m_groupName = QString::fromLatin1(grp->gr_name);
73 TrashProtocol::~TrashProtocol()
77 void TrashProtocol::enterLoop()
79 QEventLoop eventLoop;
80 connect(this, SIGNAL(leaveModality()),
81 &eventLoop, SLOT(quit()));
82 eventLoop.exec(QEventLoop::ExcludeUserInputEvents);
85 void TrashProtocol::restore( const KUrl& trashURL )
87 int trashId;
88 QString fileId, relativePath;
89 bool ok = TrashImpl::parseURL( trashURL, trashId, fileId, relativePath );
90 if ( !ok ) {
91 error( KIO::ERR_SLAVE_DEFINED, i18n( "Malformed URL %1", trashURL.prettyUrl() ) );
92 return;
94 TrashedFileInfo info;
95 ok = impl.infoForFile( trashId, fileId, info );
96 if ( !ok ) {
97 error( impl.lastErrorCode(), impl.lastErrorMessage() );
98 return;
100 KUrl dest;
101 dest.setPath( info.origPath );
102 if ( !relativePath.isEmpty() )
103 dest.addPath( relativePath );
105 // Check that the destination directory exists, to improve the error code in case it doesn't.
106 const QString destDir = dest.directory();
107 KDE_struct_stat buff;
108 if ( KDE_lstat( QFile::encodeName( destDir ), &buff ) == -1 ) {
109 error( KIO::ERR_SLAVE_DEFINED,
110 i18n( "The directory %1 does not exist anymore, so it is not possible to restore this item to its original location. "
111 "You can either recreate that directory and use the restore operation again, or drag the item anywhere else to restore it.", destDir ) );
112 return;
115 copyOrMove( trashURL, dest, false /*overwrite*/, Move );
118 void TrashProtocol::rename( const KUrl &oldURL, const KUrl &newURL, KIO::JobFlags flags )
120 INIT_IMPL;
122 kDebug()<<"TrashProtocol::rename(): old="<<oldURL<<" new="<<newURL<<" overwrite=" << (flags & KIO::Overwrite);
124 if ( oldURL.protocol() == "trash" && newURL.protocol() == "trash" ) {
125 error( KIO::ERR_CANNOT_RENAME, oldURL.prettyUrl() );
126 return;
129 copyOrMove( oldURL, newURL, (flags & KIO::Overwrite), Move );
132 void TrashProtocol::copy( const KUrl &src, const KUrl &dest, int /*permissions*/, KIO::JobFlags flags )
134 INIT_IMPL;
136 kDebug()<<"TrashProtocol::copy(): " << src << " " << dest;
138 if ( src.protocol() == "trash" && dest.protocol() == "trash" ) {
139 error( KIO::ERR_UNSUPPORTED_ACTION, i18n( "This file is already in the trash bin." ) );
140 return;
143 copyOrMove( src, dest, (flags & KIO::Overwrite), Copy );
146 void TrashProtocol::copyOrMove( const KUrl &src, const KUrl &dest, bool overwrite, CopyOrMove action )
148 if ( src.protocol() == "trash" && dest.isLocalFile() ) {
149 // Extracting (e.g. via dnd). Ignore original location stored in info file.
150 int trashId;
151 QString fileId, relativePath;
152 bool ok = TrashImpl::parseURL( src, trashId, fileId, relativePath );
153 if ( !ok ) {
154 error( KIO::ERR_SLAVE_DEFINED, i18n( "Malformed URL %1", src.prettyUrl() ) );
155 return;
157 const QString destPath = dest.path();
158 if ( QFile::exists( destPath ) ) {
159 if ( overwrite ) {
160 ok = QFile::remove( destPath );
161 Q_ASSERT( ok ); // ### TODO
162 } else {
163 error( KIO::ERR_FILE_ALREADY_EXIST, destPath );
164 return;
168 if ( action == Move ) {
169 kDebug() << "calling moveFromTrash(" << destPath << " " << trashId << " " << fileId << ")";
170 ok = impl.moveFromTrash( destPath, trashId, fileId, relativePath );
171 } else { // Copy
172 kDebug() << "calling copyFromTrash(" << destPath << " " << trashId << " " << fileId << ")";
173 ok = impl.copyFromTrash( destPath, trashId, fileId, relativePath );
175 if ( !ok ) {
176 error( impl.lastErrorCode(), impl.lastErrorMessage() );
177 } else {
178 if ( action == Move && relativePath.isEmpty() )
179 (void)impl.deleteInfo( trashId, fileId );
180 finished();
182 return;
183 } else if ( src.isLocalFile() && dest.protocol() == "trash" ) {
184 QString dir = dest.directory();
185 //kDebug() << "trashing a file to " << dir;
186 // Trashing a file
187 // We detect the case where this isn't normal trashing, but
188 // e.g. if kwrite tries to save (moving tempfile over destination)
189 if ( dir.length() <= 1 && src.fileName() == dest.fileName() ) // new toplevel entry
191 const QString srcPath = src.path();
192 // In theory we should use TrashImpl::parseURL to give the right filename to createInfo,
193 // in case the trash URL didn't contain the same filename as srcPath.
194 // But this can only happen with copyAs/moveAs, not available in the GUI
195 // for the trash (New/... or Rename from iconview/listview).
196 int trashId;
197 QString fileId;
198 if ( !impl.createInfo( srcPath, trashId, fileId ) ) {
199 error( impl.lastErrorCode(), impl.lastErrorMessage() );
200 } else {
201 bool ok;
202 if ( action == Move ) {
203 kDebug() << "calling moveToTrash(" << srcPath << " " << trashId << " " << fileId << ")";
204 ok = impl.moveToTrash( srcPath, trashId, fileId );
205 } else { // Copy
206 kDebug() << "calling copyToTrash(" << srcPath << " " << trashId << " " << fileId << ")";
207 ok = impl.copyToTrash( srcPath, trashId, fileId );
209 if ( !ok ) {
210 (void)impl.deleteInfo( trashId, fileId );
211 error( impl.lastErrorCode(), impl.lastErrorMessage() );
212 } else {
213 // Inform caller of the final URL. Used by konq_undo.
214 const KUrl url = impl.makeURL( trashId, fileId, QString() );
215 setMetaData( "trashURL-" + srcPath, url.url() );
216 finished();
219 return;
220 } else {
221 kDebug() << "returning KIO::ERR_ACCESS_DENIED, it's not allowed to add a file to an existing trash directory";
222 // It's not allowed to add a file to an existing trash directory.
223 error( KIO::ERR_ACCESS_DENIED, dest.prettyUrl() );
224 return;
226 } else
227 error( KIO::ERR_UNSUPPORTED_ACTION, "should never happen" );
230 void TrashProtocol::createTopLevelDirEntry(KIO::UDSEntry& entry)
232 entry.clear();
233 entry.insert( KIO::UDSEntry::UDS_NAME, QString::fromLatin1("."));
234 entry.insert( KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR);
235 entry.insert( KIO::UDSEntry::UDS_ACCESS, 0700);
236 entry.insert( KIO::UDSEntry::UDS_MIME_TYPE, QString::fromLatin1("inode/directory"));
237 entry.insert( KIO::UDSEntry::UDS_USER, m_userName);
238 entry.insert( KIO::UDSEntry::UDS_GROUP, m_groupName);
241 void TrashProtocol::stat(const KUrl& url)
243 INIT_IMPL;
244 const QString path = url.path();
245 if( path.isEmpty() || path == "/" ) {
246 // The root is "virtual" - it's not a single physical directory
247 KIO::UDSEntry entry;
248 createTopLevelDirEntry( entry );
249 statEntry( entry );
250 finished();
251 } else {
252 int trashId;
253 QString fileId, relativePath;
255 bool ok = TrashImpl::parseURL( url, trashId, fileId, relativePath );
257 if ( !ok ) {
258 // ######## do we still need this?
259 kDebug() << url << " looks fishy, returning does-not-exist";
260 // A URL like trash:/file simply means that CopyJob is trying to see if
261 // the destination exists already (it made up the URL by itself).
262 error( KIO::ERR_DOES_NOT_EXIST, url.prettyUrl() );
263 //error( KIO::ERR_SLAVE_DEFINED, i18n( "Malformed URL %1" ).arg( url.prettyUrl() ) );
264 return;
267 const QString filePath = impl.physicalPath( trashId, fileId, relativePath );
268 if ( filePath.isEmpty() ) {
269 error( impl.lastErrorCode(), impl.lastErrorMessage() );
270 return;
273 QString fileName = filePath.section('/', -1, -1, QString::SectionSkipEmpty);
275 KUrl fileURL;
276 if ( url.path().length() > 1 ) {
277 fileURL = url;
280 KIO::UDSEntry entry;
281 TrashedFileInfo info;
282 ok = impl.infoForFile( trashId, fileId, info );
283 if ( ok )
284 ok = createUDSEntry( filePath, fileName, fileURL.fileName(), entry, info );
286 if ( !ok ) {
287 error( KIO::ERR_COULD_NOT_STAT, url.prettyUrl() );
290 statEntry( entry );
291 finished();
295 void TrashProtocol::del( const KUrl &url, bool /*isfile*/ )
297 int trashId;
298 QString fileId, relativePath;
300 bool ok = TrashImpl::parseURL( url, trashId, fileId, relativePath );
301 if ( !ok ) {
302 error( KIO::ERR_SLAVE_DEFINED, i18n( "Malformed URL %1", url.prettyUrl() ) );
303 return;
306 ok = relativePath.isEmpty();
307 if ( !ok ) {
308 error( KIO::ERR_ACCESS_DENIED, url.prettyUrl() );
309 return;
312 ok = impl.del(trashId, fileId);
313 if ( !ok ) {
314 error( impl.lastErrorCode(), impl.lastErrorMessage() );
315 return;
318 finished();
321 void TrashProtocol::listDir(const KUrl& url)
323 INIT_IMPL;
324 kDebug() << "listdir: " << url;
325 if ( url.path(KUrl::AddTrailingSlash) == QLatin1String("/") ) {
326 listRoot();
327 return;
329 int trashId;
330 QString fileId;
331 QString relativePath;
332 bool ok = TrashImpl::parseURL( url, trashId, fileId, relativePath );
333 if ( !ok ) {
334 error( KIO::ERR_SLAVE_DEFINED, i18n( "Malformed URL %1", url.prettyUrl() ) );
335 return;
337 //was: const QString physicalPath = impl.physicalPath( trashId, fileId, relativePath );
339 // Get info for deleted directory - the date of deletion and orig path will be used
340 // for all the items in it, and we need the physicalPath.
341 TrashedFileInfo info;
342 ok = impl.infoForFile( trashId, fileId, info );
343 if ( !ok || info.physicalPath.isEmpty() ) {
344 error( impl.lastErrorCode(), impl.lastErrorMessage() );
345 return;
347 if ( !relativePath.isEmpty() ) {
348 info.physicalPath += '/';
349 info.physicalPath += relativePath;
352 // List subdir. Can't use kio_file here since we provide our own info...
353 kDebug() << "listing " << info.physicalPath;
354 const QStringList entryNames = impl.listDir( info.physicalPath );
355 totalSize( entryNames.count() );
356 KIO::UDSEntry entry;
357 for ( QStringList::const_iterator entryIt = entryNames.begin(), entryEnd = entryNames.end();
358 entryIt != entryEnd ; ++entryIt )
360 const QString fileName = *entryIt;
361 if ( fileName == ".." )
362 continue;
363 const QString filePath = info.physicalPath + '/' + fileName;
364 // shouldn't be necessary
365 //const QString url = TrashImpl::makeURL( trashId, fileId, relativePath + '/' + fileName );
366 entry.clear();
367 TrashedFileInfo infoForItem( info );
368 infoForItem.origPath += '/';
369 infoForItem.origPath += fileName;
370 if ( ok && createUDSEntry( filePath, fileName, fileName, entry, infoForItem ) ) {
371 listEntry( entry, false );
374 entry.clear();
375 listEntry( entry, true );
376 finished();
379 bool TrashProtocol::createUDSEntry( const QString& physicalPath, const QString& displayFileName, const QString& internalFileName, KIO::UDSEntry& entry, const TrashedFileInfo& info )
381 QByteArray physicalPath_c = QFile::encodeName( physicalPath );
382 KDE_struct_stat buff;
383 if ( KDE_lstat( physicalPath_c, &buff ) == -1 ) {
384 kWarning() << "couldn't stat " << physicalPath ;
385 return false;
387 if (S_ISLNK(buff.st_mode)) {
388 char buffer2[ 1000 ];
389 int n = readlink( physicalPath_c, buffer2, 1000 );
390 if ( n != -1 ) {
391 buffer2[ n ] = 0;
394 entry.insert( KIO::UDSEntry::UDS_LINK_DEST, QFile::decodeName( buffer2 ) );
395 // Follow symlink
396 // That makes sense in kio_file, but not in the trash, especially for the size
397 // #136876
398 #if 0
399 if ( KDE_stat( physicalPath_c, &buff ) == -1 ) {
400 // It is a link pointing to nowhere
401 buff.st_mode = S_IFLNK | S_IRWXU | S_IRWXG | S_IRWXO;
402 buff.st_mtime = 0;
403 buff.st_atime = 0;
404 buff.st_size = 0;
406 #endif
408 mode_t type = buff.st_mode & S_IFMT; // extract file type
409 mode_t access = buff.st_mode & 07777; // extract permissions
410 access &= 07555; // make it readonly, since it's in the trashcan
411 Q_ASSERT(!internalFileName.isEmpty());
412 entry.insert( KIO::UDSEntry::UDS_NAME, internalFileName ); // internal filename, like "0-foo"
413 entry.insert( KIO::UDSEntry::UDS_DISPLAY_NAME, displayFileName ); // user-visible filename, like "foo"
414 entry.insert( KIO::UDSEntry::UDS_FILE_TYPE, type );
415 //if ( !url.isEmpty() )
416 // entry.insert( KIO::UDSEntry::UDS_URL, url );
418 KMimeType::Ptr mt = KMimeType::findByPath( physicalPath, buff.st_mode );
419 if ( mt )
420 entry.insert( KIO::UDSEntry::UDS_MIME_TYPE, mt->name() );
421 entry.insert( KIO::UDSEntry::UDS_ACCESS, access );
422 entry.insert( KIO::UDSEntry::UDS_SIZE, buff.st_size );
423 entry.insert( KIO::UDSEntry::UDS_USER, m_userName ); // assumption
424 entry.insert( KIO::UDSEntry::UDS_GROUP, m_groupName ); // assumption
425 entry.insert( KIO::UDSEntry::UDS_MODIFICATION_TIME, buff.st_mtime );
426 entry.insert( KIO::UDSEntry::UDS_ACCESS_TIME, buff.st_atime ); // ## or use it for deletion time?
427 entry.insert( KIO::UDSEntry::UDS_EXTRA, info.origPath );
428 entry.insert( KIO::UDSEntry::UDS_EXTRA, info.deletionDate.toString( Qt::ISODate ) );
429 return true;
432 void TrashProtocol::listRoot()
434 INIT_IMPL;
435 const TrashedFileInfoList lst = impl.list();
436 totalSize( lst.count() );
437 KIO::UDSEntry entry;
438 createTopLevelDirEntry( entry );
439 listEntry( entry, false );
440 for ( TrashedFileInfoList::ConstIterator it = lst.begin(); it != lst.end(); ++it ) {
441 const KUrl url = TrashImpl::makeURL( (*it).trashId, (*it).fileId, QString() );
442 KUrl origURL;
443 origURL.setPath( (*it).origPath );
444 entry.clear();
445 if ( createUDSEntry( (*it).physicalPath, origURL.fileName(), url.fileName(), entry, *it ) )
446 listEntry( entry, false );
448 entry.clear();
449 listEntry( entry, true );
450 finished();
453 void TrashProtocol::special( const QByteArray & data )
455 INIT_IMPL;
456 QDataStream stream( data );
457 int cmd;
458 stream >> cmd;
460 switch (cmd) {
461 case 1:
462 if ( impl.emptyTrash() )
463 finished();
464 else
465 error( impl.lastErrorCode(), impl.lastErrorMessage() );
466 break;
467 case 2:
468 impl.migrateOldTrash();
469 finished();
470 break;
471 case 3:
473 KUrl url;
474 stream >> url;
475 restore( url );
476 break;
478 default:
479 kWarning(7116) << "Unknown command in special(): " << cmd ;
480 error( KIO::ERR_UNSUPPORTED_ACTION, QString::number(cmd) );
481 break;
485 void TrashProtocol::put( const KUrl& url, int /*permissions*/, KIO::JobFlags )
487 INIT_IMPL;
488 kDebug() << "put: " << url;
489 // create deleted file. We need to get the mtime and original location from metadata...
490 // Maybe we can find the info file for url.fileName(), in case ::rename() was called first, and failed...
491 error( KIO::ERR_ACCESS_DENIED, url.prettyUrl() );
494 void TrashProtocol::get( const KUrl& url )
496 INIT_IMPL;
497 kDebug() << "get() : " << url;
498 if ( !url.isValid() ) {
499 kDebug() << kBacktrace();
500 error( KIO::ERR_SLAVE_DEFINED, i18n( "Malformed URL %1", url.url() ) );
501 return;
503 if ( url.path().length() <= 1 ) {
504 error( KIO::ERR_IS_DIRECTORY, url.prettyUrl() );
505 return;
507 int trashId;
508 QString fileId;
509 QString relativePath;
510 bool ok = TrashImpl::parseURL( url, trashId, fileId, relativePath );
511 if ( !ok ) {
512 error( KIO::ERR_SLAVE_DEFINED, i18n( "Malformed URL %1", url.prettyUrl() ) );
513 return;
515 const QString physicalPath = impl.physicalPath( trashId, fileId, relativePath );
516 if ( physicalPath.isEmpty() ) {
517 error( impl.lastErrorCode(), impl.lastErrorMessage() );
518 return;
521 // Usually we run jobs in TrashImpl (for e.g. future kdedmodule)
522 // But for this one we wouldn't use DCOP for every bit of data...
523 KUrl fileURL;
524 fileURL.setPath( physicalPath );
525 KIO::Job* job = KIO::get( fileURL, KIO::NoReload, KIO::HideProgressInfo );
526 connect( job, SIGNAL( data( KIO::Job*, const QByteArray& ) ),
527 this, SLOT( slotData( KIO::Job*, const QByteArray& ) ) );
528 connect( job, SIGNAL( mimetype( KIO::Job*, const QString& ) ),
529 this, SLOT( slotMimetype( KIO::Job*, const QString& ) ) );
530 connect( job, SIGNAL( result(KJob*) ),
531 this, SLOT( jobFinished(KJob*) ) );
532 enterLoop();
535 void TrashProtocol::slotData( KIO::Job*, const QByteArray&arr )
537 data( arr );
540 void TrashProtocol::slotMimetype( KIO::Job*, const QString& mt )
542 mimeType( mt );
545 void TrashProtocol::jobFinished( KJob* job )
547 if ( job->error() )
548 error( job->error(), job->errorText() );
549 else
550 finished();
551 emit leaveModality();
554 #if 0
555 void TrashProtocol::mkdir( const KUrl& url, int /*permissions*/ )
557 INIT_IMPL;
558 // create info about deleted dir
559 // ############ Problem: we don't know the original path.
560 // Let's try to avoid this case (we should get to copy() instead, for local files)
561 kDebug() << "mkdir: " << url;
562 QString dir = url.directory();
564 if ( dir.length() <= 1 ) // new toplevel entry
566 // ## we should use TrashImpl::parseURL to give the right filename to createInfo
567 int trashId;
568 QString fileId;
569 if ( !impl.createInfo( url.path(), trashId, fileId ) ) {
570 error( impl.lastErrorCode(), impl.lastErrorMessage() );
571 } else {
572 if ( !impl.mkdir( trashId, fileId, permissions ) ) {
573 (void)impl.deleteInfo( trashId, fileId );
574 error( impl.lastErrorCode(), impl.lastErrorMessage() );
575 } else
576 finished();
578 } else {
579 // Well it's not allowed to add a directory to an existing deleted directory.
580 error( KIO::ERR_ACCESS_DENIED, url.prettyUrl() );
583 #endif
585 #include "kio_trash.moc"