diff --git a/autotests/kdirlistertest.cpp b/autotests/kdirlistertest.cpp index 4c511633..7aa2d2a3 100644 --- a/autotests/kdirlistertest.cpp +++ b/autotests/kdirlistertest.cpp @@ -1,1368 +1,1368 @@ /* This file is part of the KDE project Copyright (C) 2007 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 "kdirlistertest.h" #include #include #include QTEST_MAIN(KDirListerTest) #include #include "kiotesthelper.h" #include #include #include #include #include #define WORKAROUND_BROKEN_INOTIFY 0 void MyDirLister::handleError(KIO::Job *job) { // Currently we don't expect any errors. qCritical() << "KDirLister called handleError!" << job << job->error() << job->errorString(); qFatal("aborting"); } void KDirListerTest::initTestCase() { // To avoid a runtime dependency on klauncher qputenv("KDE_FORK_SLAVES", "yes"); // To avoid failing on broken locally defined mime types QStandardPaths::setTestModeEnabled(true); KIO::setDefaultJobUiDelegateExtension(nullptr); // no "skip" dialogs m_exitCount = 1; s_referenceTimeStamp = QDateTime::currentDateTime().addSecs(-120); // 2 minutes ago // Create test data: /* * PATH/toplevelfile_1 * PATH/toplevelfile_2 * PATH/toplevelfile_3 * PATH/subdir * PATH/subdir/testfile * PATH/subdir/subsubdir * PATH/subdir/subsubdir/testfile */ const QString path = m_tempDir.path() + '/'; createTestFile(path + "toplevelfile_1"); createTestFile(path + "toplevelfile_2"); createTestFile(path + "toplevelfile_3"); createTestDirectory(path + "subdir"); createTestDirectory(path + "subdir/subsubdir"); qRegisterMetaType > >(); } void KDirListerTest::cleanup() { m_dirLister.clearSpies(); disconnect(&m_dirLister, nullptr, this, nullptr); } void KDirListerTest::testOpenUrl() { m_items.clear(); const QString path = m_tempDir.path() + '/'; connect(&m_dirLister, &KCoreDirLister::newItems, this, &KDirListerTest::slotNewItems); // The call to openUrl itself, emits started m_dirLister.openUrl(QUrl::fromLocalFile(path), KDirLister::NoFlags); QCOMPARE(m_dirLister.spyStarted.count(), 1); QCOMPARE(m_dirLister.spyCompleted.count(), 0); QCOMPARE(m_dirLister.spyCompletedQUrl.count(), 0); QCOMPARE(m_dirLister.spyCanceled.count(), 0); QCOMPARE(m_dirLister.spyCanceledQUrl.count(), 0); QCOMPARE(m_dirLister.spyClear.count(), 1); QCOMPARE(m_dirLister.spyClearQUrl.count(), 0); QCOMPARE(m_dirLister.spyRedirection.count(), 0); QCOMPARE(m_items.count(), 0); QVERIFY(!m_dirLister.isFinished()); // then wait for completed qDebug("waiting for completed"); QTRY_COMPARE(m_dirLister.spyStarted.count(), 1); QTRY_COMPARE(m_dirLister.spyCompleted.count(), 1); QCOMPARE(m_dirLister.spyCompletedQUrl.count(), 1); QCOMPARE(m_dirLister.spyCanceled.count(), 0); QCOMPARE(m_dirLister.spyCanceledQUrl.count(), 0); QCOMPARE(m_dirLister.spyClear.count(), 1); QCOMPARE(m_dirLister.spyClearQUrl.count(), 0); QCOMPARE(m_dirLister.spyRedirection.count(), 0); //qDebug() << m_items; //qDebug() << "In dir" << QDir(path).entryList( QDir::AllEntries | QDir::NoDotAndDotDot); QCOMPARE(m_items.count(), fileCount()); QVERIFY(m_dirLister.isFinished()); disconnect(&m_dirLister, nullptr, this, nullptr); const QString fileName = QStringLiteral("toplevelfile_3"); const QUrl itemUrl = QUrl::fromLocalFile(path + fileName); KFileItem byName = m_dirLister.findByName(fileName); QVERIFY(!byName.isNull()); QCOMPARE(byName.url().toString(), itemUrl.toString()); QCOMPARE(byName.entry().stringValue(KIO::UDSEntry::UDS_NAME), fileName); KFileItem byUrl = m_dirLister.findByUrl(itemUrl); QVERIFY(!byUrl.isNull()); QCOMPARE(byUrl.url().toString(), itemUrl.toString()); QCOMPARE(byUrl.entry().stringValue(KIO::UDSEntry::UDS_NAME), fileName); KFileItem itemForUrl = KDirLister::cachedItemForUrl(itemUrl); QVERIFY(!itemForUrl.isNull()); QCOMPARE(itemForUrl.url().toString(), itemUrl.toString()); QCOMPARE(itemForUrl.entry().stringValue(KIO::UDSEntry::UDS_NAME), fileName); KFileItem rootByUrl = m_dirLister.findByUrl(QUrl::fromLocalFile(path)); QVERIFY(!rootByUrl.isNull()); QCOMPARE(QString(rootByUrl.url().toLocalFile() + '/'), path); m_dirLister.clearSpies(); // for the tests that call testOpenUrl for setup } // This test assumes testOpenUrl was run before. So m_dirLister is holding the items already. void KDirListerTest::testOpenUrlFromCache() { // Do the same again, it should behave the same, even with the items in the cache testOpenUrl(); // Get into the case where another dirlister is holding the items { m_items.clear(); const QString path = m_tempDir.path() + '/'; MyDirLister secondDirLister; connect(&secondDirLister, &KCoreDirLister::newItems, this, &KDirListerTest::slotNewItems); secondDirLister.openUrl(QUrl::fromLocalFile(path), KDirLister::NoFlags); QCOMPARE(secondDirLister.spyStarted.count(), 1); QCOMPARE(secondDirLister.spyCompleted.count(), 0); QCOMPARE(secondDirLister.spyCompletedQUrl.count(), 0); QCOMPARE(secondDirLister.spyCanceled.count(), 0); QCOMPARE(secondDirLister.spyCanceledQUrl.count(), 0); QCOMPARE(secondDirLister.spyClear.count(), 1); QCOMPARE(secondDirLister.spyClearQUrl.count(), 0); QCOMPARE(m_items.count(), 0); QVERIFY(!secondDirLister.isFinished()); // then wait for completed qDebug("waiting for completed"); QTRY_COMPARE(secondDirLister.spyStarted.count(), 1); QTRY_COMPARE(secondDirLister.spyCompleted.count(), 1); QCOMPARE(secondDirLister.spyCompletedQUrl.count(), 1); QCOMPARE(secondDirLister.spyCanceled.count(), 0); QCOMPARE(secondDirLister.spyCanceledQUrl.count(), 0); QCOMPARE(secondDirLister.spyClear.count(), 1); QCOMPARE(secondDirLister.spyClearQUrl.count(), 0); QCOMPARE(m_items.count(), 4); QVERIFY(secondDirLister.isFinished()); } disconnect(&m_dirLister, nullptr, this, nullptr); } // This test assumes testOpenUrl was run before. So m_dirLister is holding the items already. // This test creates 1 file in the temporary directory void KDirListerTest::testNewItem() { QCOMPARE(m_items.count(), 4); const QString path = m_tempDir.path() + '/'; connect(&m_dirLister, &KCoreDirLister::newItems, this, &KDirListerTest::slotNewItems); qDebug() << "Creating a new file"; const QString fileName = QStringLiteral("toplevelfile_new"); createSimpleFile(path + fileName); QTRY_COMPARE(m_items.count(), 5); QCOMPARE(m_dirLister.spyStarted.count(), 1); // Updates call started QCOMPARE(m_dirLister.spyCompleted.count(), 1); // and completed QCOMPARE(m_dirLister.spyCompletedQUrl.count(), 1); QCOMPARE(m_dirLister.spyCanceled.count(), 0); QCOMPARE(m_dirLister.spyCanceledQUrl.count(), 0); QCOMPARE(m_dirLister.spyClear.count(), 0); QCOMPARE(m_dirLister.spyClearQUrl.count(), 0); const QUrl itemUrl = QUrl::fromLocalFile(path + fileName); KFileItem itemForUrl = KDirLister::cachedItemForUrl(itemUrl); QVERIFY(!itemForUrl.isNull()); QCOMPARE(itemForUrl.url().toString(), itemUrl.toString()); QCOMPARE(itemForUrl.entry().stringValue(KIO::UDSEntry::UDS_NAME), fileName); disconnect(&m_dirLister, nullptr, this, nullptr); } // This test assumes testNewItem was run before. So m_dirLister is holding the items already. // This test creates 100 more files in the temporary directory in reverse order void KDirListerTest::testNewItems() { QCOMPARE(m_items.count(), 5); connect(&m_dirLister, &KCoreDirLister::newItems, this, &KDirListerTest::slotNewItems); const QString path = m_tempDir.path() + '/'; qDebug() << "Creating 100 new files"; for (int i = 50; i > 0; i--) { createSimpleFile(path + QString("toplevelfile_new_%1").arg(i)); } QTest::qWait(1000); // Create them with 1s difference for (int i = 100; i > 50; i--) { createSimpleFile(path + QString("toplevelfile_new_%1").arg(i)); } // choose one of the new created files const QString fileName = QStringLiteral("toplevelfile_new_50"); QTRY_COMPARE(m_items.count(), 105); QVERIFY(m_dirLister.spyStarted.count() > 0 && m_dirLister.spyStarted.count() < 3); // Updates call started, probably twice QVERIFY(m_dirLister.spyCompleted.count() > 0 && m_dirLister.spyCompleted.count() < 3); // and completed, probably twice QVERIFY(m_dirLister.spyCompletedQUrl.count() < 3); QCOMPARE(m_dirLister.spyCanceled.count(), 0); QCOMPARE(m_dirLister.spyCanceledQUrl.count(), 0); QCOMPARE(m_dirLister.spyClear.count(), 0); QCOMPARE(m_dirLister.spyClearQUrl.count(), 0); const QUrl itemUrl = QUrl::fromLocalFile(path + fileName); KFileItem itemForUrl = KDirLister::cachedItemForUrl(itemUrl); QVERIFY(!itemForUrl.isNull()); QCOMPARE(itemForUrl.url().toString(), itemUrl.toString()); QCOMPARE(itemForUrl.entry().stringValue(KIO::UDSEntry::UDS_NAME), fileName); } void KDirListerTest::benchFindByUrl() { // The time used should be in the order of O(100*log2(100)) const QString path = m_tempDir.path() + '/'; QBENCHMARK { for (int i = 100; i > 0; i--) { KFileItem cachedItem = m_dirLister.findByUrl(QUrl::fromLocalFile(path + QString("toplevelfile_new_%1").arg(i))); QVERIFY(!cachedItem.isNull()); } } } void KDirListerTest::testNewItemByCopy() { // This test creates a file using KIO::copyAs, like knewmenu.cpp does. // Useful for testing #192185, i.e. whether we catch the kdirwatch event and avoid // a KFileItem::refresh(). const int origItemCount = m_items.count(); const QString path = m_tempDir.path() + '/'; connect(&m_dirLister, &KCoreDirLister::newItems, this, &KDirListerTest::slotNewItems); QTest::qWait(1000); // We need a 1s timestamp difference on the dir, otherwise FAM won't notice anything. const QString fileName = QStringLiteral("toplevelfile_copy"); const QUrl itemUrl = QUrl::fromLocalFile(path + fileName); KIO::CopyJob *job = KIO::copyAs(QUrl::fromLocalFile(path + "toplevelfile_3"), itemUrl, KIO::HideProgressInfo); job->exec(); // Give time for KDirWatch/KDirNotify to notify us QTRY_COMPARE(m_items.count(), origItemCount + 1); QCOMPARE(m_dirLister.spyStarted.count(), 1); // Updates call started QCOMPARE(m_dirLister.spyCompleted.count(), 1); // and completed QCOMPARE(m_dirLister.spyCompletedQUrl.count(), 1); QCOMPARE(m_dirLister.spyCanceled.count(), 0); QCOMPARE(m_dirLister.spyCanceledQUrl.count(), 0); QCOMPARE(m_dirLister.spyClear.count(), 0); QCOMPARE(m_dirLister.spyClearQUrl.count(), 0); // Give some time to KDirWatch QTest::qWait(1000); KFileItem itemForUrl = KDirLister::cachedItemForUrl(itemUrl); QVERIFY(!itemForUrl.isNull()); QCOMPARE(itemForUrl.url().toString(), itemUrl.toString()); QCOMPARE(itemForUrl.entry().stringValue(KIO::UDSEntry::UDS_NAME), fileName); } void KDirListerTest::testNewItemsInSymlink() // #213799 { const int origItemCount = m_items.count(); QCOMPARE(fileCount(), origItemCount); const QString path = m_tempDir.path() + '/'; QTemporaryFile tempFile; QVERIFY(tempFile.open()); const QString symPath = tempFile.fileName() + "_link"; tempFile.close(); const bool symlinkOk = KIOPrivate::createSymlink(path, symPath); if (!symlinkOk) { const QString error = QString::fromLatin1("Failed to create symlink '%1' pointing to '%2': %3") .arg(symPath, path, QString::fromLocal8Bit(strerror(errno))); QVERIFY2(symlinkOk, qPrintable(error)); } MyDirLister dirLister2; m_items2.clear(); connect(&dirLister2, &KCoreDirLister::newItems, this, &KDirListerTest::slotNewItems2); connect(&m_dirLister, &KCoreDirLister::newItems, this, &KDirListerTest::slotNewItems); // The initial listing dirLister2.openUrl(QUrl::fromLocalFile(symPath), KDirLister::NoFlags); QTRY_COMPARE(m_items.count(), origItemCount); QTRY_VERIFY(dirLister2.isFinished()); QTest::qWait(1000); // We need a 1s timestamp difference on the dir, otherwise FAM won't notice anything. qDebug() << "Creating new file"; const QString fileName = QStringLiteral("toplevelfile_newinlink"); createSimpleFile(path + fileName); #if WORKAROUND_BROKEN_INOTIFY org::kde::KDirNotify::emitFilesAdded(path); #endif // Give time for KDirWatch to notify us QTRY_COMPARE(m_items2.count(), origItemCount + 1); QTRY_COMPARE(m_items.count(), origItemCount + 1); // Now create an item using the symlink-path const QString fileName2 = QStringLiteral("toplevelfile_newinlink2"); { createSimpleFile(path + fileName2); // Give time for KDirWatch to notify us QTRY_COMPARE(m_items2.count(), origItemCount + 2); QTRY_COMPARE(m_items.count(), origItemCount + 2); } QCOMPARE(fileCount(), m_items.count()); // Test file deletion { qDebug() << "Deleting" << (path + fileName); QTest::qWait(1000); // for timestamp difference QFile::remove(path + fileName); QTRY_COMPARE(dirLister2.spyItemsDeleted.count(), 1); const KFileItem item = dirLister2.spyItemsDeleted[0][0].value().at(0); QCOMPARE(item.url().toLocalFile(), QString(symPath + '/' + fileName)); } // TODO: test file update. disconnect(&m_dirLister, nullptr, this, nullptr); QFile::remove(symPath); } // This test assumes testOpenUrl was run before. So m_dirLister is holding the items already. // Modifies one of the files to have html content void KDirListerTest::testRefreshItems() { m_refreshedItems.clear(); const QString path = m_tempDir.path() + '/'; const QString fileName = path + "toplevelfile_1"; KFileItem cachedItem = m_dirLister.findByUrl(QUrl::fromLocalFile(fileName)); QVERIFY(!cachedItem.isNull()); QCOMPARE(cachedItem.mimetype(), QString("application/octet-stream")); connect(&m_dirLister, &KCoreDirLister::refreshItems, this, &KDirListerTest::slotRefreshItems); QFile file(fileName); QVERIFY(file.open(QIODevice::Append)); file.write(QByteArray("")); file.close(); QCOMPARE(QFileInfo(fileName).size(), 11LL /*Hello world*/ + 6 /**/); QTRY_VERIFY(!m_refreshedItems.isEmpty()); QCOMPARE(m_dirLister.spyStarted.count(), 0); // fast path: no directory listing needed QVERIFY(m_dirLister.spyCompleted.count() < 2); QVERIFY(m_dirLister.spyCompletedQUrl.count() < 2); QCOMPARE(m_dirLister.spyCanceled.count(), 0); QCOMPARE(m_dirLister.spyCanceledQUrl.count(), 0); QCOMPARE(m_dirLister.spyClear.count(), 0); QCOMPARE(m_dirLister.spyClearQUrl.count(), 0); QCOMPARE(m_refreshedItems.count(), 1); QPair entry = m_refreshedItems.first(); QCOMPARE(entry.first.url().toLocalFile(), fileName); QCOMPARE(entry.first.size(), KIO::filesize_t(11)); QCOMPARE(entry.first.mimetype(), QString("application/octet-stream")); QCOMPARE(entry.second.url().toLocalFile(), fileName); QCOMPARE(entry.second.size(), KIO::filesize_t(11 /*Hello world*/ + 6 /**/)); QCOMPARE(entry.second.mimetype(), QString("text/html")); // Let's see what KDirLister has in cache now cachedItem = m_dirLister.findByUrl(QUrl::fromLocalFile(fileName)); QCOMPARE(cachedItem.size(), KIO::filesize_t(11 /*Hello world*/ + 6 /**/)); m_refreshedItems.clear(); } // Refresh the root item, plus a hidden file, e.g. changing its icon. #190535 void KDirListerTest::testRefreshRootItem() { // This test assumes testOpenUrl was run before. So m_dirLister is holding the items already. m_refreshedItems.clear(); m_refreshedItems2.clear(); // The item will be the root item of dirLister2, but also a child item // of m_dirLister. // In #190535 it would show "." instead of the subdir name, after a refresh... const QString path = m_tempDir.path() + '/' + "subdir"; MyDirLister dirLister2; fillDirLister2(dirLister2, path); // Change the subdir by creating a file in it waitUntilMTimeChange(path); const QString foobar = path + "/.foobar"; createSimpleFile(foobar); connect(&m_dirLister, &KCoreDirLister::refreshItems, this, &KDirListerTest::slotRefreshItems); // Arguably, the mtime change of "subdir" should lead to a refreshItem of subdir in the root dir. // So the next line shouldn't be necessary, if KDirLister did this correctly. This isn't what this test is about though. org::kde::KDirNotify::emitFilesChanged(QList() << QUrl::fromLocalFile(path)); QTRY_VERIFY(!m_refreshedItems.isEmpty()); QCOMPARE(m_dirLister.spyStarted.count(), 0); QCOMPARE(m_dirLister.spyCompleted.count(), 0); QCOMPARE(m_dirLister.spyCompletedQUrl.count(), 0); QCOMPARE(m_dirLister.spyCanceled.count(), 0); QCOMPARE(m_dirLister.spyCanceledQUrl.count(), 0); QCOMPARE(m_dirLister.spyClear.count(), 0); QCOMPARE(m_dirLister.spyClearQUrl.count(), 0); QCOMPARE(m_refreshedItems.count(), 1); QPair entry = m_refreshedItems.first(); QCOMPARE(entry.first.url().toLocalFile(), path); QCOMPARE(entry.first.name(), QString("subdir")); QCOMPARE(entry.second.url().toLocalFile(), path); QCOMPARE(entry.second.name(), QString("subdir")); QCOMPARE(m_refreshedItems2.count(), 1); entry = m_refreshedItems2.first(); QCOMPARE(entry.first.url().toLocalFile(), path); QCOMPARE(entry.second.url().toLocalFile(), path); // item name() doesn't matter here, it's the root item. m_refreshedItems.clear(); m_refreshedItems2.clear(); waitUntilMTimeChange(path); const QString directoryFile = path + "/.directory"; createSimpleFile(directoryFile); org::kde::KDirNotify::emitFilesAdded(QUrl::fromLocalFile(path)); QTest::qWait(200); // The order of these two is not deterministic org::kde::KDirNotify::emitFilesChanged(QList() << QUrl::fromLocalFile(directoryFile)); org::kde::KDirNotify::emitFilesChanged(QList() << QUrl::fromLocalFile(path)); QTRY_VERIFY(!m_refreshedItems.isEmpty()); QCOMPARE(m_refreshedItems.count(), 1); entry = m_refreshedItems.first(); QCOMPARE(entry.first.url().toLocalFile(), path); QCOMPARE(entry.second.url().toLocalFile(), path); m_refreshedItems.clear(); m_refreshedItems2.clear(); // Note: this test leaves the .directory file as a side effect. // Hidden though, shouldn't matter. } void KDirListerTest::testDeleteItem() { testOpenUrl(); // ensure m_items is uptodate const int origItemCount = m_items.count(); QCOMPARE(fileCount(), origItemCount); const QString path = m_tempDir.path() + '/'; //qDebug() << "Removing " << path+"toplevelfile_new"; QFile::remove(path + QString("toplevelfile_new")); // the remove() doesn't always trigger kdirwatch in stat mode, if this all happens in the same second KDirWatch::self()->setDirty(path); // The signal should be emitted once with the deleted file QTRY_COMPARE(m_dirLister.spyItemsDeleted.count(), 1); // OK now kdirlister told us the file was deleted, let's try a re-listing m_items.clear(); connect(&m_dirLister, &KCoreDirLister::newItems, this, &KDirListerTest::slotNewItems); m_dirLister.openUrl(QUrl::fromLocalFile(path), KDirLister::NoFlags); QVERIFY(!m_dirLister.isFinished()); QTRY_COMPARE(m_items.count(), origItemCount - 1); QVERIFY(m_dirLister.isFinished()); disconnect(&m_dirLister, nullptr, this, nullptr); QCOMPARE(fileCount(), m_items.count()); } void KDirListerTest::testDeleteItems() { testOpenUrl(); // ensure m_items is uptodate const int origItemCount = m_items.count(); QCOMPARE(fileCount(), origItemCount); const QString path = m_tempDir.path() + '/'; qDebug() << "Removing 100 files from " << path; for (int i=0; i <= 100; ++i) { QFile::remove(path + QString("toplevelfile_new_%1").arg(i)); } // the remove() doesn't always trigger kdirwatch in stat mode, if this all happens in the same second KDirWatch::self()->setDirty(path); // The signal could be emitted 1 time with all the deleted files or more times QTRY_VERIFY(m_dirLister.spyItemsDeleted.count() > 0); // OK now kdirlister told us the file was deleted, let's try a re-listing m_items.clear(); connect(&m_dirLister, &KCoreDirLister::newItems, this, &KDirListerTest::slotNewItems); m_dirLister.openUrl(QUrl::fromLocalFile(path), KDirLister::NoFlags); QTRY_COMPARE(m_items.count(), origItemCount - 100); QVERIFY(m_dirLister.isFinished()); disconnect(&m_dirLister, nullptr, this, nullptr); QCOMPARE(fileCount(), m_items.count()); } void KDirListerTest::testRenameItem() { m_refreshedItems2.clear(); const QString dirPath = m_tempDir.path() + '/'; connect(&m_dirLister, &KCoreDirLister::refreshItems, this, &KDirListerTest::slotRefreshItems2); const QString path = dirPath + "toplevelfile_2"; const QString newPath = dirPath + "toplevelfile_2.renamed.html"; KIO::SimpleJob *job = KIO::rename(QUrl::fromLocalFile(path), QUrl::fromLocalFile(newPath), KIO::HideProgressInfo); QVERIFY(job->exec()); - QSignalSpy spyRefreshItems(&m_dirLister, SIGNAL(refreshItems(QList >))); + QSignalSpy spyRefreshItems(&m_dirLister, SIGNAL(refreshItems(QList>))); QVERIFY(spyRefreshItems.wait(2000)); QTRY_COMPARE(m_refreshedItems2.count(), 1); QPair entry = m_refreshedItems2.first(); QCOMPARE(entry.first.url().toLocalFile(), path); QCOMPARE(entry.first.mimetype(), QString("application/octet-stream")); QCOMPARE(entry.second.url().toLocalFile(), newPath); QCOMPARE(entry.second.mimetype(), QString("text/html")); disconnect(&m_dirLister, nullptr, this, nullptr); // Let's see what KDirLister has in cache now KFileItem cachedItem = m_dirLister.findByUrl(QUrl::fromLocalFile(newPath)); QVERIFY(!cachedItem.isNull()); QCOMPARE(cachedItem.url().toLocalFile(), newPath); KFileItem oldCachedItem = m_dirLister.findByUrl(QUrl::fromLocalFile(path)); QVERIFY(oldCachedItem.isNull()); m_refreshedItems2.clear(); } void KDirListerTest::testRenameAndOverwrite() // has to be run after testRenameItem { // Rename toplevelfile_2.renamed.html to toplevelfile_2, overwriting it. const QString dirPath = m_tempDir.path() + '/'; const QString path = dirPath + "toplevelfile_2"; createTestFile(path); #if WORKAROUND_BROKEN_INOTIFY org::kde::KDirNotify::emitFilesAdded(dirPath); #endif KFileItem existingItem; while (existingItem.isNull()) { QTest::qWait(100); existingItem = m_dirLister.findByUrl(QUrl::fromLocalFile(path)); }; QCOMPARE(existingItem.url().toLocalFile(), path); m_refreshedItems.clear(); connect(&m_dirLister, &KCoreDirLister::refreshItems, this, &KDirListerTest::slotRefreshItems); const QString newPath = dirPath + "toplevelfile_2.renamed.html"; KIO::SimpleJob *job = KIO::rename(QUrl::fromLocalFile(newPath), QUrl::fromLocalFile(path), KIO::Overwrite | KIO::HideProgressInfo); bool ok = job->exec(); QVERIFY(ok); if (m_refreshedItems.isEmpty()) { QTRY_VERIFY(!m_refreshedItems.isEmpty()); // could come from KDirWatch or KDirNotify. } // Check that itemsDeleted was emitted -- preferably BEFORE refreshItems, // but we can't easily check that with QSignalSpy... QCOMPARE(m_dirLister.spyItemsDeleted.count(), 1); QCOMPARE(m_refreshedItems.count(), 1); QPair entry = m_refreshedItems.first(); QCOMPARE(entry.first.url().toLocalFile(), newPath); QCOMPARE(entry.second.url().toLocalFile(), path); disconnect(&m_dirLister, nullptr, this, nullptr); // Let's see what KDirLister has in cache now KFileItem cachedItem = m_dirLister.findByUrl(QUrl::fromLocalFile(path)); QCOMPARE(cachedItem.url().toLocalFile(), path); KFileItem oldCachedItem = m_dirLister.findByUrl(QUrl::fromLocalFile(newPath)); QVERIFY(oldCachedItem.isNull()); m_refreshedItems.clear(); } void KDirListerTest::testConcurrentListing() { const int origItemCount = m_items.count(); QCOMPARE(fileCount(), origItemCount); m_items.clear(); m_items2.clear(); MyDirLister dirLister2; const QString path = m_tempDir.path() + '/'; connect(&m_dirLister, &KCoreDirLister::newItems, this, &KDirListerTest::slotNewItems); connect(&dirLister2, &KCoreDirLister::newItems, this, &KDirListerTest::slotNewItems2); // Before dirLister2 has time to emit the items, let's make m_dirLister move to another dir. // This reproduces the use case "clicking on a folder in dolphin iconview, and dirlister2 // is the one used by the "folder panel". m_dirLister is going to list the subdir, // while dirLister2 wants to list the folder that m_dirLister has just left. dirLister2.stop(); // like dolphin does, noop. dirLister2.openUrl(QUrl::fromLocalFile(path), KDirLister::NoFlags); m_dirLister.openUrl(QUrl::fromLocalFile(path + "subdir"), KDirLister::NoFlags); QCOMPARE(m_dirLister.spyStarted.count(), 1); QCOMPARE(m_dirLister.spyCompleted.count(), 0); QCOMPARE(m_dirLister.spyCompletedQUrl.count(), 0); QCOMPARE(m_dirLister.spyCanceled.count(), 0); QCOMPARE(m_dirLister.spyCanceledQUrl.count(), 0); QCOMPARE(m_dirLister.spyClear.count(), 1); QCOMPARE(m_dirLister.spyClearQUrl.count(), 0); QCOMPARE(m_items.count(), 0); QCOMPARE(dirLister2.spyStarted.count(), 1); QCOMPARE(dirLister2.spyCompleted.count(), 0); QCOMPARE(dirLister2.spyCompletedQUrl.count(), 0); QCOMPARE(dirLister2.spyCanceled.count(), 0); QCOMPARE(dirLister2.spyCanceledQUrl.count(), 0); QCOMPARE(dirLister2.spyClear.count(), 1); QCOMPARE(dirLister2.spyClearQUrl.count(), 0); QCOMPARE(m_items2.count(), 0); QVERIFY(!m_dirLister.isFinished()); QVERIFY(!dirLister2.isFinished()); // then wait for completed qDebug("waiting for completed"); //QCOMPARE(m_dirLister.spyStarted.count(), 1); // 2 when subdir is already in cache. QTRY_COMPARE(m_dirLister.spyCompleted.count(), 1); QCOMPARE(m_dirLister.spyCompletedQUrl.count(), 1); QCOMPARE(m_dirLister.spyCanceled.count(), 0); QCOMPARE(m_dirLister.spyCanceledQUrl.count(), 0); QCOMPARE(m_dirLister.spyClear.count(), 1); QCOMPARE(m_dirLister.spyClearQUrl.count(), 0); QCOMPARE(m_items.count(), 3); QTRY_COMPARE(dirLister2.spyStarted.count(), 1); QTRY_COMPARE(dirLister2.spyCompleted.count(), 1); QCOMPARE(dirLister2.spyCompletedQUrl.count(), 1); QCOMPARE(dirLister2.spyCanceled.count(), 0); QCOMPARE(dirLister2.spyCanceledQUrl.count(), 0); QCOMPARE(dirLister2.spyClear.count(), 1); QCOMPARE(dirLister2.spyClearQUrl.count(), 0); QCOMPARE(m_items2.count(), origItemCount); if (!m_dirLister.isFinished()) { // false when an update is running because subdir is already in cache // TODO check why this fails QVERIFY(m_dirLister.spyCanceled.wait(1000)); QTest::qWait(1000); } disconnect(&m_dirLister, nullptr, this, nullptr); disconnect(&dirLister2, nullptr, this, nullptr); } void KDirListerTest::testConcurrentHoldingListing() { // #167851. // A dirlister holding the items, and a second dirlister does // openUrl(reload) (which triggers updateDirectory()) // and the first lister immediately does openUrl() (which emits cached items). testOpenUrl(); // ensure m_dirLister holds the items. const int origItemCount = m_items.count(); connect(&m_dirLister, &KCoreDirLister::newItems, this, &KDirListerTest::slotNewItems); m_items.clear(); m_items2.clear(); const QString path = m_tempDir.path() + '/'; MyDirLister dirLister2; connect(&dirLister2, &KCoreDirLister::newItems, this, &KDirListerTest::slotNewItems2); dirLister2.openUrl(QUrl::fromLocalFile(path), KDirLister::Reload); // will start a list job QCOMPARE(dirLister2.spyStarted.count(), 1); QCOMPARE(dirLister2.spyCompleted.count(), 0); QCOMPARE(m_items.count(), 0); QCOMPARE(m_items2.count(), 0); qDebug("calling m_dirLister.openUrl"); m_dirLister.openUrl(QUrl::fromLocalFile(path), KDirLister::NoFlags); // should emit cached items, and then "join" the running listjob QCOMPARE(m_dirLister.spyStarted.count(), 1); QCOMPARE(m_dirLister.spyCompleted.count(), 0); QCOMPARE(m_items.count(), 0); QCOMPARE(m_items2.count(), 0); qDebug("waiting for completed"); QTRY_COMPARE(dirLister2.spyStarted.count(), 1); QTRY_COMPARE(dirLister2.spyCompleted.count(), 1); QCOMPARE(dirLister2.spyCompletedQUrl.count(), 1); QCOMPARE(dirLister2.spyCanceled.count(), 0); QCOMPARE(dirLister2.spyCanceledQUrl.count(), 0); QCOMPARE(dirLister2.spyClear.count(), 1); QCOMPARE(dirLister2.spyClearQUrl.count(), 0); QCOMPARE(m_items2.count(), origItemCount); QTRY_COMPARE(m_dirLister.spyStarted.count(), 1); QTRY_COMPARE(m_dirLister.spyCompleted.count(), 1); QCOMPARE(m_dirLister.spyCompletedQUrl.count(), 1); QCOMPARE(m_dirLister.spyCanceled.count(), 0); QCOMPARE(m_dirLister.spyCanceledQUrl.count(), 0); QCOMPARE(m_dirLister.spyClear.count(), 1); QCOMPARE(m_dirLister.spyClearQUrl.count(), 0); QVERIFY(dirLister2.isFinished()); QVERIFY(m_dirLister.isFinished()); disconnect(&m_dirLister, nullptr, this, nullptr); QCOMPARE(m_items.count(), origItemCount); } void KDirListerTest::testConcurrentListingAndStop() { m_items.clear(); m_items2.clear(); MyDirLister dirLister2; // Use a new tempdir for this test, so that we don't use the cache at all. QTemporaryDir tempDir; const QString path = tempDir.path() + '/'; createTestFile(path + "file_1"); createTestFile(path + "file_2"); createTestFile(path + "file_3"); connect(&m_dirLister, &KCoreDirLister::newItems, this, &KDirListerTest::slotNewItems); connect(&dirLister2, &KCoreDirLister::newItems, this, &KDirListerTest::slotNewItems2); // Before m_dirLister has time to emit the items, let's make dirLister2 call stop(). // This should not stop the list job for m_dirLister (#267709). dirLister2.openUrl(QUrl::fromLocalFile(path), KDirLister::Reload); m_dirLister.openUrl(QUrl::fromLocalFile(path)/*, KDirLister::Reload*/); QCOMPARE(m_dirLister.spyStarted.count(), 1); QCOMPARE(m_dirLister.spyCompleted.count(), 0); QCOMPARE(m_dirLister.spyCompletedQUrl.count(), 0); QCOMPARE(m_dirLister.spyCanceled.count(), 0); QCOMPARE(m_dirLister.spyCanceledQUrl.count(), 0); QCOMPARE(m_dirLister.spyClear.count(), 1); QCOMPARE(m_dirLister.spyClearQUrl.count(), 0); QCOMPARE(m_items.count(), 0); QCOMPARE(dirLister2.spyStarted.count(), 1); QCOMPARE(dirLister2.spyCompleted.count(), 0); QCOMPARE(dirLister2.spyCompletedQUrl.count(), 0); QCOMPARE(dirLister2.spyCanceled.count(), 0); QCOMPARE(dirLister2.spyCanceledQUrl.count(), 0); QCOMPARE(dirLister2.spyClear.count(), 1); QCOMPARE(dirLister2.spyClearQUrl.count(), 0); QCOMPARE(m_items2.count(), 0); QVERIFY(!m_dirLister.isFinished()); QVERIFY(!dirLister2.isFinished()); dirLister2.stop(); QCOMPARE(dirLister2.spyStarted.count(), 1); QCOMPARE(dirLister2.spyCompleted.count(), 0); QCOMPARE(dirLister2.spyCompletedQUrl.count(), 0); QCOMPARE(dirLister2.spyCanceled.count(), 1); QCOMPARE(dirLister2.spyCanceledQUrl.count(), 1); QCOMPARE(dirLister2.spyClear.count(), 1); QCOMPARE(dirLister2.spyClearQUrl.count(), 0); QCOMPARE(m_items2.count(), 0); // then wait for completed qDebug("waiting for completed"); QTRY_COMPARE(m_items.count(), 3); QTRY_COMPARE(m_items2.count(), 0); QTRY_VERIFY(m_dirLister.isFinished()); //QCOMPARE(m_dirLister.spyStarted.count(), 1); // 2 when in cache QCOMPARE(m_dirLister.spyCompleted.count(), 1); QCOMPARE(m_dirLister.spyCompletedQUrl.count(), 1); QCOMPARE(m_dirLister.spyCanceled.count(), 0); QCOMPARE(m_dirLister.spyCanceledQUrl.count(), 0); QCOMPARE(m_dirLister.spyClear.count(), 1); QCOMPARE(m_dirLister.spyClearQUrl.count(), 0); disconnect(&m_dirLister, nullptr, this, nullptr); } void KDirListerTest::testDeleteListerEarly() { // Do the same again, it should behave the same, even with the items in the cache testOpenUrl(); // Start a second lister, it will get a cached items job, but delete it before the job can run //qDebug() << "=========================================="; { m_items.clear(); const QString path = m_tempDir.path() + '/'; MyDirLister secondDirLister; secondDirLister.openUrl(QUrl::fromLocalFile(path), KDirLister::NoFlags); QVERIFY(!secondDirLister.isFinished()); } //qDebug() << "=========================================="; // Check if we didn't keep the deleted dirlister in one of our lists. // I guess the best way to do that is to just list the same dir again. testOpenUrl(); } void KDirListerTest::testOpenUrlTwice() { // Calling openUrl(reload)+openUrl(normal) before listing even starts. const int origItemCount = m_items.count(); m_items.clear(); const QString path = m_tempDir.path() + '/'; MyDirLister secondDirLister; connect(&secondDirLister, &KCoreDirLister::newItems, this, &KDirListerTest::slotNewItems); secondDirLister.openUrl(QUrl::fromLocalFile(path), KDirLister::Reload); // will start QCOMPARE(secondDirLister.spyStarted.count(), 1); QCOMPARE(secondDirLister.spyCompleted.count(), 0); qDebug("calling openUrl again"); secondDirLister.openUrl(QUrl::fromLocalFile(path), KDirLister::NoFlags); // will stop + start qDebug("waiting for completed"); QTRY_COMPARE(secondDirLister.spyStarted.count(), 2); QTRY_COMPARE(secondDirLister.spyCompleted.count(), 1); QCOMPARE(secondDirLister.spyCompletedQUrl.count(), 1); QCOMPARE(secondDirLister.spyCanceled.count(), 0); // should not be emitted, see next test QCOMPARE(secondDirLister.spyCanceledQUrl.count(), 0); QCOMPARE(secondDirLister.spyClear.count(), 2); QCOMPARE(secondDirLister.spyClearQUrl.count(), 0); if (origItemCount) { // 0 if running this test separately QCOMPARE(m_items.count(), origItemCount); } QVERIFY(secondDirLister.isFinished()); disconnect(&secondDirLister, nullptr, this, nullptr); } void KDirListerTest::testOpenUrlTwiceWithKeep() { // Calling openUrl(reload)+openUrl(keep) on a new dir, // before listing even starts (#177387) // Well, in 177387 the second openUrl call was made from within slotCanceled // called by the first openUrl // (slotLoadingFinished -> setCurrentItem -> expandToUrl -> listDir), // which messed things up in kdirlister (unexpected reentrancy). m_items.clear(); const QString path = m_tempDir.path() + "/newsubdir"; QDir().mkdir(path); MyDirLister secondDirLister; connect(&secondDirLister, &KCoreDirLister::newItems, this, &KDirListerTest::slotNewItems); secondDirLister.openUrl(QUrl::fromLocalFile(path)); // will start a list job QCOMPARE(secondDirLister.spyStarted.count(), 1); QCOMPARE(secondDirLister.spyCanceled.count(), 0); QCOMPARE(secondDirLister.spyCompleted.count(), 0); qDebug("calling openUrl again"); secondDirLister.openUrl(QUrl::fromLocalFile(path), KDirLister::Keep); // stops and restarts the job qDebug("waiting for completed"); QTRY_COMPARE(secondDirLister.spyStarted.count(), 2); QTRY_COMPARE(secondDirLister.spyCompleted.count(), 1); QCOMPARE(secondDirLister.spyCompletedQUrl.count(), 1); QCOMPARE(secondDirLister.spyCanceled.count(), 0); // should not be emitted, it led to recursion QCOMPARE(secondDirLister.spyCanceledQUrl.count(), 0); QCOMPARE(secondDirLister.spyClear.count(), 1); QCOMPARE(secondDirLister.spyClearQUrl.count(), 1); QCOMPARE(m_items.count(), 0); QVERIFY(secondDirLister.isFinished()); disconnect(&secondDirLister, nullptr, this, nullptr); QDir().remove(path); } void KDirListerTest::testOpenAndStop() { m_items.clear(); const QString path = QStringLiteral("/"); // better not use a directory that we already listed! connect(&m_dirLister, &KCoreDirLister::newItems, this, &KDirListerTest::slotNewItems); m_dirLister.openUrl(QUrl::fromLocalFile(path), KDirLister::NoFlags); qDebug() << "Calling stop!"; m_dirLister.stop(); // we should also test stop(QUrl::fromLocalFile(path))... QCOMPARE(m_dirLister.spyStarted.count(), 1); // The call to openUrl itself, emits started QCOMPARE(m_dirLister.spyCompleted.count(), 0); // we had time to stop before the job even started QCOMPARE(m_dirLister.spyCompletedQUrl.count(), 0); QCOMPARE(m_dirLister.spyCanceled.count(), 1); QCOMPARE(m_dirLister.spyCanceledQUrl.count(), 1); QCOMPARE(m_dirLister.spyClear.count(), 1); QCOMPARE(m_dirLister.spyClearQUrl.count(), 0); QCOMPARE(m_items.count(), 0); // we had time to stop before the job even started QVERIFY(m_dirLister.isFinished()); disconnect(&m_dirLister, nullptr, this, nullptr); } // A bug in the decAutoUpdate/incAutoUpdate logic made KDirLister stop watching a directory for changes, // and never watch it again when opening it from the cache. void KDirListerTest::testBug211472() { m_items.clear(); QTemporaryDir newDir; const QString path = newDir.path() + "/newsubdir/"; QDir().mkdir(path); MyDirLister dirLister; connect(&dirLister, &KCoreDirLister::newItems, this, &KDirListerTest::slotNewItems); dirLister.openUrl(QUrl::fromLocalFile(path)); QSignalSpy spyCompleted(&dirLister, SIGNAL(completed())); QVERIFY(spyCompleted.wait(1000)); QVERIFY(dirLister.isFinished()); QVERIFY(m_items.isEmpty()); if (true) { // This block is required to trigger bug 211472. // Go 'up' to the parent of 'newsubdir'. dirLister.openUrl(QUrl::fromLocalFile(newDir.path())); QVERIFY(spyCompleted.wait(1000)); QTRY_VERIFY(dirLister.isFinished()); QTRY_VERIFY(!m_items.isEmpty()); m_items.clear(); // Create a file in 'newsubdir' while we are listing its parent dir. createTestFile(path + "newFile-1"); // At this point, newsubdir is not used, so it's moved to the cache. // This happens in checkUpdate, called when receiving a notification for the cached dir, // this is why this unittest needs to create a test file in the subdir. // wait a second and ensure the list is still empty afterwards QTest::qWait(1000); QTRY_VERIFY(m_items.isEmpty()); // Return to 'newsubdir'. It will be emitted from the cache, then an update will happen. dirLister.openUrl(QUrl::fromLocalFile(path)); // Check that completed is emitted twice QVERIFY(spyCompleted.wait(1000)); QVERIFY(spyCompleted.wait(1000)); QTRY_VERIFY(dirLister.isFinished()); QTRY_COMPARE(m_items.count(), 1); m_items.clear(); } // Now try to create a second file in 'newsubdir' and verify that the // dir lister notices it. QTest::qWait(1000); // We need a 1s timestamp difference on the dir, otherwise FAM won't notice anything. createTestFile(path + "newFile-2"); QTRY_COMPARE(m_items.count(), 1); newDir.remove(); QSignalSpy spyClear(&dirLister, SIGNAL(clear())); QVERIFY(spyClear.wait(1000)); } void KDirListerTest::testRenameCurrentDir() // #294445 { m_items.clear(); const QString path = m_tempDir.path() + "/newsubdir-1"; QVERIFY(QDir().mkdir(path)); MyDirLister secondDirLister; connect(&secondDirLister, &KCoreDirLister::newItems, this, &KDirListerTest::slotNewItems); secondDirLister.openUrl(QUrl::fromLocalFile(path)); QSignalSpy spyCompleted(&secondDirLister, SIGNAL(completed())); QVERIFY(spyCompleted.wait(1000)); QVERIFY(secondDirLister.isFinished()); QVERIFY(m_items.empty()); QCOMPARE(secondDirLister.rootItem().url().toLocalFile(), path); const QString newPath = m_tempDir.path() + "/newsubdir-2"; QVERIFY(QDir().rename(path, newPath)); org::kde::KDirNotify::emitFileRenamed(QUrl::fromLocalFile(path), QUrl::fromLocalFile(newPath)); QSignalSpy spyRedirection(&secondDirLister, SIGNAL(redirection(QUrl,QUrl))); QVERIFY(spyRedirection.wait(1000)); // Check that the URL of the root item got updated QCOMPARE(secondDirLister.rootItem().url().toLocalFile(), newPath); disconnect(&secondDirLister, nullptr, this, nullptr); QDir().rmdir(newPath); } void KDirListerTest::slotOpenUrlOnRename(const QUrl &newUrl) { QVERIFY(m_dirLister.openUrl(newUrl)); } //This tests for a crash if you connect redirects to openUrl, due //to internal data being inconsistently exposed. //Matches usage in gwenview. void KDirListerTest::testRenameCurrentDirOpenUrl() { m_items.clear(); const QString path = m_tempDir.path() + "/newsubdir-1/"; QVERIFY(QDir().mkdir(path)); connect(&m_dirLister, &KCoreDirLister::newItems, this, &KDirListerTest::slotNewItems); m_dirLister.openUrl(QUrl::fromLocalFile(path)); QSignalSpy spyCompleted(&m_dirLister, SIGNAL(completed())); // Wait for the signal completed to be emitted QVERIFY(spyCompleted.wait(1000)); QVERIFY(m_dirLister.isFinished()); const QString newPath = m_tempDir.path() + "/newsubdir-2"; QVERIFY(QDir().rename(path, newPath)); org::kde::KDirNotify::emitFileRenamed(QUrl::fromLocalFile(path), QUrl::fromLocalFile(newPath)); //Connect the redirection to openURL, so that on a rename the new location is opened. //This matches usage in gwenview, and crashes connect(&m_dirLister, QOverload::of(&KCoreDirLister::redirection), this, &KDirListerTest::slotOpenUrlOnRename); QTRY_VERIFY(m_dirLister.isFinished()); disconnect(&m_dirLister, nullptr, this, nullptr); QDir().rmdir(newPath); } void KDirListerTest::testRedirection() { m_items.clear(); const QUrl url(QStringLiteral("file://somemachine/")); if (!KProtocolInfo::isKnownProtocol(QStringLiteral("smb"))) { QSKIP("smb not installed"); } connect(&m_dirLister, &KCoreDirLister::newItems, this, &KDirListerTest::slotNewItems); // The call to openUrl itself, emits started m_dirLister.openUrl(url, KDirLister::NoFlags); QCOMPARE(m_dirLister.spyStarted.count(), 1); QCOMPARE(m_dirLister.spyCompleted.count(), 0); QCOMPARE(m_dirLister.spyCompletedQUrl.count(), 0); QCOMPARE(m_dirLister.spyCanceled.count(), 0); QCOMPARE(m_dirLister.spyCanceledQUrl.count(), 0); QCOMPARE(m_dirLister.spyClear.count(), 1); QCOMPARE(m_dirLister.spyClearQUrl.count(), 0); QCOMPARE(m_dirLister.spyRedirection.count(), 0); QCOMPARE(m_items.count(), 0); QVERIFY(!m_dirLister.isFinished()); // then wait for the redirection signal qDebug("waiting for redirection"); QTRY_COMPARE(m_dirLister.spyStarted.count(), 1); QCOMPARE(m_dirLister.spyCompleted.count(), 0); // we stopped before the listing. QCOMPARE(m_dirLister.spyCompletedQUrl.count(), 0); QCOMPARE(m_dirLister.spyCanceled.count(), 0); QCOMPARE(m_dirLister.spyCanceledQUrl.count(), 0); QTRY_COMPARE(m_dirLister.spyClear.count(), 2); // redirection cleared a second time (just in case...) QCOMPARE(m_dirLister.spyClearQUrl.count(), 0); QTRY_COMPARE(m_dirLister.spyRedirection.count(), 1); QVERIFY(m_items.isEmpty()); QVERIFY(!m_dirLister.isFinished()); m_dirLister.stop(url); QVERIFY(!m_dirLister.isFinished()); disconnect(&m_dirLister, nullptr, this, nullptr); } void KDirListerTest::testListEmptyDirFromCache() // #278431 { m_items.clear(); QTemporaryDir newDir; const QUrl url = QUrl::fromLocalFile(newDir.path()); // List and watch an empty dir connect(&m_dirLister, &KCoreDirLister::newItems, this, &KDirListerTest::slotNewItems); m_dirLister.openUrl(url); QSignalSpy spyCompleted(&m_dirLister, SIGNAL(completed())); QVERIFY(spyCompleted.wait(1000)); QVERIFY(m_dirLister.isFinished()); QVERIFY(m_items.isEmpty()); // List it with two more dirlisters (one will create a cached items job, the second should also benefit from it) MyDirLister secondDirLister; connect(&secondDirLister, &KCoreDirLister::newItems, this, &KDirListerTest::slotNewItems); secondDirLister.openUrl(url); MyDirLister thirdDirLister; connect(&thirdDirLister, &KCoreDirLister::newItems, this, &KDirListerTest::slotNewItems); thirdDirLister.openUrl(url); // The point of this test is that (with DEBUG_CACHE enabled) it used to assert here // with "HUH? Lister KDirLister(0x7ffd1f044260) is supposed to be listing, but has no job!" // due to the if (!itemU->lstItems.isEmpty()) check which is now removed. QVERIFY(!secondDirLister.isFinished()); // we didn't go to the event loop yet QSignalSpy spySecondCompleted(&secondDirLister, SIGNAL(completed())); QVERIFY(spySecondCompleted.wait(1000)); if (!thirdDirLister.isFinished()) { QSignalSpy spyThirdCompleted(&thirdDirLister, SIGNAL(completed())); QVERIFY(spyThirdCompleted.wait(1000)); } } void KDirListerTest::testWatchingAfterCopyJob() // #331582 { m_items.clear(); QTemporaryDir newDir; const QString path = newDir.path() + '/'; // List and watch an empty dir connect(&m_dirLister, &KCoreDirLister::newItems, this, &KDirListerTest::slotNewItems); m_dirLister.openUrl(QUrl::fromLocalFile(path)); QSignalSpy spyCompleted(&m_dirLister, SIGNAL(completed())); QVERIFY(spyCompleted.wait(1000)); QVERIFY(m_dirLister.isFinished()); QVERIFY(m_items.isEmpty()); // Create three subfolders. QVERIFY(QDir().mkdir(path + "New Folder")); QVERIFY(QDir().mkdir(path + "New Folder 1")); QVERIFY(QDir().mkdir(path + "New Folder 2")); QVERIFY(spyCompleted.wait(1000)); QTRY_VERIFY(m_dirLister.isFinished()); QTRY_COMPARE(m_items.count(), 3); // Create a new file and verify that the dir lister notices it. m_items.clear(); createTestFile(path + "a"); QVERIFY(spyCompleted.wait(1000)); QTRY_VERIFY(m_dirLister.isFinished()); QTRY_COMPARE(m_items.count(), 1); // Rename one of the subfolders. const QString oldPath = path + "New Folder 1"; const QString newPath = path + "New Folder 1a"; // NOTE: The following two lines are required to trigger the bug! KIO::Job *job = KIO::moveAs(QUrl::fromLocalFile(oldPath), QUrl::fromLocalFile(newPath), KIO::HideProgressInfo); job->exec(); // Now try to create a second new file and verify that the // dir lister notices it. m_items.clear(); createTestFile(path + "b"); // This should end up in "KCoreDirListerCache::slotFileDirty" QTRY_COMPARE(m_items.count(), 1); newDir.remove(); QSignalSpy clearSpy(&m_dirLister, SIGNAL(clear())); QVERIFY(clearSpy.wait(1000)); } void KDirListerTest::testRemoveWatchedDirectory() { m_items.clear(); QTemporaryDir newDir; const QString path = newDir.path() + '/'; // List and watch an empty dir connect(&m_dirLister, &KCoreDirLister::newItems, this, &KDirListerTest::slotNewItems); m_dirLister.openUrl(QUrl::fromLocalFile(path)); QSignalSpy spyCompleted(&m_dirLister, SIGNAL(completed())); QVERIFY(spyCompleted.wait(1000)); QTRY_VERIFY(m_dirLister.isFinished()); QTRY_VERIFY(m_items.isEmpty()); // Create a subfolder. const QString subDirPath = path + "abc"; QVERIFY(QDir().mkdir(subDirPath)); QVERIFY(spyCompleted.wait(1000)); QTRY_VERIFY(m_dirLister.isFinished()); QTRY_COMPARE(m_items.count(), 1); const KFileItem item = m_items.at(0); // Watch the subfolder for changes, independently. // This is what triggers the bug. // (Technically, this could become a KDirWatch unittest, but if one day we use QFSW, good to have the tests here) KDirWatch watcher; watcher.addDir(subDirPath); // Remove the subfolder. m_items.clear(); QVERIFY(QDir().rmdir(path + "abc")); // This should trigger an update. QVERIFY(spyCompleted.wait(1000)); QVERIFY(m_dirLister.isFinished()); QCOMPARE(m_items.count(), 0); QCOMPARE(m_dirLister.spyItemsDeleted.count(), 1); const KFileItem deletedItem = m_dirLister.spyItemsDeleted.at(0).at(0).value().at(0); QCOMPARE(item, deletedItem); } void KDirListerTest::testDirPermissionChange() { QTemporaryDir tempDir; const QString path = tempDir.path() + '/'; const QString subdir = path + QLatin1String("subdir"); QVERIFY(QDir().mkdir(subdir)); MyDirLister mylister; mylister.openUrl(QUrl::fromLocalFile(tempDir.path())); QSignalSpy spyCompleted(&mylister, SIGNAL(completed())); QVERIFY(spyCompleted.wait(1000)); KFileItemList list = mylister.items(); QVERIFY(mylister.isFinished()); QCOMPARE(list.count(), 1); QCOMPARE(mylister.rootItem().url().toLocalFile(), tempDir.path()); const mode_t permissions = (S_IRUSR | S_IWUSR | S_IXUSR); KIO::SimpleJob *job = KIO::chmod(list.first().url(), permissions); QVERIFY(job->exec()); - QSignalSpy spyRefreshItems(&mylister, SIGNAL(refreshItems(QList >))); + QSignalSpy spyRefreshItems(&mylister, SIGNAL(refreshItems(QList>))); QVERIFY(spyRefreshItems.wait(2000)); list = mylister.items(); QCOMPARE(list.first().permissions(), permissions); QVERIFY(QDir().rmdir(subdir)); } void KDirListerTest::slotNewItems(const KFileItemList &lst) { m_items += lst; } void KDirListerTest::slotNewItems2(const KFileItemList &lst) { m_items2 += lst; } void KDirListerTest::slotRefreshItems(const QList > &lst) { m_refreshedItems += lst; emit refreshItemsReceived(); } void KDirListerTest::slotRefreshItems2(const QList > &lst) { m_refreshedItems2 += lst; } void KDirListerTest::testCopyAfterListingAndMove() // #353195 { const QString dirA = m_tempDir.path() + "/a"; QVERIFY(QDir().mkdir(dirA)); const QString dirB = m_tempDir.path() + "/b"; QVERIFY(QDir().mkdir(dirB)); // ensure m_dirLister holds the items. m_dirLister.openUrl(QUrl::fromLocalFile(path()), KDirLister::NoFlags); QSignalSpy spyCompleted(&m_dirLister, SIGNAL(completed())); QVERIFY(spyCompleted.wait()); // Move b into a KIO::Job *moveJob = KIO::move(QUrl::fromLocalFile(dirB), QUrl::fromLocalFile(dirA)); moveJob->setUiDelegate(nullptr); QVERIFY(moveJob->exec()); QVERIFY(QFileInfo(m_tempDir.path() + "/a/b").isDir()); // Give some time to processPendingUpdates QTest::qWait(1000); // Copy folder a elsewhere const QString dest = m_tempDir.path() + "/subdir"; KIO::Job *copyJob = KIO::copy(QUrl::fromLocalFile(dirA), QUrl::fromLocalFile(dest)); copyJob->setUiDelegate(nullptr); QVERIFY(copyJob->exec()); QVERIFY(QFileInfo(m_tempDir.path() + "/subdir/a/b").isDir()); } void KDirListerTest::testDeleteCurrentDir() { // ensure m_dirLister holds the items. m_dirLister.openUrl(QUrl::fromLocalFile(path()), KDirLister::NoFlags); m_dirLister.clearSpies(); KIO::DeleteJob *job = KIO::del(QUrl::fromLocalFile(path()), KIO::HideProgressInfo); bool ok = job->exec(); QVERIFY(ok); QTRY_COMPARE(m_dirLister.spyClear.count(), 1); QCOMPARE(m_dirLister.spyClearQUrl.count(), 0); QList deletedUrls; for (int i = 0; i < m_dirLister.spyItemsDeleted.count(); ++i) { deletedUrls += m_dirLister.spyItemsDeleted[i][0].value().urlList(); } //qDebug() << deletedUrls; QUrl currentDirUrl = QUrl::fromLocalFile(path()).adjusted(QUrl::StripTrailingSlash); // Sometimes I get ("current/subdir", "current") here, but that seems ok. QVERIFY(deletedUrls.contains(currentDirUrl)); } int KDirListerTest::fileCount() const { return QDir(path()).entryList(QDir::AllEntries | QDir::NoDotAndDotDot).count(); } void KDirListerTest::createSimpleFile(const QString &fileName) { QFile file(fileName); QVERIFY(file.open(QIODevice::WriteOnly)); file.write(QByteArray("foo")); file.close(); } void KDirListerTest::fillDirLister2(MyDirLister &lister, const QString &path) { m_items2.clear(); connect(&lister, &KCoreDirLister::newItems, this, &KDirListerTest::slotNewItems2); connect(&m_dirLister, &KCoreDirLister::refreshItems, this, &KDirListerTest::slotRefreshItems2); lister.openUrl(QUrl::fromLocalFile(path), KDirLister::NoFlags); QTRY_VERIFY(lister.isFinished()); } void KDirListerTest::waitUntilMTimeChange(const QString &path) { // Wait until the current second is more than the file's mtime // otherwise this change will go unnoticed QFileInfo fi(path); QVERIFY(fi.exists()); const QDateTime ctime = qMax(fi.lastModified(), fi.created()); waitUntilAfter(ctime); } void KDirListerTest::waitUntilAfter(const QDateTime &ctime) { int totalWait = 0; QDateTime now; Q_FOREVER { now = QDateTime::currentDateTime(); if (now.toTime_t() == ctime.toTime_t()) { // truncate milliseconds totalWait += 50; QTest::qWait(50); } else { QVERIFY(now > ctime); // can't go back in time ;) QTest::qWait(50); // be safe break; } } //if (totalWait > 0) qDebug() << "Waited" << totalWait << "ms so that now" << now.toString(Qt::ISODate) << "is >" << ctime.toString(Qt::ISODate); } #include "moc_kdirlistertest.cpp" diff --git a/src/core/chmodjob.cpp b/src/core/chmodjob.cpp index 19d5de77..0c013da7 100644 --- a/src/core/chmodjob.cpp +++ b/src/core/chmodjob.cpp @@ -1,295 +1,294 @@ /* This file is part of the KDE libraries Copyright (C) 2000 Stephan Kulow David Faure Waldo Bastian 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 "chmodjob.h" #include "../pathhelpers_p.h" #include #include #include #include #include #include "listjob.h" #include "job_p.h" #include "jobuidelegatefactory.h" #include "kioglobal_p.h" namespace KIO { struct ChmodInfo { QUrl url; int permissions; }; enum ChmodJobState { CHMODJOB_STATE_LISTING, CHMODJOB_STATE_CHMODING }; class ChmodJobPrivate: public KIO::JobPrivate { public: ChmodJobPrivate(const KFileItemList &lstItems, int permissions, int mask, KUserId newOwner, KGroupId newGroup, bool recursive) : state(CHMODJOB_STATE_LISTING) , m_permissions(permissions) , m_mask(mask) , m_newOwner(newOwner) , m_newGroup(newGroup) , m_recursive(recursive) , m_bAutoSkipFiles(false) , m_lstItems(lstItems) { } ChmodJobState state; int m_permissions; int m_mask; KUserId m_newOwner; KGroupId m_newGroup; bool m_recursive; bool m_bAutoSkipFiles; KFileItemList m_lstItems; QLinkedList m_infos; // linkedlist since we keep removing the first item void _k_chmodNextFile(); void _k_slotEntries(KIO::Job *, const KIO::UDSEntryList &); void _k_processList(); Q_DECLARE_PUBLIC(ChmodJob) static inline ChmodJob *newJob(const KFileItemList &lstItems, int permissions, int mask, KUserId newOwner, KGroupId newGroup, bool recursive, JobFlags flags) { ChmodJob *job = new ChmodJob(*new ChmodJobPrivate(lstItems, permissions, mask, newOwner, newGroup, recursive)); job->setUiDelegate(KIO::createDefaultJobUiDelegate()); if (!(flags & HideProgressInfo)) { KIO::getJobTracker()->registerJob(job); } if (!(flags & NoPrivilegeExecution)) { job->d_func()->m_privilegeExecutionEnabled = true; job->d_func()->m_operationType = ChangeAttr; } return job; } }; } // namespace KIO using namespace KIO; ChmodJob::ChmodJob(ChmodJobPrivate &dd) : KIO::Job(dd) { QMetaObject::invokeMethod(this, "_k_processList", Qt::QueuedConnection); } ChmodJob::~ChmodJob() { } void ChmodJobPrivate::_k_processList() { Q_Q(ChmodJob); while (!m_lstItems.isEmpty()) { const KFileItem item = m_lstItems.first(); if (!item.isLink()) { // don't do anything with symlinks // File or directory -> remember to chmod ChmodInfo info; info.url = item.url(); // This is a toplevel file, we apply changes directly (no +X emulation here) const mode_t permissions = item.permissions() & 0777; // get rid of "set gid" and other special flags info.permissions = (m_permissions & m_mask) | (permissions & ~m_mask); /*//qDebug() << "toplevel url:" << info.url << "\n current permissions=" << QString::number(permissions,8) << "\n wanted permission=" << QString::number(m_permissions,8) << "\n with mask=" << QString::number(m_mask,8) << "\n with ~mask (mask bits we keep) =" << QString::number((uint)~m_mask,8) << "\n bits we keep =" << QString::number(permissions & ~m_mask,8) << "\n new permissions = " << QString::number(info.permissions,8);*/ m_infos.prepend(info); //qDebug() << "processList : Adding info for " << info.url; // Directory and recursive -> list if (item.isDir() && m_recursive) { //qDebug() << "ChmodJob::processList dir -> listing"; KIO::ListJob *listJob = KIO::listRecursive(item.url(), KIO::HideProgressInfo); - q->connect(listJob, SIGNAL(entries(KIO::Job *, - const KIO::UDSEntryList &)), + q->connect(listJob, SIGNAL(entries(KIO::Job*,KIO::UDSEntryList)), SLOT(_k_slotEntries(KIO::Job*,KIO::UDSEntryList))); q->addSubjob(listJob); return; // we'll come back later, when this one's finished } } m_lstItems.removeFirst(); } //qDebug() << "ChmodJob::processList -> going to STATE_CHMODING"; // We have finished, move on state = CHMODJOB_STATE_CHMODING; _k_chmodNextFile(); } void ChmodJobPrivate::_k_slotEntries(KIO::Job *, const KIO::UDSEntryList &list) { KIO::UDSEntryList::ConstIterator it = list.begin(); KIO::UDSEntryList::ConstIterator end = list.end(); for (; it != end; ++it) { const KIO::UDSEntry &entry = *it; const bool isLink = !entry.stringValue(KIO::UDSEntry::UDS_LINK_DEST).isEmpty(); const QString relativePath = entry.stringValue(KIO::UDSEntry::UDS_NAME); if (!isLink && relativePath != QLatin1String("..")) { const mode_t permissions = entry.numberValue(KIO::UDSEntry::UDS_ACCESS) & 0777; // get rid of "set gid" and other special flags ChmodInfo info; info.url = m_lstItems.first().url(); // base directory info.url.setPath(concatPaths(info.url.path(), relativePath)); int mask = m_mask; // Emulate -X: only give +x to files that had a +x bit already // So the check is the opposite : if the file had no x bit, don't touch x bits // For dirs this doesn't apply if (!entry.isDir()) { int newPerms = m_permissions & mask; if ((newPerms & 0111) && !(permissions & 0111)) { // don't interfere with mandatory file locking if (newPerms & 02000) { mask = mask & ~0101; } else { mask = mask & ~0111; } } } info.permissions = (m_permissions & mask) | (permissions & ~mask); /*//qDebug() << info.url << "\n current permissions=" << QString::number(permissions,8) << "\n wanted permission=" << QString::number(m_permissions,8) << "\n with mask=" << QString::number(mask,8) << "\n with ~mask (mask bits we keep) =" << QString::number((uint)~mask,8) << "\n bits we keep =" << QString::number(permissions & ~mask,8) << "\n new permissions = " << QString::number(info.permissions,8);*/ // Prepend this info in our todo list. // This way, the toplevel dirs are done last. m_infos.prepend(info); } } } void ChmodJobPrivate::_k_chmodNextFile() { Q_Q(ChmodJob); if (!m_infos.isEmpty()) { ChmodInfo info = m_infos.takeFirst(); // First update group / owner (if local file) // (permissions have to set after, in case of suid and sgid) if (info.url.isLocalFile() && (m_newOwner.isValid() || m_newGroup.isValid())) { QString path = info.url.toLocalFile(); if (!KIOPrivate::changeOwnership(path, m_newOwner, m_newGroup)) { if (!m_uiDelegateExtension) { emit q->warning(q, i18n("Could not modify the ownership of file %1", path)); } else if (!m_bAutoSkipFiles) { const QString errMsg = i18n("Could not modify the ownership of file %1. You have insufficient access to the file to perform the change.", path); SkipDialog_Options options; if (m_infos.count() > 1) { options |= SkipDialog_MultipleItems; } const SkipDialog_Result skipResult = m_uiDelegateExtension->askSkip(q, options, errMsg); switch (skipResult) { case Result_AutoSkip: m_bAutoSkipFiles = true; // fall through Q_FALLTHROUGH(); case Result_Skip: QMetaObject::invokeMethod(q, "_k_chmodNextFile", Qt::QueuedConnection); return; case Result_Retry: m_infos.prepend(info); QMetaObject::invokeMethod(q, "_k_chmodNextFile", Qt::QueuedConnection); return; case Result_Cancel: default: q->setError(ERR_USER_CANCELED); q->emitResult(); return; } } } } /*qDebug() << "chmod'ing" << info.url << "to" << QString::number(info.permissions,8);*/ KIO::SimpleJob *job = KIO::chmod(info.url, info.permissions); job->setParentJob(q); // copy the metadata for acl and default acl const QString aclString = q->queryMetaData(QStringLiteral("ACL_STRING")); const QString defaultAclString = q->queryMetaData(QStringLiteral("DEFAULT_ACL_STRING")); if (!aclString.isEmpty()) { job->addMetaData(QStringLiteral("ACL_STRING"), aclString); } if (!defaultAclString.isEmpty()) { job->addMetaData(QStringLiteral("DEFAULT_ACL_STRING"), defaultAclString); } q->addSubjob(job); } else // We have finished { q->emitResult(); } } void ChmodJob::slotResult(KJob *job) { Q_D(ChmodJob); removeSubjob(job); if (job->error()) { setError(job->error()); setErrorText(job->errorText()); emitResult(); return; } //qDebug() << "d->m_lstItems:" << d->m_lstItems.count(); switch (d->state) { case CHMODJOB_STATE_LISTING: d->m_lstItems.removeFirst(); //qDebug() << "-> processList"; d->_k_processList(); return; case CHMODJOB_STATE_CHMODING: //qDebug() << "-> chmodNextFile"; d->_k_chmodNextFile(); return; default: Q_ASSERT(false); return; } } ChmodJob *KIO::chmod(const KFileItemList &lstItems, int permissions, int mask, const QString &owner, const QString &group, bool recursive, JobFlags flags) { KUserId uid = KUserId::fromName(owner); KGroupId gid = KGroupId::fromName(group); return ChmodJobPrivate::newJob(lstItems, permissions, mask, uid, gid, recursive, flags); } #include "moc_chmodjob.cpp" diff --git a/src/filewidgets/kimagefilepreview.cpp b/src/filewidgets/kimagefilepreview.cpp index 3562b8d0..519b1ea9 100644 --- a/src/filewidgets/kimagefilepreview.cpp +++ b/src/filewidgets/kimagefilepreview.cpp @@ -1,281 +1,279 @@ /* * This file is part of the KDE project * Copyright (C) 2001 Martin R. Jones * 2001 Carsten Pfeiffer * 2008 Rafael Fernández López * * You can Freely distribute this program under the GNU Library General Public * License. See the file "COPYING" for the exact licensing terms. */ #include "kimagefilepreview.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /**** KImageFilePreview ****/ class Q_DECL_HIDDEN KImageFilePreview::KImageFilePreviewPrivate { public: KImageFilePreviewPrivate() : m_job(nullptr) , clear(true) { m_timeLine = new QTimeLine(150); m_timeLine->setCurveShape(QTimeLine::EaseInCurve); m_timeLine->setDirection(QTimeLine::Forward); m_timeLine->setFrameRange(0, 100); } ~KImageFilePreviewPrivate() { delete m_timeLine; } void _k_slotResult(KJob *); void _k_slotFailed(const KFileItem &); void _k_slotStepAnimation(int frame); void _k_slotFinished(); void _k_slotActuallyClear(); QUrl currentURL; QUrl lastShownURL; QLabel *imageLabel; KIO::PreviewJob *m_job; QTimeLine *m_timeLine; QPixmap m_pmCurrent; QPixmap m_pmTransition; float m_pmCurrentOpacity; float m_pmTransitionOpacity; bool clear; }; KImageFilePreview::KImageFilePreview(QWidget *parent) : KPreviewWidgetBase(parent), d(new KImageFilePreviewPrivate) { QVBoxLayout *vb = new QVBoxLayout(this); vb->setMargin(0); d->imageLabel = new QLabel(this); d->imageLabel->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter); d->imageLabel->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding)); vb->addWidget(d->imageLabel); setSupportedMimeTypes(KIO::PreviewJob::supportedMimeTypes()); setMinimumWidth(50); connect(d->m_timeLine, SIGNAL(frameChanged(int)), this, SLOT(_k_slotStepAnimation(int))); connect(d->m_timeLine, SIGNAL(finished()), this, SLOT(_k_slotFinished())); } KImageFilePreview::~KImageFilePreview() { if (d->m_job) { d->m_job->kill(); } delete d; } void KImageFilePreview::showPreview() { // Pass a copy since clearPreview() will clear currentURL QUrl url = d->currentURL; showPreview(url, true); } // called via KPreviewWidgetBase interface void KImageFilePreview::showPreview(const QUrl &url) { showPreview(url, false); } void KImageFilePreview::showPreview(const QUrl &url, bool force) { if (!url.isValid() || (d->lastShownURL.isValid() && url.matches(d->lastShownURL, QUrl::StripTrailingSlash) && d->currentURL.isValid())) { return; } d->clear = false; d->currentURL = url; d->lastShownURL = url; int w = d->imageLabel->contentsRect().width() - 4; int h = d->imageLabel->contentsRect().height() - 4; if (d->m_job) { disconnect(d->m_job, SIGNAL(result(KJob*)), this, SLOT(_k_slotResult(KJob*))); - disconnect(d->m_job, SIGNAL(gotPreview(const KFileItem &, - const QPixmap &)), this, - SLOT(gotPreview(KFileItem,QPixmap))); + disconnect(d->m_job, SIGNAL(gotPreview(KFileItem,QPixmap)), + this, SLOT(gotPreview(KFileItem,QPixmap))); disconnect(d->m_job, SIGNAL(failed(KFileItem)), this, SLOT(_k_slotFailed(KFileItem))); d->m_job->kill(); } d->m_job = createJob(url, w, h); if (force) { // explicitly requested previews shall always be generated! d->m_job->setIgnoreMaximumSize(true); } connect(d->m_job, SIGNAL(result(KJob*)), this, SLOT(_k_slotResult(KJob*))); - connect(d->m_job, SIGNAL(gotPreview(const KFileItem &, - const QPixmap &)), + connect(d->m_job, SIGNAL(gotPreview(KFileItem,QPixmap)), SLOT(gotPreview(KFileItem,QPixmap))); connect(d->m_job, SIGNAL(failed(KFileItem)), this, SLOT(_k_slotFailed(KFileItem))); } void KImageFilePreview::resizeEvent(QResizeEvent *) { clearPreview(); d->currentURL = QUrl(); // force this to actually happen showPreview(d->lastShownURL); } QSize KImageFilePreview::sizeHint() const { return QSize(100, 200); } KIO::PreviewJob *KImageFilePreview::createJob(const QUrl &url, int w, int h) { if (url.isValid()) { KFileItemList items; items.append(KFileItem(url)); QStringList plugins = KIO::PreviewJob::availablePlugins(); KIO::PreviewJob *previewJob = KIO::filePreview(items, QSize(w, h), &plugins); previewJob->setOverlayIconAlpha(0); previewJob->setScaleType(KIO::PreviewJob::Scaled); return previewJob; } else { return nullptr; } } void KImageFilePreview::gotPreview(const KFileItem &item, const QPixmap &pm) { if (item.url() == d->currentURL) { // should always be the case if (style()->styleHint(QStyle::SH_Widget_Animate, nullptr, this)) { if (d->m_timeLine->state() == QTimeLine::Running) { d->m_timeLine->setCurrentTime(0); } d->m_pmTransition = pm; d->m_pmTransitionOpacity = 0; d->m_pmCurrentOpacity = 1; d->m_timeLine->setDirection(QTimeLine::Forward); d->m_timeLine->start(); } else { d->imageLabel->setPixmap(pm); } } } void KImageFilePreview::KImageFilePreviewPrivate::_k_slotFailed(const KFileItem &item) { if (item.isDir()) { imageLabel->clear(); } else if (item.url() == currentURL) // should always be the case imageLabel->setPixmap(SmallIcon(QStringLiteral("image-missing"), KIconLoader::SizeLarge, KIconLoader::DisabledState)); } void KImageFilePreview::KImageFilePreviewPrivate::_k_slotResult(KJob *job) { if (job == m_job) { m_job = nullptr; } } void KImageFilePreview::KImageFilePreviewPrivate::_k_slotStepAnimation(int frame) { Q_UNUSED(frame) QPixmap pm(QSize(qMax(m_pmCurrent.size().width(), m_pmTransition.size().width()), qMax(m_pmCurrent.size().height(), m_pmTransition.size().height()))); pm.fill(Qt::transparent); QPainter p(&pm); p.setOpacity(m_pmCurrentOpacity); //If we have a current pixmap if (!m_pmCurrent.isNull()) p.drawPixmap(QPoint(((float) pm.size().width() - m_pmCurrent.size().width()) / 2.0, ((float) pm.size().height() - m_pmCurrent.size().height()) / 2.0), m_pmCurrent); if (!m_pmTransition.isNull()) { p.setOpacity(m_pmTransitionOpacity); p.drawPixmap(QPoint(((float) pm.size().width() - m_pmTransition.size().width()) / 2.0, ((float) pm.size().height() - m_pmTransition.size().height()) / 2.0), m_pmTransition); } p.end(); imageLabel->setPixmap(pm); m_pmCurrentOpacity = qMax(m_pmCurrentOpacity - 0.4, 0.0); // krazy:exclude=qminmax m_pmTransitionOpacity = qMin(m_pmTransitionOpacity + 0.4, 1.0); //krazy:exclude=qminmax } void KImageFilePreview::KImageFilePreviewPrivate::_k_slotFinished() { m_pmCurrent = m_pmTransition; m_pmTransitionOpacity = 0; m_pmCurrentOpacity = 1; m_pmTransition = QPixmap(); // The animation might have lost some frames. Be sure that if the last one // was dropped, the last image shown is the opaque one. imageLabel->setPixmap(m_pmCurrent); clear = false; } void KImageFilePreview::clearPreview() { if (d->m_job) { d->m_job->kill(); d->m_job = nullptr; } if (d->clear || d->m_timeLine->state() == QTimeLine::Running) { return; } if (style()->styleHint(QStyle::SH_Widget_Animate, nullptr, this)) { d->m_pmTransition = QPixmap(); //If we add a previous preview then we run the animation if (!d->m_pmCurrent.isNull()) { d->m_timeLine->setCurrentTime(0); d->m_timeLine->setDirection(QTimeLine::Backward); d->m_timeLine->start(); } d->currentURL = QUrl(); d->clear = true; } else { d->imageLabel->clear(); } } #include "moc_kimagefilepreview.cpp" diff --git a/tests/kdirlistertest_gui.cpp b/tests/kdirlistertest_gui.cpp index 9f424c5e..f9883690 100644 --- a/tests/kdirlistertest_gui.cpp +++ b/tests/kdirlistertest_gui.cpp @@ -1,165 +1,165 @@ /* This file is part of the KDE desktop environment Copyright (C) 2001, 2002 Michael Brade 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 "kdirlistertest_gui.h" #include #include #include #include #include #include KDirListerTest::KDirListerTest(QWidget *parent) : QWidget(parent) { lister = new KDirLister(this); debug = new PrintSignals; QVBoxLayout *layout = new QVBoxLayout(this); QPushButton *startH = new QPushButton(QStringLiteral("Start listing Home"), this); QPushButton *startR = new QPushButton(QStringLiteral("Start listing Root"), this); QPushButton *test = new QPushButton(QStringLiteral("Many"), this); QPushButton *startT = new QPushButton(QStringLiteral("tarfile"), this); layout->addWidget(startH); layout->addWidget(startR); layout->addWidget(startT); layout->addWidget(test); resize(layout->sizeHint()); connect(startR, SIGNAL(clicked()), SLOT(startRoot())); connect(startH, SIGNAL(clicked()), SLOT(startHome())); connect(startT, SIGNAL(clicked()), SLOT(startTar())); connect(test, SIGNAL(clicked()), SLOT(test())); connect(lister, SIGNAL(started(QUrl)), debug, SLOT(started(QUrl))); connect(lister, SIGNAL(completed()), debug, SLOT(completed())); connect(lister, SIGNAL(completed(QUrl)), debug, SLOT(completed(QUrl))); connect(lister, SIGNAL(canceled()), debug, SLOT(canceled())); connect(lister, SIGNAL(canceled(QUrl)), debug, SLOT(canceled(QUrl))); connect(lister, SIGNAL(redirection(QUrl)), debug, SLOT(redirection(QUrl))); connect(lister, SIGNAL(redirection(QUrl,QUrl)), debug, SLOT(redirection(QUrl,QUrl))); connect(lister, SIGNAL(clear()), debug, SLOT(clear())); connect(lister, SIGNAL(newItems(KFileItemList)), debug, SLOT(newItems(KFileItemList))); connect(lister, SIGNAL(itemsFilteredByMime(KFileItemList)), debug, SLOT(itemsFilteredByMime(KFileItemList))); connect(lister, SIGNAL(itemsDeleted(KFileItemList)), debug, SLOT(itemsDeleted(KFileItemList))); - connect(lister, SIGNAL(refreshItems(QList >)), - debug, SLOT(refreshItems(QList >))); + connect(lister, SIGNAL(refreshItems(QList>)), + debug, SLOT(refreshItems(QList>))); connect(lister, SIGNAL(infoMessage(QString)), debug, SLOT(infoMessage(QString))); connect(lister, SIGNAL(percent(int)), debug, SLOT(percent(int))); connect(lister, SIGNAL(totalSize(KIO::filesize_t)), debug, SLOT(totalSize(KIO::filesize_t))); connect(lister, SIGNAL(processedSize(KIO::filesize_t)), debug, SLOT(processedSize(KIO::filesize_t))); connect(lister, SIGNAL(speed(int)), debug, SLOT(speed(int))); connect(lister, SIGNAL(completed()), this, SLOT(completed())); } KDirListerTest::~KDirListerTest() { } void KDirListerTest::startHome() { QUrl home = QUrl::fromLocalFile(QDir::homePath()); lister->openUrl(home, KDirLister::NoFlags); // lister->stop(); } void KDirListerTest::startRoot() { QUrl root = QUrl::fromLocalFile(QDir::rootPath()); lister->openUrl(root, KDirLister::Keep | KDirLister::Reload); // lister->stop( root ); } void KDirListerTest::startTar() { QUrl root = QUrl::fromLocalFile(QDir::homePath() + "/aclocal_1.tgz"); lister->openUrl(root, KDirLister::Keep | KDirLister::Reload); // lister->stop( root ); } void KDirListerTest::test() { QUrl home = QUrl::fromLocalFile(QDir::homePath()); QUrl root = QUrl::fromLocalFile(QDir::rootPath()); #ifdef Q_OS_WIN lister->openUrl(home, KDirLister::Keep); lister->openUrl(root, KDirLister::Keep | KDirLister::Reload); #else /* lister->openUrl( home, KDirLister::Keep ); lister->openUrl( root, KDirLister::Keep | KDirLister::Reload ); lister->openUrl( QUrl::fromLocalFile("file:/etc"), KDirLister::Keep | KDirLister::Reload ); lister->openUrl( root, KDirLister::Keep | KDirLister::Reload ); lister->openUrl( QUrl::fromLocalFile("file:/dev"), KDirLister::Keep | KDirLister::Reload ); lister->openUrl( QUrl::fromLocalFile("file:/tmp"), KDirLister::Keep | KDirLister::Reload ); lister->openUrl( QUrl::fromLocalFile("file:/usr/include"), KDirLister::Keep | KDirLister::Reload ); lister->updateDirectory( QUrl::fromLocalFile("file:/usr/include") ); lister->updateDirectory( QUrl::fromLocalFile("file:/usr/include") ); lister->openUrl( QUrl::fromLocalFile("file:/usr/"), KDirLister::Keep | KDirLister::Reload ); */ lister->openUrl(QUrl::fromLocalFile(QStringLiteral("/dev")), KDirLister::Keep | KDirLister::Reload); #endif } void KDirListerTest::completed() { if (lister->url().toLocalFile() == QDir::rootPath()) { const KFileItem item = lister->findByUrl(QUrl::fromLocalFile(QDir::tempPath())); if (!item.isNull()) { qDebug() << "Found " << QDir::tempPath() << ": " << item.name(); } else { qWarning() << QDir::tempPath() << " not found! Bug in findByURL?"; } } } int main(int argc, char *argv[]) { QApplication::setApplicationName(QStringLiteral("kdirlistertest")); QApplication app(argc, argv); KDirListerTest *test = new KDirListerTest(nullptr); test->show(); return app.exec(); } #include "moc_kdirlistertest_gui.cpp"