1 /*
2     SPDX-FileCopyrightText: 2003 Joseph Wenninger <jowenn@kde.org>
3 
4     SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6 
7 #include <KLocalizedString>
8 #include <kio/slavebase.h>
9 #include <kservice.h>
10 #include <kservicegroup.h>
11 #include <sys/stat.h>
12 #include <time.h>
13 
14 #include <QStandardPaths>
15 #include <QUrl>
16 
17 // Pseudo plugin class to embed meta data
18 class KIOPluginForMetaData : public QObject
19 {
20     Q_OBJECT
21     Q_PLUGIN_METADATA(IID "org.kde.kio.slave.applications" FILE "applications.json")
22 };
23 
24 class ApplicationsProtocol : public KIO::SlaveBase
25 {
26 public:
27     enum RunMode {
28         ProgramsMode,
29         ApplicationsMode,
30     };
31     ApplicationsProtocol(const QByteArray &protocol, const QByteArray &pool, const QByteArray &app);
32     ~ApplicationsProtocol() override;
33     void get(const QUrl &url) override;
34     void stat(const QUrl &url) override;
35     void listDir(const QUrl &url) override;
36 
37 private:
38     RunMode m_runMode;
39 };
40 
41 extern "C" {
kdemain(int argc,char ** argv)42 Q_DECL_EXPORT int kdemain(int argc, char **argv)
43 {
44     QCoreApplication app(argc, argv);
45     app.setApplicationName("kio_applications");
46 
47     ApplicationsProtocol slave(argv[1], argv[2], argv[3]);
48     slave.dispatchLoop();
49     return 0;
50 }
51 }
52 
createFileEntry(KIO::UDSEntry & entry,const KService::Ptr & service,const QUrl & parentUrl)53 static void createFileEntry(KIO::UDSEntry &entry, const KService::Ptr &service, const QUrl &parentUrl)
54 {
55     entry.clear();
56     entry.fastInsert(KIO::UDSEntry::UDS_NAME, KIO::encodeFileName(service->name()));
57     entry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFREG);
58     const QString fileUrl = parentUrl.url() + '/' + service->desktopEntryName();
59     entry.fastInsert(KIO::UDSEntry::UDS_URL, fileUrl);
60     entry.fastInsert(KIO::UDSEntry::UDS_ACCESS, 0500);
61     entry.fastInsert(KIO::UDSEntry::UDS_MIME_TYPE, QStringLiteral("application/x-desktop"));
62     entry.fastInsert(KIO::UDSEntry::UDS_SIZE, 0);
63     const QString localPath = QStandardPaths::locate(QStandardPaths::ApplicationsLocation, QStringLiteral("%1.desktop").arg(service->desktopEntryName()));
64     entry.fastInsert(KIO::UDSEntry::UDS_LOCAL_PATH, localPath);
65     entry.fastInsert(KIO::UDSEntry::UDS_MODIFICATION_TIME, time(nullptr));
66     entry.fastInsert(KIO::UDSEntry::UDS_ICON_NAME, service->icon());
67 }
68 
createDirEntry(KIO::UDSEntry & entry,const QString & name,const QString & url,const QString & mime,const QString & iconName)69 static void createDirEntry(KIO::UDSEntry &entry, const QString &name, const QString &url, const QString &mime, const QString &iconName)
70 {
71     entry.clear();
72     entry.fastInsert(KIO::UDSEntry::UDS_NAME, name);
73     entry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR);
74     entry.fastInsert(KIO::UDSEntry::UDS_ACCESS, 0500);
75     entry.fastInsert(KIO::UDSEntry::UDS_MIME_TYPE, mime);
76     if (!url.isEmpty())
77         entry.fastInsert(KIO::UDSEntry::UDS_URL, url);
78     entry.fastInsert(KIO::UDSEntry::UDS_ICON_NAME, iconName);
79 }
80 
ApplicationsProtocol(const QByteArray & protocol,const QByteArray & pool,const QByteArray & app)81 ApplicationsProtocol::ApplicationsProtocol(const QByteArray &protocol, const QByteArray &pool, const QByteArray &app)
82     : SlaveBase(protocol, pool, app)
83 {
84     // Adjusts which part of the K Menu to virtualize.
85     if (protocol == "programs")
86         m_runMode = ProgramsMode;
87     else // if (protocol == "applications")
88         m_runMode = ApplicationsMode;
89 }
90 
~ApplicationsProtocol()91 ApplicationsProtocol::~ApplicationsProtocol()
92 {
93 }
94 
get(const QUrl & url)95 void ApplicationsProtocol::get(const QUrl &url)
96 {
97     KService::Ptr service = KService::serviceByDesktopName(url.fileName());
98     if (service && service->isValid()) {
99         const QString localPath = QStandardPaths::locate(QStandardPaths::ApplicationsLocation, QStringLiteral("%1.desktop").arg(service->desktopEntryName()));
100         QUrl redirUrl(QUrl::fromLocalFile(localPath));
101         redirection(redirUrl);
102         finished();
103     } else {
104         error(KIO::ERR_IS_DIRECTORY, url.toDisplayString());
105     }
106 }
107 
stat(const QUrl & url)108 void ApplicationsProtocol::stat(const QUrl &url)
109 {
110     KIO::UDSEntry entry;
111 
112     QString servicePath(url.path());
113     if (!servicePath.endsWith('/'))
114         servicePath.append('/');
115     servicePath.remove(0, 1); // remove starting '/'
116 
117     KServiceGroup::Ptr grp = KServiceGroup::group(servicePath);
118 
119     if (grp && grp->isValid()) {
120         createDirEntry(entry,
121                        ((m_runMode == ApplicationsMode) ? i18n("Applications") : i18n("Programs")),
122                        url.url(),
123                        QStringLiteral("inode/directory"),
124                        grp->icon());
125     } else {
126         KService::Ptr service = KService::serviceByDesktopName(url.fileName());
127         if (service && service->isValid()) {
128             createFileEntry(entry, service, url);
129         } else {
130             error(KIO::ERR_SLAVE_DEFINED, i18n("Unknown application folder"));
131             return;
132         }
133     }
134 
135     statEntry(entry);
136     finished();
137 }
138 
listDir(const QUrl & url)139 void ApplicationsProtocol::listDir(const QUrl &url)
140 {
141     QString groupPath = url.path();
142     if (!groupPath.endsWith('/'))
143         groupPath.append('/');
144     groupPath.remove(0, 1); // remove starting '/'
145 
146     KServiceGroup::Ptr grp = KServiceGroup::group(groupPath);
147 
148     if (!grp || !grp->isValid()) {
149         error(KIO::ERR_DOES_NOT_EXIST, groupPath);
150         return;
151     }
152 
153     unsigned int count = 0;
154     KIO::UDSEntry entry;
155 
156     foreach (const KSycocaEntry::Ptr &e, grp->entries(true, true)) {
157         if (e->isType(KST_KServiceGroup)) {
158             KServiceGroup::Ptr g(static_cast<KServiceGroup *>(e.data()));
159 
160             // qDebug() << "ADDING SERVICE GROUP WITH PATH " << g->relPath();
161 
162             // Avoid adding empty groups.
163             KServiceGroup::Ptr subMenuRoot = KServiceGroup::group(g->relPath());
164             if (subMenuRoot->childCount() == 0)
165                 continue;
166 
167             // Ignore dotfiles.
168             if (g->name().startsWith('.'))
169                 continue;
170 
171             QString relPath = g->relPath();
172             QUrl dirUrl = url; // preserve protocol, whether that's programs:/ or applications:/
173             dirUrl.setPath('/' + relPath);
174             dirUrl = dirUrl.adjusted(QUrl::StripTrailingSlash);
175             // qDebug() << "ApplicationsProtocol: adding entry" << dirUrl;
176             createDirEntry(entry, g->caption(), dirUrl.url(), QStringLiteral("inode/directory"), g->icon());
177         } else {
178             KService::Ptr service(static_cast<KService *>(e.data()));
179 
180             // qDebug() << "the entry name is" << service->desktopEntryName()
181             //         << "with path" << service->entryPath();
182 
183             if (!service->isApplication()) // how could this happen?
184                 continue;
185             createFileEntry(entry, service, url);
186         }
187 
188         listEntry(entry);
189         count++;
190     }
191 
192     totalSize(count);
193     finished();
194 }
195 
196 #include "kio_applications.moc"
197