Lomiri
Loading...
Searching...
No Matches
xdgwatcher.cpp
1/*
2 * Copyright (C) 2019 UBports Foundation
3 * Author(s): Marius Gripsgard <marius@ubports.com>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; version 3.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18#include "xdgwatcher.h"
19
20#include <QDebug>
21#include <QDir>
22#include <QFile>
23#include <QStandardPaths>
24#include <QTextStream>
25
26XdgWatcher::XdgWatcher(QObject* parent)
27 : QObject(parent),
28 m_watcher(new QFileSystemWatcher(this))
29{
30 connect(m_watcher, &QFileSystemWatcher::directoryChanged, this, &XdgWatcher::onDirectoryChanged);
31 connect(m_watcher, &QFileSystemWatcher::fileChanged, this, &XdgWatcher::onFileChanged);
32
33 const auto paths = QStandardPaths::standardLocations(QStandardPaths::ApplicationsLocation);
34 for (const auto &path: paths) {
35 const auto qdir = QDir(path);
36 if (!qdir.exists()) {
37 continue;
38 }
39
40 // Add the path itself to watch for newly added apps
41 m_watcher->addPath(path);
42
43 // Add watcher for eatch app to watch for changes
44 const auto files = qdir.entryInfoList(QDir::Files);
45 for (const auto &file: files) {
46 if (file.suffix() == "desktop") {
47 const auto path = file.absoluteFilePath();
48 m_watcher->addPath(path);
49 m_registry.insert(path, getAppId(file));
50 }
51 }
52 }
53}
54
55// "Lomiri style" appID is filename without versionNumber after last "_"
56const QString XdgWatcher::stripAppIdVersion(const QString rawAppID) const {
57 auto appIdComponents = rawAppID.split("_");
58 appIdComponents.removeLast();
59 return appIdComponents.join("_");
60}
61
62// Standard appID see:
63// https://standards.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#desktop-file-id
64const QString XdgWatcher::toStandardAppId(const QFileInfo fileInfo) const {
65 const auto paths = QStandardPaths::standardLocations(QStandardPaths::ApplicationsLocation);
66 for (const auto &path: paths) {
67 if (fileInfo.absolutePath() == path) {
68 break;
69 }
70 if (fileInfo.absolutePath().contains(path)) {
71 auto fileStr = fileInfo.absoluteFilePath();
72 fileStr.replace(path, "");
73 fileStr.replace("/", "-");
74 fileStr.replace(".desktop", "");
75 return fileStr;
76 }
77 }
78 return fileInfo.completeBaseName();
79}
80
81const QString XdgWatcher::getAppId(const QFileInfo fileInfo) const {
82 // We need to open the file to check if its and Ual application
83 // as we cant just rely on the app name as "normal" apps can also
84 // contain 3 _ causing us to belive its an lomiri app
85 // Example kde_org_kate would become kde_org
86 QFile qFile(fileInfo.absoluteFilePath());
87 qFile.open(QIODevice::ReadOnly);
88 QTextStream fileStream(&qFile);
89 QString line;
90 while (fileStream.readLineInto(&line)) {
91 if (line.startsWith("X-Lomiri-Application-ID=")) {
92 auto rawAppID = line.replace("X-Lomiri-Application-ID=", "");
93 qFile.close();
94 return stripAppIdVersion(rawAppID);
95 }
96 }
97 qFile.close();
98
99 // If it's not an "Lomiri" appID, we follow freedesktop standard
100 return toStandardAppId(fileInfo);
101}
102
103// Watch for newly added apps
104void XdgWatcher::onDirectoryChanged(const QString &path) {
105 const auto files = QDir(path).entryInfoList(QDir::Files);
106 const auto watchedFiles = m_watcher->files();
107 for (const auto &file: files) {
108 const auto appPath = file.absoluteFilePath();
109 if (file.suffix() == "desktop" && !watchedFiles.contains(appPath)) {
110 m_watcher->addPath(appPath);
111
112 const auto appId = getAppId(file);
113 m_registry.insert(appPath, appId);
114 Q_EMIT appAdded(appId);
115 }
116 }
117}
118
119void XdgWatcher::onFileChanged(const QString &path) {
120 const auto watchedFiles = m_watcher->files();
121 if (watchedFiles.contains(path)) {
122 // The file is still watched, this must be an modify event
123 Q_EMIT appInfoChanged(m_registry.value(path));
124 } else {
125 // File is not watched anymore, this is a remove event.
126 // onDirectoryChanged will handle rename event
127 Q_EMIT appRemoved(m_registry.take(path));
128 }
129}