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"
23 #include <kapplication.h>
27 #include <kcmdlineargs.h>
28 #include <kmimetype.h>
30 #include <QDataStream>
31 #include <QTextStream>
38 #include <sys/types.h>
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) ) );
68 if ( !impl.init() ) { \
69 error( impl.lastErrorCode(), impl.lastErrorMessage() ); \
73 TrashProtocol::TrashProtocol( const QByteArray
& protocol
, const QByteArray
&pool
, const QByteArray
&app
)
74 : SlaveBase(protocol
, pool
, app
)
76 struct passwd
*user
= getpwuid( getuid() );
78 m_userName
= QString::fromLatin1(user
->pw_name
);
79 struct group
*grp
= getgrgid( getgid() );
81 m_groupName
= QString::fromLatin1(grp
->gr_name
);
84 TrashProtocol::~TrashProtocol()
88 void TrashProtocol::enterLoop()
91 connect(this, SIGNAL(leaveModality()),
92 &eventLoop
, SLOT(quit()));
93 eventLoop
.exec(QEventLoop::ExcludeUserInputEvents
);
96 void TrashProtocol::restore( const KUrl
& trashURL
)
99 QString fileId
, relativePath
;
100 bool ok
= TrashImpl::parseURL( trashURL
, trashId
, fileId
, relativePath
);
102 error( KIO::ERR_SLAVE_DEFINED
, i18n( "Malformed URL %1", trashURL
.prettyUrl() ) );
105 TrashedFileInfo info
;
106 ok
= impl
.infoForFile( trashId
, fileId
, info
);
108 error( impl
.lastErrorCode(), impl
.lastErrorMessage() );
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
) );
126 copyOrMove( trashURL
, dest
, false /*overwrite*/, Move
);
129 void TrashProtocol::rename( const KUrl
&oldURL
, const KUrl
&newURL
, KIO::JobFlags flags
)
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() );
140 copyOrMove( oldURL
, newURL
, (flags
& KIO::Overwrite
), Move
);
143 void TrashProtocol::copy( const KUrl
&src
, const KUrl
&dest
, int /*permissions*/, KIO::JobFlags flags
)
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." ) );
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.
162 QString fileId
, relativePath
;
163 bool ok
= TrashImpl::parseURL( src
, trashId
, fileId
, relativePath
);
165 error( KIO::ERR_SLAVE_DEFINED
, i18n( "Malformed URL %1", src
.prettyUrl() ) );
168 const QString destPath
= dest
.path();
169 if ( QFile::exists( destPath
) ) {
171 ok
= QFile::remove( destPath
);
172 Q_ASSERT( ok
); // ### TODO
174 error( KIO::ERR_FILE_ALREADY_EXIST
, destPath
);
179 if ( action
== Move
) {
180 kDebug() << "calling moveFromTrash(" << destPath
<< " " << trashId
<< " " << fileId
<< ")";
181 ok
= impl
.moveFromTrash( destPath
, trashId
, fileId
, relativePath
);
183 kDebug() << "calling copyFromTrash(" << destPath
<< " " << trashId
<< " " << fileId
<< ")";
184 ok
= impl
.copyFromTrash( destPath
, trashId
, fileId
, relativePath
);
187 error( impl
.lastErrorCode(), impl
.lastErrorMessage() );
189 if ( action
== Move
&& relativePath
.isEmpty() )
190 (void)impl
.deleteInfo( trashId
, fileId
);
194 } else if ( src
.isLocalFile() && dest
.protocol() == "trash" ) {
195 QString dir
= dest
.directory();
196 //kDebug() << "trashing a file to " << dir;
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).
209 if ( !impl
.createInfo( srcPath
, trashId
, fileId
) ) {
210 error( impl
.lastErrorCode(), impl
.lastErrorMessage() );
213 if ( action
== Move
) {
214 kDebug() << "calling moveToTrash(" << srcPath
<< " " << trashId
<< " " << fileId
<< ")";
215 ok
= impl
.moveToTrash( srcPath
, trashId
, fileId
);
217 kDebug() << "calling copyToTrash(" << srcPath
<< " " << trashId
<< " " << fileId
<< ")";
218 ok
= impl
.copyToTrash( srcPath
, trashId
, fileId
);
221 (void)impl
.deleteInfo( trashId
, fileId
);
222 error( impl
.lastErrorCode(), impl
.lastErrorMessage() );
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() );
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() );
238 error( KIO::ERR_UNSUPPORTED_ACTION
, "should never happen" );
241 void TrashProtocol::createTopLevelDirEntry(KIO::UDSEntry
& entry
)
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
)
255 const QString path
= url
.path();
256 if( path
.isEmpty() || path
== "/" ) {
257 // The root is "virtual" - it's not a single physical directory
259 createTopLevelDirEntry( entry
);
264 QString fileId
, relativePath
;
266 bool ok
= TrashImpl::parseURL( url
, trashId
, fileId
, relativePath
);
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() ) );
278 const QString filePath
= impl
.physicalPath( trashId
, fileId
, relativePath
);
279 if ( filePath
.isEmpty() ) {
280 error( impl
.lastErrorCode(), impl
.lastErrorMessage() );
284 QString fileName
= filePath
.section('/', -1, -1, QString::SectionSkipEmpty
);
286 QString fileURL
= QString();
287 if ( url
.path().length() > 1 ) {
292 TrashedFileInfo info
;
293 ok
= impl
.infoForFile( trashId
, fileId
, info
);
295 ok
= createUDSEntry( filePath
, fileName
, fileURL
, entry
, info
);
298 error( KIO::ERR_COULD_NOT_STAT
, url
.prettyUrl() );
306 void TrashProtocol::del( const KUrl
&url
, bool /*isfile*/ )
309 QString fileId
, relativePath
;
311 bool ok
= TrashImpl::parseURL( url
, trashId
, fileId
, relativePath
);
313 error( KIO::ERR_SLAVE_DEFINED
, i18n( "Malformed URL %1", url
.prettyUrl() ) );
317 ok
= relativePath
.isEmpty();
319 error( KIO::ERR_ACCESS_DENIED
, url
.prettyUrl() );
323 ok
= impl
.del(trashId
, fileId
);
325 error( impl
.lastErrorCode(), impl
.lastErrorMessage() );
332 void TrashProtocol::listDir(const KUrl
& url
)
335 kDebug() << "listdir: " << url
;
336 if ( url
.path().length() <= 1 ) {
342 QString relativePath
;
343 bool ok
= TrashImpl::parseURL( url
, trashId
, fileId
, relativePath
);
345 error( KIO::ERR_SLAVE_DEFINED
, i18n( "Malformed URL %1", url
.prettyUrl() ) );
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() );
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() );
368 for ( QStringList::const_iterator entryIt
= entryNames
.begin(), entryEnd
= entryNames
.end();
369 entryIt
!= entryEnd
; ++entryIt
)
371 const QString fileName
= *entryIt
;
372 if ( fileName
== ".." )
374 const QString filePath
= info
.physicalPath
+ '/' + fileName
;
375 // shouldn't be necessary
376 //const QString url = TrashImpl::makeURL( trashId, fileId, relativePath + '/' + fileName );
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 );
386 listEntry( entry
, true );
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
;
398 if (S_ISLNK(buff
.st_mode
)) {
399 char buffer2
[ 1000 ];
400 int n
= readlink( physicalPath_c
, buffer2
, 1000 );
405 entry
.insert( KIO::UDSEntry::UDS_LINK_DEST
, QFile::decodeName( buffer2
) );
407 // That makes sense in kio_file, but not in the trash, especially for the size
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
;
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
);
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
) );
441 void TrashProtocol::listRoot()
444 const TrashedFileInfoList lst
= impl
.list();
445 totalSize( lst
.count() );
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() );
452 origURL
.setPath( (*it
).origPath
);
454 if ( createUDSEntry( (*it
).physicalPath
, origURL
.fileName(), url
.url(), entry
, *it
) )
455 listEntry( entry
, false );
458 listEntry( entry
, true );
462 void TrashProtocol::special( const QByteArray
& data
)
465 QDataStream
stream( data
);
471 if ( impl
.emptyTrash() )
474 error( impl
.lastErrorCode(), impl
.lastErrorMessage() );
477 impl
.migrateOldTrash();
488 kWarning(7116) << "Unknown command in special(): " << cmd
;
489 error( KIO::ERR_UNSUPPORTED_ACTION
, QString::number(cmd
) );
494 void TrashProtocol::put( const KUrl
& url
, int /*permissions*/, KIO::JobFlags
)
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
)
506 kDebug() << "get() : " << url
;
507 if ( !url
.isValid() ) {
508 kDebug() << kBacktrace();
509 error( KIO::ERR_SLAVE_DEFINED
, i18n( "Malformed URL %1", url
.url() ) );
512 if ( url
.path().length() <= 1 ) {
513 error( KIO::ERR_IS_DIRECTORY
, url
.prettyUrl() );
518 QString relativePath
;
519 bool ok
= TrashImpl::parseURL( url
, trashId
, fileId
, relativePath
);
521 error( KIO::ERR_SLAVE_DEFINED
, i18n( "Malformed URL %1", url
.prettyUrl() ) );
524 const QString physicalPath
= impl
.physicalPath( trashId
, fileId
, relativePath
);
525 if ( physicalPath
.isEmpty() ) {
526 error( impl
.lastErrorCode(), impl
.lastErrorMessage() );
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...
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
*) ) );
544 void TrashProtocol::slotData( KIO::Job
*, const QByteArray
&arr
)
549 void TrashProtocol::slotMimetype( KIO::Job
*, const QString
& mt
)
554 void TrashProtocol::jobFinished( KJob
* job
)
557 error( job
->error(), job
->errorText() );
560 emit
leaveModality();
564 void TrashProtocol::mkdir( const KUrl
& url
, int /*permissions*/ )
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
578 if ( !impl
.createInfo( url
.path(), trashId
, fileId
) ) {
579 error( impl
.lastErrorCode(), impl
.lastErrorMessage() );
581 if ( !impl
.mkdir( trashId
, fileId
, permissions
) ) {
582 (void)impl
.deleteInfo( trashId
, fileId
);
583 error( impl
.lastErrorCode(), impl
.lastErrorMessage() );
588 // Well it's not allowed to add a directory to an existing deleted directory.
589 error( KIO::ERR_ACCESS_DENIED
, url
.prettyUrl() );
594 #include "kio_trash.moc"