summaryrefslogtreecommitdiffstats
path: root/kioslave/trash/testtrash.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'kioslave/trash/testtrash.cpp')
-rw-r--r--kioslave/trash/testtrash.cpp1198
1 files changed, 1198 insertions, 0 deletions
diff --git a/kioslave/trash/testtrash.cpp b/kioslave/trash/testtrash.cpp
new file mode 100644
index 000000000..4557c5031
--- /dev/null
+++ b/kioslave/trash/testtrash.cpp
@@ -0,0 +1,1198 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 David Faure <[email protected]>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+// Get those asserts to work
+#undef NDEBUG
+#undef NO_DEBUG
+
+#include "kio_trash.h"
+#include "testtrash.h"
+
+#include <config.h>
+
+#include <kurl.h>
+#include <klocale.h>
+#include <kapplication.h>
+#include <kio/netaccess.h>
+#include <kio/job.h>
+#include <kdebug.h>
+#include <kcmdlineargs.h>
+
+#include <qdir.h>
+#include <qfileinfo.h>
+#include <qvaluevector.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <assert.h>
+#include <kfileitem.h>
+#include <kstandarddirs.h>
+
+static bool check(const QString& txt, QString a, QString b)
+{
+ if (a.isEmpty())
+ a = QString::null;
+ if (b.isEmpty())
+ b = QString::null;
+ if (a == b) {
+ kdDebug() << txt << " : checking '" << a << "' against expected value '" << b << "'... " << "ok" << endl;
+ }
+ else {
+ kdDebug() << txt << " : checking '" << a << "' against expected value '" << b << "'... " << "KO !" << endl;
+ exit(1);
+ }
+ return true;
+}
+
+// There are two ways to test encoding things:
+// * with utf8 filenames
+// * with latin1 filenames
+//
+//#define UTF8TEST 1
+
+int main(int argc, char *argv[])
+{
+ // Ensure a known QFile::encodeName behavior for trashUtf8FileFromHome
+ // However this assume your $HOME doesn't use characters from other locales...
+ setenv( "LC_ALL", "en_GB.ISO-8859-1", 1 );
+#ifdef UTF8TEST
+ setenv( "KDE_UTF8_FILENAMES", "true", 1 );
+#else
+ unsetenv( "KDE_UTF8_FILENAMES" );
+#endif
+
+ // Use another directory than the real one, just to keep things clean
+ setenv( "XDG_DATA_HOME", QFile::encodeName( QDir::homeDirPath() + "/.local-testtrash" ), true );
+ setenv( "KDE_FORK_SLAVES", "yes", true );
+
+ KApplication::disableAutoDcopRegistration();
+ KCmdLineArgs::init(argc,argv,"testtrash", 0, 0, 0, 0);
+ KApplication app;
+
+ TestTrash test;
+ test.setup();
+ test.runAll();
+ kdDebug() << "All tests OK." << endl;
+ return 0; // success. The exit(1) in check() is what happens in case of failure.
+}
+
+QString TestTrash::homeTmpDir() const
+{
+ return QDir::homeDirPath() + "/.kde/testtrash/";
+}
+
+QString TestTrash::readOnlyDirPath() const
+{
+ return homeTmpDir() + QString( "readonly" );
+}
+
+QString TestTrash::otherTmpDir() const
+{
+ // This one needs to be on another partition
+ return "/tmp/testtrash/";
+}
+
+QString TestTrash::utf8FileName() const
+{
+ return QString( "test" ) + QChar( 0x2153 ); // "1/3" character, not part of latin1
+}
+
+QString TestTrash::umlautFileName() const
+{
+ return QString( "umlaut" ) + QChar( 0xEB );
+}
+
+static void removeFile( const QString& trashDir, const QString& fileName )
+{
+ QDir dir;
+ dir.remove( trashDir + fileName );
+ assert( !QDir( trashDir + fileName ).exists() );
+}
+
+static void removeDir( const QString& trashDir, const QString& dirName )
+{
+ QDir dir;
+ dir.rmdir( trashDir + dirName );
+ assert( !QDir( trashDir + dirName ).exists() );
+}
+
+void TestTrash::setup()
+{
+ m_trashDir = KGlobal::dirs()->localxdgdatadir() + "Trash";
+ kdDebug() << "setup: using trash directory " << m_trashDir << endl;
+
+ // Look for another writable partition than $HOME (not mandatory)
+ TrashImpl impl;
+ impl.init();
+
+ TrashImpl::TrashDirMap trashDirs = impl.trashDirectories();
+ TrashImpl::TrashDirMap topDirs = impl.topDirectories();
+ bool foundTrashDir = false;
+ m_otherPartitionId = 0;
+ m_tmpIsWritablePartition = false;
+ m_tmpTrashId = -1;
+ QValueVector<int> writableTopDirs;
+ for ( TrashImpl::TrashDirMap::ConstIterator it = trashDirs.begin(); it != trashDirs.end() ; ++it ) {
+ if ( it.key() == 0 ) {
+ assert( it.data() == m_trashDir );
+ assert( topDirs.find( 0 ) == topDirs.end() );
+ foundTrashDir = true;
+ } else {
+ assert( topDirs.find( it.key() ) != topDirs.end() );
+ const QString topdir = topDirs[it.key()];
+ if ( QFileInfo( topdir ).isWritable() ) {
+ writableTopDirs.append( it.key() );
+ if ( topdir == "/tmp/" ) {
+ m_tmpIsWritablePartition = true;
+ m_tmpTrashId = it.key();
+ kdDebug() << "/tmp is on its own partition (trashid=" << m_tmpTrashId << "), some tests will be skipped" << endl;
+ removeFile( it.data(), "/info/fileFromOther.trashinfo" );
+ removeFile( it.data(), "/files/fileFromOther" );
+ removeFile( it.data(), "/info/symlinkFromOther.trashinfo" );
+ removeFile( it.data(), "/files/symlinkFromOther" );
+ removeFile( it.data(), "/info/trashDirFromOther.trashinfo" );
+ removeFile( it.data(), "/files/trashDirFromOther/testfile" );
+ removeDir( it.data(), "/files/trashDirFromOther" );
+ }
+ }
+ }
+ }
+ for ( QValueVector<int>::const_iterator it = writableTopDirs.begin(); it != writableTopDirs.end(); ++it ) {
+ const QString topdir = topDirs[ *it ];
+ const QString trashdir = trashDirs[ *it ];
+ assert( !topdir.isEmpty() );
+ assert( !trashDirs.isEmpty() );
+ if ( topdir != "/tmp/" || // we'd prefer not to use /tmp here, to separate the tests
+ ( writableTopDirs.count() > 1 ) ) // but well, if we have no choice, take it
+ {
+ m_otherPartitionTopDir = topdir;
+ m_otherPartitionTrashDir = trashdir;
+ m_otherPartitionId = *it;
+ kdDebug() << "OK, found another writable partition: topDir=" << m_otherPartitionTopDir
+ << " trashDir=" << m_otherPartitionTrashDir << " id=" << m_otherPartitionId << endl;
+ break;
+ }
+ }
+ // Check that m_trashDir got listed
+ assert( foundTrashDir );
+ if ( m_otherPartitionTrashDir.isEmpty() )
+ kdWarning() << "No writable partition other than $HOME found, some tests will be skipped" << endl;
+
+ // Start with a clean base dir
+ if ( QFileInfo( homeTmpDir() ).exists() ) {
+ bool ok = KIO::NetAccess::del( homeTmpDir(), 0 );
+ if ( !ok )
+ kdFatal() << "Couldn't delete " << homeTmpDir() << endl;
+ }
+ if ( QFileInfo( otherTmpDir() ).exists() ) {
+ bool ok = KIO::NetAccess::del( otherTmpDir(), 0 );
+ if ( !ok )
+ kdFatal() << "Couldn't delete " << otherTmpDir() << endl;
+ }
+ QDir dir; // TT: why not a static method?
+ bool ok = dir.mkdir( homeTmpDir() );
+ if ( !ok )
+ kdFatal() << "Couldn't create " << homeTmpDir() << endl;
+ ok = dir.mkdir( otherTmpDir() );
+ if ( !ok )
+ kdFatal() << "Couldn't create " << otherTmpDir() << endl;
+ cleanTrash();
+}
+
+
+void TestTrash::cleanTrash()
+{
+ kdDebug() << k_funcinfo << endl;
+ // Start with a relatively clean trash too
+ removeFile( m_trashDir, "/info/fileFromHome.trashinfo" );
+ removeFile( m_trashDir, "/files/fileFromHome" );
+ removeFile( m_trashDir, "/info/fileFromHome_1.trashinfo" );
+ removeFile( m_trashDir, "/files/fileFromHome_1" );
+ removeFile( m_trashDir, "/info/file%2f.trashinfo" );
+ removeFile( m_trashDir, "/files/file%2f" );
+ removeFile( m_trashDir, "/info/" + utf8FileName() + ".trashinfo" );
+ removeFile( m_trashDir, "/files/" + utf8FileName() );
+ removeFile( m_trashDir, "/info/" + umlautFileName() + ".trashinfo" );
+ removeFile( m_trashDir, "/files/" + umlautFileName() );
+ removeFile( m_trashDir, "/info/fileFromOther.trashinfo" );
+ removeFile( m_trashDir, "/files/fileFromOther" );
+ removeFile( m_trashDir, "/info/symlinkFromHome.trashinfo" );
+ removeFile( m_trashDir, "/files/symlinkFromHome" );
+ removeFile( m_trashDir, "/info/symlinkFromOther.trashinfo" );
+ removeFile( m_trashDir, "/files/symlinkFromOther" );
+ removeFile( m_trashDir, "/info/brokenSymlinkFromHome.trashinfo" );
+ removeFile( m_trashDir, "/files/brokenSymlinkFromHome" );
+ removeFile( m_trashDir, "/info/trashDirFromHome.trashinfo" );
+ removeFile( m_trashDir, "/files/trashDirFromHome/testfile" );
+ removeFile( m_trashDir, "/info/readonly.trashinfo" );
+ removeDir( m_trashDir, "/files/trashDirFromHome" );
+ removeFile( m_trashDir, "/info/trashDirFromHome_1.trashinfo" );
+ removeFile( m_trashDir, "/files/trashDirFromHome_1/testfile" );
+ removeDir( m_trashDir, "/files/trashDirFromHome_1" );
+ removeFile( m_trashDir, "/info/trashDirFromOther.trashinfo" );
+ removeFile( m_trashDir, "/files/trashDirFromOther/testfile" );
+ removeDir( m_trashDir, "/files/trashDirFromOther" );
+ KIO::NetAccess::del( m_trashDir + "/files/readonly", 0 );
+ // for trashDirectoryOwnedByRoot
+ KIO::NetAccess::del( m_trashDir + "/files/cups", 0 );
+ KIO::NetAccess::del( m_trashDir + "/files/boot", 0 );
+ KIO::NetAccess::del( m_trashDir + "/files/etc", 0 );
+
+ //system( "find ~/.local-testtrash/share/Trash" );
+}
+
+void TestTrash::runAll()
+{
+ urlTestFile();
+ urlTestDirectory();
+ urlTestSubDirectory();
+
+ trashFileFromHome();
+ trashPercentFileFromHome();
+#ifdef UTF8TEST
+ trashUtf8FileFromHome();
+#endif
+ trashUmlautFileFromHome();
+ trashReadOnlyDirFromHome();
+ testTrashNotEmpty();
+ trashFileFromOther();
+ trashFileIntoOtherPartition();
+ trashFileOwnedByRoot();
+ trashSymlinkFromHome();
+ trashSymlinkFromOther();
+ trashBrokenSymlinkFromHome();
+ trashDirectoryFromHome();
+ trashDirectoryFromOther();
+ trashDirectoryOwnedByRoot();
+
+ tryRenameInsideTrash();
+
+ statRoot();
+ statFileInRoot();
+ statDirectoryInRoot();
+ statSymlinkInRoot();
+ statFileInDirectory();
+
+ copyFileFromTrash();
+ // To test case of already-existing destination, uncomment this.
+ // This brings up the "rename" dialog though, so it can't be fully automated
+ //copyFileFromTrash();
+ copyFileInDirectoryFromTrash();
+ copyDirectoryFromTrash();
+ copySymlinkFromTrash();
+
+ moveFileFromTrash();
+ moveFileInDirectoryFromTrash();
+ moveDirectoryFromTrash();
+ moveSymlinkFromTrash();
+
+ listRootDir();
+ listRecursiveRootDir();
+ listSubDir();
+
+ delRootFile();
+ delFileInDirectory();
+ delDirectory();
+
+ getFile();
+ restoreFile();
+ restoreFileFromSubDir();
+ restoreFileToDeletedDirectory();
+
+ emptyTrash();
+
+ // TODO: test
+ // - trash migration
+ // - the actual updating of the trash icon on the desktop
+}
+
+void TestTrash::urlTestFile()
+{
+ const KURL url = TrashImpl::makeURL( 1, "fileId", QString::null );
+ check( "makeURL for a file", url.url(), "trash:/1-fileId" );
+
+ int trashId;
+ QString fileId;
+ QString relativePath;
+ bool ok = TrashImpl::parseURL( url, trashId, fileId, relativePath );
+ assert( ok );
+ check( "parseURL: trashId", QString::number( trashId ), "1" );
+ check( "parseURL: fileId", fileId, "fileId" );
+ check( "parseURL: relativePath", relativePath, QString::null );
+}
+
+void TestTrash::urlTestDirectory()
+{
+ const KURL url = TrashImpl::makeURL( 1, "fileId", "subfile" );
+ check( "makeURL", url.url(), "trash:/1-fileId/subfile" );
+
+ int trashId;
+ QString fileId;
+ QString relativePath;
+ bool ok = TrashImpl::parseURL( url, trashId, fileId, relativePath );
+ assert( ok );
+ check( "parseURL: trashId", QString::number( trashId ), "1" );
+ check( "parseURL: fileId", fileId, "fileId" );
+ check( "parseURL: relativePath", relativePath, "subfile" );
+}
+
+void TestTrash::urlTestSubDirectory()
+{
+ const KURL url = TrashImpl::makeURL( 1, "fileId", "subfile/foobar" );
+ check( "makeURL", url.url(), "trash:/1-fileId/subfile/foobar" );
+
+ int trashId;
+ QString fileId;
+ QString relativePath;
+ bool ok = TrashImpl::parseURL( url, trashId, fileId, relativePath );
+ assert( ok );
+ check( "parseURL: trashId", QString::number( trashId ), "1" );
+ check( "parseURL: fileId", fileId, "fileId" );
+ check( "parseURL: relativePath", relativePath, "subfile/foobar" );
+}
+
+static void checkInfoFile( const QString& infoPath, const QString& origFilePath )
+{
+ kdDebug() << k_funcinfo << infoPath << endl;
+ QFileInfo info( infoPath );
+ assert( info.exists() );
+ assert( info.isFile() );
+ KSimpleConfig infoFile( info.absFilePath(), true );
+ if ( !infoFile.hasGroup( "Trash Info" ) )
+ kdFatal() << "no Trash Info group in " << info.absFilePath() << endl;
+ infoFile.setGroup( "Trash Info" );
+ const QString origPath = infoFile.readEntry( "Path" );
+ assert( !origPath.isEmpty() );
+ assert( origPath == KURL::encode_string( origFilePath, KGlobal::locale()->fileEncodingMib() ) );
+ const QString date = infoFile.readEntry( "DeletionDate" );
+ assert( !date.isEmpty() );
+ assert( date.contains( "T" ) );
+}
+
+static void createTestFile( const QString& path )
+{
+ QFile f( path );
+ if ( !f.open( IO_WriteOnly ) )
+ kdFatal() << "Can't create " << path << endl;
+ f.writeBlock( "Hello world\n", 12 );
+ f.close();
+ assert( QFile::exists( path ) );
+}
+
+void TestTrash::trashFile( const QString& origFilePath, const QString& fileId )
+{
+ // setup
+ if ( !QFile::exists( origFilePath ) )
+ createTestFile( origFilePath );
+ KURL u;
+ u.setPath( origFilePath );
+
+ // test
+ KIO::Job* job = KIO::move( u, "trash:/" );
+ QMap<QString, QString> metaData;
+ //bool ok = KIO::NetAccess::move( u, "trash:/" );
+ bool ok = KIO::NetAccess::synchronousRun( job, 0, 0, 0, &metaData );
+ if ( !ok )
+ kdError() << "moving " << u << " to trash failed with error " << KIO::NetAccess::lastError() << " " << KIO::NetAccess::lastErrorString() << endl;
+ assert( ok );
+ if ( origFilePath.startsWith( "/tmp" ) && m_tmpIsWritablePartition ) {
+ kdDebug() << " TESTS SKIPPED" << endl;
+ } else {
+ checkInfoFile( m_trashDir + "/info/" + fileId + ".trashinfo", origFilePath );
+
+ QFileInfo files( m_trashDir + "/files/" + fileId );
+ assert( files.isFile() );
+ assert( files.size() == 12 );
+ }
+
+ // coolo suggests testing that the original file is actually gone, too :)
+ assert( !QFile::exists( origFilePath ) );
+
+ assert( !metaData.isEmpty() );
+ bool found = false;
+ QMap<QString, QString>::ConstIterator it = metaData.begin();
+ for ( ; it != metaData.end() ; ++it ) {
+ if ( it.key().startsWith( "trashURL" ) ) {
+ const QString origPath = it.key().mid( 9 );
+ KURL trashURL( it.data() );
+ kdDebug() << trashURL << endl;
+ assert( !trashURL.isEmpty() );
+ assert( trashURL.protocol() == "trash" );
+ int trashId = 0;
+ if ( origFilePath.startsWith( "/tmp" ) && m_tmpIsWritablePartition )
+ trashId = m_tmpTrashId;
+ assert( trashURL.path() == "/" + QString::number( trashId ) + "-" + fileId );
+ found = true;
+ }
+ }
+ assert( found );
+}
+
+void TestTrash::trashFileFromHome()
+{
+ kdDebug() << k_funcinfo << endl;
+ const QString fileName = "fileFromHome";
+ trashFile( homeTmpDir() + fileName, fileName );
+
+ // Do it again, check that we got a different id
+ trashFile( homeTmpDir() + fileName, fileName + "_1" );
+}
+
+void TestTrash::trashPercentFileFromHome()
+{
+ kdDebug() << k_funcinfo << endl;
+ const QString fileName = "file%2f";
+ trashFile( homeTmpDir() + fileName, fileName );
+}
+
+void TestTrash::trashUtf8FileFromHome()
+{
+ kdDebug() << k_funcinfo << endl;
+ const QString fileName = utf8FileName();
+ trashFile( homeTmpDir() + fileName, fileName );
+}
+
+void TestTrash::trashUmlautFileFromHome()
+{
+ kdDebug() << k_funcinfo << endl;
+ const QString fileName = umlautFileName();
+ trashFile( homeTmpDir() + fileName, fileName );
+}
+
+void TestTrash::testTrashNotEmpty()
+{
+ KSimpleConfig cfg( "trashrc", true );
+ assert( cfg.hasGroup( "Status" ) );
+ cfg.setGroup( "Status" );
+ assert( cfg.readBoolEntry( "Empty", true ) == false );
+}
+
+void TestTrash::trashFileFromOther()
+{
+ kdDebug() << k_funcinfo << endl;
+ const QString fileName = "fileFromOther";
+ trashFile( otherTmpDir() + fileName, fileName );
+}
+
+void TestTrash::trashFileIntoOtherPartition()
+{
+ if ( m_otherPartitionTrashDir.isEmpty() ) {
+ kdDebug() << k_funcinfo << " - SKIPPED" << endl;
+ return;
+ }
+ kdDebug() << k_funcinfo << endl;
+ const QString fileName = "testtrash-file";
+ const QString origFilePath = m_otherPartitionTopDir + fileName;
+ const QString fileId = fileName;
+ // cleanup
+ QFile::remove( m_otherPartitionTrashDir + "/info/" + fileId + ".trashinfo" );
+ QFile::remove( m_otherPartitionTrashDir + "/files/" + fileId );
+
+ // setup
+ if ( !QFile::exists( origFilePath ) )
+ createTestFile( origFilePath );
+ KURL u;
+ u.setPath( origFilePath );
+
+ // test
+ KIO::Job* job = KIO::move( u, "trash:/" );
+ QMap<QString, QString> metaData;
+ bool ok = KIO::NetAccess::synchronousRun( job, 0, 0, 0, &metaData );
+ assert( ok );
+ // Note that the Path stored in the info file is relative, on other partitions (#95652)
+ checkInfoFile( m_otherPartitionTrashDir + "/info/" + fileId + ".trashinfo", fileName );
+
+ QFileInfo files( m_otherPartitionTrashDir + "/files/" + fileId );
+ assert( files.isFile() );
+ assert( files.size() == 12 );
+
+ // coolo suggests testing that the original file is actually gone, too :)
+ assert( !QFile::exists( origFilePath ) );
+
+ assert( !metaData.isEmpty() );
+ bool found = false;
+ QMap<QString, QString>::ConstIterator it = metaData.begin();
+ for ( ; it != metaData.end() ; ++it ) {
+ if ( it.key().startsWith( "trashURL" ) ) {
+ const QString origPath = it.key().mid( 9 );
+ KURL trashURL( it.data() );
+ kdDebug() << trashURL << endl;
+ assert( !trashURL.isEmpty() );
+ assert( trashURL.protocol() == "trash" );
+ assert( trashURL.path() == QString( "/%1-%2" ).arg( m_otherPartitionId ).arg( fileId ) );
+ found = true;
+ }
+ }
+ assert( found );
+}
+
+void TestTrash::trashFileOwnedByRoot()
+{
+ kdDebug() << k_funcinfo << endl;
+ KURL u;
+ u.setPath( "/etc/passwd" );
+ const QString fileId = "passwd";
+
+ KIO::CopyJob* job = KIO::move( u, "trash:/" );
+ job->setInteractive( false ); // no skip dialog, thanks
+ QMap<QString, QString> metaData;
+ //bool ok = KIO::NetAccess::move( u, "trash:/" );
+ bool ok = KIO::NetAccess::synchronousRun( job, 0, 0, 0, &metaData );
+ assert( !ok );
+ assert( KIO::NetAccess::lastError() == KIO::ERR_ACCESS_DENIED );
+ const QString infoPath( m_trashDir + "/info/" + fileId + ".trashinfo" );
+ assert( !QFile::exists( infoPath ) );
+
+ QFileInfo files( m_trashDir + "/files/" + fileId );
+ assert( !files.exists() );
+
+ assert( QFile::exists( u.path() ) );
+}
+
+void TestTrash::trashSymlink( const QString& origFilePath, const QString& fileId, bool broken )
+{
+ kdDebug() << k_funcinfo << endl;
+ // setup
+ const char* target = broken ? "/nonexistent" : "/tmp";
+ bool ok = ::symlink( target, QFile::encodeName( origFilePath ) ) == 0;
+ assert( ok );
+ KURL u;
+ u.setPath( origFilePath );
+
+ // test
+ ok = KIO::NetAccess::move( u, "trash:/" );
+ assert( ok );
+ if ( origFilePath.startsWith( "/tmp" ) && m_tmpIsWritablePartition ) {
+ kdDebug() << " TESTS SKIPPED" << endl;
+ return;
+ }
+ checkInfoFile( m_trashDir + "/info/" + fileId + ".trashinfo", origFilePath );
+
+ QFileInfo files( m_trashDir + "/files/" + fileId );
+ assert( files.isSymLink() );
+ assert( files.readLink() == QFile::decodeName( target ) );
+ assert( !QFile::exists( origFilePath ) );
+}
+
+void TestTrash::trashSymlinkFromHome()
+{
+ kdDebug() << k_funcinfo << endl;
+ const QString fileName = "symlinkFromHome";
+ trashSymlink( homeTmpDir() + fileName, fileName, false );
+}
+
+void TestTrash::trashSymlinkFromOther()
+{
+ kdDebug() << k_funcinfo << endl;
+ const QString fileName = "symlinkFromOther";
+ trashSymlink( otherTmpDir() + fileName, fileName, false );
+}
+
+void TestTrash::trashBrokenSymlinkFromHome()
+{
+ kdDebug() << k_funcinfo << endl;
+ const QString fileName = "brokenSymlinkFromHome";
+ trashSymlink( homeTmpDir() + fileName, fileName, true );
+}
+
+void TestTrash::trashDirectory( const QString& origPath, const QString& fileId )
+{
+ kdDebug() << k_funcinfo << fileId << endl;
+ // setup
+ if ( !QFileInfo( origPath ).exists() ) {
+ QDir dir;
+ bool ok = dir.mkdir( origPath );
+ Q_ASSERT( ok );
+ }
+ createTestFile( origPath + "/testfile" );
+ KURL u; u.setPath( origPath );
+
+ // test
+ bool ok = KIO::NetAccess::move( u, "trash:/" );
+ assert( ok );
+ if ( origPath.startsWith( "/tmp" ) && m_tmpIsWritablePartition ) {
+ kdDebug() << " TESTS SKIPPED" << endl;
+ return;
+ }
+ checkInfoFile( m_trashDir + "/info/" + fileId + ".trashinfo", origPath );
+
+ QFileInfo filesDir( m_trashDir + "/files/" + fileId );
+ assert( filesDir.isDir() );
+ QFileInfo files( m_trashDir + "/files/" + fileId + "/testfile" );
+ assert( files.exists() );
+ assert( files.isFile() );
+ assert( files.size() == 12 );
+ assert( !QFile::exists( origPath ) );
+}
+
+void TestTrash::trashDirectoryFromHome()
+{
+ kdDebug() << k_funcinfo << endl;
+ QString dirName = "trashDirFromHome";
+ trashDirectory( homeTmpDir() + dirName, dirName );
+ // Do it again, check that we got a different id
+ trashDirectory( homeTmpDir() + dirName, dirName + "_1" );
+}
+
+void TestTrash::trashReadOnlyDirFromHome()
+{
+ kdDebug() << k_funcinfo << endl;
+ const QString dirName = readOnlyDirPath();
+ QDir dir;
+ bool ok = dir.mkdir( dirName );
+ Q_ASSERT( ok );
+ // #130780
+ const QString subDirPath = dirName + "/readonly_subdir";
+ ok = dir.mkdir( subDirPath );
+ Q_ASSERT( ok );
+ createTestFile( subDirPath + "/testfile_in_subdir" );
+ ::chmod( QFile::encodeName( subDirPath ), 0500 );
+
+ trashDirectory( dirName, "readonly" );
+}
+
+void TestTrash::trashDirectoryFromOther()
+{
+ kdDebug() << k_funcinfo << endl;
+ QString dirName = "trashDirFromOther";
+ trashDirectory( otherTmpDir() + dirName, dirName );
+}
+
+void TestTrash::tryRenameInsideTrash()
+{
+ kdDebug() << k_funcinfo << " with file_move" << endl;
+ bool worked = KIO::NetAccess::file_move( "trash:/0-tryRenameInsideTrash", "trash:/foobar" );
+ assert( !worked );
+ assert( KIO::NetAccess::lastError() == KIO::ERR_CANNOT_RENAME );
+
+ kdDebug() << k_funcinfo << " with move" << endl;
+ worked = KIO::NetAccess::move( "trash:/0-tryRenameInsideTrash", "trash:/foobar" );
+ assert( !worked );
+ assert( KIO::NetAccess::lastError() == KIO::ERR_CANNOT_RENAME );
+}
+
+void TestTrash::delRootFile()
+{
+ kdDebug() << k_funcinfo << endl;
+
+ // test deleting a trashed file
+ bool ok = KIO::NetAccess::del( "trash:/0-fileFromHome", 0L );
+ assert( ok );
+
+ QFileInfo file( m_trashDir + "/files/fileFromHome" );
+ assert( !file.exists() );
+ QFileInfo info( m_trashDir + "/info/fileFromHome.trashinfo" );
+ assert( !info.exists() );
+
+ // trash it again, we might need it later
+ const QString fileName = "fileFromHome";
+ trashFile( homeTmpDir() + fileName, fileName );
+}
+
+void TestTrash::delFileInDirectory()
+{
+ kdDebug() << k_funcinfo << endl;
+
+ // test deleting a file inside a trashed directory -> not allowed
+ bool ok = KIO::NetAccess::del( "trash:/0-trashDirFromHome/testfile", 0L );
+ assert( !ok );
+ assert( KIO::NetAccess::lastError() == KIO::ERR_ACCESS_DENIED );
+
+ QFileInfo dir( m_trashDir + "/files/trashDirFromHome" );
+ assert( dir.exists() );
+ QFileInfo file( m_trashDir + "/files/trashDirFromHome/testfile" );
+ assert( file.exists() );
+ QFileInfo info( m_trashDir + "/info/trashDirFromHome.trashinfo" );
+ assert( info.exists() );
+}
+
+void TestTrash::delDirectory()
+{
+ kdDebug() << k_funcinfo << endl;
+
+ // test deleting a trashed directory
+ bool ok = KIO::NetAccess::del( "trash:/0-trashDirFromHome", 0L );
+ assert( ok );
+
+ QFileInfo dir( m_trashDir + "/files/trashDirFromHome" );
+ assert( !dir.exists() );
+ QFileInfo file( m_trashDir + "/files/trashDirFromHome/testfile" );
+ assert( !file.exists() );
+ QFileInfo info( m_trashDir + "/info/trashDirFromHome.trashinfo" );
+ assert( !info.exists() );
+
+ // trash it again, we'll need it later
+ QString dirName = "trashDirFromHome";
+ trashDirectory( homeTmpDir() + dirName, dirName );
+}
+
+void TestTrash::statRoot()
+{
+ kdDebug() << k_funcinfo << endl;
+ KURL url( "trash:/" );
+ KIO::UDSEntry entry;
+ bool ok = KIO::NetAccess::stat( url, entry, 0 );
+ assert( ok );
+ KFileItem item( entry, url );
+ assert( item.isDir() );
+ assert( !item.isLink() );
+ assert( item.isReadable() );
+ assert( item.isWritable() );
+ assert( !item.isHidden() );
+ assert( item.name() == "." );
+ assert( item.acceptsDrops() );
+}
+
+void TestTrash::statFileInRoot()
+{
+ kdDebug() << k_funcinfo << endl;
+ KURL url( "trash:/0-fileFromHome" );
+ KIO::UDSEntry entry;
+ bool ok = KIO::NetAccess::stat( url, entry, 0 );
+ assert( ok );
+ KFileItem item( entry, url );
+ assert( item.isFile() );
+ assert( !item.isDir() );
+ assert( !item.isLink() );
+ assert( item.isReadable() );
+ assert( !item.isWritable() );
+ assert( !item.isHidden() );
+ assert( item.name() == "fileFromHome" );
+ assert( !item.acceptsDrops() );
+}
+
+void TestTrash::statDirectoryInRoot()
+{
+ kdDebug() << k_funcinfo << endl;
+ KURL url( "trash:/0-trashDirFromHome" );
+ KIO::UDSEntry entry;
+ bool ok = KIO::NetAccess::stat( url, entry, 0 );
+ assert( ok );
+ KFileItem item( entry, url );
+ assert( item.isDir() );
+ assert( !item.isLink() );
+ assert( item.isReadable() );
+ assert( !item.isWritable() );
+ assert( !item.isHidden() );
+ assert( item.name() == "trashDirFromHome" );
+ assert( !item.acceptsDrops() );
+}
+
+void TestTrash::statSymlinkInRoot()
+{
+ kdDebug() << k_funcinfo << endl;
+ KURL url( "trash:/0-symlinkFromHome" );
+ KIO::UDSEntry entry;
+ bool ok = KIO::NetAccess::stat( url, entry, 0 );
+ assert( ok );
+ KFileItem item( entry, url );
+ assert( item.isLink() );
+ assert( item.linkDest() == "/tmp" );
+ assert( item.isReadable() );
+ assert( !item.isWritable() );
+ assert( !item.isHidden() );
+ assert( item.name() == "symlinkFromHome" );
+ assert( !item.acceptsDrops() );
+}
+
+void TestTrash::statFileInDirectory()
+{
+ kdDebug() << k_funcinfo << endl;
+ KURL url( "trash:/0-trashDirFromHome/testfile" );
+ KIO::UDSEntry entry;
+ bool ok = KIO::NetAccess::stat( url, entry, 0 );
+ assert( ok );
+ KFileItem item( entry, url );
+ assert( item.isFile() );
+ assert( !item.isLink() );
+ assert( item.isReadable() );
+ assert( !item.isWritable() );
+ assert( !item.isHidden() );
+ assert( item.name() == "testfile" );
+ assert( !item.acceptsDrops() );
+}
+
+void TestTrash::copyFromTrash( const QString& fileId, const QString& destPath, const QString& relativePath )
+{
+ KURL src( "trash:/0-" + fileId );
+ if ( !relativePath.isEmpty() )
+ src.addPath( relativePath );
+ KURL dest;
+ dest.setPath( destPath );
+
+ assert( KIO::NetAccess::exists( src, true, (QWidget*)0 ) );
+
+ // A dnd would use copy(), but we use copyAs to ensure the final filename
+ //kdDebug() << k_funcinfo << "copyAs:" << src << " -> " << dest << endl;
+ KIO::Job* job = KIO::copyAs( src, dest );
+ bool ok = KIO::NetAccess::synchronousRun( job, 0 );
+ assert( ok );
+ QString infoFile( m_trashDir + "/info/" + fileId + ".trashinfo" );
+ assert( QFile::exists( infoFile ) );
+
+ QFileInfo filesItem( m_trashDir + "/files/" + fileId );
+ assert( filesItem.exists() );
+
+ assert( QFile::exists( destPath ) );
+}
+
+void TestTrash::copyFileFromTrash()
+{
+ kdDebug() << k_funcinfo << endl;
+ const QString destPath = otherTmpDir() + "fileFromHome_copied";
+ copyFromTrash( "fileFromHome", destPath );
+ assert( QFileInfo( destPath ).isFile() );
+ assert( QFileInfo( destPath ).size() == 12 );
+}
+
+void TestTrash::copyFileInDirectoryFromTrash()
+{
+ kdDebug() << k_funcinfo << endl;
+ const QString destPath = otherTmpDir() + "testfile_copied";
+ copyFromTrash( "trashDirFromHome", destPath, "testfile" );
+ assert( QFileInfo( destPath ).isFile() );
+ assert( QFileInfo( destPath ).size() == 12 );
+}
+
+void TestTrash::copyDirectoryFromTrash()
+{
+ kdDebug() << k_funcinfo << endl;
+ const QString destPath = otherTmpDir() + "trashDirFromHome_copied";
+ copyFromTrash( "trashDirFromHome", destPath );
+ assert( QFileInfo( destPath ).isDir() );
+}
+
+void TestTrash::copySymlinkFromTrash()
+{
+ kdDebug() << k_funcinfo << endl;
+ const QString destPath = otherTmpDir() + "symlinkFromHome_copied";
+ copyFromTrash( "symlinkFromHome", destPath );
+ assert( QFileInfo( destPath ).isSymLink() );
+}
+
+void TestTrash::moveFromTrash( const QString& fileId, const QString& destPath, const QString& relativePath )
+{
+ KURL src( "trash:/0-" + fileId );
+ if ( !relativePath.isEmpty() )
+ src.addPath( relativePath );
+ KURL dest;
+ dest.setPath( destPath );
+
+ assert( KIO::NetAccess::exists( src, true, (QWidget*)0 ) );
+
+ // A dnd would use move(), but we use moveAs to ensure the final filename
+ KIO::Job* job = KIO::moveAs( src, dest );
+ bool ok = KIO::NetAccess::synchronousRun( job, 0 );
+ assert( ok );
+ QString infoFile( m_trashDir + "/info/" + fileId + ".trashinfo" );
+ assert( !QFile::exists( infoFile ) );
+
+ QFileInfo filesItem( m_trashDir + "/files/" + fileId );
+ assert( !filesItem.exists() );
+
+ assert( QFile::exists( destPath ) );
+}
+
+void TestTrash::moveFileFromTrash()
+{
+ kdDebug() << k_funcinfo << endl;
+ const QString destPath = otherTmpDir() + "fileFromHome_restored";
+ moveFromTrash( "fileFromHome", destPath );
+ assert( QFileInfo( destPath ).isFile() );
+ assert( QFileInfo( destPath ).size() == 12 );
+
+ // trash it again for later
+ const QString fileName = "fileFromHome";
+ trashFile( homeTmpDir() + fileName, fileName );
+}
+
+void TestTrash::moveFileInDirectoryFromTrash()
+{
+ kdDebug() << k_funcinfo << endl;
+ const QString destPath = otherTmpDir() + "testfile_restored";
+ copyFromTrash( "trashDirFromHome", destPath, "testfile" );
+ assert( QFileInfo( destPath ).isFile() );
+ assert( QFileInfo( destPath ).size() == 12 );
+}
+
+void TestTrash::moveDirectoryFromTrash()
+{
+ kdDebug() << k_funcinfo << endl;
+ const QString destPath = otherTmpDir() + "trashDirFromHome_restored";
+ moveFromTrash( "trashDirFromHome", destPath );
+ assert( QFileInfo( destPath ).isDir() );
+
+ // trash it again, we'll need it later
+ QString dirName = "trashDirFromHome";
+ trashDirectory( homeTmpDir() + dirName, dirName );
+}
+
+void TestTrash::trashDirectoryOwnedByRoot()
+{
+ KURL u;
+ if ( QFile::exists( "/etc/cups" ) )
+ u.setPath( "/etc/cups" );
+ else if ( QFile::exists( "/boot" ) )
+ u.setPath( "/boot" );
+ else
+ u.setPath( "/etc" );
+ const QString fileId = u.path();
+ kdDebug() << k_funcinfo << "fileId=" << fileId << endl;
+
+ KIO::CopyJob* job = KIO::move( u, "trash:/" );
+ job->setInteractive( false ); // no skip dialog, thanks
+ QMap<QString, QString> metaData;
+ bool ok = KIO::NetAccess::synchronousRun( job, 0, 0, 0, &metaData );
+ assert( !ok );
+ const int err = KIO::NetAccess::lastError();
+ assert( err == KIO::ERR_ACCESS_DENIED
+ || err == KIO::ERR_CANNOT_OPEN_FOR_READING );
+
+ const QString infoPath( m_trashDir + "/info/" + fileId + ".trashinfo" );
+ assert( !QFile::exists( infoPath ) );
+
+ QFileInfo files( m_trashDir + "/files/" + fileId );
+ assert( !files.exists() );
+
+ assert( QFile::exists( u.path() ) );
+}
+
+void TestTrash::moveSymlinkFromTrash()
+{
+ kdDebug() << k_funcinfo << endl;
+ const QString destPath = otherTmpDir() + "symlinkFromHome_restored";
+ moveFromTrash( "symlinkFromHome", destPath );
+ assert( QFileInfo( destPath ).isSymLink() );
+}
+
+void TestTrash::getFile()
+{
+ kdDebug() << k_funcinfo << endl;
+ const QString fileId = "fileFromHome_1";
+ const KURL url = TrashImpl::makeURL( 0, fileId, QString::null );
+ QString tmpFile;
+ bool ok = KIO::NetAccess::download( url, tmpFile, 0 );
+ assert( ok );
+ QFile file( tmpFile );
+ ok = file.open( IO_ReadOnly );
+ assert( ok );
+ QByteArray str = file.readAll();
+ QCString cstr( str.data(), str.size() + 1 );
+ if ( cstr != "Hello world\n" )
+ kdFatal() << "get() returned the following data:" << cstr << endl;
+ file.close();
+ KIO::NetAccess::removeTempFile( tmpFile );
+}
+
+void TestTrash::restoreFile()
+{
+ kdDebug() << k_funcinfo << endl;
+ const QString fileId = "fileFromHome_1";
+ const KURL url = TrashImpl::makeURL( 0, fileId, QString::null );
+ const QString infoFile( m_trashDir + "/info/" + fileId + ".trashinfo" );
+ const QString filesItem( m_trashDir + "/files/" + fileId );
+
+ assert( QFile::exists( infoFile ) );
+ assert( QFile::exists( filesItem ) );
+
+ QByteArray packedArgs;
+ QDataStream stream( packedArgs, IO_WriteOnly );
+ stream << (int)3 << url;
+ KIO::Job* job = KIO::special( url, packedArgs );
+ bool ok = KIO::NetAccess::synchronousRun( job, 0 );
+ assert( ok );
+
+ assert( !QFile::exists( infoFile ) );
+ assert( !QFile::exists( filesItem ) );
+
+ const QString destPath = homeTmpDir() + "fileFromHome";
+ assert( QFile::exists( destPath ) );
+}
+
+void TestTrash::restoreFileFromSubDir()
+{
+ kdDebug() << k_funcinfo << endl;
+ const QString fileId = "trashDirFromHome_1/testfile";
+ assert( !QFile::exists( homeTmpDir() + "trashDirFromHome_1" ) );
+
+ const KURL url = TrashImpl::makeURL( 0, fileId, QString::null );
+ const QString infoFile( m_trashDir + "/info/trashDirFromHome_1.trashinfo" );
+ const QString filesItem( m_trashDir + "/files/trashDirFromHome_1/testfile" );
+
+ assert( QFile::exists( infoFile ) );
+ assert( QFile::exists( filesItem ) );
+
+ QByteArray packedArgs;
+ QDataStream stream( packedArgs, IO_WriteOnly );
+ stream << (int)3 << url;
+ KIO::Job* job = KIO::special( url, packedArgs );
+ bool ok = KIO::NetAccess::synchronousRun( job, 0 );
+ assert( !ok );
+ // dest dir doesn't exist -> error message
+ assert( KIO::NetAccess::lastError() == KIO::ERR_SLAVE_DEFINED );
+
+ // check that nothing happened
+ assert( QFile::exists( infoFile ) );
+ assert( QFile::exists( filesItem ) );
+ assert( !QFile::exists( homeTmpDir() + "trashDirFromHome_1" ) );
+}
+
+void TestTrash::restoreFileToDeletedDirectory()
+{
+ kdDebug() << k_funcinfo << endl;
+ // Ensure we'll get "fileFromHome" as fileId
+ removeFile( m_trashDir, "/info/fileFromHome.trashinfo" );
+ removeFile( m_trashDir, "/files/fileFromHome" );
+ trashFileFromHome();
+ // Delete orig dir
+ bool delOK = KIO::NetAccess::del( homeTmpDir(), 0 );
+ assert( delOK );
+
+ const QString fileId = "fileFromHome";
+ const KURL url = TrashImpl::makeURL( 0, fileId, QString::null );
+ const QString infoFile( m_trashDir + "/info/" + fileId + ".trashinfo" );
+ const QString filesItem( m_trashDir + "/files/" + fileId );
+
+ assert( QFile::exists( infoFile ) );
+ assert( QFile::exists( filesItem ) );
+
+ QByteArray packedArgs;
+ QDataStream stream( packedArgs, IO_WriteOnly );
+ stream << (int)3 << url;
+ KIO::Job* job = KIO::special( url, packedArgs );
+ bool ok = KIO::NetAccess::synchronousRun( job, 0 );
+ assert( !ok );
+ // dest dir doesn't exist -> error message
+ assert( KIO::NetAccess::lastError() == KIO::ERR_SLAVE_DEFINED );
+
+ // check that nothing happened
+ assert( QFile::exists( infoFile ) );
+ assert( QFile::exists( filesItem ) );
+
+ const QString destPath = homeTmpDir() + "fileFromHome";
+ assert( !QFile::exists( destPath ) );
+}
+
+void TestTrash::listRootDir()
+{
+ kdDebug() << k_funcinfo << endl;
+ m_entryCount = 0;
+ m_listResult.clear();
+ KIO::ListJob* job = KIO::listDir( "trash:/" );
+ connect( job, SIGNAL( entries( KIO::Job*, const KIO::UDSEntryList& ) ),
+ SLOT( slotEntries( KIO::Job*, const KIO::UDSEntryList& ) ) );
+ bool ok = KIO::NetAccess::synchronousRun( job, 0 );
+ assert( ok );
+ kdDebug() << "listDir done - m_entryCount=" << m_entryCount << endl;
+ assert( m_entryCount > 1 );
+
+ kdDebug() << k_funcinfo << m_listResult << endl;
+ assert( m_listResult.contains( "." ) == 1 ); // found it, and only once
+}
+
+void TestTrash::listRecursiveRootDir()
+{
+ kdDebug() << k_funcinfo << endl;
+ m_entryCount = 0;
+ m_listResult.clear();
+ KIO::ListJob* job = KIO::listRecursive( "trash:/" );
+ connect( job, SIGNAL( entries( KIO::Job*, const KIO::UDSEntryList& ) ),
+ SLOT( slotEntries( KIO::Job*, const KIO::UDSEntryList& ) ) );
+ bool ok = KIO::NetAccess::synchronousRun( job, 0 );
+ assert( ok );
+ kdDebug() << "listDir done - m_entryCount=" << m_entryCount << endl;
+ assert( m_entryCount > 1 );
+
+ kdDebug() << k_funcinfo << m_listResult << endl;
+ assert( m_listResult.contains( "." ) == 1 ); // found it, and only once
+}
+
+void TestTrash::listSubDir()
+{
+ kdDebug() << k_funcinfo << endl;
+ m_entryCount = 0;
+ m_listResult.clear();
+ KIO::ListJob* job = KIO::listDir( "trash:/0-trashDirFromHome" );
+ connect( job, SIGNAL( entries( KIO::Job*, const KIO::UDSEntryList& ) ),
+ SLOT( slotEntries( KIO::Job*, const KIO::UDSEntryList& ) ) );
+ bool ok = KIO::NetAccess::synchronousRun( job, 0 );
+ assert( ok );
+ kdDebug() << "listDir done - m_entryCount=" << m_entryCount << endl;
+ assert( m_entryCount == 2 );
+
+ kdDebug() << k_funcinfo << m_listResult << endl;
+ assert( m_listResult.contains( "." ) == 1 ); // found it, and only once
+ assert( m_listResult.contains( "testfile" ) == 1 ); // found it, and only once
+}
+
+void TestTrash::slotEntries( KIO::Job*, const KIO::UDSEntryList& lst )
+{
+ for( KIO::UDSEntryList::ConstIterator it = lst.begin(); it != lst.end(); ++it ) {
+ KIO::UDSEntry::ConstIterator it2 = (*it).begin();
+ QString displayName;
+ KURL url;
+ for( ; it2 != (*it).end(); it2++ ) {
+ switch ((*it2).m_uds) {
+ case KIO::UDS_NAME:
+ displayName = (*it2).m_str;
+ break;
+ case KIO::UDS_URL:
+ url = (*it2).m_str;
+ break;
+ }
+ }
+ kdDebug() << k_funcinfo << displayName << " " << url << endl;
+ if ( !url.isEmpty() ) {
+ assert( url.protocol() == "trash" );
+ }
+ m_listResult << displayName;
+ }
+ m_entryCount += lst.count();
+}
+
+void TestTrash::emptyTrash()
+{
+ // ## Even though we use a custom XDG_DATA_HOME value, emptying the
+ // trash would still empty the other trash directories in other partitions.
+ // So we can't activate this test by default.
+#if 0
+ kdDebug() << k_funcinfo << endl;
+ QByteArray packedArgs;
+ QDataStream stream( packedArgs, IO_WriteOnly );
+ stream << (int)1;
+ KIO::Job* job = KIO::special( "trash:/", packedArgs );
+ bool ok = KIO::NetAccess::synchronousRun( job, 0 );
+ assert( ok );
+
+ KSimpleConfig cfg( "trashrc", true );
+ assert( cfg.hasGroup( "Status" ) );
+ cfg.setGroup( "Status" );
+ assert( cfg.readBoolEntry( "Empty", false ) == true );
+
+ assert( !QFile::exists( m_trashDir + "/files/fileFromHome" ) );
+ assert( !QFile::exists( m_trashDir + "/files/readonly" ) );
+ assert( !QFile::exists( m_trashDir + "/info/readonly.trashinfo" ) );
+
+#else
+ kdDebug() << k_funcinfo << " : SKIPPED" << endl;
+#endif
+}
+
+#include "testtrash.moc"