summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorVolker Hilsheimer <volker.hilsheimer@qt.io>2020-01-21 15:51:31 +0100
committerVolker Hilsheimer <volker.hilsheimer@qt.io>2020-02-05 20:21:45 +0100
commit74a2467edd1bf0800cbaf1878984c6bccd42570d (patch)
tree10d141287c8e9b32a37c5241432be5a800e8a847
parent7dd6d32657cb079f8b3e1ff6b1dc27248ec0a2d4 (diff)
Add QFile::moveToTrash, which moves a file to the trash
Due to the nature of QFile just operating on a file path, this also works for paths that are actually directories. The test covers files from different locations on which this operation should typically succeed, but tries to handle the case where trashing files will fail because of the file system structure. On Windows 7, running the test will open a confirmation dialog as the implementation of IFileOperation doesn't respect the various flags. This might depend on the specific Windows 7 patch level, and the option to always use SHFileOperation on that platform needs to be evaluated further. [ChangeLog][QtCore][QFile] Introduce QFile::moveToTrash to allow applications to move files to the trash. Change-Id: I45019040c25b30f7db293b6933c63aca2f319514 Fixes: QTBUG-47703 Reviewed-by: Vitaly Fanaskov <vitaly.fanaskov@qt.io>
-rw-r--r--src/corelib/io/qfile.cpp57
-rw-r--r--src/corelib/io/qfile.h3
-rw-r--r--tests/auto/corelib/io/qfile/tst_qfile.cpp143
3 files changed, 203 insertions, 0 deletions
diff --git a/src/corelib/io/qfile.cpp b/src/corelib/io/qfile.cpp
index 5320ae2986..a516c6cd27 100644
--- a/src/corelib/io/qfile.cpp
+++ b/src/corelib/io/qfile.cpp
@@ -552,6 +552,63 @@ QFile::remove(const QString &fileName)
}
/*!
+ Moves the file specified by fileName() to the trash. Returns \c true if successful,
+ and sets the fileName() to the path at which the file can be found within the trash;
+ otherwise returns \c false.
+
+ \note On systems where the system API doesn't report the location of the file in the
+ trash, fileName() will be set to the null string once the file has been moved. On
+ systems that don't have a trash can, this function always returns false.
+*/
+bool
+QFile::moveToTrash()
+{
+ Q_D(QFile);
+ if (d->fileName.isEmpty() &&
+ !static_cast<QFSFileEngine *>(d->engine())->isUnnamedFile()) {
+ qWarning("QFile::remove: Empty or null file name");
+ return false;
+ }
+ unsetError();
+ close();
+ if (error() == QFile::NoError) {
+ QFileSystemEntry fileEntry(d->fileName);
+ QFileSystemEntry trashEntry;
+ QSystemError error;
+ if (QFileSystemEngine::moveFileToTrash(fileEntry, trashEntry, error)) {
+ setFileName(trashEntry.filePath());
+ unsetError();
+ return true;
+ }
+ d->setError(QFile::RenameError, error.toString());
+ }
+ return false;
+}
+
+/*!
+ \overload
+
+ Moves the file specified by fileName() to the trash. Returns \c true if successful,
+ and sets \a pathInTrash (if provided) to the path at which the file can be found within
+ the trash; otherwise returns \c false.
+
+ \note On systems where the system API doesn't report the path of the file in the
+ trash, \a pathInTrash will be set to the null string once the file has been moved.
+ On systems that don't have a trash can, this function always returns false.
+*/
+bool
+QFile::moveToTrash(const QString &fileName, QString *pathInTrash)
+{
+ QFile file(fileName);
+ if (file.moveToTrash()) {
+ if (pathInTrash)
+ *pathInTrash = file.fileName();
+ return true;
+ }
+ return false;
+}
+
+/*!
Renames the file currently specified by fileName() to \a newName.
Returns \c true if successful; otherwise returns \c false.
diff --git a/src/corelib/io/qfile.h b/src/corelib/io/qfile.h
index 2099b2852f..917fec4e1a 100644
--- a/src/corelib/io/qfile.h
+++ b/src/corelib/io/qfile.h
@@ -125,6 +125,9 @@ public:
bool remove();
static bool remove(const QString &fileName);
+ bool moveToTrash();
+ static bool moveToTrash(const QString &fileName, QString *pathInTrash = nullptr);
+
bool rename(const QString &newName);
static bool rename(const QString &oldName, const QString &newName);
diff --git a/tests/auto/corelib/io/qfile/tst_qfile.cpp b/tests/auto/corelib/io/qfile/tst_qfile.cpp
index 16a30af892..b065e1f10f 100644
--- a/tests/auto/corelib/io/qfile/tst_qfile.cpp
+++ b/tests/auto/corelib/io/qfile/tst_qfile.cpp
@@ -279,6 +279,9 @@ private slots:
void reuseQFile();
+ void moveToTrash_data();
+ void moveToTrash();
+
private:
#ifdef BUILTIN_TESTDATA
QSharedPointer<QTemporaryDir> m_dataDir;
@@ -3672,5 +3675,145 @@ void tst_QFile::reuseQFile()
}
}
+void tst_QFile::moveToTrash_data()
+{
+ QTest::addColumn<QString>("source");
+ QTest::addColumn<bool>("create");
+ QTest::addColumn<bool>("result");
+
+ if (QOperatingSystemVersion::current() <= QOperatingSystemVersion::Windows7)
+ QSKIP("Windows 7 insists on showing a confirmation dialog", SkipAll);
+
+ // success cases
+ {
+ QTemporaryFile temp;
+ QVERIFY(temp.open());
+ QTest::newRow("temporary file") << temp.fileName() << true << true;
+ }
+ {
+ QTemporaryDir tempDir;
+ tempDir.setAutoRemove(false);
+ QTest::newRow("temporary dir")
+ << tempDir.path() + QLatin1Char('/')
+ << true << true;
+ }
+ {
+ QTemporaryDir homeDir(QDir::homePath() + QLatin1String("/XXXXXX"));
+ homeDir.setAutoRemove(false);
+ QTemporaryFile homeFile(homeDir.path()
+ + QLatin1String("/tst_qfile-XXXXXX"));
+ homeFile.open();
+ QTest::newRow("home file")
+ << homeFile.fileName()
+ << true << true;
+
+ QTest::newRow("home dir")
+ << homeDir.path() + QLatin1Char('/')
+ << true << true;
+ }
+ QTest::newRow("relative") << QStringLiteral("tst_qfile_moveToTrash.tmp") << true << true;
+
+ // failure cases
+ QTest::newRow("root") << QDir::rootPath() << false << false;
+ QTest::newRow("no-such-file") << QString::fromLatin1("no/such/file") << false << false;
+}
+
+void tst_QFile::moveToTrash()
+{
+ QFETCH(QString, source);
+ QFETCH(bool, create);
+ QFETCH(bool, result);
+
+ /* This test makes assumptions about the file system layout
+ which might be wrong - moveToTrash may fail if the file lives
+ on a file system that is different from the home file system, and
+ has no .Trash directory.
+ */
+ const bool mayFail = QStorageInfo(source) != QStorageInfo(QDir::home());
+
+#if defined(Q_OS_WINRT)
+ QSKIP("WinRT does not have a trash", SkipAll);
+#endif
+
+ auto ensureFile = [](const QString &source, bool create) {
+ if (QFileInfo::exists(source) || !create)
+ return;
+ if (source.endsWith(QLatin1Char('/'))) {
+ QDir::root().mkdir(source);
+ QFile file(source + QLatin1String("test"));
+ if (!file.open(QIODevice::WriteOnly))
+ QSKIP("Couldn't create directory with file");
+ } else {
+ QFile sourceFile(source);
+ QVERIFY2(sourceFile.open(QFile::WriteOnly | QFile::Text), qPrintable(sourceFile.errorString()));
+ sourceFile.close();
+ }
+ };
+ auto cleanupFile = [source, create]() {
+ if (!QFileInfo::exists(source) || !create)
+ return;
+ if (source.endsWith(QLatin1Char('/'))) {
+ QDir(source).removeRecursively();
+ } else {
+ QFile sourceFile(source);
+ sourceFile.remove();
+ }
+ };
+ // non-static version
+ {
+ ensureFile(source, create);
+ QFile sourceFile(source);
+ const bool success = sourceFile.moveToTrash();
+
+ // tolerate moveToTrash failing
+ if (result && !success && mayFail)
+ result = false;
+
+ if (result) {
+ // if any of the test fails, we still want to remove the file
+ auto onFailure = qScopeGuard(cleanupFile);
+ QVERIFY2(success, qPrintable(sourceFile.errorString()));
+ QCOMPARE(sourceFile.error(), QFile::NoError);
+ QVERIFY(source != sourceFile.fileName());
+ if (!sourceFile.fileName().isEmpty()) {
+ QVERIFY2(sourceFile.exists(), qPrintable(sourceFile.fileName()));
+ // remove file/dir in trash as well, don't fill disk
+ if (source.endsWith(QLatin1Char('/')))
+ QDir(sourceFile.fileName()).removeRecursively();
+ else
+ sourceFile.remove();
+ }
+ } else {
+ QVERIFY(!success);
+ QVERIFY(!sourceFile.errorString().isEmpty());
+ QCOMPARE(source, sourceFile.fileName());
+ }
+ }
+
+ // don't retry
+ if (mayFail)
+ return;
+
+ // static version
+ {
+ ensureFile(source, create);
+ QString pathInTrash;
+ const bool success = QFile::moveToTrash(source, &pathInTrash);
+ QCOMPARE(success, result);
+ if (result) {
+ auto onFailure = qScopeGuard(cleanupFile);
+ QVERIFY(source != pathInTrash);
+ if (!pathInTrash.isEmpty()) {
+ // remove file/dir in trash as well, don't fill disk
+ QVERIFY2(QFile::exists(pathInTrash), qPrintable(pathInTrash));
+ if (source.endsWith(QLatin1Char('/')))
+ QDir(pathInTrash).removeRecursively();
+ else
+ QFile::remove(pathInTrash);
+ }
+ }
+ }
+}
+
QTEST_MAIN(tst_QFile)
#include "tst_qfile.moc"