1 /*
2 SPDX-FileCopyrightText: 2009 David Faure <faure@kde.org>
3
4 SPDX-License-Identifier: LGPL-2.0-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
5 */
6
7 #include "module_manager.h"
8 #include <kdesktopfile.h>
9 #include <kio/deletejob.h>
10 #include <kconfig.h>
11 #include <kconfiggroup.h>
12 #include <QDir>
13 #include <QUrl>
14 #include <QStandardPaths>
15 #include <KSharedConfig>
16
17 #include "sidebar_debug.h"
18
19 // Input data:
20 // Global dir: list of desktop files.
21 // Local dir: list of desktop files, for modules added or modified
22 // (When the user modifies something, we make a local copy)
23 // Local KConfig file: config file with list (per profile) of "added" and "removed" desktop files
24
25 // Algorithm:
26 // Then we create buttons:
27 // * desktop files from the most-global directory (pre-installed modules),
28 // skipping those listed as "removed"
29 // * desktop files from other (more local) directories, if listed as "added" (for the current profile).
30 //
31 // (module not present globally and not "added" are either modified copies of
32 // not-installed-anymore modules, or modules that were only added for another profile).
33
ModuleManager(KConfigGroup * config)34 ModuleManager::ModuleManager(KConfigGroup *config)
35 : m_config(config)
36 {
37 m_localPath = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1Char('/') + relativeDataPath();
38 }
39
modules() const40 QStringList ModuleManager::modules() const
41 {
42 QStringList fileNames;
43 const QStringList addedModules = m_config->readEntry("AddedModules", QStringList());
44 const QStringList deletedModules = m_config->readEntry("DeletedModules", QStringList());
45
46 const QStringList entries_dirs = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, relativeDataPath(), QStandardPaths::LocateDirectory);
47 if (entries_dirs.isEmpty()) { // Ooops
48 qCWarning(SIDEBAR_LOG) << "No global directory found for" << relativeDataPath() << "Installation problem!";
49 return QStringList();
50 }
51 // We only list the most-global dir. Other levels use AddedModules.
52 QDir globalDir(entries_dirs.last());
53 //qCDebug(SIDEBAR_LOG) << "Listing" << entries_dirs.last();
54 const QStringList globalDirEntries = globalDir.entryList(QDir::Files | QDir::NoDotAndDotDot);
55 Q_FOREACH (const QString &globalEntry, globalDirEntries) {
56 //qCDebug(SIDEBAR_LOG) << " found" << globalEntry;
57 if (!deletedModules.contains(globalEntry)) {
58 fileNames.append(globalEntry);
59 }
60 }
61 sortGlobalEntries(fileNames);
62 //qCDebug(SIDEBAR_LOG) << "Adding local modules:" << addedModules;
63 Q_FOREACH (const QString &module, addedModules) {
64 if (!fileNames.contains(module)) {
65 fileNames.append(module);
66 }
67 }
68
69 return fileNames;
70 }
71
availablePlugins() const72 KService::List ModuleManager::availablePlugins() const
73 {
74 // For the "add" menu, we need all available plugins.
75 // We could use KServiceTypeTrader for that; not sure 2 files make a big performance difference though.
76
77 KService::List services;
78 const QStringList pluginDirs = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, "konqsidebartng/plugins", QStandardPaths::LocateDirectory);
79 for (const QString &pluginDir : pluginDirs) {
80 QDir dir(pluginDir);
81 QStringList files = dir.entryList((QStringList() << "*.desktop"), QDir::Files);
82
83 for (const QString &file : files) {
84 KDesktopFile df(dir.absoluteFilePath(file)); // no merging. KService warns, and we don't need it.
85 services.append(KService::Ptr(new KService(&df)));
86 }
87 }
88 return services;
89 }
90
moduleDataPath(const QString & fileName) const91 QString ModuleManager::moduleDataPath(const QString &fileName) const
92 {
93 return relativeDataPath() + fileName;
94 }
95
moduleFullPath(const QString & fileName) const96 QString ModuleManager::moduleFullPath(const QString &fileName) const
97 {
98 return QStandardPaths::locate(QStandardPaths::GenericDataLocation, moduleDataPath(fileName));
99 }
100
saveOpenViews(const QStringList & fileName)101 void ModuleManager::saveOpenViews(const QStringList &fileName)
102 {
103 // TODO: this would be best stored per-window, in the session file
104
105 m_config->writeEntry("OpenViews", fileName);
106 m_config->sync();
107 }
108
restoreDeletedButtons()109 void ModuleManager::restoreDeletedButtons()
110 {
111 m_config->writeEntry("DeletedModules", QStringList());
112 m_config->sync();
113 }
114
rollbackToDefault()115 void ModuleManager::rollbackToDefault()
116 {
117 const QString loc = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/konqsidebartng/";
118 QDir dir(loc);
119 const QStringList dirEntries = dir.entryList(QDir::Dirs | QDir::NoSymLinks | QDir::NoDotAndDotDot);
120 Q_FOREACH (const QString &subdir, dirEntries) {
121 if (subdir != "add") {
122 qCDebug(SIDEBAR_LOG) << "Deleting" << (loc + subdir);
123 KIO::Job *job = KIO::del(QUrl::fromLocalFile(loc + subdir), KIO::HideProgressInfo);
124 job->exec();
125 }
126 }
127 m_config->writeEntry("DeletedModules", QStringList());
128 m_config->writeEntry("AddedModules", QStringList());
129 m_config->sync();
130 }
131
setModuleName(const QString & fileName,const QString & moduleName)132 void ModuleManager::setModuleName(const QString &fileName, const QString &moduleName)
133 {
134 // Write the name in the .desktop file of this button.
135 KConfig desktopFile(m_localPath + fileName, KConfig::SimpleConfig);
136 KConfigGroup ksc(&desktopFile, "Desktop Entry");
137 ksc.writeEntry("Name", moduleName);
138 ksc.writeEntry("Name", moduleName, KConfigBase::Persistent | KConfigBase::Localized);
139 ksc.sync();
140 }
141
setModuleUrl(const QString & fileName,const QUrl & url)142 void ModuleManager::setModuleUrl(const QString &fileName, const QUrl &url)
143 {
144 KConfig desktopFile(m_localPath + fileName, KConfig::SimpleConfig);
145 KConfigGroup ksc(&desktopFile, "Desktop Entry");
146 ksc.writePathEntry("URL", url.toDisplayString());
147 ksc.sync();
148 }
149
setModuleIcon(const QString & fileName,const QString & icon)150 void ModuleManager::setModuleIcon(const QString &fileName, const QString &icon)
151 {
152 KConfig desktopFile(m_localPath + fileName, KConfig::SimpleConfig);
153 KConfigGroup ksc(&desktopFile, "Desktop Entry");
154 ksc.writePathEntry("Icon", icon);
155 ksc.sync();
156 }
157
setShowHiddenFolders(const QString & fileName,const bool & newState)158 void ModuleManager::setShowHiddenFolders(const QString &fileName, const bool &newState)
159 {
160 KConfig desktopFile(m_localPath + fileName, KConfig::SimpleConfig);
161 KConfigGroup ksc(&desktopFile, "Desktop Entry");
162 ksc.writeEntry("ShowHiddenFolders", newState);
163 ksc.sync();
164 }
165
getMaxKDEWeight()166 int ModuleManager::getMaxKDEWeight()
167 {
168 int curMax = 1; // 0 is reserved for the treeModule
169 for (const QString &fileName : modules()) {
170 const QString path = moduleDataPath(fileName);
171 if (! QStandardPaths::locate(QStandardPaths::GenericDataLocation, path).isEmpty()) {
172 KSharedConfig::Ptr config = KSharedConfig::openConfig(path,
173 KConfig::NoGlobals,
174 QStandardPaths::GenericDataLocation);
175 KConfigGroup configGroup(config, "Desktop Entry");
176 const int weight = configGroup.readEntry("X-KDE-Weight", 0);
177 if (curMax < weight) {
178 curMax = weight;
179 }
180 }
181 }
182 return curMax;
183 }
184
getNextAvailableKDEWeight()185 int ModuleManager::getNextAvailableKDEWeight()
186 {
187 return getMaxKDEWeight() + 1;
188 }
189
removeModule(const QString & fileName)190 void ModuleManager::removeModule(const QString &fileName)
191 {
192 // Remove the local file (if it exists)
193 QFile f(m_localPath + fileName);
194 f.remove();
195
196 // Mark module as deleted (so that we skip global file, if there's one)
197 QStringList deletedModules = m_config->readEntry("DeletedModules", QStringList());
198 QStringList addedModules = m_config->readEntry("AddedModules", QStringList());
199 if ( !addedModules.contains(fileName) && !deletedModules.contains(fileName)) { // only add it to the "deletedModules" list if it is a global module
200 deletedModules.append(fileName);
201 }
202
203 addedModules.removeAll(fileName);
204 m_config->writeEntry("DeletedModules", deletedModules);
205 m_config->writeEntry("AddedModules", addedModules);
206 m_config->sync();
207 }
208
moduleAdded(const QString & fileName)209 void ModuleManager::moduleAdded(const QString &fileName)
210 {
211 qCDebug(SIDEBAR_LOG) << fileName;
212 QStringList deletedModules = m_config->readEntry("DeletedModules", QStringList());
213 QStringList addedModules = m_config->readEntry("AddedModules", QStringList());
214 if (!addedModules.contains(fileName)) {
215 addedModules.append(fileName);
216 }
217 deletedModules.removeAll(fileName);
218 m_config->writeEntry("DeletedModules", deletedModules);
219 m_config->writeEntry("AddedModules", addedModules);
220 m_config->sync();
221 }
222
addModuleFromTemplate(QString & templ)223 QString ModuleManager::addModuleFromTemplate(QString &templ)
224 {
225 if (!templ.contains("%1")) {
226 qCWarning(SIDEBAR_LOG) << "Template filename should contain %1";
227 }
228
229 QString filename = templ.arg(QString());
230 QString myFile = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1Char('/') + moduleDataPath(filename);
231
232 if (QFile::exists(myFile)) {
233 for (ulong l = 1; l < ULONG_MAX; l++) {
234 filename = templ.arg(l);
235 myFile = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1Char('/') + moduleDataPath(filename);
236 if (!QFile::exists(myFile)) {
237 break;
238 } else {
239 filename.clear();
240 myFile.clear();
241 }
242 }
243 }
244 templ = filename;
245 return myFile;
246 }
247
localModulePaths(const QString & filter) const248 QStringList ModuleManager::localModulePaths(const QString &filter) const
249 {
250 return QDir(m_localPath).entryList(QStringList() << filter);
251 }
252
sortGlobalEntries(QStringList & fileNames) const253 void ModuleManager::sortGlobalEntries(QStringList &fileNames) const
254 {
255 QMap<int, QString> sorter;
256 Q_FOREACH (const QString &fileName, fileNames) {
257 const QString path = moduleDataPath(fileName);
258 if (QStandardPaths::locate(QStandardPaths::GenericDataLocation, path).isEmpty()) {
259 // doesn't exist anymore, skip it
260 qCDebug(SIDEBAR_LOG) << "Skipping" << path;
261 } else {
262 KSharedConfig::Ptr config = KSharedConfig::openConfig(path,
263 KConfig::NoGlobals,
264 QStandardPaths::GenericDataLocation);
265 KConfigGroup configGroup(config, "Desktop Entry");
266 const int weight = configGroup.readEntry("X-KDE-Weight", 0);
267 sorter.insert(weight, fileName);
268
269 // While we have the config file open, fix migration issue between old and new history sidebar
270 if (configGroup.readEntry("X-KDE-TreeModule") == "History") {
271 // Old local file still there; remove it
272 const QString localFile = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1Char('/') + path;
273 QFile::rename(localFile, localFile + ".orig");
274 qCDebug(SIDEBAR_LOG) << "Migration: moving" << localFile << "out of the way";
275 }
276 }
277 }
278 fileNames = sorter.values();
279 qCDebug(SIDEBAR_LOG) << fileNames;
280 }
281