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"
26 #include <kcomponentdata.h>
27 #include <kmimetype.h>
29 #include <QCoreApplication>
30 #include <QDataStream>
31 #include <QTextStream>
38 #include <sys/types.h>
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
);
50 TrashProtocol
slave( argv
[1], argv
[2], argv
[3] );
57 if ( !impl.init() ) { \
58 error( impl.lastErrorCode(), impl.lastErrorMessage() ); \
62 TrashProtocol::TrashProtocol( const QByteArray
& protocol
, const QByteArray
&pool
, const QByteArray
&app
)
63 : SlaveBase(protocol
, pool
, app
)
65 struct passwd
*user
= getpwuid( getuid() );
67 m_userName
= QString::fromLatin1(user
->pw_name
);
68 struct group
*grp
= getgrgid( getgid() );
70 m_groupName
= QString::fromLatin1(grp
->gr_name
);
73 TrashProtocol::~TrashProtocol()
77 void TrashProtocol::enterLoop()
80 connect(this, SIGNAL(leaveModality()),
81 &eventLoop
, SLOT(quit()));
82 eventLoop
.exec(QEventLoop::ExcludeUserInputEvents
);
85 void TrashProtocol::restore( const KUrl
& trashURL
)
88 QString fileId
, relativePath
;
89 bool ok
= TrashImpl::parseURL( trashURL
, trashId
, fileId
, relativePath
);
91 error( KIO::ERR_SLAVE_DEFINED
, i18n( "Malformed URL %1", trashURL
.prettyUrl() ) );
95 ok
= impl
.infoForFile( trashId
, fileId
, info
);
97 error( impl
.lastErrorCode(), impl
.lastErrorMessage() );
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
) );
115 copyOrMove( trashURL
, dest
, false /*overwrite*/, Move
);
118 void TrashProtocol::rename( const KUrl
&oldURL
, const KUrl
&newURL
, KIO::JobFlags flags
)
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() );
129 copyOrMove( oldURL
, newURL
, (flags
& KIO::Overwrite
), Move
);
132 void TrashProtocol::copy( const KUrl
&src
, const KUrl
&dest
, int /*permissions*/, KIO::JobFlags flags
)
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." ) );
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.
151 QString fileId
, relativePath
;
152 bool ok
= TrashImpl::parseURL( src
, trashId
, fileId
, relativePath
);
154 error( KIO::ERR_SLAVE_DEFINED
, i18n( "Malformed URL %1", src
.prettyUrl() ) );
157 const QString destPath
= dest
.path();
158 if ( QFile::exists( destPath
) ) {
160 ok
= QFile::remove( destPath
);
161 Q_ASSERT( ok
); // ### TODO
163 error( KIO::ERR_FILE_ALREADY_EXIST
, destPath
);
168 if ( action
== Move
) {
169 kDebug() << "calling moveFromTrash(" << destPath
<< " " << trashId
<< " " << fileId
<< ")";
170 ok
= impl
.moveFromTrash( destPath
, trashId
, fileId
, relativePath
);
172 kDebug() << "calling copyFromTrash(" << destPath
<< " " << trashId
<< " " << fileId
<< ")";
173 ok
= impl
.copyFromTrash( destPath
, trashId
, fileId
, relativePath
);
176 error( impl
.lastErrorCode(), impl
.lastErrorMessage() );
178 if ( action
== Move
&& relativePath
.isEmpty() )
179 (void)impl
.deleteInfo( trashId
, fileId
);
183 } else if ( src
.isLocalFile() && dest
.protocol() == "trash" ) {
184 QString dir
= dest
.directory();
185 //kDebug() << "trashing a file to " << dir;
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).
198 if ( !impl
.createInfo( srcPath
, trashId
, fileId
) ) {
199 error( impl
.lastErrorCode(), impl
.lastErrorMessage() );
202 if ( action
== Move
) {
203 kDebug() << "calling moveToTrash(" << srcPath
<< " " << trashId
<< " " << fileId
<< ")";
204 ok
= impl
.moveToTrash( srcPath
, trashId
, fileId
);
206 kDebug() << "calling copyToTrash(" << srcPath
<< " " << trashId
<< " " << fileId
<< ")";
207 ok
= impl
.copyToTrash( srcPath
, trashId
, fileId
);
210 (void)impl
.deleteInfo( trashId
, fileId
);
211 error( impl
.lastErrorCode(), impl
.lastErrorMessage() );
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() );
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() );
227 error( KIO::ERR_UNSUPPORTED_ACTION
, "should never happen" );
230 void TrashProtocol::createTopLevelDirEntry(KIO::UDSEntry
& entry
)
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
)
244 const QString path
= url
.path();
245 if( path
.isEmpty() || path
== "/" ) {
246 // The root is "virtual" - it's not a single physical directory
248 createTopLevelDirEntry( entry
);
253 QString fileId
, relativePath
;
255 bool ok
= TrashImpl::parseURL( url
, trashId
, fileId
, relativePath
);
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() ) );
267 const QString filePath
= impl
.physicalPath( trashId
, fileId
, relativePath
);
268 if ( filePath
.isEmpty() ) {
269 error( impl
.lastErrorCode(), impl
.lastErrorMessage() );
273 QString fileName
= filePath
.section('/', -1, -1, QString::SectionSkipEmpty
);
276 if ( url
.path().length() > 1 ) {
281 TrashedFileInfo info
;
282 ok
= impl
.infoForFile( trashId
, fileId
, info
);
284 ok
= createUDSEntry( filePath
, fileName
, fileURL
.fileName(), entry
, info
);
287 error( KIO::ERR_COULD_NOT_STAT
, url
.prettyUrl() );
295 void TrashProtocol::del( const KUrl
&url
, bool /*isfile*/ )
298 QString fileId
, relativePath
;
300 bool ok
= TrashImpl::parseURL( url
, trashId
, fileId
, relativePath
);
302 error( KIO::ERR_SLAVE_DEFINED
, i18n( "Malformed URL %1", url
.prettyUrl() ) );
306 ok
= relativePath
.isEmpty();
308 error( KIO::ERR_ACCESS_DENIED
, url
.prettyUrl() );
312 ok
= impl
.del(trashId
, fileId
);
314 error( impl
.lastErrorCode(), impl
.lastErrorMessage() );
321 void TrashProtocol::listDir(const KUrl
& url
)
324 kDebug() << "listdir: " << url
;
325 if ( url
.path(KUrl::AddTrailingSlash
) == QLatin1String("/") ) {
331 QString relativePath
;
332 bool ok
= TrashImpl::parseURL( url
, trashId
, fileId
, relativePath
);
334 error( KIO::ERR_SLAVE_DEFINED
, i18n( "Malformed URL %1", url
.prettyUrl() ) );
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() );
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() );
357 for ( QStringList::const_iterator entryIt
= entryNames
.begin(), entryEnd
= entryNames
.end();
358 entryIt
!= entryEnd
; ++entryIt
)
360 const QString fileName
= *entryIt
;
361 if ( fileName
== ".." )
363 const QString filePath
= info
.physicalPath
+ '/' + fileName
;
364 // shouldn't be necessary
365 //const QString url = TrashImpl::makeURL( trashId, fileId, relativePath + '/' + fileName );
367 TrashedFileInfo
infoForItem( info
);
368 infoForItem
.origPath
+= '/';
369 infoForItem
.origPath
+= fileName
;
370 if ( ok
&& createUDSEntry( filePath
, fileName
, fileName
, entry
, infoForItem
) ) {
371 listEntry( entry
, false );
375 listEntry( entry
, true );
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
;
387 if (S_ISLNK(buff
.st_mode
)) {
388 char buffer2
[ 1000 ];
389 int n
= readlink( physicalPath_c
, buffer2
, 1000 );
394 entry
.insert( KIO::UDSEntry::UDS_LINK_DEST
, QFile::decodeName( buffer2
) );
396 // That makes sense in kio_file, but not in the trash, especially for the size
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
;
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
);
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
) );
432 void TrashProtocol::listRoot()
435 const TrashedFileInfoList lst
= impl
.list();
436 totalSize( lst
.count() );
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() );
443 origURL
.setPath( (*it
).origPath
);
445 if ( createUDSEntry( (*it
).physicalPath
, origURL
.fileName(), url
.fileName(), entry
, *it
) )
446 listEntry( entry
, false );
449 listEntry( entry
, true );
453 void TrashProtocol::special( const QByteArray
& data
)
456 QDataStream
stream( data
);
462 if ( impl
.emptyTrash() )
465 error( impl
.lastErrorCode(), impl
.lastErrorMessage() );
468 impl
.migrateOldTrash();
479 kWarning(7116) << "Unknown command in special(): " << cmd
;
480 error( KIO::ERR_UNSUPPORTED_ACTION
, QString::number(cmd
) );
485 void TrashProtocol::put( const KUrl
& url
, int /*permissions*/, KIO::JobFlags
)
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
)
497 kDebug() << "get() : " << url
;
498 if ( !url
.isValid() ) {
499 kDebug() << kBacktrace();
500 error( KIO::ERR_SLAVE_DEFINED
, i18n( "Malformed URL %1", url
.url() ) );
503 if ( url
.path().length() <= 1 ) {
504 error( KIO::ERR_IS_DIRECTORY
, url
.prettyUrl() );
509 QString relativePath
;
510 bool ok
= TrashImpl::parseURL( url
, trashId
, fileId
, relativePath
);
512 error( KIO::ERR_SLAVE_DEFINED
, i18n( "Malformed URL %1", url
.prettyUrl() ) );
515 const QString physicalPath
= impl
.physicalPath( trashId
, fileId
, relativePath
);
516 if ( physicalPath
.isEmpty() ) {
517 error( impl
.lastErrorCode(), impl
.lastErrorMessage() );
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...
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
*) ) );
535 void TrashProtocol::slotData( KIO::Job
*, const QByteArray
&arr
)
540 void TrashProtocol::slotMimetype( KIO::Job
*, const QString
& mt
)
545 void TrashProtocol::jobFinished( KJob
* job
)
548 error( job
->error(), job
->errorText() );
551 emit
leaveModality();
555 void TrashProtocol::mkdir( const KUrl
& url
, int /*permissions*/ )
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
569 if ( !impl
.createInfo( url
.path(), trashId
, fileId
) ) {
570 error( impl
.lastErrorCode(), impl
.lastErrorMessage() );
572 if ( !impl
.mkdir( trashId
, fileId
, permissions
) ) {
573 (void)impl
.deleteInfo( trashId
, fileId
);
574 error( impl
.lastErrorCode(), impl
.lastErrorMessage() );
579 // Well it's not allowed to add a directory to an existing deleted directory.
580 error( KIO::ERR_ACCESS_DENIED
, url
.prettyUrl() );
585 #include "kio_trash.moc"