diff --git a/src/baloo/localbaloofilelisting.cpp b/src/baloo/localbaloofilelisting.cpp index 87b4981b..896529b7 100644 --- a/src/baloo/localbaloofilelisting.cpp +++ b/src/baloo/localbaloofilelisting.cpp @@ -1,427 +1,426 @@ /* * Copyright 2016-2017 Matthieu Gallien * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #include "localbaloofilelisting.h" #include "baloo/baloocommon.h" #include "musicaudiotrack.h" #include "elisa_settings.h" #include "elisautils.h" #include "baloo/scheduler.h" #include "baloo/fileindexer.h" #include "baloo/main.h" #include "baloowatcherapplicationadaptor.h" #include "filescanner.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include class LocalBalooFileListingPrivate { public: Baloo::Query mQuery; QHash> mAllAlbums; QDBusServiceWatcher mServiceWatcher; QScopedPointer mBalooMainInterface; QScopedPointer mBalooIndexer; QScopedPointer mBalooScheduler; BalooWatcherApplicationAdaptor *mDbusAdaptor = nullptr; QAtomicInt mStopRequest = 0; bool mIsRegisteredToBaloo = false; bool mIsRegisteringToBaloo = false; bool mIsRegisteredToBalooWatcher = false; bool mIsRegisteringToBalooWatcher = false; }; LocalBalooFileListing::LocalBalooFileListing(QObject *parent) : AbstractFileListing(parent), d(std::make_unique()) { d->mQuery.addType(QStringLiteral("Audio")); setHandleNewFiles(false); auto sessionBus = QDBusConnection::sessionBus(); d->mDbusAdaptor = new BalooWatcherApplicationAdaptor(this); sessionBus.registerObject(QStringLiteral("/org/kde/BalooWatcherApplication"), d->mDbusAdaptor, QDBusConnection::ExportAllContents); connect(&d->mServiceWatcher, &QDBusServiceWatcher::serviceRegistered, this, &LocalBalooFileListing::serviceRegistered); connect(&d->mServiceWatcher, &QDBusServiceWatcher::serviceOwnerChanged, this, &LocalBalooFileListing::serviceOwnerChanged); connect(&d->mServiceWatcher, &QDBusServiceWatcher::serviceUnregistered, this, &LocalBalooFileListing::serviceUnregistered); d->mServiceWatcher.setConnection(sessionBus); d->mServiceWatcher.addWatchedService(QStringLiteral("org.kde.baloo")); if (sessionBus.interface()->isServiceRegistered(QStringLiteral("org.kde.baloo"))) { registerToBaloo(); } } LocalBalooFileListing::~LocalBalooFileListing() { } void LocalBalooFileListing::applicationAboutToQuit() { AbstractFileListing::applicationAboutToQuit(); d->mStopRequest = 1; } void LocalBalooFileListing::newBalooFile(const QString &fileName) { qCDebug(orgKdeElisaBaloo()) << "LocalBalooFileListing::newBalooFile" << fileName; auto scanFileInfo = QFileInfo(fileName); if (!scanFileInfo.exists()) { return; } const auto &fileMimeType = mimeDatabase().mimeTypeForFile(fileName); if (!fileMimeType.name().startsWith(QLatin1String("audio/"))) { return; } Q_EMIT indexingStarted(); auto newFile = QUrl::fromLocalFile(fileName); auto newTrack = scanOneFile(newFile, scanFileInfo); if (newTrack.isValid()) { QFileInfo newFileInfo(fileName); addFileInDirectory(newFile, QUrl::fromLocalFile(newFileInfo.absoluteDir().absolutePath())); emitNewFiles({newTrack}); } Q_EMIT indexingFinished(); } void LocalBalooFileListing::registeredToBaloo(QDBusPendingCallWatcher *watcher) { qCDebug(orgKdeElisaBaloo) << "LocalBalooFileListing::registeredToBaloo"; if (!watcher) { return; } QDBusPendingReply<> reply = *watcher; if (reply.isError()) { qCDebug(orgKdeElisaBaloo) << "LocalBalooFileListing::registeredToBaloo" << reply.error().name() << reply.error().message(); d->mIsRegisteredToBaloo = false; } else { d->mIsRegisteredToBaloo = true; } d->mIsRegisteringToBaloo = false; watcher->deleteLater(); } void LocalBalooFileListing::registeredToBalooWatcher(QDBusPendingCallWatcher *watcher) { qCDebug(orgKdeElisaBaloo) << "LocalBalooFileListing::registeredToBalooWatcher"; if (!watcher) { return; } QDBusPendingReply<> reply = *watcher; if (reply.isError()) { qCDebug(orgKdeElisaBaloo) << "LocalBalooFileListing::registeredToBalooWatcher" << reply.error().name() << reply.error().message(); d->mIsRegisteredToBalooWatcher = false; } else { d->mIsRegisteredToBalooWatcher = true; } d->mIsRegisteringToBalooWatcher = false; watcher->deleteLater(); } void LocalBalooFileListing::registerToBaloo() { if (d->mIsRegisteringToBaloo || d->mIsRegisteringToBalooWatcher) { qCDebug(orgKdeElisaBaloo) << "LocalBalooFileListing::registerToBaloo" << "already registering"; return; } qCDebug(orgKdeElisaBaloo) << "LocalBalooFileListing::registerToBaloo"; d->mIsRegisteringToBaloo = true; d->mIsRegisteringToBalooWatcher = true; auto sessionBus = QDBusConnection::sessionBus(); d->mBalooMainInterface.reset(new org::kde::baloo::main(QStringLiteral("org.kde.baloo"), QStringLiteral("/"), sessionBus, this)); if (!d->mBalooMainInterface->isValid()) { qCDebug(orgKdeElisaBaloo) << "LocalBalooFileListing::registerToBaloo" << "invalid org.kde.baloo/main interface"; return; } d->mBalooIndexer.reset(new org::kde::baloo::fileindexer(QStringLiteral("org.kde.baloo"), QStringLiteral("/fileindexer"), sessionBus, this)); if (!d->mBalooIndexer->isValid()) { qCDebug(orgKdeElisaBaloo) << "LocalBalooFileListing::registerToBaloo" << "invalid org.kde.baloo/fileindexer interface"; return; } connect(d->mBalooIndexer.data(), &org::kde::baloo::fileindexer::finishedIndexingFile, this, &LocalBalooFileListing::newBalooFile); d->mBalooScheduler.reset(new org::kde::baloo::scheduler(QStringLiteral("org.kde.baloo"), QStringLiteral("/scheduler"), sessionBus, this)); if (!d->mBalooScheduler->isValid()) { qCDebug(orgKdeElisaBaloo) << "LocalBalooFileListing::registerToBaloo" << "invalid org.kde.baloo/scheduler interface"; return; } qCDebug(orgKdeElisaBaloo) << "LocalBalooFileListing::registerToBaloo" << "call registerMonitor"; auto answer = d->mBalooIndexer->registerMonitor(); if (answer.isError()) { qCDebug(orgKdeElisaBaloo) << "LocalBalooFileListing::executeInit" << answer.error().name() << answer.error().message(); } auto pendingCallWatcher = new QDBusPendingCallWatcher(answer); connect(pendingCallWatcher, &QDBusPendingCallWatcher::finished, this, &LocalBalooFileListing::registeredToBaloo); if (pendingCallWatcher->isFinished()) { registeredToBaloo(pendingCallWatcher); } auto pendingCall = d->mBalooMainInterface->registerBalooWatcher(QStringLiteral("org.mpris.MediaPlayer2.elisa/org/kde/BalooWatcherApplication")); qCDebug(orgKdeElisaBaloo) << "LocalBalooFileListing::registerToBaloo" << "call registerBalooWatcher"; auto pendingCallWatcher2 = new QDBusPendingCallWatcher(pendingCall); connect(pendingCallWatcher2, &QDBusPendingCallWatcher::finished, this, &LocalBalooFileListing::registeredToBalooWatcher); if (pendingCallWatcher2->isFinished()) { registeredToBalooWatcher(pendingCallWatcher2); } } void LocalBalooFileListing::renamedFiles(const QString &from, const QString &to, const QStringList &listFiles) { qCDebug(orgKdeElisaBaloo) << "LocalBalooFileListing::renamedFiles" << from << to << listFiles; } void LocalBalooFileListing::serviceOwnerChanged(const QString &serviceName, const QString &oldOwner, const QString &newOwner) { Q_UNUSED(oldOwner); Q_UNUSED(newOwner); qCDebug(orgKdeElisaBaloo) << "LocalBalooFileListing::serviceOwnerChanged" << serviceName << oldOwner << newOwner; if (serviceName == QLatin1String("org.kde.baloo") && !newOwner.isEmpty()) { d->mIsRegisteredToBaloo = false; d->mIsRegisteredToBalooWatcher = false; registerToBaloo(); } } void LocalBalooFileListing::serviceRegistered(const QString &serviceName) { qCDebug(orgKdeElisaBaloo) << "LocalBalooFileListing::serviceRegistered" << serviceName; if (serviceName == QLatin1String("org.kde.baloo")) { registerToBaloo(); } } void LocalBalooFileListing::serviceUnregistered(const QString &serviceName) { qCDebug(orgKdeElisaBaloo) << "LocalBalooFileListing::serviceUnregistered" << serviceName; if (serviceName == QLatin1String("org.kde.baloo")) { d->mIsRegisteredToBaloo = false; d->mIsRegisteredToBalooWatcher = false; } } void LocalBalooFileListing::executeInit(QHash allFiles) { AbstractFileListing::executeInit(std::move(allFiles)); } void LocalBalooFileListing::triggerRefreshOfContent() { qCDebug(orgKdeElisaBaloo()) << "LocalBalooFileListing::triggerRefreshOfContent"; Q_EMIT indexingStarted(); AbstractFileListing::triggerRefreshOfContent(); const auto &rootPaths = allRootPaths(); bool hasSingleRootPath = (rootPaths.size() == 1); auto singleRootPath = rootPaths.at(0); auto resultIterator = d->mQuery.exec(); auto newFiles = QList(); while(resultIterator.next() && d->mStopRequest == 0) { const auto &fileName = resultIterator.filePath(); if (hasSingleRootPath) { if (!fileName.startsWith(singleRootPath)) { qCDebug(orgKdeElisaBaloo()) << "LocalBalooFileListing::triggerRefreshOfContent" << fileName << "does not match root paths"; continue; } } else { bool isIncluded = false; for (const auto &oneRootPath : rootPaths) { if (fileName.startsWith(oneRootPath)) { isIncluded = true; break; } } if (!isIncluded) { qCDebug(orgKdeElisaBaloo()) << "LocalBalooFileListing::triggerRefreshOfContent" << fileName << "does not match root paths"; continue; } } const auto &newFileUrl = QUrl::fromLocalFile(resultIterator.filePath()); auto scanFileInfo = QFileInfo(fileName); if (!scanFileInfo.exists()) { qCDebug(orgKdeElisaBaloo()) << "LocalBalooFileListing::triggerRefreshOfContent" << fileName << "file does not exists"; continue; } auto itExistingFile = allFiles().find(newFileUrl); if (itExistingFile != allFiles().end()) { if (*itExistingFile >= scanFileInfo.fileTime(QFile::FileModificationTime)) { allFiles().erase(itExistingFile); qCDebug(orgKdeElisaBaloo()) << "LocalBalooFileListing::triggerRefreshOfContent" << fileName << "file not modified since last scan"; continue; } } const auto currentDirectory = QUrl::fromLocalFile(scanFileInfo.absoluteDir().absolutePath()); addFileInDirectory(newFileUrl, currentDirectory); const auto &newTrack = scanOneFile(newFileUrl, scanFileInfo); if (newTrack.isValid()) { newFiles.push_back(newTrack); if (newFiles.size() > 500 && d->mStopRequest == 0) { qCDebug(orgKdeElisaBaloo()) << "LocalBalooFileListing::triggerRefreshOfContent" << "insert new tracks in database" << newFiles.count(); emitNewFiles(newFiles); newFiles.clear(); } } else { qCDebug(orgKdeElisaBaloo()) << "LocalBalooFileListing::triggerRefreshOfContent" << fileName << "invalid track" << newTrack; } } if (!newFiles.isEmpty() && d->mStopRequest == 0) { qCDebug(orgKdeElisaBaloo()) << "LocalBalooFileListing::triggerRefreshOfContent" << "insert new tracks in database" << newFiles.count(); emitNewFiles(newFiles); } setWaitEndTrackRemoval(false); checkFilesToRemove(); if (!waitEndTrackRemoval()) { Q_EMIT indexingFinished(); } } MusicAudioTrack LocalBalooFileListing::scanOneFile(const QUrl &scanFile, const QFileInfo &scanFileInfo) { auto newTrack = MusicAudioTrack(); auto localFileName = scanFile.toLocalFile(); Baloo::File match(localFileName); match.load(); newTrack.setFileModificationTime(scanFileInfo.metadataChangeTime()); newTrack.setResourceURI(scanFile); fileScanner().scanProperties(match, newTrack); if (!newTrack.isValid()) { qCDebug(orgKdeElisaBaloo) << "LocalBalooFileListing::scanOneFile" << scanFile << "falling back to plain file metadata analysis"; newTrack = AbstractFileListing::scanOneFile(scanFile, scanFileInfo); } if (newTrack.isValid()) { newTrack.setHasEmbeddedCover(checkEmbeddedCoverImage(localFileName)); addCover(newTrack); - watchPath(localFileName); } else { qCDebug(orgKdeElisaBaloo) << "LocalBalooFileListing::scanOneFile" << scanFile << "invalid track"; } return newTrack; } #include "moc_localbaloofilelisting.cpp" diff --git a/src/filescanner.cpp b/src/filescanner.cpp index 4454533c..7e80f835 100644 --- a/src/filescanner.cpp +++ b/src/filescanner.cpp @@ -1,290 +1,290 @@ /* * Copyright 2018 Matthieu Gallien * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #include "filescanner.h" #include "config-upnp-qt.h" #include "abstractfile/indexercommon.h" #if defined KF5FileMetaData_FOUND && KF5FileMetaData_FOUND #include #include #include #include #include #include #if defined KF5Baloo_FOUND && KF5Baloo_FOUND #include #endif #endif #include #include #include #include class FileScannerPrivate { public: static const QStringList constSearchStrings; #if defined KF5FileMetaData_FOUND && KF5FileMetaData_FOUND KFileMetaData::ExtractorCollection mAllExtractors; KFileMetaData::PropertyMap mAllProperties; KFileMetaData::EmbeddedImageData mImageScanner; #endif }; const QStringList FileScannerPrivate::constSearchStrings = { QStringLiteral("*[Cc]over*.jpg"), QStringLiteral("*[Cc]over*.png"), QStringLiteral("*[Ff]older*.jpg"), QStringLiteral("*[Ff]older*.png"), QStringLiteral("*[Ff]ront*.jpg"), QStringLiteral("*[Ff]ront*.png") }; FileScanner::FileScanner() : d(std::make_unique()) { } FileScanner::~FileScanner() = default; MusicAudioTrack FileScanner::scanOneFile(const QUrl &scanFile, const QMimeDatabase &mimeDatabase) { MusicAudioTrack newTrack; auto localFileName = scanFile.toLocalFile(); QFileInfo scanFileInfo(localFileName); newTrack.setFileModificationTime(scanFileInfo.metadataChangeTime()); newTrack.setResourceURI(scanFile); newTrack.setRating(0); #if defined KF5FileMetaData_FOUND && KF5FileMetaData_FOUND const auto &fileMimeType = mimeDatabase.mimeTypeForFile(localFileName); if (!fileMimeType.name().startsWith(QLatin1String("audio/"))) { return newTrack; } QString mimetype = fileMimeType.name(); QList exList = d->mAllExtractors.fetchExtractors(mimetype); if (exList.isEmpty()) { return newTrack; } KFileMetaData::Extractor* ex = exList.first(); KFileMetaData::SimpleExtractionResult result(localFileName, mimetype, KFileMetaData::ExtractionResult::ExtractMetaData); ex->extract(&result); d->mAllProperties = result.properties(); scanProperties(localFileName, newTrack); qCDebug(orgKdeElisaIndexer()) << "scanOneFile" << scanFile << "using KFileMetaData" << newTrack; #else Q_UNUSED(mimeDatabase) newTrack.setTitle(scanFileInfo.fileName()); newTrack.setValid(true); qCDebug(orgKdeElisaIndexer()) << "scanOneFile" << scanFile << "no metadata provider" << newTrack; #endif return newTrack; } void FileScanner::scanProperties(const Baloo::File &match, MusicAudioTrack &trackData) { #if defined KF5Baloo_FOUND && KF5Baloo_FOUND d->mAllProperties = match.properties(); scanProperties(match.path(), trackData); qCDebug(orgKdeElisaIndexer()) << "scanProperties" << match.path() << "using Baloo" << trackData; #else Q_UNUSED(match) Q_UNUSED(trackData) qCDebug(orgKdeElisaIndexer()) << "scanProperties" << "no metadata provider" << trackData; #endif } void FileScanner::scanProperties(const QString &localFileName, MusicAudioTrack &trackData) { #if defined KF5FileMetaData_FOUND && KF5FileMetaData_FOUND if (d->mAllProperties.isEmpty()) { return; } using entry = std::pair; auto rangeBegin = d->mAllProperties.constKeyValueBegin(); QVariant value; while (rangeBegin != d->mAllProperties.constKeyValueEnd()) { auto key = (*rangeBegin).first; auto rangeEnd = std::find_if(rangeBegin, d->mAllProperties.constKeyValueEnd(), [key](const entry& e) { return e.first != key; }); auto distance = std::distance(rangeBegin, rangeEnd); if (distance > 1) { QStringList list; list.reserve(static_cast(distance)); std::for_each(rangeBegin, rangeEnd, [&list](const entry& s) { list.append(s.second.toString()); }); value = QLocale().createSeparatedList(list); } else { value = (*rangeBegin).second; if (value.canConvert()) { value = QLocale().createSeparatedList(value.toStringList()); } } switch (key) { case KFileMetaData::Property::Artist: trackData.setArtist(value.toString()); break; case KFileMetaData::Property::AlbumArtist: trackData.setAlbumArtist(value.toString()); break; case KFileMetaData::Property::Genre: trackData.setGenre(value.toString()); break; case KFileMetaData::Property::Composer: trackData.setComposer(value.toString()); break; case KFileMetaData::Property::Lyricist: trackData.setLyricist(value.toString()); break; case KFileMetaData::Property::Title: trackData.setTitle(value.toString()); break; case KFileMetaData::Property::Duration: trackData.setDuration(QTime::fromMSecsSinceStartOfDay(int(1000 * value.toDouble()))); break; case KFileMetaData::Property::Album: trackData.setAlbumName(value.toString()); break; case KFileMetaData::Property::TrackNumber: trackData.setTrackNumber(value.toInt()); break; case KFileMetaData::Property::DiscNumber: trackData.setDiscNumber(value.toInt()); break; case KFileMetaData::Property::ReleaseYear: trackData.setYear(value.toInt()); break; case KFileMetaData::Property::Lyrics: trackData.setLyrics(value.toString()); break; case KFileMetaData::Property::Channels: trackData.setChannels(value.toInt()); break; case KFileMetaData::Property::BitRate: trackData.setBitRate(value.toInt()); break; case KFileMetaData::Property::SampleRate: trackData.setSampleRate(value.toInt()); break; case KFileMetaData::Property::Comment: trackData.setComment(value.toString()); break; case KFileMetaData::Property::Rating: trackData.setRating(value.toInt()); break; default: break; } rangeBegin = rangeEnd; } #if !defined Q_OS_ANDROID && !defined Q_OS_WIN auto fileData = KFileMetaData::UserMetaData(localFileName); QString comment = fileData.userComment(); if (!comment.isEmpty()) { trackData.setComment(comment); } int rating = fileData.rating(); - if (rating > 0) { + if (rating >= 0) { trackData.setRating(rating); } #endif if (!trackData.duration().isValid()) { return; } trackData.setValid(true); #else Q_UNUSED(localFileName) Q_UNUSED(trackData) #endif } QUrl FileScanner::searchForCoverFile(const QString &localFileName) { QFileInfo trackFilePath(localFileName); QDir trackFileDir = trackFilePath.absoluteDir(); trackFileDir.setFilter(QDir::Files); trackFileDir.setNameFilters(d->constSearchStrings); QFileInfoList coverFiles = trackFileDir.entryInfoList(); if (coverFiles.isEmpty()) { QString dirNamePattern = QLatin1String("*") + trackFileDir.dirName() + QLatin1String("*"); QString dirNameNoSpaces = dirNamePattern.remove(QLatin1Char(' ')); QStringList filters = { dirNamePattern + QStringLiteral(".jpg"), dirNamePattern + QStringLiteral(".png"), dirNameNoSpaces + QStringLiteral(".jpg"), dirNameNoSpaces + QStringLiteral(".png") }; trackFileDir.setNameFilters(filters); coverFiles = trackFileDir.entryInfoList(); } if (coverFiles.isEmpty()) { return QUrl(); } return QUrl::fromLocalFile(coverFiles.first().absoluteFilePath()); } bool FileScanner::checkEmbeddedCoverImage(const QString &localFileName) { #if defined KF5FileMetaData_FOUND && KF5FileMetaData_FOUND auto imageData = d->mImageScanner.imageData(localFileName); if (imageData.contains(KFileMetaData::EmbeddedImageData::FrontCover)) { if (!imageData[KFileMetaData::EmbeddedImageData::FrontCover].isEmpty()) { return true; } } #else Q_UNUSED(localFileName) #endif return false; }