Skip to content

Commit 7aca016

Browse files
committed
desktopentry: extract scanning into thread, cleanups
1 parent a6368eb commit 7aca016

File tree

7 files changed

+203
-215
lines changed

7 files changed

+203
-215
lines changed

src/core/CMakeLists.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ qt_add_library(quickshell-core STATIC
2424
elapsedtimer.cpp
2525
desktopentry.cpp
2626
desktopentrymonitor.cpp
27-
desktoputils.cpp
2827
objectrepeater.cpp
2928
platformmenu.cpp
3029
qsmenu.cpp

src/core/desktopentry.cpp

Lines changed: 119 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,17 @@
44
#include <qcontainerfwd.h>
55
#include <qdebug.h>
66
#include <qdir.h>
7+
#include <qfile.h>
78
#include <qfileinfo.h>
8-
#include <qhash.h>
9-
#include <qlist.h>
10-
#include <qlogging.h>
119
#include <qloggingcategory.h>
10+
#include <qmetaobject.h>
1211
#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>
1714
#include <ranges>
1815

1916
#include "../io/processcore.hpp"
2017
#include "desktopentrymonitor.hpp"
21-
#include "desktoputils.hpp"
2218
#include "logcat.hpp"
2319
#include "model.hpp"
2420
#include "qmlglobal.hpp"
@@ -271,8 +267,55 @@ void DesktopAction::execute() const {
271267
DesktopEntry::doExec(this->mCommand, this->entry->mWorkingDirectory);
272268
}
273269

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+
274318
DesktopEntryManager::DesktopEntryManager() {
275-
// Create file watcher for desktop entries
276319
this->monitor = new DesktopEntryMonitor(this);
277320
connect(
278321
this->monitor,
@@ -281,16 +324,16 @@ DesktopEntryManager::DesktopEntryManager() {
281324
&DesktopEntryManager::handleFileChanges
282325
);
283326

284-
// Initial scan
285327
this->scanDesktopEntries();
286-
this->populateApplications();
287328
}
288329

289330
void DesktopEntryManager::scanDesktopEntries() {
290-
auto desktopPaths = DesktopUtils::getDesktopDirectories();
331+
auto desktopPaths = this->getDesktopDirectories();
332+
auto scanResults = DesktopEntryScanResults();
291333

292334
qCDebug(logDesktopEntry) << "Creating desktop entry scanners";
293335

336+
auto scanner = DesktopEntryScanner(this);
294337
for (auto& path: std::ranges::reverse_view(desktopPaths)) {
295338
auto file = QFileInfo(path);
296339

@@ -300,72 +343,11 @@ void DesktopEntryManager::scanDesktopEntries() {
300343
}
301344

302345
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);
310347
}
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);
337348

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;
369351
}
370352

371353
DesktopEntryManager* DesktopEntryManager::instance() {
@@ -404,51 +386,32 @@ DesktopEntry* DesktopEntryManager::heuristicLookup(const QString& name) {
404386

405387
ObjectModel<DesktopEntry>* DesktopEntryManager::applications() { return &this->mApplications; }
406388

407-
void DesktopEntryManager::handleFileChanges(
408-
const QHash<QString, DesktopEntryMonitor::ChangeEvent>&
409-
) {
389+
void DesktopEntryManager::handleFileChanges() {
410390
qCDebug(logDesktopEntry) << "Directory change detected, performing full rescan";
411391

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;
424395
}
425396

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+
}
433401

434-
emit applicationsChanged();
402+
QStringList DesktopEntryManager::getDesktopDirectories() const {
403+
return this->monitor->getDesktopDirectories();
435404
}
436405

437406
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-
442407
auto info = QFileInfo(path);
443408
auto id = info.completeBaseName();
444409

445-
// Find the applications directory in the path
446410
auto appIdx = path.lastIndexOf("/applications/");
447411
if (appIdx != -1) {
448-
auto relativePath = path.mid(appIdx + 14); // Skip "/applications/"
412+
auto relativePath = path.mid(appIdx + 14);
449413
auto relInfo = QFileInfo(relativePath);
450414

451-
// Replace directory separators with dashes
452415
auto dirPath = relInfo.path();
453416
if (dirPath != ".") {
454417
id = dirPath.replace('/', '-') + '-' + relInfo.completeBaseName();
@@ -458,12 +421,59 @@ QString DesktopEntryManager::extractIdFromPath(const QString& path) {
458421
return id;
459422
}
460423

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*>();
463461
for (auto& entry: this->desktopEntries.values()) {
464-
if (!entry->noDisplay()) newApplications.append(entry);
462+
if (!entry->noDisplay()) {
463+
newApplications.append(entry);
464+
}
465465
}
466+
466467
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();
467477
}
468478

469479
DesktopEntries::DesktopEntries() {

src/core/desktopentry.hpp

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@
77
#include <qhash.h>
88
#include <qobject.h>
99
#include <qqmlintegration.h>
10+
#include <qrunnable.h>
1011
#include <qtmetamacros.h>
1112

13+
#include "desktopentrymonitor.hpp"
1214
#include "doc.hpp"
1315
#include "model.hpp"
1416

@@ -149,7 +151,26 @@ class DesktopAction: public QObject {
149151
friend class DesktopEntry;
150152
};
151153

152-
#include "desktopentrymonitor.hpp"
154+
class DesktopEntryManager;
155+
156+
struct ParsedDesktopEntry {
157+
QString id;
158+
QString content;
159+
};
160+
161+
using DesktopEntryScanResults = QList<ParsedDesktopEntry>;
162+
Q_DECLARE_METATYPE(DesktopEntryScanResults)
163+
164+
class DesktopEntryScanner: public QRunnable {
165+
public:
166+
explicit DesktopEntryScanner(DesktopEntryManager* manager);
167+
168+
void run() override;
169+
void scanDirectory(const QDir& dir, DesktopEntryScanResults& entries);
170+
171+
private:
172+
DesktopEntryManager* manager;
173+
};
153174

154175
class DesktopEntryManager: public QObject {
155176
Q_OBJECT;
@@ -164,24 +185,26 @@ class DesktopEntryManager: public QObject {
164185

165186
static DesktopEntryManager* instance();
166187

188+
QString extractIdFromPath(const QString& path);
189+
QStringList getDesktopDirectories() const;
190+
167191
signals:
168192
void applicationsChanged();
169193

170194
private slots:
171-
void handleFileChanges(const QHash<QString, DesktopEntryMonitor::ChangeEvent>& changes);
195+
void handleFileChanges();
196+
void onScanCompleted(const DesktopEntryScanResults& scanResults);
172197

173198
private:
174199
explicit DesktopEntryManager();
175200

176-
void populateApplications();
177-
void scanPath(const QDir& dir);
178-
QString extractIdFromPath(const QString& path);
179-
void updateApplicationModel();
180-
181201
QHash<QString, DesktopEntry*> desktopEntries;
182202
QHash<QString, DesktopEntry*> lowercaseDesktopEntries;
183203
ObjectModel<DesktopEntry> mApplications {this};
184204
DesktopEntryMonitor* monitor = nullptr;
205+
bool scanInProgress = false;
206+
207+
friend class DesktopEntryScanner;
185208
};
186209

187210
///! Desktop entry index.

0 commit comments

Comments
 (0)