4
4
#include < qcontainerfwd.h>
5
5
#include < qdebug.h>
6
6
#include < qdir.h>
7
+ #include < qfile.h>
7
8
#include < qfileinfo.h>
8
- #include < qhash.h>
9
- #include < qlist.h>
10
- #include < qlogging.h>
11
9
#include < qloggingcategory.h>
10
+ #include < qmetaobject.h>
12
11
#include < qnamespace.h>
13
- #include < qobject.h>
14
- #include < qpair.h>
15
- #include < qstringview.h>
16
- #include < qtenvironmentvariables.h>
12
+ #include < qscopeguard.h>
13
+ #include < qthreadpool.h>
17
14
#include < ranges>
18
15
19
16
#include " ../io/processcore.hpp"
20
17
#include " desktopentrymonitor.hpp"
21
- #include " desktoputils.hpp"
22
18
#include " logcat.hpp"
23
19
#include " model.hpp"
24
20
#include " qmlglobal.hpp"
@@ -271,8 +267,55 @@ void DesktopAction::execute() const {
271
267
DesktopEntry::doExec (this ->mCommand , this ->entry ->mWorkingDirectory );
272
268
}
273
269
270
+ DesktopEntryScanner::DesktopEntryScanner (DesktopEntryManager* manager): manager(manager) {
271
+ this ->setAutoDelete (true );
272
+ }
273
+
274
+ void DesktopEntryScanner::run () {
275
+ auto desktopPaths = manager->getDesktopDirectories ();
276
+ auto scanResults = DesktopEntryScanResults ();
277
+
278
+ for (auto & path: std::ranges::reverse_view (desktopPaths)) {
279
+ auto file = QFileInfo (path);
280
+ if (!file.isDir ()) continue ;
281
+
282
+ this ->scanDirectory (QDir (path), scanResults);
283
+ }
284
+
285
+ QMetaObject::invokeMethod (
286
+ this ->manager ,
287
+ " onScanCompleted" ,
288
+ Qt::QueuedConnection,
289
+ Q_ARG (DesktopEntryScanResults, scanResults)
290
+ );
291
+ }
292
+
293
+ void DesktopEntryScanner::scanDirectory (const QDir& dir, DesktopEntryScanResults& entries) {
294
+ auto dirEntries = dir.entryInfoList (QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot);
295
+
296
+ for (auto & entry: dirEntries) {
297
+ if (entry.isDir ()) {
298
+ this ->scanDirectory (QDir (entry.absoluteFilePath ()), entries);
299
+ } else if (entry.isFile ()) {
300
+ auto path = entry.filePath ();
301
+ if (!path.endsWith (" .desktop" )) continue ;
302
+
303
+ auto file = QFile (path);
304
+ if (!file.open (QFile::ReadOnly)) continue ;
305
+
306
+ auto id = manager->extractIdFromPath (entry.absoluteFilePath ());
307
+ auto content = QString::fromUtf8 (file.readAll ());
308
+
309
+ ParsedDesktopEntry parsed;
310
+ parsed.id = id;
311
+ parsed.content = std::move (content);
312
+
313
+ entries.append (std::move (parsed));
314
+ }
315
+ }
316
+ }
317
+
274
318
DesktopEntryManager::DesktopEntryManager () {
275
- // Create file watcher for desktop entries
276
319
this ->monitor = new DesktopEntryMonitor (this );
277
320
connect (
278
321
this ->monitor ,
@@ -281,16 +324,16 @@ DesktopEntryManager::DesktopEntryManager() {
281
324
&DesktopEntryManager::handleFileChanges
282
325
);
283
326
284
- // Initial scan
285
327
this ->scanDesktopEntries ();
286
- this ->populateApplications ();
287
328
}
288
329
289
330
void DesktopEntryManager::scanDesktopEntries () {
290
- auto desktopPaths = DesktopUtils::getDesktopDirectories ();
331
+ auto desktopPaths = this ->getDesktopDirectories ();
332
+ auto scanResults = DesktopEntryScanResults ();
291
333
292
334
qCDebug (logDesktopEntry) << " Creating desktop entry scanners" ;
293
335
336
+ auto scanner = DesktopEntryScanner (this );
294
337
for (auto & path: std::ranges::reverse_view (desktopPaths)) {
295
338
auto file = QFileInfo (path);
296
339
@@ -300,72 +343,11 @@ void DesktopEntryManager::scanDesktopEntries() {
300
343
}
301
344
302
345
qCDebug (logDesktopEntry) << " Scanning path" << path;
303
- this ->scanPath (path);
304
- }
305
- }
306
-
307
- void DesktopEntryManager::populateApplications () {
308
- for (auto & entry: this ->desktopEntries .values ()) {
309
- if (!entry->noDisplay ()) this ->mApplications .insertObject (entry);
346
+ scanner.scanDirectory (QDir (path), scanResults);
310
347
}
311
- }
312
-
313
- void DesktopEntryManager::scanPath (const QDir& dir) {
314
- auto entries = dir.entryInfoList (QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot);
315
-
316
- for (auto & entry: entries) {
317
- if (entry.isDir ()) this ->scanPath (entry.absoluteFilePath ());
318
- else if (entry.isFile ()) {
319
- auto path = entry.filePath ();
320
- if (!path.endsWith (" .desktop" )) {
321
- qCDebug (logDesktopEntry) << " Skipping file" << path << " as it has no .desktop extension" ;
322
- continue ;
323
- }
324
-
325
- auto file = QFile (path);
326
- if (!file.open (QFile::ReadOnly)) {
327
- qCDebug (logDesktopEntry) << " Could not open file" << path;
328
- continue ;
329
- }
330
-
331
- auto id = this ->extractIdFromPath (entry.absoluteFilePath ());
332
- auto lowerId = id.toLower ();
333
-
334
- auto text = QString::fromUtf8 (file.readAll ());
335
- auto * dentry = new DesktopEntry (id, this );
336
- dentry->parseEntry (text);
337
348
338
- if (!dentry->isValid ()) {
339
- qCDebug (logDesktopEntry) << " Skipping desktop entry" << path;
340
- delete dentry;
341
- continue ;
342
- }
343
-
344
- qCDebug (logDesktopEntry) << " Found desktop entry" << id << " at" << path;
345
-
346
- auto conflictingId = this ->desktopEntries .contains (id);
347
-
348
- if (conflictingId) {
349
- qCDebug (logDesktopEntry) << " Replacing old entry for" << id;
350
- delete this ->desktopEntries .value (id);
351
- this ->desktopEntries .remove (id);
352
- this ->lowercaseDesktopEntries .remove (lowerId);
353
- }
354
-
355
- this ->desktopEntries .insert (id, dentry);
356
-
357
- if (this ->lowercaseDesktopEntries .contains (lowerId)) {
358
- qCInfo (logDesktopEntry).nospace ()
359
- << " Multiple desktop entries have the same lowercased id " << lowerId
360
- << " . This can cause ambiguity when byId requests are not made with the correct case "
361
- " already." ;
362
-
363
- this ->lowercaseDesktopEntries .remove (lowerId);
364
- }
365
-
366
- this ->lowercaseDesktopEntries .insert (lowerId, dentry);
367
- }
368
- }
349
+ this ->onScanCompleted (scanResults);
350
+ this ->scanInProgress = false ;
369
351
}
370
352
371
353
DesktopEntryManager* DesktopEntryManager::instance () {
@@ -404,51 +386,32 @@ DesktopEntry* DesktopEntryManager::heuristicLookup(const QString& name) {
404
386
405
387
ObjectModel<DesktopEntry>* DesktopEntryManager::applications () { return &this ->mApplications ; }
406
388
407
- void DesktopEntryManager::handleFileChanges (
408
- const QHash<QString, DesktopEntryMonitor::ChangeEvent>&
409
- ) {
389
+ void DesktopEntryManager::handleFileChanges () {
410
390
qCDebug (logDesktopEntry) << " Directory change detected, performing full rescan" ;
411
391
412
- auto oldEntries = this ->desktopEntries ;
413
-
414
- this ->desktopEntries .clear ();
415
- this ->lowercaseDesktopEntries .clear ();
416
-
417
- this ->scanDesktopEntries ();
418
-
419
- QVector<DesktopEntry*> newApplications;
420
- for (auto & entry: this ->desktopEntries .values ()) {
421
- if (!entry->noDisplay ()) {
422
- newApplications.append (entry);
423
- }
392
+ if (this ->scanInProgress ) {
393
+ qCDebug (logDesktopEntry) << " Scan already in progress, skipping" ;
394
+ return ;
424
395
}
425
396
426
- this ->mApplications .diffUpdate (newApplications);
427
-
428
- for (auto * e: oldEntries) {
429
- if (!this ->desktopEntries .contains (e->mId )) {
430
- e->deleteLater ();
431
- }
432
- }
397
+ this ->scanInProgress = true ;
398
+ auto * scanner = new DesktopEntryScanner (this );
399
+ QThreadPool::globalInstance ()->start (scanner);
400
+ }
433
401
434
- emit applicationsChanged ();
402
+ QStringList DesktopEntryManager::getDesktopDirectories () const {
403
+ return this ->monitor ->getDesktopDirectories ();
435
404
}
436
405
437
406
QString DesktopEntryManager::extractIdFromPath (const QString& path) {
438
- // Extract ID from path following XDG spec
439
- // e.g., /usr/share/applications/firefox.desktop -> firefox
440
- // e.g., /usr/share/applications/kde4/kate.desktop -> kde4-kate
441
-
442
407
auto info = QFileInfo (path);
443
408
auto id = info.completeBaseName ();
444
409
445
- // Find the applications directory in the path
446
410
auto appIdx = path.lastIndexOf (" /applications/" );
447
411
if (appIdx != -1 ) {
448
- auto relativePath = path.mid (appIdx + 14 ); // Skip "/applications/"
412
+ auto relativePath = path.mid (appIdx + 14 );
449
413
auto relInfo = QFileInfo (relativePath);
450
414
451
- // Replace directory separators with dashes
452
415
auto dirPath = relInfo.path ();
453
416
if (dirPath != " ." ) {
454
417
id = dirPath.replace (' /' , ' -' ) + ' -' + relInfo.completeBaseName ();
@@ -458,12 +421,59 @@ QString DesktopEntryManager::extractIdFromPath(const QString& path) {
458
421
return id;
459
422
}
460
423
461
- void DesktopEntryManager::updateApplicationModel () {
462
- QVector<DesktopEntry*> newApplications;
424
+ void DesktopEntryManager::onScanCompleted (const DesktopEntryScanResults& scanResults) {
425
+ auto guard = qScopeGuard ([this ] { this ->scanInProgress = false ; });
426
+
427
+ auto oldEntries = this ->desktopEntries ;
428
+ auto newEntries = QHash<QString, DesktopEntry*>();
429
+ auto newLowercaseEntries = QHash<QString, DesktopEntry*>();
430
+
431
+ for (const auto & parsed: scanResults) {
432
+ auto * dentry = new DesktopEntry (parsed.id , this );
433
+ dentry->parseEntry (parsed.content );
434
+
435
+ if (!dentry->isValid ()) {
436
+ delete dentry;
437
+ continue ;
438
+ }
439
+
440
+ auto lowerId = parsed.id .toLower ();
441
+
442
+ if (newEntries.contains (parsed.id )) {
443
+ delete newEntries.value (parsed.id );
444
+ newEntries.remove (parsed.id );
445
+ newLowercaseEntries.remove (lowerId);
446
+ }
447
+
448
+ newEntries.insert (parsed.id , dentry);
449
+
450
+ if (newLowercaseEntries.contains (lowerId)) {
451
+ newLowercaseEntries.remove (lowerId);
452
+ }
453
+
454
+ newLowercaseEntries.insert (lowerId, dentry);
455
+ }
456
+
457
+ this ->desktopEntries = newEntries;
458
+ this ->lowercaseDesktopEntries = newLowercaseEntries;
459
+
460
+ auto newApplications = QVector<DesktopEntry*>();
463
461
for (auto & entry: this ->desktopEntries .values ()) {
464
- if (!entry->noDisplay ()) newApplications.append (entry);
462
+ if (!entry->noDisplay ()) {
463
+ newApplications.append (entry);
464
+ }
465
465
}
466
+
466
467
this ->mApplications .diffUpdate (newApplications);
468
+
469
+ for (auto * e: oldEntries) {
470
+ if (!this ->desktopEntries .contains (e->mId )) {
471
+ e->deleteLater ();
472
+ }
473
+ }
474
+ oldEntries.clear ();
475
+
476
+ emit applicationsChanged ();
467
477
}
468
478
469
479
DesktopEntries::DesktopEntries () {
0 commit comments