diff --git a/kioslave/desktop/kio_desktop.cpp b/kioslave/desktop/kio_desktop.cpp index 06616d1ae..69d88b6aa 100644 --- a/kioslave/desktop/kio_desktop.cpp +++ b/kioslave/desktop/kio_desktop.cpp @@ -1,227 +1,235 @@ /* This file is part of the KDE project Copyright (C) 2008, 2009 Fredrik Höglund 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. */ #include "kio_desktop.h" #include #include #include #include #include #include #include #include #include #include #include #include extern "C" { int Q_DECL_EXPORT kdemain(int argc, char **argv) { // necessary to use other kio slaves QCoreApplication app(argc, argv); app.setApplicationName("kio_desktop"); // start the slave DesktopProtocol slave(argv[1], argv[2], argv[3]); slave.dispatchLoop(); return 0; } } DesktopProtocol::DesktopProtocol(const QByteArray& protocol, const QByteArray &pool, const QByteArray &app) : KIO::ForwardingSlaveBase(protocol, pool, app) { checkLocalInstall(); QDBusInterface kded(QStringLiteral("org.kde.kded5"), QStringLiteral("/kded"), QStringLiteral("org.kde.kded5")); kded.call(QStringLiteral("loadModule"), "desktopnotifier"); } DesktopProtocol::~DesktopProtocol() { } void DesktopProtocol::checkLocalInstall() { #ifndef Q_WS_WIN // We can't use QStandardPaths::writableLocation(QStandardPaths::DesktopLocation) here, since it returns the home dir // if the desktop folder doesn't exist. QString desktopPath = QDesktopServices::storageLocation(QDesktopServices::DesktopLocation); if (desktopPath.isEmpty()) desktopPath = QDir::homePath() + "/Desktop"; const QDir desktopDir(desktopPath); bool desktopIsEmpty; // Create the desktop folder if it doesn't exist if (!desktopDir.exists()) { ::mkdir(QFile::encodeName(desktopPath), S_IRWXU); desktopIsEmpty = true; } else desktopIsEmpty = desktopDir.entryList(QDir::AllEntries | QDir::Hidden | QDir::NoDotAndDotDot).isEmpty(); if (desktopIsEmpty) { // Copy the .directory file QFile::copy(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("kio_desktop/directory.desktop")), desktopPath + "/.directory"); // Copy the trash link QFile::copy(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("kio_desktop/directory.trash")), desktopPath + "/trash.desktop"); // Copy the desktop links QSet links; const auto dirs = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("kio_desktop/DesktopLinks"), QStandardPaths::LocateDirectory); for (const auto &dir : dirs) { const auto fileNames = QDir(dir).entryList({QStringLiteral("*.desktop")}); for (const auto &file : fileNames) { links += file; } } foreach (const QString &link, links) { const auto fullPath = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("kio_desktop/DesktopLinks/%1").arg(link)); KDesktopFile file(fullPath); if (!file.desktopGroup().readEntry("Hidden", false)) QFile::copy(fullPath, QStringLiteral("%1/%2").arg(desktopPath, link)); } } #endif } bool DesktopProtocol::rewriteUrl(const QUrl &url, QUrl &newUrl) { newUrl.setScheme(QStringLiteral("file")); - newUrl.setPath(QStandardPaths::writableLocation(QStandardPaths::DesktopLocation) + url.path()); + QString desktopPath = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation); + if (desktopPath.endsWith('/')) { + desktopPath.chop(1); + } + QString filePath = desktopPath + url.path(); + if (filePath.endsWith('/')) { + filePath.chop(1); // ForwardingSlaveBase always appends a '/' + } + newUrl.setPath(filePath); return true; } void DesktopProtocol::listDir(const QUrl &url) { KIO::ForwardingSlaveBase::listDir(url); QUrl actual; rewriteUrl(url, actual); QDBusInterface kded(QStringLiteral("org.kde.kded5"), QStringLiteral("/modules/desktopnotifier"), QStringLiteral("org.kde.DesktopNotifier")); kded.call(QStringLiteral("watchDir"), actual.path()); } QString DesktopProtocol::desktopFile(KIO::UDSEntry &entry) const { const QString name = entry.stringValue(KIO::UDSEntry::UDS_NAME); if (name == QLatin1String(".") || name == QLatin1String("..")) return QString(); QUrl url = processedUrl(); url.setPath(QStringLiteral("%1/%2").arg(url.path(), name)); if (entry.isDir()) { url.setPath(QStringLiteral("%1/.directory").arg(url.path())); if (!QFileInfo::exists(url.path())) return QString(); return url.path(); } if (KDesktopFile::isDesktopFile(url.path())) return url.path(); return QString(); } void DesktopProtocol::prepareUDSEntry(KIO::UDSEntry &entry, bool listing) const { ForwardingSlaveBase::prepareUDSEntry(entry, listing); const QString path = desktopFile(entry); if (!path.isEmpty()) { KDesktopFile file(path); const QString name = file.readName(); if (!name.isEmpty()) entry.insert(KIO::UDSEntry::UDS_DISPLAY_NAME, name); if (file.noDisplay() || !file.tryExec()) entry.insert(KIO::UDSEntry::UDS_HIDDEN, 1); } // Set a descriptive display name for the root item if (requestedUrl().path() == QLatin1String("/") && entry.stringValue(KIO::UDSEntry::UDS_NAME) == QLatin1String(".")) { entry.insert(KIO::UDSEntry::UDS_DISPLAY_NAME, i18n("Desktop Folder")); } // Set the target URL to the local path QUrl localUrl(QUrl::fromLocalFile(entry.stringValue(KIO::UDSEntry::UDS_LOCAL_PATH))); entry.insert(KIO::UDSEntry::UDS_TARGET_URL, localUrl.toString()); } void DesktopProtocol::rename(const QUrl &_src, const QUrl &_dest, KIO::JobFlags flags) { Q_UNUSED(flags) if (_src == _dest) { finished(); return; } QUrl src; rewriteUrl(_src, src); const QString srcPath = src.toLocalFile(); QUrl dest; rewriteUrl(_dest, dest); const QString destPath = dest.toLocalFile(); if (KDesktopFile::isDesktopFile(srcPath)) { QString friendlyName; if (destPath.endsWith(QLatin1String(".desktop"))) { const QString fileName = dest.fileName(); friendlyName = KIO::decodeFileName(fileName.left(fileName.length() - 8)); } else { friendlyName = KIO::decodeFileName(dest.fileName()); } // Update the value of the Name field in the file. KDesktopFile file(src.toLocalFile()); KConfigGroup cg(file.desktopGroup()); cg.writeEntry("Name", friendlyName); cg.writeEntry("Name", friendlyName, KConfigGroup::Persistent | KConfigGroup::Localized); cg.sync(); } if (QFile(srcPath).rename(destPath)) { #if KIO_VERSION >= QT_VERSION_CHECK(5, 20, 0) org::kde::KDirNotify::emitFileRenamedWithLocalPath(_src, _dest, destPath); #else org::kde::KDirNotify::emitFileRenamed(_src, _dest); #endif finished(); } else { error(KIO::ERR_CANNOT_RENAME, srcPath); } } diff --git a/kioslave/desktop/tests/CMakeLists.txt b/kioslave/desktop/tests/CMakeLists.txt index cb5f343de..1784f50ed 100644 --- a/kioslave/desktop/tests/CMakeLists.txt +++ b/kioslave/desktop/tests/CMakeLists.txt @@ -1,6 +1,6 @@ add_executable(testdesktop kio_desktop_test.cpp) -target_link_libraries(testdesktop KF5::KIOWidgets KF5::Solid Qt5::Test) +target_link_libraries(testdesktop KF5::KIOWidgets KF5::Solid Qt5::Test Qt5::DBus) ecm_mark_as_test(testdesktop) add_test(testdesktop testdesktop) diff --git a/kioslave/desktop/tests/kio_desktop_test.cpp b/kioslave/desktop/tests/kio_desktop_test.cpp index 0ea74d404..71ed2df42 100644 --- a/kioslave/desktop/tests/kio_desktop_test.cpp +++ b/kioslave/desktop/tests/kio_desktop_test.cpp @@ -1,165 +1,182 @@ /* This file is part of the KDE project Copyright (C) 2009 David Faure 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. */ #include #include #include +#include #include #include #include #include #include class TestDesktop : public QObject { Q_OBJECT public: TestDesktop() {} private Q_SLOTS: void initTestCase() { setenv( "KDE_FORK_SLAVES", "yes", true ); //make KIOs use test mode too setenv("KIOSLAVE_ENABLE_TESTMODE", "1", 1); QStandardPaths::setTestModeEnabled(true); // Warning: even with test mode enabled, this is the real user's Desktop directory m_desktopPath = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation); m_testFileName = QLatin1String("kio_desktop_test_file"); cleanupTestCase(); } void cleanupTestCase() { QFile::remove(m_desktopPath + '/' + m_testFileName); QFile::remove(m_desktopPath + '/' + m_testFileName + ".part"); QFile::remove(m_desktopPath + '/' + m_testFileName + "_link"); } void testCopyToDesktop() { QTemporaryFile tempFile; QVERIFY(tempFile.open()); tempFile.write( "Hello world\n", 12 ); QString fileName = tempFile.fileName(); tempFile.close(); KIO::Job* job = KIO::file_copy(QUrl::fromLocalFile(fileName), QUrl("desktop:/" + m_testFileName), -1, KIO::HideProgressInfo); job->setUiDelegate(0); QVERIFY(job->exec()); QVERIFY(QFile::exists(m_desktopPath + '/' + m_testFileName)); } void testMostLocalUrl() // relies on testCopyToDesktop being run before { const QUrl desktopUrl("desktop:/" + m_testFileName); const QString filePath(m_desktopPath + '/' + m_testFileName); KIO::StatJob* job = KIO::mostLocalUrl(desktopUrl, KIO::HideProgressInfo); QVERIFY(job); bool ok = job->exec(); QVERIFY(ok); QCOMPARE(job->mostLocalUrl().toLocalFile(), filePath); } void testCreateSymlink() { const QUrl desktopUrl("desktop:/" + m_testFileName); const QUrl desktopLink("desktop:/" + m_testFileName + "_link"); const QString source = m_desktopPath + '/' + m_testFileName; const QString localLink = source + "_link"; // Create a symlink using kio_desktop KIO::Job* linkJob = KIO::symlink(m_testFileName, desktopLink, KIO::HideProgressInfo); QVERIFY(linkJob->exec()); QVERIFY(QFileInfo(localLink).isSymLink()); QCOMPARE(QFileInfo(localLink).symLinkTarget(), source); // Now try changing the link target, without Overwrite -> error linkJob = KIO::symlink(m_testFileName + "2", desktopLink, KIO::HideProgressInfo); QVERIFY(!linkJob->exec()); QCOMPARE(linkJob->error(), (int)KIO::ERR_FILE_ALREADY_EXIST); #if KIO_VERSION >= QT_VERSION_CHECK(5, 31, 0) // fixed since 5.30.0, actually, but playing it safe for pre-5.30-users // Now try changing the link target, with Overwrite (bug 360487) linkJob = KIO::symlink(m_testFileName + "3", desktopLink, KIO::Overwrite | KIO::HideProgressInfo); QVERIFY(linkJob->exec()); QVERIFY(QFileInfo(localLink).isSymLink()); QCOMPARE(QFileInfo(localLink).symLinkTarget(), source + "3"); #else QSKIP("Skipping symlink+Overwrite test, fixed in KIO 5.30.0"); #endif } void testRename_data() { QTest::addColumn("withDirListerCache"); - QTest::addColumn("srcFile"); - QTest::addColumn("destFile"); - - const QString orig = "desktop:/" + m_testFileName; - const QString part = orig + ".part"; - QTest::newRow("from orig to .part") << false << orig << part; - QTest::newRow("from .part to orig") << false << part << orig; + QTest::addColumn("srcUrl"); + QTest::addColumn("destUrl"); + + const QString str = "desktop:/" + m_testFileName; + const QUrl orig(str); + const QUrl part(str + ".part"); + QTest::newRow("orig_to_part") << false << orig << part; + QTest::newRow("part_to_orig") << false << part << orig; // Warnings: all tests without dirlister cache should above this line // and all tests with it should be below - the cache stays forever once it exists. - QTest::newRow("from orig to .part, with cache") << true << orig << part; - QTest::newRow("from .part to orig, with cache (#218719)") << true << part << orig; + QTest::newRow("orig_to_part_with_cache") << true << orig << part; + QTest::newRow("part_to_orig_with_cache") << true << part << orig; // #218719 } void testRename() // relies on testCopyToDesktop being run before { QFETCH(bool, withDirListerCache); - QFETCH(QString, srcFile); - QFETCH(QString, destFile); + QFETCH(QUrl, srcUrl); + QFETCH(QUrl, destUrl); + QScopedPointer lister(nullptr); if (withDirListerCache) { - KDirLister lister; - lister.openUrl(QUrl(QStringLiteral("desktop:/"))); - QEventLoop eventLoop; - connect(&lister, static_cast(&KDirLister::completed), &eventLoop, &QEventLoop::quit); - eventLoop.exec(QEventLoop::ExcludeUserInputEvents); + lister.reset(new KDirLister); + lister->openUrl(QUrl(QStringLiteral("desktop:/"))); + QSignalSpy spyCompleted(lister.data(), static_cast(&KDirLister::completed)); + spyCompleted.wait(); } - const QUrl srcUrl = QUrl(srcFile); - const QUrl destUrl = QUrl(destFile); + org::kde::KDirNotify kdirnotify(QString(), QString(), QDBusConnection::sessionBus(), this); + QSignalSpy spyFilesAdded(&kdirnotify, &org::kde::KDirNotify::FilesAdded); + QSignalSpy spyFileRenamed(&kdirnotify, &org::kde::KDirNotify::FileRenamed); + QSignalSpy spyFileRenamedWithLocalPath(&kdirnotify, &org::kde::KDirNotify::FileRenamedWithLocalPath); const QString srcFilePath(m_desktopPath + srcUrl.path()); QVERIFY(QFile::exists(srcFilePath)); const QString destFilePath(m_desktopPath + destUrl.path()); QVERIFY(!QFile::exists(destFilePath)); - KIO::CopyJob* job = KIO::move(srcUrl, destUrl, KIO::HideProgressInfo); + KIO::Job* job = KIO::rename(srcUrl, destUrl, KIO::HideProgressInfo); job->setUiDelegate(0); QVERIFY(job); bool ok = job->exec(); QVERIFY(ok); QVERIFY(!QFile::exists(srcFilePath)); QVERIFY(QFile::exists(destFilePath)); + + // kio_desktop's rename() calls org::kde::KDirNotify::emitFileRenamedWithLocalPath + QTRY_COMPARE(spyFileRenamed.count(), 1); + QTRY_COMPARE(spyFileRenamedWithLocalPath.count(), 1); + // and then desktopnotifier notices something changed and emits KDirNotify::FilesAdded + QTRY_VERIFY(spyFilesAdded.count() >= 1); // can be 3, depending on kdirwatch's behaviour in desktopnotifier + + // check that KDirLister now has the correct item (#382341) + if (lister) { + const KFileItem fileItem = lister->findByUrl(destUrl); + QVERIFY(!fileItem.isNull()); + QCOMPARE(fileItem.targetUrl(), QUrl::fromLocalFile(destFilePath)); + } } private: QString m_desktopPath; QString m_testFileName; }; QTEST_GUILESS_MAIN(TestDesktop) #include "kio_desktop_test.moc"