Unity 8
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 
26 XdgWatcher::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 // "Ubuntu style" appID is filename without versionNumber after last "_"
56 const 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
64 const 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 
81 const 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 ubuntu 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-Ubuntu-Application-ID=")) {
92  auto rawAppID = line.replace("X-Ubuntu-Application-ID=", "");
93  qFile.close();
94  return stripAppIdVersion(rawAppID);
95  }
96  }
97  qFile.close();
98 
99  // If it's not an "Ubuntu" appID, we follow freedesktop standard
100  return toStandardAppId(fileInfo);
101 }
102 
103 // Watch for newly added apps
104 void 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 
119 void XdgWatcher::onFileChanged(const QString &path) {
120  QFileInfo file(path);
121  if (file.exists()) {
122  // The file exists, this must be an modify event
123  Q_EMIT appInfoChanged(m_registry.value(path));
124  } else {
125  // File does not exist, assume this is an remove event.
126  // onDirectoryChanged will handle rename event
127  Q_EMIT appRemoved(m_registry.take(path));
128  }
129 }