1 /*
2 SPDX-FileCopyrightText: 2016 (c) Matthieu Gallien <matthieu_gallien@yahoo.fr>
3
4 SPDX-License-Identifier: LGPL-3.0-or-later
5 */
6
7 #include "localbaloofilelisting.h"
8
9 #include "baloo/baloocommon.h"
10
11 #include "elisa_settings.h"
12 #include "elisautils.h"
13
14 #include "baloo/scheduler.h"
15 #include "baloo/fileindexer.h"
16 #include "baloo/main.h"
17
18 #include "baloowatcherapplicationadaptor.h"
19
20 #include "filescanner.h"
21
22 #include <Baloo/Query>
23 #include <Baloo/File>
24 #include <Baloo/IndexerConfig>
25
26 #include <KFileMetaData/Properties>
27 #include <KFileMetaData/UserMetaData>
28
29
30 #include <QDBusConnection>
31 #include <QDBusConnectionInterface>
32 #include <QDBusServiceWatcher>
33
34 #include <QThread>
35 #include <QHash>
36 #include <QFileInfo>
37 #include <QAtomicInt>
38 #include <QScopedPointer>
39
40 #include <algorithm>
41 #include <memory>
42
43 class LocalBalooFileListingPrivate
44 {
45 public:
46
47 Baloo::Query mQuery;
48
49 QDBusServiceWatcher mServiceWatcher;
50
51 QScopedPointer<org::kde::baloo::main> mBalooMainInterface;
52
53 QScopedPointer<org::kde::baloo::fileindexer> mBalooIndexer;
54
55 QScopedPointer<org::kde::baloo::scheduler> mBalooScheduler;
56
57 BalooWatcherApplicationAdaptor *mDbusAdaptor = nullptr;
58
59 QAtomicInt mStopRequest = 0;
60
61 bool mIsRegisteredToBaloo = false;
62
63 bool mIsRegisteringToBaloo = false;
64
65 bool mIsRegisteredToBalooWatcher = false;
66
67 bool mIsRegisteringToBalooWatcher = false;
68
69 };
70
LocalBalooFileListing(QObject * parent)71 LocalBalooFileListing::LocalBalooFileListing(QObject *parent)
72 : AbstractFileListing(parent), d(std::make_unique<LocalBalooFileListingPrivate>())
73 {
74 d->mQuery.addType(QStringLiteral("Audio"));
75 setHandleNewFiles(false);
76
77 auto sessionBus = QDBusConnection::sessionBus();
78
79 d->mDbusAdaptor = new BalooWatcherApplicationAdaptor(this);
80
81 sessionBus.registerObject(QStringLiteral("/org/kde/BalooWatcherApplication"), d->mDbusAdaptor, QDBusConnection::ExportAllContents);
82
83 connect(&d->mServiceWatcher, &QDBusServiceWatcher::serviceRegistered,
84 this, &LocalBalooFileListing::serviceRegistered);
85 connect(&d->mServiceWatcher, &QDBusServiceWatcher::serviceOwnerChanged,
86 this, &LocalBalooFileListing::serviceOwnerChanged);
87 connect(&d->mServiceWatcher, &QDBusServiceWatcher::serviceUnregistered,
88 this, &LocalBalooFileListing::serviceUnregistered);
89
90 d->mServiceWatcher.setConnection(sessionBus);
91 d->mServiceWatcher.addWatchedService(QStringLiteral("org.kde.baloo"));
92
93 if (sessionBus.interface()->isServiceRegistered(QStringLiteral("org.kde.baloo"))) {
94 registerToBaloo();
95 }
96 }
97
~LocalBalooFileListing()98 LocalBalooFileListing::~LocalBalooFileListing()
99 {
100 d->mDbusAdaptor->setParent(nullptr);
101 d->mDbusAdaptor->deleteLater();
102 }
103
applicationAboutToQuit()104 void LocalBalooFileListing::applicationAboutToQuit()
105 {
106 AbstractFileListing::applicationAboutToQuit();
107 d->mStopRequest = 1;
108 }
109
canHandleRootPaths() const110 bool LocalBalooFileListing::canHandleRootPaths() const
111 {
112 Baloo::IndexerConfig balooConfiguration;
113
114 auto balooIncludedFolders = balooConfiguration.includeFolders();
115 auto balooExcludedFolders = balooConfiguration.excludeFolders();
116
117 for (const auto &onePath : allRootPaths()) {
118 auto onePathInfo = QFileInfo{onePath};
119 auto onePathCanonicalPath = onePathInfo.canonicalFilePath();
120
121 auto includedPath = false;
122
123 for (const auto &balooIncludedPath : balooIncludedFolders) {
124 auto balooIncludedPathInfo = QFileInfo{balooIncludedPath};
125 auto balooIncludedCanonicalPath = balooIncludedPathInfo.canonicalFilePath();
126
127 if (onePathCanonicalPath.startsWith(balooIncludedCanonicalPath)) {
128 includedPath = true;
129 break;
130 }
131 }
132
133 for (const auto &balooExcludedPath : balooExcludedFolders) {
134 auto balooExcludedPathInfo = QFileInfo{balooExcludedPath};
135 auto balooExcludedCanonicalPath = balooExcludedPathInfo.canonicalFilePath();
136
137 if (onePathCanonicalPath.startsWith(balooExcludedCanonicalPath)) {
138 includedPath = false;
139 break;
140 }
141 }
142
143 if (!includedPath) {
144 return false;
145 }
146 }
147 return true;
148 }
149
newBalooFile(const QString & fileName)150 void LocalBalooFileListing::newBalooFile(const QString &fileName)
151 {
152 qCDebug(orgKdeElisaBaloo()) << "LocalBalooFileListing::newBalooFile" << fileName;
153
154 if (!isActive()) {
155 qCDebug(orgKdeElisaBaloo()) << "LocalBalooFileListing::newBalooFile is inactive";
156 return;
157 }
158
159 auto scanFileInfo = QFileInfo(fileName);
160
161 if (!scanFileInfo.exists()) {
162 return;
163 }
164
165 if (!fileScanner().shouldScanFile(fileName)) {
166 return;
167 }
168
169 Q_EMIT indexingStarted();
170
171 auto newFile = QUrl::fromLocalFile(fileName);
172
173 auto newTrack = scanOneFile(newFile, scanFileInfo, DoNotWatchFileSystemChanges);
174
175 if (newTrack.isValid()) {
176 QFileInfo newFileInfo(fileName);
177
178 addFileInDirectory(newFile, QUrl::fromLocalFile(newFileInfo.absoluteDir().absolutePath()), DoNotWatchFileSystemChanges);
179
180 emitNewFiles({newTrack});
181 }
182
183 Q_EMIT indexingFinished();
184 }
185
registeredToBaloo(QDBusPendingCallWatcher * watcher)186 void LocalBalooFileListing::registeredToBaloo(QDBusPendingCallWatcher *watcher)
187 {
188 qCDebug(orgKdeElisaBaloo) << "LocalBalooFileListing::registeredToBaloo";
189
190 if (!watcher) {
191 return;
192 }
193
194 QDBusPendingReply<> reply = *watcher;
195 if (reply.isError()) {
196 qCDebug(orgKdeElisaBaloo) << "LocalBalooFileListing::registeredToBaloo" << reply.error().name() << reply.error().message();
197 d->mIsRegisteredToBaloo = false;
198 } else {
199 d->mIsRegisteredToBaloo = true;
200 }
201
202 d->mIsRegisteringToBaloo = false;
203
204 watcher->deleteLater();
205 }
206
registeredToBalooWatcher(QDBusPendingCallWatcher * watcher)207 void LocalBalooFileListing::registeredToBalooWatcher(QDBusPendingCallWatcher *watcher)
208 {
209 qCDebug(orgKdeElisaBaloo) << "LocalBalooFileListing::registeredToBalooWatcher";
210
211 if (!watcher) {
212 return;
213 }
214
215 QDBusPendingReply<> reply = *watcher;
216 if (reply.isError()) {
217 qCDebug(orgKdeElisaBaloo) << "LocalBalooFileListing::registeredToBalooWatcher" << reply.error().name() << reply.error().message();
218 d->mIsRegisteredToBalooWatcher = false;
219 } else {
220 d->mIsRegisteredToBalooWatcher = true;
221 }
222
223 d->mIsRegisteringToBalooWatcher = false;
224
225 watcher->deleteLater();
226 }
227
registerToBaloo()228 void LocalBalooFileListing::registerToBaloo()
229 {
230 if (d->mIsRegisteringToBaloo || d->mIsRegisteringToBalooWatcher) {
231 qCDebug(orgKdeElisaBaloo) << "LocalBalooFileListing::registerToBaloo" << "already registering";
232 return;
233 }
234
235 qCDebug(orgKdeElisaBaloo) << "LocalBalooFileListing::registerToBaloo";
236
237 d->mIsRegisteringToBaloo = true;
238 d->mIsRegisteringToBalooWatcher = true;
239
240 auto sessionBus = QDBusConnection::sessionBus();
241
242 d->mBalooMainInterface.reset(new org::kde::baloo::main(QStringLiteral("org.kde.baloo"), QStringLiteral("/"),
243 sessionBus, this));
244
245 if (!d->mBalooMainInterface->isValid()) {
246 qCDebug(orgKdeElisaBaloo) << "LocalBalooFileListing::registerToBaloo" << "invalid org.kde.baloo/main interface";
247 return;
248 }
249
250 d->mBalooIndexer.reset(new org::kde::baloo::fileindexer(QStringLiteral("org.kde.baloo"), QStringLiteral("/fileindexer"),
251 sessionBus, this));
252
253 if (!d->mBalooIndexer->isValid()) {
254 qCDebug(orgKdeElisaBaloo) << "LocalBalooFileListing::registerToBaloo" << "invalid org.kde.baloo/fileindexer interface";
255 return;
256 }
257
258 connect(d->mBalooIndexer.data(), &org::kde::baloo::fileindexer::finishedIndexingFile,
259 this, &LocalBalooFileListing::newBalooFile);
260
261 d->mBalooScheduler.reset(new org::kde::baloo::scheduler(QStringLiteral("org.kde.baloo"), QStringLiteral("/scheduler"),
262 sessionBus, this));
263
264 if (!d->mBalooScheduler->isValid()) {
265 qCDebug(orgKdeElisaBaloo) << "LocalBalooFileListing::registerToBaloo" << "invalid org.kde.baloo/scheduler interface";
266 return;
267 }
268
269 qCDebug(orgKdeElisaBaloo) << "LocalBalooFileListing::registerToBaloo" << "call registerMonitor";
270 auto answer = d->mBalooIndexer->registerMonitor();
271
272 if (answer.isError()) {
273 qCDebug(orgKdeElisaBaloo) << "LocalBalooFileListing::executeInit" << answer.error().name() << answer.error().message();
274 }
275
276 auto pendingCallWatcher = new QDBusPendingCallWatcher(answer);
277
278 connect(pendingCallWatcher, &QDBusPendingCallWatcher::finished, this, &LocalBalooFileListing::registeredToBaloo);
279 if (pendingCallWatcher->isFinished()) {
280 registeredToBaloo(pendingCallWatcher);
281 }
282
283 auto pendingCall = d->mBalooMainInterface->registerBalooWatcher(QStringLiteral("org.mpris.MediaPlayer2.elisa/org/kde/BalooWatcherApplication"));
284 qCDebug(orgKdeElisaBaloo) << "LocalBalooFileListing::registerToBaloo" << "call registerBalooWatcher";
285 auto pendingCallWatcher2 = new QDBusPendingCallWatcher(pendingCall);
286
287 connect(pendingCallWatcher2, &QDBusPendingCallWatcher::finished, this, &LocalBalooFileListing::registeredToBalooWatcher);
288 if (pendingCallWatcher2->isFinished()) {
289 registeredToBalooWatcher(pendingCallWatcher2);
290 }
291 }
292
renamedFiles(const QString & from,const QString & to,const QStringList & listFiles)293 void LocalBalooFileListing::renamedFiles(const QString &from, const QString &to, const QStringList &listFiles)
294 {
295 qCDebug(orgKdeElisaBaloo) << "LocalBalooFileListing::renamedFiles" << from << to << listFiles;
296 }
297
serviceOwnerChanged(const QString & serviceName,const QString & oldOwner,const QString & newOwner)298 void LocalBalooFileListing::serviceOwnerChanged(const QString &serviceName, const QString &oldOwner, const QString &newOwner)
299 {
300 Q_UNUSED(oldOwner);
301 Q_UNUSED(newOwner);
302
303 qCDebug(orgKdeElisaBaloo) << "LocalBalooFileListing::serviceOwnerChanged" << serviceName << oldOwner << newOwner;
304
305 if (serviceName == QLatin1String("org.kde.baloo") && !newOwner.isEmpty()) {
306 d->mIsRegisteredToBaloo = false;
307 d->mIsRegisteredToBalooWatcher = false;
308 registerToBaloo();
309 }
310 }
311
serviceRegistered(const QString & serviceName)312 void LocalBalooFileListing::serviceRegistered(const QString &serviceName)
313 {
314 qCDebug(orgKdeElisaBaloo) << "LocalBalooFileListing::serviceRegistered" << serviceName;
315
316 if (serviceName == QLatin1String("org.kde.baloo")) {
317 registerToBaloo();
318 }
319 }
320
serviceUnregistered(const QString & serviceName)321 void LocalBalooFileListing::serviceUnregistered(const QString &serviceName)
322 {
323 qCDebug(orgKdeElisaBaloo) << "LocalBalooFileListing::serviceUnregistered" << serviceName;
324
325 if (serviceName == QLatin1String("org.kde.baloo")) {
326 d->mIsRegisteredToBaloo = false;
327 d->mIsRegisteredToBalooWatcher = false;
328 }
329 }
330
executeInit(QHash<QUrl,QDateTime> allFiles)331 void LocalBalooFileListing::executeInit(QHash<QUrl, QDateTime> allFiles)
332 {
333 if (!isActive()) {
334 qCDebug(orgKdeElisaBaloo()) << "LocalBalooFileListing::executeInit is inactive";
335 return;
336 }
337
338 qCDebug(orgKdeElisaBaloo()) << "LocalBalooFileListing::executeInit" << "with" << allFiles.size() << "files";
339 AbstractFileListing::executeInit(std::move(allFiles));
340 }
341
triggerRefreshOfContent()342 void LocalBalooFileListing::triggerRefreshOfContent()
343 {
344 qCDebug(orgKdeElisaBaloo()) << "LocalBalooFileListing::triggerRefreshOfContent";
345
346 if (!isActive()) {
347 qCDebug(orgKdeElisaBaloo()) << "LocalBalooFileListing::triggerRefreshOfContent is inactive";
348 return;
349 }
350
351 Q_EMIT indexingStarted();
352
353 AbstractFileListing::triggerRefreshOfContent();
354
355 const auto &rootPaths = allRootPaths();
356 bool hasSingleRootPath = (rootPaths.size() == 1);
357 auto singleRootPath = rootPaths.at(0);
358
359 auto resultIterator = d->mQuery.exec();
360 auto newFiles = DataTypes::ListTrackDataType();
361
362 while(resultIterator.next() && d->mStopRequest == 0) {
363 const auto &fileName = resultIterator.filePath();
364
365 if (hasSingleRootPath) {
366 if (!fileName.startsWith(singleRootPath)) {
367 qCDebug(orgKdeElisaBaloo()) << "LocalBalooFileListing::triggerRefreshOfContent" << fileName << "does not match root paths";
368 continue;
369 }
370 } else {
371 bool isIncluded = false;
372 for (const auto &oneRootPath : rootPaths) {
373 if (fileName.startsWith(oneRootPath)) {
374 isIncluded = true;
375 break;
376 }
377 }
378 if (!isIncluded) {
379 qCDebug(orgKdeElisaBaloo()) << "LocalBalooFileListing::triggerRefreshOfContent" << fileName << "does not match root paths";
380 continue;
381 }
382 }
383
384 const auto &newFileUrl = QUrl::fromLocalFile(resultIterator.filePath());
385
386 auto scanFileInfo = QFileInfo(fileName);
387
388 if (!scanFileInfo.exists()) {
389 qCDebug(orgKdeElisaBaloo()) << "LocalBalooFileListing::triggerRefreshOfContent" << fileName << "file does not exists";
390 continue;
391 }
392
393 auto itExistingFile = allFiles().find(newFileUrl);
394 if (itExistingFile != allFiles().end()) {
395 if (*itExistingFile >= scanFileInfo.metadataChangeTime()) {
396 allFiles().erase(itExistingFile);
397 qCDebug(orgKdeElisaBaloo()) << "LocalBalooFileListing::triggerRefreshOfContent" << fileName << "file not modified since last scan";
398 continue;
399 }
400 }
401
402 const auto currentDirectory = QUrl::fromLocalFile(scanFileInfo.absoluteDir().absolutePath());
403
404 addFileInDirectory(newFileUrl, currentDirectory, DoNotWatchFileSystemChanges);
405
406 const auto &newTrack = scanOneFile(newFileUrl, scanFileInfo, DoNotWatchFileSystemChanges);
407
408 if (newTrack.isValid()) {
409 newFiles.push_back(newTrack);
410 if (newFiles.size() > 500 && d->mStopRequest == 0) {
411 qCDebug(orgKdeElisaBaloo()) << "LocalBalooFileListing::triggerRefreshOfContent" << "insert new tracks in database" << newFiles.count();
412 emitNewFiles(newFiles);
413 newFiles.clear();
414 }
415 } else {
416 qCDebug(orgKdeElisaBaloo()) << "LocalBalooFileListing::triggerRefreshOfContent" << fileName << "invalid track" << newTrack;
417 }
418 }
419
420 if (!newFiles.isEmpty() && d->mStopRequest == 0) {
421 qCDebug(orgKdeElisaBaloo()) << "LocalBalooFileListing::triggerRefreshOfContent" << "insert new tracks in database" << newFiles.count();
422 emitNewFiles(newFiles);
423 }
424
425 setWaitEndTrackRemoval(false);
426
427 checkFilesToRemove();
428
429 if (!waitEndTrackRemoval()) {
430 Q_EMIT indexingFinished();
431 }
432 }
433
triggerStop()434 void LocalBalooFileListing::triggerStop()
435 {
436 qCDebug(orgKdeElisaBaloo()) << "LocalBalooFileListing::triggerStop";
437 AbstractFileListing::triggerStop();
438 }
439
scanOneFile(const QUrl & scanFile,const QFileInfo & scanFileInfo,FileSystemWatchingModes watchForFileSystemChanges)440 DataTypes::TrackDataType LocalBalooFileListing::scanOneFile(const QUrl &scanFile, const QFileInfo &scanFileInfo, FileSystemWatchingModes watchForFileSystemChanges)
441 {
442
443 auto trackData = fileScanner().scanOneBalooFile(scanFile, scanFileInfo);
444
445 if (!trackData.isValid()) {
446 qCDebug(orgKdeElisaBaloo) << "LocalBalooFileListing::scanOneFile" << scanFile << "falling back to plain file metadata analysis";
447 trackData = AbstractFileListing::scanOneFile(scanFile, scanFileInfo, watchForFileSystemChanges);
448 }
449
450 if (trackData.isValid()) {
451 addCover(trackData);
452 } else {
453 qCDebug(orgKdeElisaBaloo) << "LocalBalooFileListing::scanOneFile" << scanFile << "invalid track";
454 }
455
456 return trackData;
457 }
458
459 #include "moc_localbaloofilelisting.cpp"
460