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 <qtest_kde.h>
22 #include "kio_trash.h"
23 #include "testtrash.h"
24 #include <ktemporaryfile.h>
28 #include <kapplication.h>
29 #include <kio/netaccess.h>
31 #include <kio/copyjob.h>
32 #include <kio/deletejob.h>
34 #include <kcmdlineargs.h>
35 #include <kconfiggroup.h>
40 #include <kjobuidelegate.h>
45 #include <kfileitem.h>
46 #include <kstandarddirs.h>
47 #include <kio/chmodjob.h>
48 #include <kio/directorysizejob.h>
50 // There are two ways to test encoding things:
51 // * with utf8 filenames
52 // * with latin1 filenames -- not sure this still works.
57 QString
TestTrash::homeTmpDir() const
59 return QDir::homePath() + "/.kde/testtrash/";
62 QString
TestTrash::readOnlyDirPath() const
64 return homeTmpDir() + QString( "readonly" );
67 QString
TestTrash::otherTmpDir() const
69 // This one needs to be on another partition
70 return "/tmp/testtrash/";
73 QString
TestTrash::utf8FileName() const
75 return QString( "test" ) + QChar( 0x2153 ); // "1/3" character, not part of latin1
78 QString
TestTrash::umlautFileName() const
80 return QString( "umlaut" ) + QChar( 0xEB );
83 static void removeFile( const QString
& trashDir
, const QString
& fileName
)
86 dir
.remove( trashDir
+ fileName
);
87 QVERIFY( !QDir( trashDir
+ fileName
).exists() );
90 static void removeDir( const QString
& trashDir
, const QString
& dirName
)
93 dir
.rmdir( trashDir
+ dirName
);
94 QVERIFY( !QDir( trashDir
+ dirName
).exists() );
97 static void removeDirRecursive( const QString
& dir
)
99 if ( QFile::exists( dir
) ) {
101 // Make it work even with readonly dirs, like trashReadOnlyDirFromHome() creates
102 KUrl u
= KUrl::fromPath( dir
);
103 KFileItem
fileItem( u
, "inode/directory", KFileItem::Unknown
);
104 KFileItemList fileItemList
;
105 fileItemList
.append( fileItem
);
106 KIO::ChmodJob
* chmodJob
= KIO::chmod( fileItemList
, 0200, 0200, QString(), QString(), true /*recursive*/, KIO::HideProgressInfo
);
107 KIO::NetAccess::synchronousRun( chmodJob
, 0 );
109 KIO::Job
* delJob
= KIO::del(u
, KIO::HideProgressInfo
);
110 if (!KIO::NetAccess::synchronousRun(delJob
, 0))
111 kFatal() << "Couldn't delete " << dir
;
115 void TestTrash::initTestCase()
118 // Assume utf8 system
119 setenv( "LC_ALL", "en_GB.utf-8", 1 );
120 setenv( "KDE_UTF8_FILENAMES", "true", 1 );
122 // Ensure a known QFile::encodeName behavior for trashUtf8FileFromHome
123 // However this assume your $HOME doesn't use characters from other locales...
124 setenv( "LC_ALL", "en_GB.ISO-8859-1", 1 );
125 unsetenv( "KDE_UTF8_FILENAMES" );
128 setenv( "KDE_FORK_SLAVES", "yes", true );
131 m_trashDir
= KGlobal::dirs()->localxdgdatadir() + "Trash";
132 kDebug() << "setup: using trash directory " << m_trashDir
;
134 // Look for another writable partition than $HOME (not mandatory)
138 TrashImpl::TrashDirMap trashDirs
= impl
.trashDirectories();
139 TrashImpl::TrashDirMap topDirs
= impl
.topDirectories();
140 bool foundTrashDir
= false;
141 m_otherPartitionId
= 0;
142 m_tmpIsWritablePartition
= false;
144 QVector
<int> writableTopDirs
;
145 for ( TrashImpl::TrashDirMap::ConstIterator it
= trashDirs
.begin(); it
!= trashDirs
.end() ; ++it
) {
146 if ( it
.key() == 0 ) {
147 QVERIFY( it
.value() == m_trashDir
);
148 QVERIFY( topDirs
.find( 0 ) == topDirs
.end() );
149 foundTrashDir
= true;
151 QVERIFY( topDirs
.find( it
.key() ) != topDirs
.end() );
152 const QString topdir
= topDirs
[it
.key()];
153 if ( QFileInfo( topdir
).isWritable() ) {
154 writableTopDirs
.append( it
.key() );
155 if ( topdir
== "/tmp/" ) {
156 m_tmpIsWritablePartition
= true;
157 m_tmpTrashId
= it
.key();
158 kDebug() << "/tmp is on its own partition (trashid=" << m_tmpTrashId
<< "), some tests will be skipped";
159 removeFile( it
.value(), "/info/fileFromOther.trashinfo" );
160 removeFile( it
.value(), "/files/fileFromOther" );
161 removeFile( it
.value(), "/info/symlinkFromOther.trashinfo" );
162 removeFile( it
.value(), "/files/symlinkFromOther" );
163 removeFile( it
.value(), "/info/trashDirFromOther.trashinfo" );
164 removeFile( it
.value(), "/files/trashDirFromOther/testfile" );
165 removeDir( it
.value(), "/files/trashDirFromOther" );
170 for ( QVector
<int>::const_iterator it
= writableTopDirs
.begin(); it
!= writableTopDirs
.end(); ++it
) {
171 const QString topdir
= topDirs
[ *it
];
172 const QString trashdir
= trashDirs
[ *it
];
173 QVERIFY( !topdir
.isEmpty() );
174 QVERIFY( !trashDirs
.isEmpty() );
175 if ( topdir
!= "/tmp/" || // we'd prefer not to use /tmp here, to separate the tests
176 ( writableTopDirs
.count() > 1 ) ) // but well, if we have no choice, take it
178 m_otherPartitionTopDir
= topdir
;
179 m_otherPartitionTrashDir
= trashdir
;
180 m_otherPartitionId
= *it
;
181 kDebug() << "OK, found another writable partition: topDir=" << m_otherPartitionTopDir
182 << " trashDir=" << m_otherPartitionTrashDir
<< " id=" << m_otherPartitionId
<< endl
;
186 // Check that m_trashDir got listed
187 QVERIFY( foundTrashDir
);
188 if ( m_otherPartitionTrashDir
.isEmpty() )
189 kWarning() << "No writable partition other than $HOME found, some tests will be skipped" ;
191 // Start with a clean base dir
192 removeDirRecursive( homeTmpDir() );
193 removeDirRecursive( otherTmpDir() );
195 QDir dir
; // TT: why not a static method?
196 bool ok
= dir
.mkdir( homeTmpDir() );
198 kFatal() << "Couldn't create " << homeTmpDir() ;
199 ok
= dir
.mkdir( otherTmpDir() );
201 kFatal() << "Couldn't create " << otherTmpDir() ;
203 // Start with a clean trash too
204 removeDirRecursive( m_trashDir
);
207 void TestTrash::urlTestFile()
209 const KUrl url
= TrashImpl::makeURL( 1, "fileId", QString() );
210 QCOMPARE( url
.url(), QString( "trash:/1-fileId" ) );
214 QString relativePath
;
215 bool ok
= TrashImpl::parseURL( url
, trashId
, fileId
, relativePath
);
217 QCOMPARE( QString::number( trashId
), QString::fromLatin1( "1" ) );
218 QCOMPARE( fileId
, QString::fromLatin1( "fileId" ) );
219 QCOMPARE( relativePath
, QString() );
222 void TestTrash::urlTestDirectory()
224 const KUrl url
= TrashImpl::makeURL( 1, "fileId", "subfile" );
225 QCOMPARE( url
.url(), QString::fromLatin1( "trash:/1-fileId/subfile" ) );
229 QString relativePath
;
230 bool ok
= TrashImpl::parseURL( url
, trashId
, fileId
, relativePath
);
232 QCOMPARE( trashId
, 1 );
233 QCOMPARE( fileId
, QString::fromLatin1( "fileId" ) );
234 QCOMPARE( relativePath
, QString::fromLatin1( "subfile" ) );
237 void TestTrash::urlTestSubDirectory()
239 const KUrl url
= TrashImpl::makeURL( 1, "fileId", "subfile/foobar" );
240 QCOMPARE( url
.url(), QString::fromLatin1( "trash:/1-fileId/subfile/foobar" ) );
244 QString relativePath
;
245 bool ok
= TrashImpl::parseURL( url
, trashId
, fileId
, relativePath
);
247 QCOMPARE( trashId
, 1 );
248 QCOMPARE( fileId
, QString::fromLatin1( "fileId" ) );
249 QCOMPARE( relativePath
, QString::fromLatin1( "subfile/foobar" ) );
252 static void checkInfoFile( const QString
& infoPath
, const QString
& origFilePath
)
254 kDebug() << infoPath
;
255 QFileInfo
info( infoPath
);
256 QVERIFY( info
.exists() );
257 QVERIFY( info
.isFile() );
258 KConfig
infoFile( info
.absoluteFilePath() );
259 KConfigGroup group
= infoFile
.group( "Trash Info" );
260 if ( !group
.exists() )
261 kFatal() << "no Trash Info group in " << info
.absoluteFilePath() ;
262 const QString origPath
= group
.readEntry( "Path" );
263 QVERIFY( !origPath
.isEmpty() );
264 QVERIFY( origPath
== QUrl::toPercentEncoding( origFilePath
, "/" ) );
265 if (origFilePath
.contains(QChar(0x2153)) || origFilePath
.contains('%') || origFilePath
.contains("umlaut")) {
266 QVERIFY(origPath
.contains('%'));
268 QVERIFY(!origPath
.contains('%'));
270 const QString date
= group
.readEntry( "DeletionDate" );
271 QVERIFY( !date
.isEmpty() );
272 QVERIFY( date
.contains( "T" ) );
275 static void createTestFile( const QString
& path
)
278 if ( !f
.open( QIODevice::WriteOnly
) )
279 kFatal() << "Can't create " << path
;
280 f
.write( "Hello world\n", 12 );
282 QVERIFY( QFile::exists( path
) );
285 void TestTrash::trashFile( const QString
& origFilePath
, const QString
& fileId
)
288 if ( !QFile::exists( origFilePath
) )
289 createTestFile( origFilePath
);
291 u
.setPath( origFilePath
);
294 KIO::Job
* job
= KIO::move( u
, KUrl("trash:/"), KIO::HideProgressInfo
);
295 QMap
<QString
, QString
> metaData
;
296 bool ok
= KIO::NetAccess::synchronousRun( job
, 0, 0, 0, &metaData
);
298 kError() << "moving " << u
<< " to trash failed with error " << KIO::NetAccess::lastError() << " " << KIO::NetAccess::lastErrorString() << endl
;
300 if ( origFilePath
.startsWith( "/tmp" ) && m_tmpIsWritablePartition
) {
301 kDebug() << " TESTS SKIPPED";
303 checkInfoFile( m_trashDir
+ "/info/" + fileId
+ ".trashinfo", origFilePath
);
305 QFileInfo
files( m_trashDir
+ "/files/" + fileId
);
306 QVERIFY( files
.isFile() );
307 QVERIFY( files
.size() == 12 );
310 // coolo suggests testing that the original file is actually gone, too :)
311 QVERIFY( !QFile::exists( origFilePath
) );
313 QVERIFY( !metaData
.isEmpty() );
315 QMap
<QString
, QString
>::ConstIterator it
= metaData
.begin();
316 for ( ; it
!= metaData
.end() ; ++it
) {
317 if ( it
.key().startsWith( "trashURL" ) ) {
318 const QString origPath
= it
.key().mid( 9 );
319 KUrl
trashURL( it
.value() );
320 kDebug() << trashURL
;
321 QVERIFY( !trashURL
.isEmpty() );
322 QVERIFY( trashURL
.protocol() == "trash" );
324 if ( origFilePath
.startsWith( "/tmp" ) && m_tmpIsWritablePartition
)
325 trashId
= m_tmpTrashId
;
326 QVERIFY( trashURL
.path() == "/" + QString::number( trashId
) + '-' + fileId
);
333 void TestTrash::trashFileFromHome()
335 const QString fileName
= "fileFromHome";
336 trashFile( homeTmpDir() + fileName
, fileName
);
338 // Do it again, check that we got a different id
339 trashFile( homeTmpDir() + fileName
, fileName
+ "_1" );
342 void TestTrash::trashPercentFileFromHome()
344 const QString fileName
= "file%2f";
345 trashFile( homeTmpDir() + fileName
, fileName
);
348 void TestTrash::trashUtf8FileFromHome()
351 const QString fileName
= utf8FileName();
352 trashFile( homeTmpDir() + fileName
, fileName
);
356 void TestTrash::trashUmlautFileFromHome()
358 const QString fileName
= umlautFileName();
359 trashFile( homeTmpDir() + fileName
, fileName
);
362 void TestTrash::testTrashNotEmpty()
364 KConfig
cfg( "trashrc", KConfig::SimpleConfig
);
365 const KConfigGroup group
= cfg
.group( "Status" );
366 QVERIFY( group
.exists() );
367 QVERIFY( group
.readEntry( "Empty", true ) == false );
370 void TestTrash::trashFileFromOther()
372 const QString fileName
= "fileFromOther";
373 trashFile( otherTmpDir() + fileName
, fileName
);
376 void TestTrash::trashFileIntoOtherPartition()
378 if ( m_otherPartitionTrashDir
.isEmpty() ) {
379 kDebug() << " - SKIPPED";
382 const QString fileName
= "testtrash-file";
383 const QString origFilePath
= m_otherPartitionTopDir
+ fileName
;
384 const QString fileId
= fileName
;
386 QFile::remove( m_otherPartitionTrashDir
+ "/info/" + fileId
+ ".trashinfo" );
387 QFile::remove( m_otherPartitionTrashDir
+ "/files/" + fileId
);
390 if ( !QFile::exists( origFilePath
) )
391 createTestFile( origFilePath
);
393 u
.setPath( origFilePath
);
396 KIO::Job
* job
= KIO::move( u
, KUrl("trash:/"), KIO::HideProgressInfo
);
397 QMap
<QString
, QString
> metaData
;
398 bool ok
= KIO::NetAccess::synchronousRun( job
, 0, 0, 0, &metaData
);
400 // Note that the Path stored in the info file is relative, on other partitions (#95652)
401 checkInfoFile( m_otherPartitionTrashDir
+ "/info/" + fileId
+ ".trashinfo", fileName
);
403 QFileInfo
files( m_otherPartitionTrashDir
+ "/files/" + fileId
);
404 QVERIFY( files
.isFile() );
405 QVERIFY( files
.size() == 12 );
407 // coolo suggests testing that the original file is actually gone, too :)
408 QVERIFY( !QFile::exists( origFilePath
) );
410 QVERIFY( !metaData
.isEmpty() );
412 QMap
<QString
, QString
>::ConstIterator it
= metaData
.begin();
413 for ( ; it
!= metaData
.end() ; ++it
) {
414 if ( it
.key().startsWith( "trashURL" ) ) {
415 const QString origPath
= it
.key().mid( 9 );
416 KUrl
trashURL( it
.value() );
417 kDebug() << trashURL
;
418 QVERIFY( !trashURL
.isEmpty() );
419 QVERIFY( trashURL
.protocol() == "trash" );
420 QVERIFY( trashURL
.path() == QString( "/%1-%2" ).arg( m_otherPartitionId
).arg( fileId
) );
427 void TestTrash::trashFileOwnedByRoot()
429 KUrl
u( "/etc/passwd" );
430 const QString fileId
= "passwd";
432 KIO::CopyJob
* job
= KIO::move( u
, KUrl("trash:/"), KIO::HideProgressInfo
);
433 job
->setUiDelegate(0); // no skip dialog, thanks
434 QMap
<QString
, QString
> metaData
;
435 bool ok
= KIO::NetAccess::synchronousRun( job
, 0, 0, 0, &metaData
);
437 QVERIFY( KIO::NetAccess::lastError() == KIO::ERR_ACCESS_DENIED
);
438 const QString
infoPath( m_trashDir
+ "/info/" + fileId
+ ".trashinfo" );
439 QVERIFY( !QFile::exists( infoPath
) );
441 QFileInfo
files( m_trashDir
+ "/files/" + fileId
);
442 QVERIFY( !files
.exists() );
444 QVERIFY( QFile::exists( u
.path() ) );
447 void TestTrash::trashSymlink( const QString
& origFilePath
, const QString
& fileId
, bool broken
)
450 const char* target
= broken
? "/nonexistent" : "/tmp";
451 bool ok
= ::symlink( target
, QFile::encodeName( origFilePath
) ) == 0;
454 u
.setPath( origFilePath
);
457 KIO::Job
* job
= KIO::move( u
, KUrl("trash:/"), KIO::HideProgressInfo
);
460 if ( origFilePath
.startsWith( "/tmp" ) && m_tmpIsWritablePartition
) {
461 kDebug() << " TESTS SKIPPED";
464 checkInfoFile( m_trashDir
+ "/info/" + fileId
+ ".trashinfo", origFilePath
);
466 QFileInfo
files( m_trashDir
+ "/files/" + fileId
);
467 QVERIFY( files
.isSymLink() );
468 QVERIFY( files
.readLink() == QFile::decodeName( target
) );
469 QVERIFY( !QFile::exists( origFilePath
) );
472 void TestTrash::trashSymlinkFromHome()
474 const QString fileName
= "symlinkFromHome";
475 trashSymlink( homeTmpDir() + fileName
, fileName
, false );
478 void TestTrash::trashSymlinkFromOther()
480 const QString fileName
= "symlinkFromOther";
481 trashSymlink( otherTmpDir() + fileName
, fileName
, false );
484 void TestTrash::trashBrokenSymlinkFromHome()
486 const QString fileName
= "brokenSymlinkFromHome";
487 trashSymlink( homeTmpDir() + fileName
, fileName
, true );
490 void TestTrash::trashDirectory( const QString
& origPath
, const QString
& fileId
)
494 if ( !QFileInfo( origPath
).exists() ) {
496 bool ok
= dir
.mkdir( origPath
);
499 createTestFile( origPath
+ "/testfile" );
500 KUrl u
; u
.setPath( origPath
);
503 KIO::Job
* job
= KIO::move( u
, KUrl("trash:/"), KIO::HideProgressInfo
);
504 QVERIFY( job
->exec() );
505 if ( origPath
.startsWith( "/tmp" ) && m_tmpIsWritablePartition
) {
506 kDebug() << " TESTS SKIPPED";
509 checkInfoFile( m_trashDir
+ "/info/" + fileId
+ ".trashinfo", origPath
);
511 QFileInfo
filesDir( m_trashDir
+ "/files/" + fileId
);
512 QVERIFY( filesDir
.isDir() );
513 QFileInfo
files( m_trashDir
+ "/files/" + fileId
+ "/testfile" );
514 QVERIFY( files
.exists() );
515 QVERIFY( files
.isFile() );
516 QVERIFY( files
.size() == 12 );
517 QVERIFY( !QFile::exists( origPath
) );
520 void TestTrash::trashDirectoryFromHome()
522 QString dirName
= "trashDirFromHome";
523 trashDirectory( homeTmpDir() + dirName
, dirName
);
524 // Do it again, check that we got a different id
525 trashDirectory( homeTmpDir() + dirName
, dirName
+ "_1" );
528 void TestTrash::trashReadOnlyDirFromHome()
530 const QString dirName
= readOnlyDirPath();
532 bool ok
= dir
.mkdir( dirName
);
535 const QString subDirPath
= dirName
+ "/readonly_subdir";
536 ok
= dir
.mkdir( subDirPath
);
538 createTestFile( subDirPath
+ "/testfile_in_subdir" );
539 ::chmod( QFile::encodeName( subDirPath
), 0500 );
541 trashDirectory( dirName
, "readonly" );
544 void TestTrash::trashDirectoryFromOther()
546 QString dirName
= "trashDirFromOther";
547 trashDirectory( otherTmpDir() + dirName
, dirName
);
550 void TestTrash::tryRenameInsideTrash()
552 kDebug() << " with file_move";
553 KIO::Job
* job
= KIO::file_move( KUrl("trash:/0-tryRenameInsideTrash"), KUrl("trash:/foobar"), -1, KIO::HideProgressInfo
);
554 bool worked
= KIO::NetAccess::synchronousRun( job
, 0 );
556 QVERIFY( KIO::NetAccess::lastError() == KIO::ERR_CANNOT_RENAME
);
558 kDebug() << " with move";
559 job
= KIO::move( KUrl("trash:/0-tryRenameInsideTrash"), KUrl("trash:/foobar"), KIO::HideProgressInfo
);
560 worked
= KIO::NetAccess::synchronousRun( job
, 0 );
562 QVERIFY( KIO::NetAccess::lastError() == KIO::ERR_CANNOT_RENAME
);
565 void TestTrash::delRootFile()
567 // test deleting a trashed file
568 KIO::Job
* delJob
= KIO::del(KUrl("trash:/0-fileFromHome"), KIO::HideProgressInfo
);
569 bool ok
= KIO::NetAccess::synchronousRun(delJob
, 0);
572 QFileInfo
file( m_trashDir
+ "/files/fileFromHome" );
573 QVERIFY( !file
.exists() );
574 QFileInfo
info( m_trashDir
+ "/info/fileFromHome.trashinfo" );
575 QVERIFY( !info
.exists() );
577 // trash it again, we might need it later
578 const QString fileName
= "fileFromHome";
579 trashFile( homeTmpDir() + fileName
, fileName
);
582 void TestTrash::delFileInDirectory()
584 // test deleting a file inside a trashed directory -> not allowed
585 KIO::Job
* delJob
= KIO::del(KUrl("trash:/0-trashDirFromHome/testfile"), KIO::HideProgressInfo
);
586 bool ok
= KIO::NetAccess::synchronousRun(delJob
, 0);
588 QVERIFY( KIO::NetAccess::lastError() == KIO::ERR_ACCESS_DENIED
);
590 QFileInfo
dir( m_trashDir
+ "/files/trashDirFromHome" );
591 QVERIFY( dir
.exists() );
592 QFileInfo
file( m_trashDir
+ "/files/trashDirFromHome/testfile" );
593 QVERIFY( file
.exists() );
594 QFileInfo
info( m_trashDir
+ "/info/trashDirFromHome.trashinfo" );
595 QVERIFY( info
.exists() );
598 void TestTrash::delDirectory()
600 // test deleting a trashed directory
601 KIO::Job
* delJob
= KIO::del(KUrl("trash:/0-trashDirFromHome"), KIO::HideProgressInfo
);
602 bool ok
= KIO::NetAccess::synchronousRun(delJob
, 0);
605 QFileInfo
dir( m_trashDir
+ "/files/trashDirFromHome" );
606 QVERIFY( !dir
.exists() );
607 QFileInfo
file( m_trashDir
+ "/files/trashDirFromHome/testfile" );
608 QVERIFY( !file
.exists() );
609 QFileInfo
info( m_trashDir
+ "/info/trashDirFromHome.trashinfo" );
610 QVERIFY( !info
.exists() );
612 // trash it again, we'll need it later
613 QString dirName
= "trashDirFromHome";
614 trashDirectory( homeTmpDir() + dirName
, dirName
);
617 // KIO::NetAccess::stat() doesn't set HideProgressInfo - but it's not much work to do it ourselves:
618 static bool MyNetAccess_stat(const KUrl
& url
, KIO::UDSEntry
& entry
)
620 KIO::StatJob
* statJob
= KIO::stat( url
, KIO::HideProgressInfo
);
621 bool ok
= KIO::NetAccess::synchronousRun(statJob
, 0);
623 entry
= statJob
->statResult();
626 static bool MyNetAccess_exists(const KUrl
& url
)
629 return MyNetAccess_stat(url
, dummy
);
632 void TestTrash::statRoot()
634 KUrl
url( "trash:/" );
636 bool ok
= MyNetAccess_stat( url
, entry
);
638 KFileItem
item( entry
, url
);
639 QVERIFY( item
.isDir() );
640 QVERIFY( !item
.isLink() );
641 QVERIFY( item
.isReadable() );
642 QVERIFY( item
.isWritable() );
643 QVERIFY( !item
.isHidden() );
644 QVERIFY( item
.name() == "." );
647 void TestTrash::statFileInRoot()
649 KUrl
url( "trash:/0-fileFromHome" );
651 bool ok
= MyNetAccess_stat( url
, entry
);
653 KFileItem
item( entry
, url
);
654 QVERIFY( item
.isFile() );
655 QVERIFY( !item
.isDir() );
656 QVERIFY( !item
.isLink() );
657 QVERIFY( item
.isReadable() );
658 QVERIFY( !item
.isWritable() );
659 QVERIFY( !item
.isHidden() );
660 QVERIFY( item
.text() == "fileFromHome" );
663 void TestTrash::statDirectoryInRoot()
665 KUrl
url( "trash:/0-trashDirFromHome" );
667 bool ok
= MyNetAccess_stat( url
, entry
);
669 KFileItem
item( entry
, url
);
670 QVERIFY( item
.isDir() );
671 QVERIFY( !item
.isLink() );
672 QVERIFY( item
.isReadable() );
673 QVERIFY( !item
.isWritable() );
674 QVERIFY( !item
.isHidden() );
675 QVERIFY( item
.text() == "trashDirFromHome" );
678 void TestTrash::statSymlinkInRoot()
680 KUrl
url( "trash:/0-symlinkFromHome" );
682 bool ok
= MyNetAccess_stat( url
, entry
);
684 KFileItem
item( entry
, url
);
685 QVERIFY( item
.isLink() );
686 QVERIFY( item
.linkDest() == "/tmp" );
687 QVERIFY( item
.isReadable() );
688 QVERIFY( !item
.isWritable() );
689 QVERIFY( !item
.isHidden() );
690 QVERIFY( item
.text() == "symlinkFromHome" );
693 void TestTrash::statFileInDirectory()
695 KUrl
url( "trash:/0-trashDirFromHome/testfile" );
697 bool ok
= MyNetAccess_stat( url
, entry
);
699 KFileItem
item( entry
, url
);
700 QVERIFY( item
.isFile() );
701 QVERIFY( !item
.isLink() );
702 QVERIFY( item
.isReadable() );
703 QVERIFY( !item
.isWritable() );
704 QVERIFY( !item
.isHidden() );
705 QVERIFY( item
.text() == "testfile" );
708 void TestTrash::copyFromTrash( const QString
& fileId
, const QString
& destPath
, const QString
& relativePath
)
710 KUrl
src( "trash:/0-" + fileId
);
711 if ( !relativePath
.isEmpty() )
712 src
.addPath( relativePath
);
714 dest
.setPath( destPath
);
716 QVERIFY(MyNetAccess_exists(src
));
718 // A dnd would use copy(), but we use copyAs to ensure the final filename
719 //kDebug() << "copyAs:" << src << " -> " << dest;
720 KIO::Job
* job
= KIO::copyAs( src
, dest
, KIO::HideProgressInfo
);
721 bool ok
= KIO::NetAccess::synchronousRun( job
, 0 );
723 QString
infoFile( m_trashDir
+ "/info/" + fileId
+ ".trashinfo" );
724 QVERIFY( QFile::exists(infoFile
) );
726 QFileInfo
filesItem( m_trashDir
+ "/files/" + fileId
);
727 QVERIFY( filesItem
.exists() );
729 QVERIFY( QFile::exists(destPath
) );
732 void TestTrash::copyFileFromTrash()
734 // To test case of already-existing destination, uncomment this.
735 // This brings up the "rename" dialog though, so it can't be fully automated
737 const QString destPath
= otherTmpDir() + "fileFromHome_copied";
738 copyFromTrash( "fileFromHome", destPath
);
739 QVERIFY( QFileInfo( destPath
).isFile() );
740 QVERIFY( QFileInfo( destPath
).size() == 12 );
744 void TestTrash::copyFileInDirectoryFromTrash()
746 const QString destPath
= otherTmpDir() + "testfile_copied";
747 copyFromTrash( "trashDirFromHome", destPath
, "testfile" );
748 QVERIFY( QFileInfo( destPath
).isFile() );
749 QVERIFY( QFileInfo( destPath
).size() == 12 );
752 void TestTrash::copyDirectoryFromTrash()
754 const QString destPath
= otherTmpDir() + "trashDirFromHome_copied";
755 copyFromTrash( "trashDirFromHome", destPath
);
756 QVERIFY( QFileInfo( destPath
).isDir() );
759 void TestTrash::copySymlinkFromTrash()
761 const QString destPath
= otherTmpDir() + "symlinkFromHome_copied";
762 copyFromTrash( "symlinkFromHome", destPath
);
763 QVERIFY( QFileInfo( destPath
).isSymLink() );
766 void TestTrash::moveFromTrash( const QString
& fileId
, const QString
& destPath
, const QString
& relativePath
)
768 KUrl
src( "trash:/0-" + fileId
);
769 if ( !relativePath
.isEmpty() )
770 src
.addPath( relativePath
);
772 dest
.setPath( destPath
);
774 QVERIFY(MyNetAccess_exists(src
));
776 // A dnd would use move(), but we use moveAs to ensure the final filename
777 KIO::Job
* job
= KIO::moveAs( src
, dest
, KIO::HideProgressInfo
);
778 bool ok
= KIO::NetAccess::synchronousRun( job
, 0 );
780 QString
infoFile( m_trashDir
+ "/info/" + fileId
+ ".trashinfo" );
781 QVERIFY( !QFile::exists( infoFile
) );
783 QFileInfo
filesItem( m_trashDir
+ "/files/" + fileId
);
784 QVERIFY( !filesItem
.exists() );
786 QVERIFY( QFile::exists( destPath
) );
789 void TestTrash::moveFileFromTrash()
791 const QString destPath
= otherTmpDir() + "fileFromHome_restored";
792 moveFromTrash( "fileFromHome", destPath
);
793 QVERIFY( QFileInfo( destPath
).isFile() );
794 QVERIFY( QFileInfo( destPath
).size() == 12 );
796 // trash it again for later
797 const QString fileName
= "fileFromHome";
798 trashFile( homeTmpDir() + fileName
, fileName
);
801 void TestTrash::moveFileInDirectoryFromTrash()
803 const QString destPath
= otherTmpDir() + "testfile_restored";
804 copyFromTrash( "trashDirFromHome", destPath
, "testfile" );
805 QVERIFY( QFileInfo( destPath
).isFile() );
806 QVERIFY( QFileInfo( destPath
).size() == 12 );
809 void TestTrash::moveDirectoryFromTrash()
811 const QString destPath
= otherTmpDir() + "trashDirFromHome_restored";
812 moveFromTrash( "trashDirFromHome", destPath
);
813 QVERIFY( QFileInfo( destPath
).isDir() );
815 // trash it again, we'll need it later
816 QString dirName
= "trashDirFromHome";
817 trashDirectory( homeTmpDir() + dirName
, dirName
);
820 void TestTrash::trashDirectoryOwnedByRoot()
823 if ( QFile::exists( "/etc/cups" ) )
824 u
.setPath( "/etc/cups" );
825 else if ( QFile::exists( "/boot" ) )
826 u
.setPath( "/boot" );
829 const QString fileId
= u
.path();
830 kDebug() << "fileId=" << fileId
;
832 KIO::CopyJob
* job
= KIO::move( u
, KUrl("trash:/"), KIO::HideProgressInfo
);
833 job
->setUiDelegate(0); // no skip dialog, thanks
834 QMap
<QString
, QString
> metaData
;
835 bool ok
= KIO::NetAccess::synchronousRun( job
, 0, 0, 0, &metaData
);
837 const int err
= KIO::NetAccess::lastError();
838 QVERIFY( err
== KIO::ERR_ACCESS_DENIED
839 || err
== KIO::ERR_CANNOT_OPEN_FOR_READING
);
841 const QString
infoPath( m_trashDir
+ "/info/" + fileId
+ ".trashinfo" );
842 QVERIFY( !QFile::exists( infoPath
) );
844 QFileInfo
files( m_trashDir
+ "/files/" + fileId
);
845 QVERIFY( !files
.exists() );
847 QVERIFY( QFile::exists( u
.path() ) );
850 void TestTrash::moveSymlinkFromTrash()
852 const QString destPath
= otherTmpDir() + "symlinkFromHome_restored";
853 moveFromTrash( "symlinkFromHome", destPath
);
854 QVERIFY( QFileInfo( destPath
).isSymLink() );
857 void TestTrash::getFile()
859 const QString fileId
= "fileFromHome_1";
860 const KUrl url
= TrashImpl::makeURL( 0, fileId
, QString() );
862 KTemporaryFile tmpFile
;
863 QVERIFY(tmpFile
.open());
864 const QString tmpFilePath
= tmpFile
.fileName();
866 KIO::Job
* getJob
= KIO::file_copy(url
, KUrl(tmpFilePath
), -1, KIO::Overwrite
| KIO::HideProgressInfo
);
867 bool ok
= KIO::NetAccess::synchronousRun(getJob
, 0);
869 kDebug() << getJob
->errorString();
872 // Don't use tmpFile.close()+tmpFile.open() here, the size would still be 0 in the QTemporaryFile object
873 // (due to the use of fstat on the old fd). Arguably a bug (I even have a testcase), but probably
874 // not fixable without breaking the security of QTemporaryFile...
875 QFile
reader(tmpFilePath
);
876 QVERIFY(reader
.open(QIODevice::ReadOnly
));
877 QByteArray str
= reader
.readAll();
878 QCOMPARE(str
, QByteArray("Hello world\n"));
881 void TestTrash::restoreFile()
883 const QString fileId
= "fileFromHome_1";
884 const KUrl url
= TrashImpl::makeURL( 0, fileId
, QString() );
885 const QString
infoFile( m_trashDir
+ "/info/" + fileId
+ ".trashinfo" );
886 const QString
filesItem( m_trashDir
+ "/files/" + fileId
);
888 QVERIFY( QFile::exists( infoFile
) );
889 QVERIFY( QFile::exists( filesItem
) );
891 QByteArray packedArgs
;
892 QDataStream
stream( &packedArgs
, QIODevice::WriteOnly
);
893 stream
<< (int)3 << url
;
894 KIO::Job
* job
= KIO::special( url
, packedArgs
, KIO::HideProgressInfo
);
895 bool ok
= KIO::NetAccess::synchronousRun( job
, 0 );
898 QVERIFY( !QFile::exists( infoFile
) );
899 QVERIFY( !QFile::exists( filesItem
) );
901 const QString destPath
= homeTmpDir() + "fileFromHome";
902 QVERIFY( QFile::exists( destPath
) );
905 void TestTrash::restoreFileFromSubDir()
907 const QString fileId
= "trashDirFromHome_1/testfile";
908 QVERIFY( !QFile::exists( homeTmpDir() + "trashDirFromHome_1" ) );
910 const KUrl url
= TrashImpl::makeURL( 0, fileId
, QString() );
911 const QString
infoFile( m_trashDir
+ "/info/trashDirFromHome_1.trashinfo" );
912 const QString
filesItem( m_trashDir
+ "/files/trashDirFromHome_1/testfile" );
914 QVERIFY( QFile::exists( infoFile
) );
915 QVERIFY( QFile::exists( filesItem
) );
917 QByteArray packedArgs
;
918 QDataStream
stream( &packedArgs
, QIODevice::WriteOnly
);
919 stream
<< (int)3 << url
;
920 KIO::Job
* job
= KIO::special( url
, packedArgs
, KIO::HideProgressInfo
);
921 bool ok
= KIO::NetAccess::synchronousRun( job
, 0 );
923 // dest dir doesn't exist -> error message
924 QVERIFY( KIO::NetAccess::lastError() == KIO::ERR_SLAVE_DEFINED
);
926 // check that nothing happened
927 QVERIFY( QFile::exists( infoFile
) );
928 QVERIFY( QFile::exists( filesItem
) );
929 QVERIFY( !QFile::exists( homeTmpDir() + "trashDirFromHome_1" ) );
932 void TestTrash::restoreFileToDeletedDirectory()
934 // Ensure we'll get "fileFromHome" as fileId
935 removeFile( m_trashDir
, "/info/fileFromHome.trashinfo" );
936 removeFile( m_trashDir
, "/files/fileFromHome" );
939 KIO::Job
* delJob
= KIO::del(KUrl(homeTmpDir()), KIO::HideProgressInfo
);
940 bool delOK
= KIO::NetAccess::synchronousRun(delJob
, 0);
943 const QString fileId
= "fileFromHome";
944 const KUrl url
= TrashImpl::makeURL( 0, fileId
, QString() );
945 const QString
infoFile( m_trashDir
+ "/info/" + fileId
+ ".trashinfo" );
946 const QString
filesItem( m_trashDir
+ "/files/" + fileId
);
948 QVERIFY( QFile::exists( infoFile
) );
949 QVERIFY( QFile::exists( filesItem
) );
951 QByteArray packedArgs
;
952 QDataStream
stream( &packedArgs
, QIODevice::WriteOnly
);
953 stream
<< (int)3 << url
;
954 KIO::Job
* job
= KIO::special( url
, packedArgs
, KIO::HideProgressInfo
);
955 bool ok
= KIO::NetAccess::synchronousRun( job
, 0 );
957 // dest dir doesn't exist -> error message
958 QVERIFY( KIO::NetAccess::lastError() == KIO::ERR_SLAVE_DEFINED
);
960 // check that nothing happened
961 QVERIFY( QFile::exists( infoFile
) );
962 QVERIFY( QFile::exists( filesItem
) );
964 const QString destPath
= homeTmpDir() + "fileFromHome";
965 QVERIFY( !QFile::exists( destPath
) );
968 void TestTrash::listRootDir()
971 m_listResult
.clear();
972 KIO::ListJob
* job
= KIO::listDir( KUrl("trash:/"), KIO::HideProgressInfo
);
973 connect( job
, SIGNAL( entries( KIO::Job
*, const KIO::UDSEntryList
& ) ),
974 SLOT( slotEntries( KIO::Job
*, const KIO::UDSEntryList
& ) ) );
975 bool ok
= KIO::NetAccess::synchronousRun( job
, 0 );
977 kDebug() << "listDir done - m_entryCount=" << m_entryCount
;
978 QVERIFY( m_entryCount
> 1 );
980 kDebug() << m_listResult
;
981 QVERIFY( m_listResult
.contains( "." ) == 1 ); // found it, and only once
984 void TestTrash::listRecursiveRootDir()
987 m_listResult
.clear();
988 KIO::ListJob
* job
= KIO::listRecursive( KUrl("trash:/"), KIO::HideProgressInfo
);
989 connect( job
, SIGNAL( entries( KIO::Job
*, const KIO::UDSEntryList
& ) ),
990 SLOT( slotEntries( KIO::Job
*, const KIO::UDSEntryList
& ) ) );
991 bool ok
= KIO::NetAccess::synchronousRun( job
, 0 );
993 kDebug() << "listDir done - m_entryCount=" << m_entryCount
;
994 QVERIFY( m_entryCount
> 1 );
996 kDebug() << m_listResult
;
997 QVERIFY( m_listResult
.count( "." ) == 1 ); // found it, and only once
1000 void TestTrash::listSubDir()
1003 m_listResult
.clear();
1004 KIO::ListJob
* job
= KIO::listDir( KUrl("trash:/0-trashDirFromHome"), KIO::HideProgressInfo
);
1005 connect( job
, SIGNAL( entries( KIO::Job
*, const KIO::UDSEntryList
& ) ),
1006 SLOT( slotEntries( KIO::Job
*, const KIO::UDSEntryList
& ) ) );
1007 bool ok
= KIO::NetAccess::synchronousRun( job
, 0 );
1009 kDebug() << "listDir done - m_entryCount=" << m_entryCount
;
1010 QVERIFY( m_entryCount
== 2 );
1012 kDebug() << m_listResult
;
1013 QVERIFY( m_listResult
.count( "." ) == 1 ); // found it, and only once
1014 QVERIFY( m_listResult
.count( "testfile" ) == 1 ); // found it, and only once
1017 void TestTrash::slotEntries( KIO::Job
*, const KIO::UDSEntryList
& lst
)
1019 for( KIO::UDSEntryList::ConstIterator it
= lst
.begin(); it
!= lst
.end(); ++it
) {
1020 const KIO::UDSEntry
& entry (*it
);
1021 QString displayName
= entry
.stringValue( KIO::UDSEntry::UDS_NAME
);
1022 KUrl url
= entry
.stringValue( KIO::UDSEntry::UDS_URL
);
1023 kDebug() << displayName
<< " " << url
;
1024 if ( !url
.isEmpty() ) {
1025 QVERIFY( url
.protocol() == "trash" );
1027 m_listResult
<< displayName
;
1029 m_entryCount
+= lst
.count();
1032 void TestTrash::emptyTrash()
1034 // ## Even though we use a custom XDG_DATA_HOME value, emptying the
1035 // trash would still empty the other trash directories in other partitions.
1036 // So we can't activate this test by default.
1039 // To make this test standalone
1040 trashFileFromHome();
1042 // #167051: orphaned files
1043 createTestFile( m_trashDir
+ "/files/testfile_nometadata" );
1045 QByteArray packedArgs
;
1046 QDataStream
stream( &packedArgs
, QIODevice::WriteOnly
);
1048 KIO::Job
* job
= KIO::special( KUrl( "trash:/" ), packedArgs
, KIO::HideProgressInfo
);
1049 bool ok
= KIO::NetAccess::synchronousRun( job
, 0 );
1052 KConfig
cfg( "trashrc", KConfig::SimpleConfig
);
1053 QVERIFY( cfg
.hasGroup( "Status" ) );
1054 QVERIFY( cfg
.group("Status").readEntry( "Empty", false ) == true );
1056 QVERIFY( !QFile::exists( m_trashDir
+ "/files/fileFromHome" ) );
1057 QVERIFY( !QFile::exists( m_trashDir
+ "/files/readonly" ) );
1058 QVERIFY( !QFile::exists( m_trashDir
+ "/info/readonly.trashinfo" ) );
1059 QVERIFY(QDir(m_trashDir
+ "/info").entryList(QDir::NoDotAndDotDot
|QDir::AllEntries
).isEmpty());
1060 QVERIFY(QDir(m_trashDir
+ "/files").entryList(QDir::NoDotAndDotDot
|QDir::AllEntries
).isEmpty());
1063 kDebug() << " : SKIPPED";
1067 void TestTrash::testTrashSize()
1069 KIO::DirectorySizeJob
* job
= KIO::directorySize(KUrl("trash:/"));
1070 QVERIFY(job
->exec());
1071 QVERIFY(job
->totalSize() < 1000000000 /*1GB*/); // #157023
1074 static void checkIcon( const KUrl
& url
, const QString
& expectedIcon
)
1076 QString icon
= KMimeType::iconNameForUrl( url
);
1077 QCOMPARE( icon
, expectedIcon
);
1080 void TestTrash::testIcons()
1082 checkIcon( KUrl("trash:/"), "user-trash-full" ); // #100321
1083 checkIcon( KUrl("trash:/foo/"), "inode-directory" );
1086 QTEST_KDEMAIN(TestTrash
, NoGUI
)
1088 #include "testtrash.moc"