File indexing completed on 2024-04-21 04:49:01
0001 /* 0002 SPDX-FileCopyrightText: 2016 (c) Matthieu Gallien <matthieu_gallien@yahoo.fr> 0003 0004 SPDX-License-Identifier: LGPL-3.0-or-later 0005 */ 0006 0007 #include "musiclistenersmanager.h" 0008 0009 #include "config-upnp-qt.h" 0010 0011 #include "indexersManager.h" 0012 0013 #if UPNPQT_FOUND 0014 #include "upnp/upnplistener.h" 0015 #endif 0016 0017 #if QtAndroidExtras_FOUND 0018 #include "android/androidmusiclistener.h" 0019 #endif 0020 0021 #include "databaseinterface.h" 0022 #include "mediaplaylist.h" 0023 #include "file/filelistener.h" 0024 #include "file/localfilelisting.h" 0025 #include "trackslistener.h" 0026 #include "elisaapplication.h" 0027 #include "elisa_settings.h" 0028 #include "modeldataloader.h" 0029 0030 #include <KLocalizedString> 0031 0032 #include <QThread> 0033 #include <QStandardPaths> 0034 #include <QDir> 0035 #include <QCoreApplication> 0036 #include <QFileSystemWatcher> 0037 0038 #include <list> 0039 0040 class MusicListenersManagerPrivate 0041 { 0042 public: 0043 0044 QThread mDatabaseThread; 0045 0046 QThread mListenerThread; 0047 0048 #if UPNPQT_FOUND 0049 UpnpListener mUpnpListener; 0050 #endif 0051 0052 FileListener mFileListener; 0053 0054 #if QtAndroidExtras_FOUND 0055 AndroidMusicListener mAndroidMusicListener; 0056 #endif 0057 0058 DatabaseInterface mDatabaseInterface; 0059 0060 std::unique_ptr<TracksListener> mTracksListener; 0061 0062 QFileSystemWatcher mConfigFileWatcher; 0063 0064 QStringList mPreviousRootPathValue; 0065 0066 ElisaApplication *mElisaApplication = nullptr; 0067 0068 int mImportedTracksCount = 0; 0069 0070 bool mIndexerBusy = false; 0071 0072 bool mFileSystemIndexerActive = false; 0073 0074 bool mAndroidIndexerActive = false; 0075 0076 bool mAndroidIndexerAvailable = false; 0077 0078 }; 0079 0080 MusicListenersManager::MusicListenersManager(QObject *parent) 0081 : QObject(parent), d(std::make_unique<MusicListenersManagerPrivate>()) 0082 { 0083 connect(&d->mDatabaseInterface, &DatabaseInterface::tracksAdded, 0084 this, &MusicListenersManager::increaseImportedTracksCount); 0085 0086 connect(&d->mDatabaseInterface, &DatabaseInterface::requestsInitDone, 0087 this, &MusicListenersManager::databaseReady); 0088 0089 connect(this, &MusicListenersManager::clearDatabase, 0090 &d->mDatabaseInterface, &DatabaseInterface::clearData); 0091 0092 connect(&d->mDatabaseInterface, &DatabaseInterface::cleanedDatabase, 0093 this, &MusicListenersManager::cleanedDatabase); 0094 0095 connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, 0096 this, &MusicListenersManager::applicationAboutToQuit); 0097 0098 connect(&d->mConfigFileWatcher, &QFileSystemWatcher::fileChanged, 0099 this, &MusicListenersManager::configChanged); 0100 0101 connect(this, &MusicListenersManager::refreshDatabase, 0102 &d->mDatabaseInterface, &DatabaseInterface::askRestoredTracks); 0103 0104 d->mListenerThread.start(); 0105 d->mDatabaseThread.start(); 0106 0107 d->mDatabaseInterface.moveToThread(&d->mDatabaseThread); 0108 0109 const auto &localDataPaths = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation); 0110 auto databaseFileName = QString(); 0111 if (!localDataPaths.isEmpty()) { 0112 QDir myDataDirectory; 0113 myDataDirectory.mkpath(localDataPaths.first()); 0114 databaseFileName = localDataPaths.first() + QStringLiteral("/elisaDatabase.db"); 0115 } 0116 0117 QMetaObject::invokeMethod(&d->mDatabaseInterface, "init", Qt::QueuedConnection, 0118 Q_ARG(QString, QStringLiteral("listeners")), Q_ARG(QString, databaseFileName)); 0119 0120 qCInfo(orgKdeElisaIndexersManager) << "Local file system indexer is inactive"; 0121 } 0122 0123 MusicListenersManager::~MusicListenersManager() 0124 { 0125 d->mListenerThread.quit(); 0126 d->mListenerThread.wait(); 0127 0128 d->mDatabaseThread.quit(); 0129 d->mDatabaseThread.wait(); 0130 } 0131 0132 DatabaseInterface *MusicListenersManager::viewDatabase() const 0133 { 0134 return &d->mDatabaseInterface; 0135 } 0136 0137 void MusicListenersManager::subscribeForTracks(MediaPlayList *client) 0138 { 0139 createTracksListener(); 0140 connect(d->mTracksListener.get(), &TracksListener::trackHasChanged, client, &MediaPlayList::trackChanged); 0141 connect(d->mTracksListener.get(), &TracksListener::trackHasBeenRemoved, client, &MediaPlayList::trackRemoved); 0142 connect(d->mTracksListener.get(), &TracksListener::tracksListAdded, client, &MediaPlayList::tracksListAdded); 0143 connect(client, &MediaPlayList::newEntryInList, d->mTracksListener.get(), &TracksListener::newEntryInList); 0144 connect(client, &MediaPlayList::newUrlInList, d->mTracksListener.get(), &TracksListener::newUrlInList); 0145 connect(client, &MediaPlayList::newTrackByNameInList, d->mTracksListener.get(), &TracksListener::trackByNameInList); 0146 } 0147 0148 int MusicListenersManager::importedTracksCount() const 0149 { 0150 return d->mImportedTracksCount; 0151 } 0152 0153 ElisaApplication *MusicListenersManager::elisaApplication() const 0154 { 0155 return d->mElisaApplication; 0156 } 0157 0158 TracksListener *MusicListenersManager::tracksListener() const 0159 { 0160 return d->mTracksListener.get(); 0161 } 0162 0163 bool MusicListenersManager::indexerBusy() const 0164 { 0165 return d->mIndexerBusy; 0166 } 0167 0168 bool MusicListenersManager::fileSystemIndexerActive() const 0169 { 0170 return d->mFileSystemIndexerActive; 0171 } 0172 0173 bool MusicListenersManager::androidIndexerActive() const 0174 { 0175 return d->mAndroidIndexerActive; 0176 } 0177 0178 bool MusicListenersManager::androidIndexerAvailable() const 0179 { 0180 return d->mAndroidIndexerAvailable; 0181 } 0182 0183 auto MusicListenersManager::initializeRootPath() 0184 { 0185 auto initialRootPath = QStringList{}; 0186 auto systemMusicPaths = QStandardPaths::standardLocations(QStandardPaths::MusicLocation); 0187 for (const auto &musicPath : std::as_const(systemMusicPaths)) { 0188 initialRootPath.push_back(musicPath); 0189 } 0190 0191 Elisa::ElisaConfiguration::setRootPath(initialRootPath); 0192 Elisa::ElisaConfiguration::self()->save(); 0193 0194 return initialRootPath; 0195 } 0196 0197 void MusicListenersManager::databaseReady() 0198 { 0199 auto initialRootPath = Elisa::ElisaConfiguration::rootPath(); 0200 if (initialRootPath.isEmpty()) { 0201 initializeRootPath(); 0202 } 0203 0204 d->mConfigFileWatcher.addPath(Elisa::ElisaConfiguration::self()->config()->name()); 0205 0206 configChanged(); 0207 } 0208 0209 void MusicListenersManager::applicationAboutToQuit() 0210 { 0211 d->mDatabaseInterface.applicationAboutToQuit(); 0212 0213 Q_EMIT applicationIsTerminating(); 0214 0215 d->mDatabaseThread.exit(); 0216 d->mDatabaseThread.wait(); 0217 0218 d->mListenerThread.exit(); 0219 d->mListenerThread.wait(); 0220 } 0221 0222 void MusicListenersManager::setElisaApplication(ElisaApplication *elisaApplication) 0223 { 0224 if (d->mElisaApplication == elisaApplication) { 0225 return; 0226 } 0227 0228 d->mElisaApplication = elisaApplication; 0229 Q_EMIT elisaApplicationChanged(); 0230 } 0231 0232 void MusicListenersManager::playBackError(const QUrl &sourceInError, QMediaPlayer::Error playerError) 0233 { 0234 qCDebug(orgKdeElisaIndexersManager) << "MusicListenersManager::playBackError" << sourceInError; 0235 0236 if (playerError == QMediaPlayer::ResourceError) { 0237 Q_EMIT removeTracksInError({sourceInError}); 0238 0239 if (sourceInError.isLocalFile()) { 0240 Q_EMIT displayTrackError(sourceInError.toLocalFile()); 0241 } else { 0242 Q_EMIT displayTrackError(sourceInError.toString()); 0243 } 0244 } 0245 } 0246 0247 void MusicListenersManager::deleteElementById(ElisaUtils::PlayListEntryType entryType, qulonglong databaseId) 0248 { 0249 switch(entryType) 0250 { 0251 case ElisaUtils::Radio: 0252 QMetaObject::invokeMethod(&d->mDatabaseInterface, "removeRadio", Qt::QueuedConnection, 0253 Q_ARG(qulonglong, databaseId)); 0254 break; 0255 case ElisaUtils::Album: 0256 case ElisaUtils::Artist: 0257 case ElisaUtils::Genre: 0258 case ElisaUtils::Lyricist: 0259 case ElisaUtils::Composer: 0260 case ElisaUtils::Track: 0261 case ElisaUtils::FileName: 0262 case ElisaUtils::Unknown: 0263 case ElisaUtils::Container: 0264 case ElisaUtils::PlayList: 0265 break; 0266 } 0267 } 0268 0269 void MusicListenersManager::connectModel(ModelDataLoader *dataLoader) 0270 { 0271 dataLoader->moveToThread(&d->mDatabaseThread); 0272 } 0273 0274 void MusicListenersManager::scanCollection(CollectionScan scantype) 0275 { 0276 switch (scantype) 0277 { 0278 case CollectionScan::Hard: 0279 Q_EMIT clearDatabase(); 0280 break; 0281 case CollectionScan::Soft: 0282 Q_EMIT refreshDatabase(); 0283 } 0284 } 0285 0286 void MusicListenersManager::configChanged() 0287 { 0288 auto currentConfiguration = Elisa::ElisaConfiguration::self(); 0289 0290 d->mConfigFileWatcher.addPath(currentConfiguration->config()->name()); 0291 0292 currentConfiguration->load(); 0293 currentConfiguration->read(); 0294 0295 bool configurationHasChanged = false; 0296 0297 auto inputRootPath = currentConfiguration->rootPath(); 0298 configurationHasChanged = configurationHasChanged || (d->mPreviousRootPathValue != inputRootPath); 0299 0300 if (configurationHasChanged) { 0301 d->mPreviousRootPathValue = inputRootPath; 0302 } else { 0303 qCDebug(orgKdeElisaIndexersManager()) << "music paths configuration and scanning has not changed"; 0304 return; 0305 } 0306 0307 //resolve symlinks 0308 QStringList allRootPaths; 0309 for (const auto &onePath : std::as_const(inputRootPath)) { 0310 auto workPath = onePath; 0311 if (workPath.startsWith(QLatin1String("file:/"))) { 0312 auto urlPath = QUrl{workPath}; 0313 workPath = urlPath.toLocalFile(); 0314 } 0315 0316 QFileInfo pathFileInfo(workPath); 0317 auto directoryPath = pathFileInfo.canonicalFilePath(); 0318 if (!directoryPath.isEmpty()) { 0319 if (QStringView(directoryPath).right(1) != QLatin1Char('/')) 0320 { 0321 directoryPath.append(QLatin1Char('/')); 0322 } 0323 allRootPaths.push_back(directoryPath); 0324 } 0325 } 0326 0327 if (allRootPaths.isEmpty()) { 0328 allRootPaths = initializeRootPath(); 0329 } 0330 0331 d->mFileListener.setAllRootPaths(allRootPaths); 0332 0333 // trigger start of indexers if needed 0334 startLocalFileSystemIndexing(); 0335 startAndroidIndexing(); 0336 0337 if (d->mFileSystemIndexerActive) { 0338 qCInfo(orgKdeElisaIndexersManager()) << "trigger init of local file indexer"; 0339 QMetaObject::invokeMethod(d->mFileListener.fileListing(), "init", Qt::QueuedConnection); 0340 } else if (d->mAndroidIndexerActive) { 0341 #if defined Q_OS_ANDROID 0342 QMetaObject::invokeMethod(d->mAndroidMusicListener.fileListing(), "init", Qt::QueuedConnection); 0343 #endif 0344 } 0345 0346 #if UPNPQT_FOUND 0347 d->mUpnpListener.setDatabaseInterface(&d->mDatabaseInterface); 0348 d->mUpnpListener.moveToThread(&d->mDatabaseThread); 0349 connect(this, &MusicListenersManager::applicationIsTerminating, 0350 &d->mUpnpListener, &UpnpListener::applicationAboutToQuit, Qt::DirectConnection); 0351 #endif 0352 0353 } 0354 0355 void MusicListenersManager::increaseImportedTracksCount(const DataTypes::ListTrackDataType &allTracks) 0356 { 0357 d->mImportedTracksCount += allTracks.size(); 0358 0359 Q_EMIT importedTracksCountChanged(); 0360 } 0361 0362 void MusicListenersManager::decreaseImportedTracksCount() 0363 { 0364 --d->mImportedTracksCount; 0365 0366 Q_EMIT importedTracksCountChanged(); 0367 } 0368 0369 void MusicListenersManager::monitorStartingListeners() 0370 { 0371 d->mIndexerBusy = true; 0372 Q_EMIT indexerBusyChanged(); 0373 } 0374 0375 void MusicListenersManager::monitorEndingListeners() 0376 { 0377 d->mIndexerBusy = false; 0378 Q_EMIT indexerBusyChanged(); 0379 } 0380 0381 void MusicListenersManager::cleanedDatabase() 0382 { 0383 d->mImportedTracksCount = 0; 0384 Q_EMIT importedTracksCountChanged(); 0385 Q_EMIT clearedDatabase(); 0386 } 0387 0388 void MusicListenersManager::startLocalFileSystemIndexing() 0389 { 0390 if (d->mFileSystemIndexerActive) { 0391 return; 0392 } 0393 0394 d->mFileListener.setDatabaseInterface(&d->mDatabaseInterface); 0395 d->mFileListener.moveToThread(&d->mListenerThread); 0396 connect(this, &MusicListenersManager::applicationIsTerminating, 0397 &d->mFileListener, &FileListener::applicationAboutToQuit, Qt::DirectConnection); 0398 connect(&d->mFileListener, &FileListener::indexingStarted, 0399 this, &MusicListenersManager::monitorStartingListeners); 0400 connect(&d->mFileListener, &FileListener::indexingFinished, 0401 this, &MusicListenersManager::monitorEndingListeners); 0402 0403 qCInfo(orgKdeElisaIndexersManager) << "Local file system indexer is active"; 0404 0405 d->mFileSystemIndexerActive = true; 0406 Q_EMIT fileSystemIndexerActiveChanged(); 0407 } 0408 0409 void MusicListenersManager::startAndroidIndexing() 0410 { 0411 qCInfo(orgKdeElisaIndexersManager) << "MusicListenersManager::startAndroidIndexing"; 0412 #if defined Q_OS_ANDROID 0413 if (d->mAndroidIndexerActive) { 0414 return; 0415 } 0416 0417 d->mAndroidMusicListener.setDatabaseInterface(&d->mDatabaseInterface); 0418 d->mAndroidMusicListener.moveToThread(&d->mListenerThread); 0419 connect(this, &MusicListenersManager::applicationIsTerminating, 0420 &d->mAndroidMusicListener, &AndroidMusicListener::applicationAboutToQuit, Qt::DirectConnection); 0421 connect(&d->mAndroidMusicListener, &AndroidMusicListener::indexingStarted, 0422 this, &MusicListenersManager::monitorStartingListeners); 0423 connect(&d->mAndroidMusicListener, &AndroidMusicListener::indexingFinished, 0424 this, &MusicListenersManager::monitorEndingListeners); 0425 0426 qCInfo(orgKdeElisaIndexersManager) << "Android indexer is active"; 0427 0428 d->mAndroidIndexerActive = true; 0429 Q_EMIT androidIndexerActiveChanged(); 0430 #endif 0431 } 0432 0433 void MusicListenersManager::createTracksListener() 0434 { 0435 if (!d->mTracksListener) { 0436 d->mTracksListener = std::make_unique<TracksListener>(&d->mDatabaseInterface); 0437 d->mTracksListener->moveToThread(&d->mDatabaseThread); 0438 0439 connect(this, &MusicListenersManager::removeTracksInError, 0440 &d->mDatabaseInterface, &DatabaseInterface::removeTracksList); 0441 0442 connect(&d->mDatabaseInterface, &DatabaseInterface::trackRemoved, d->mTracksListener.get(), &TracksListener::trackRemoved); 0443 connect(&d->mDatabaseInterface, &DatabaseInterface::tracksAdded, d->mTracksListener.get(), &TracksListener::tracksAdded); 0444 connect(&d->mDatabaseInterface, &DatabaseInterface::trackModified, d->mTracksListener.get(), &TracksListener::trackModified); 0445 Q_EMIT tracksListenerChanged(); 0446 } 0447 } 0448 0449 void MusicListenersManager::updateSingleFileMetaData(const QUrl &url, DataTypes::ColumnsRoles role, const QVariant &data) 0450 { 0451 tracksListener()->updateSingleFileMetaData(url, role, data); 0452 } 0453 0454 #include "moc_musiclistenersmanager.cpp"