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