diff --git a/autotests/CMakeLists.txt b/autotests/CMakeLists.txt --- a/autotests/CMakeLists.txt +++ b/autotests/CMakeLists.txt @@ -119,6 +119,7 @@ knewfilemenutest.cpp kfilecopytomenutest.cpp kfileplacesmodeltest.cpp + kfileplacesviewtest.cpp kurlrequestertest.cpp NAME_PREFIX "kiofilewidgets-" LINK_LIBRARIES KF5::KIOFileWidgets KF5::KIOWidgets KF5::XmlGui KF5::Bookmarks Qt5::Test KF5::I18n diff --git a/autotests/kfileplacesmodeltest.cpp b/autotests/kfileplacesmodeltest.cpp --- a/autotests/kfileplacesmodeltest.cpp +++ b/autotests/kfileplacesmodeltest.cpp @@ -25,6 +25,8 @@ #include #include #include +#include +#include #include @@ -66,6 +68,7 @@ void testDevicePlugging(); void testDragAndDrop(); void testDeviceSetupTeardown(); + void testEnableBaloo(); private: QStringList placesUrls() const; @@ -93,6 +96,12 @@ // Ensure we'll have a clean bookmark file to start QFile::remove(bookmarksFile()); + // disable baloo by default + KConfig config(QStringLiteral("baloofilerc")); + KConfigGroup basicSettings = config.group("Basic Settings"); + basicSettings.writeEntry("Indexing-Enabled", false); + config.sync(); + qRegisterMetaType(); const QString fakeHw = QFINDTESTDATA("fakecomputer.xml"); QVERIFY(!fakeHw.isEmpty()); @@ -696,6 +705,31 @@ QCOMPARE(args.at(1).toModelIndex().row(), 6); } +void KFilePlacesModelTest::testEnableBaloo() +{ + KConfig config(QStringLiteral("baloofilerc")); + KConfigGroup basicSettings = config.group("Basic Settings"); + basicSettings.writeEntry("Indexing-Enabled", true); + config.sync(); + + KFilePlacesModel places_with_baloo; + QStringList urls; + for (int row = 0; row < places_with_baloo.rowCount(); ++row) { + QModelIndex index = places_with_baloo.index(row, 0); + urls << places_with_baloo.url(index).toDisplayString(QUrl::PreferLocalFile); + } + + QVERIFY(urls.contains("timeline:/today")); + QVERIFY(urls.contains("timeline:/yesterday")); + QVERIFY(urls.contains("timeline:/thismonth")); + QVERIFY(urls.contains("timeline:/lastmonth")); + + QVERIFY(urls.contains("search:/documents")); + QVERIFY(urls.contains("search:/images")); + QVERIFY(urls.contains("search:/audio")); + QVERIFY(urls.contains("search:/videos")); +} + QTEST_MAIN(KFilePlacesModelTest) #include "kfileplacesmodeltest.moc" diff --git a/autotests/kfileplacesviewtest.cpp b/autotests/kfileplacesviewtest.cpp new file mode 100644 --- /dev/null +++ b/autotests/kfileplacesviewtest.cpp @@ -0,0 +1,122 @@ +/* This file is part of the KDE project + Copyright (C) 2017 Renato Araujo Oliveira Filho + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 + as published by the Free Software Foundation. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; 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 +#include + +static QString bookmarksFile() +{ + return QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/user-places.xbel"; +} + +class KFilePlacesViewTest : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void initTestCase(); + void cleanupTestCase(); + + void testUrlChanged_data(); + void testUrlChanged(); +}; + +void KFilePlacesViewTest::initTestCase() +{ + qputenv("KDE_FORK_SLAVES", "yes"); // to avoid a runtime dependency on klauncher + QStandardPaths::setTestModeEnabled(true); + + cleanupTestCase(); + + KConfig config(QStringLiteral("baloofilerc")); + KConfigGroup basicSettings = config.group("Basic Settings"); + basicSettings.writeEntry("Indexing-Enabled", true); + config.sync(); + + qRegisterMetaType(); +} + +void KFilePlacesViewTest::cleanupTestCase() +{ + QFile::remove(bookmarksFile()); +} + +void KFilePlacesViewTest::testUrlChanged_data() +{ + QTest::addColumn("row"); + QTest::addColumn("expectedUrl"); + + const QDate currentDate = QDate::currentDate(); + const QDate yesterdayDate = currentDate.addDays(-1); + const QDate lastMonthDate = currentDate.addMonths(-1); + QTest::newRow("Today") << 4 << QStringLiteral("timeline:/today"); + QTest::newRow("Yesterday") << 5 << QString("timeline:/%1-%2/%1-%2-%3") + .arg(yesterdayDate.year()) + .arg(yesterdayDate.month(), 2, 10, QChar('0')) + .arg(yesterdayDate.day(), 2, 10, QChar('0')); + + QTest::newRow("This Month") << 6 << QString("timeline:/%1-%2") + .arg(currentDate.year()) + .arg(currentDate.month(), 2, 10, QChar('0')); + QTest::newRow("Last Month") << 7 << QString("timeline:/%1-%2") + .arg(lastMonthDate.year()) + .arg(lastMonthDate.month(), 2, 10, QChar('0')); + + // search + const QString baloonurl = QStringLiteral("baloosearch:?json=%7B%22dayFilter%22: 0, %22monthFilter%22: 0, %22yearFilter%22: 0, %22type%22: [ %22$TYPE$%22]%7D"); + const QString typeToReplace = QStringLiteral("$TYPE$"); + + QTest::newRow("Documents") << 8 << QString(baloonurl).replace(typeToReplace, QStringLiteral("Document")); + QTest::newRow("Images") << 9 << QString(baloonurl).replace(typeToReplace, QStringLiteral("Image")); + QTest::newRow("Audio Files") << 10 << QString(baloonurl).replace(typeToReplace, QStringLiteral("Audio")); + QTest::newRow("Videos") << 11 << QString(baloonurl).replace(typeToReplace, QStringLiteral("Video")); +} + +void KFilePlacesViewTest::testUrlChanged() +{ + QFETCH(int, row); + QFETCH(QString, expectedUrl); + + KFilePlacesView pv; + pv.show(); + pv.setModel(new KFilePlacesModel()); + QTest::qWaitForWindowActive(&pv); + + QSignalSpy urlChangedSpy(&pv, &KFilePlacesView::urlChanged); + const QModelIndex targetIndex = pv.model()->index(row, 0); + pv.scrollTo(targetIndex); + pv.clicked(targetIndex); + QTRY_COMPARE(urlChangedSpy.count(), 1); + const QList args = urlChangedSpy.takeFirst(); + QCOMPARE(args.at(0).toUrl().toString(), expectedUrl); +} + +QTEST_MAIN(KFilePlacesViewTest) + +#include "kfileplacesviewtest.moc" diff --git a/src/filewidgets/kfileplacesmodel.cpp b/src/filewidgets/kfileplacesmodel.cpp --- a/src/filewidgets/kfileplacesmodel.cpp +++ b/src/filewidgets/kfileplacesmodel.cpp @@ -46,6 +46,9 @@ #include #include +#include +#include + #include #include #include @@ -56,10 +59,25 @@ #include #include +namespace { + static bool isFileIndexingEnabled() + { + KConfig config(QStringLiteral("baloofilerc")); + KConfigGroup basicSettings = config.group("Basic Settings"); + return basicSettings.readEntry("Indexing-Enabled", true); + } +} + class Q_DECL_HIDDEN KFilePlacesModel::Private { public: - Private(KFilePlacesModel *self) : q(self), bookmarkManager(nullptr) {} + Private(KFilePlacesModel *self) + : q(self), + bookmarkManager(nullptr), + fileIndexingEnabled(isFileIndexingEnabled()) + { + } + ~Private() { qDeleteAll(items); @@ -74,6 +92,8 @@ Solid::Predicate predicate; KBookmarkManager *bookmarkManager; + const bool fileIndexingEnabled; + void reloadAndSignal(); QList loadBookmarkList(); @@ -84,6 +104,9 @@ void _k_reloadBookmarks(); void _k_storageSetupDone(Solid::ErrorType error, QVariant errorData); void _k_storageTeardownDone(Solid::ErrorType error, QVariant errorData); + +private: + bool isBalooUrl(const QUrl &url) const; }; KFilePlacesModel::KFilePlacesModel(QObject *parent) @@ -133,6 +156,39 @@ d->bookmarkManager->saveAs(file); } + // if baloo is enabled, add new urls even if the bookmark file is not empty + if (d->fileIndexingEnabled && + root.metaDataItem(QStringLiteral("withBaloo")) != QLatin1String("true")) { + + root.setMetaDataItem(QStringLiteral("withBaloo"), QStringLiteral("true")); + KFilePlacesItem::createSystemBookmark(d->bookmarkManager, + QStringLiteral("Today"), I18N_NOOP2("KFile System Bookmarks", "Today"), + QUrl(QStringLiteral("timeline:/today")), QStringLiteral("go-jump-today")); + KFilePlacesItem::createSystemBookmark(d->bookmarkManager, + QStringLiteral("Yesterday"), I18N_NOOP2("KFile System Bookmarks", "Yesterday"), + QUrl(QStringLiteral("timeline:/yesterday")), QStringLiteral("view-calendar-day")); + KFilePlacesItem::createSystemBookmark(d->bookmarkManager, + QStringLiteral("This Month"), I18N_NOOP2("KFile System Bookmarks", "This Month"), + QUrl(QStringLiteral("timeline:/thismonth")), QStringLiteral("view-calendar-month")); + KFilePlacesItem::createSystemBookmark(d->bookmarkManager, + QStringLiteral("Last Month"), I18N_NOOP2("KFile System Bookmarks", "Last Month"), + QUrl(QStringLiteral("timeline:/lastmonth")), QStringLiteral("view-calendar-month")); + KFilePlacesItem::createSystemBookmark(d->bookmarkManager, + QStringLiteral("Documents"), I18N_NOOP2("KFile System Bookmarks", "Documents"), + QUrl(QStringLiteral("search:/documents")), QStringLiteral("folder-text")); + KFilePlacesItem::createSystemBookmark(d->bookmarkManager, + QStringLiteral("Images"), I18N_NOOP2("KFile System Bookmarks", "Images"), + QUrl(QStringLiteral("search:/images")), QStringLiteral("folder-images")); + KFilePlacesItem::createSystemBookmark(d->bookmarkManager, + QStringLiteral("Audio Files"), I18N_NOOP2("KFile System Bookmarks", "Audio Files"), + QUrl(QStringLiteral("search:/audio")), QStringLiteral("folder-sound")); + KFilePlacesItem::createSystemBookmark(d->bookmarkManager, + QStringLiteral("Videos"), I18N_NOOP2("KFile System Bookmarks", "Videos"), + QUrl(QStringLiteral("search:/videos")), QStringLiteral("folder-videos")); + + d->bookmarkManager->save(); + } + QString predicate(QString::fromLatin1("[[[[ StorageVolume.ignored == false AND [ StorageVolume.usage == 'FileSystem' OR StorageVolume.usage == 'Encrypted' ]]" " OR " "[ IS StorageAccess AND StorageDrive.driveType == 'Floppy' ]]" @@ -422,6 +478,13 @@ currentItems.clear(); } +bool KFilePlacesModel::Private::isBalooUrl(const QUrl &url) const +{ + const QString scheme = url.scheme(); + return ((scheme == QLatin1String("timeline")) || + (scheme == QLatin1String("search"))); +} + QList KFilePlacesModel::Private::loadBookmarkList() { QList items; @@ -433,15 +496,18 @@ while (!bookmark.isNull()) { QString udi = bookmark.metaDataItem(QStringLiteral("UDI")); QString appName = bookmark.metaDataItem(QStringLiteral("OnlyInApp")); + QUrl url = bookmark.url(); auto it = std::find(devices.begin(), devices.end(), udi); bool deviceAvailable = (it != devices.end()); if (it != devices.end()) { devices.erase(it); } - bool allowedHere = appName.isEmpty() || (appName == QCoreApplication::instance()->applicationName()); + bool allowedHere = appName.isEmpty() || (appName == QCoreApplication::instance()->applicationName()); + bool isSupportedUrl = isBalooUrl(url) ? fileIndexingEnabled : true; + + if ((isSupportedUrl && udi.isEmpty() && allowedHere) || deviceAvailable) { - if ((udi.isEmpty() && allowedHere) || deviceAvailable) { KFilePlacesItem *item; if (deviceAvailable) { item = new KFilePlacesItem(bookmarkManager, bookmark.address(), udi); diff --git a/src/filewidgets/kfileplacesview.cpp b/src/filewidgets/kfileplacesview.cpp --- a/src/filewidgets/kfileplacesview.cpp +++ b/src/filewidgets/kfileplacesview.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include @@ -495,6 +496,13 @@ void fadeCapacityBar(const QModelIndex &index, FadeType fadeType); int sectionsCount() const; + // based on dolphin urls + QUrl convertUrl(const QUrl &url) const; + QUrl createTimelineUrl(const QUrl &url) const; + QUrl createSearchUrl(const QUrl &url) const; + QString timelineDateString(int year, int month, int day = 0) const; + QUrl searchUrlForType(const QString &type) const; + void _k_placeClicked(const QModelIndex &index); void _k_placeEntered(const QModelIndex &index); void _k_placeLeft(const QModelIndex &index); @@ -1086,7 +1094,7 @@ if (url.isValid()) { currentUrl = url; updateHiddenRows(); - emit q->urlChanged(url); + emit q->urlChanged(convertUrl(url)); if (showAll) { q->setShowAll(false); } @@ -1271,6 +1279,98 @@ return count; } +QUrl KFilePlacesView::Private::convertUrl(const QUrl &url) const +{ + QUrl newUrl = url; + if (url.scheme() == QLatin1String("timeline")) { + newUrl = createTimelineUrl(url); + } else if (url.scheme() == QLatin1String("search")) { + newUrl = createSearchUrl(url); + } + + return newUrl; +} + +QUrl KFilePlacesView::Private::createTimelineUrl(const QUrl &url) const +{ + // based on dolphin urls + const QString timelinePrefix = QStringLiteral("timeline:") + QLatin1Char('/'); + QUrl timelineUrl; + + const QString path = url.toDisplayString(QUrl::PreferLocalFile); + if (path.endsWith(QLatin1String("/yesterday"))) { + const QDate date = QDate::currentDate().addDays(-1); + const int year = date.year(); + const int month = date.month(); + const int day = date.day(); + + timelineUrl = QUrl(timelinePrefix + timelineDateString(year, month) + + '/' + timelineDateString(year, month, day)); + } else if (path.endsWith(QLatin1String("/thismonth"))) { + const QDate date = QDate::currentDate(); + timelineUrl = QUrl(timelinePrefix + timelineDateString(date.year(), date.month())); + } else if (path.endsWith(QLatin1String("/lastmonth"))) { + const QDate date = QDate::currentDate().addMonths(-1); + timelineUrl = QUrl(timelinePrefix + timelineDateString(date.year(), date.month())); + } else { + Q_ASSERT(path.endsWith(QLatin1String("/today"))); + timelineUrl = url; + } + + return timelineUrl; +} + +QString KFilePlacesView::Private::timelineDateString(int year, int month, int day) const +{ + const QString dateFormat("%1-%2"); + + QString date = dateFormat.arg(year).arg(month, 2, 10, QChar('0')); + if (day > 0) { + date += QString("-%1").arg(day, 2, 10, QChar('0')); + } + return date; +} + +QUrl KFilePlacesView::Private::createSearchUrl(const QUrl &url) const +{ + QUrl searchUrl; + + const QString path = url.toDisplayString(QUrl::PreferLocalFile); + + if (path.endsWith(QLatin1String("/documents"))) { + searchUrl = searchUrlForType(QStringLiteral("Document")); + } else if (path.endsWith(QLatin1String("/images"))) { + searchUrl = searchUrlForType(QStringLiteral("Image")); + } else if (path.endsWith(QLatin1String("/audio"))) { + searchUrl = searchUrlForType(QStringLiteral("Audio")); + } else if (path.endsWith(QLatin1String("/videos"))) { + searchUrl = searchUrlForType(QStringLiteral("Video")); + } else { + qWarning() << "Invalid search url:" << url; + searchUrl = url; + } + + return searchUrl; +} + + +QUrl KFilePlacesView::Private::searchUrlForType(const QString& type) const +{ + const QString jsonQuery(QStringLiteral("{\"dayFilter\": 0,\ + \"monthFilter\": 0, \ + \"yearFilter\": 0, \ + \"type\": [ \"%1\"]}")); + QUrl url; + url.setScheme(QStringLiteral("baloosearch")); + + QUrlQuery urlQuery; + urlQuery.addQueryItem(QStringLiteral("json"), jsonQuery.arg(type).simplified()); + url.setQuery(urlQuery); + + return url; +} + + void KFilePlacesView::Private::_k_placeClicked(const QModelIndex &index) { KFilePlacesModel *placesModel = qobject_cast(q->model());