File indexing completed on 2024-04-21 04:58:13

0001 /* This file is part of the KDE project
0002     SPDX-FileCopyrightText: 2007 David Faure <faure@kde.org>
0003     SPDX-FileCopyrightText: 2007 Eduardo Robles Elvira <edulix@gmail.com>
0004 
0005     SPDX-License-Identifier: GPL-2.0-or-later
0006 */
0007 
0008 #include "konqclosedwindowsmanager.h"
0009 #include "konqsettingsxt.h"
0010 #include "konqmisc.h"
0011 #include "konqcloseditem.h"
0012 #include "konqclosedwindowsmanageradaptor.h"
0013 #include "konqclosedwindowsmanager_interface.h"
0014 #include <kio/fileundomanager.h>
0015 #include <QDirIterator>
0016 #include <QMetaType>
0017 #include <QDBusConnection>
0018 #include <QDBusMessage>
0019 #include <QDBusReply>
0020 #include <KLocalizedString>
0021 
0022 #include <kconfig.h>
0023 #include <QStandardPaths>
0024 #include <KSharedConfig>
0025 
0026 Q_DECLARE_METATYPE(QList<QVariant>)
0027 
0028 class KonqClosedWindowsManagerPrivate
0029 {
0030 public:
0031     KonqClosedWindowsManager instance;
0032     int m_maxNumClosedItems;
0033 };
0034 
0035 static KonqClosedWindowsManagerPrivate *myKonqClosedWindowsManagerPrivate = nullptr;
0036 
0037 KonqClosedWindowsManager::KonqClosedWindowsManager()
0038 {
0039     new KonqClosedWindowsManagerAdaptor(this);
0040 
0041     const QString dbusPath = QStringLiteral("/KonqUndoManager");
0042     const QString dbusInterface = QStringLiteral("org.kde.Konqueror.UndoManager");
0043 
0044     QDBusConnection dbus = QDBusConnection::sessionBus();
0045     dbus.registerObject(dbusPath, this);
0046     dbus.connect(QString(), dbusPath, dbusInterface, QStringLiteral("notifyClosedWindowItem"), this, SLOT(slotNotifyClosedWindowItem(QString,int,QString,QString,QDBusMessage)));
0047     dbus.connect(QString(), dbusPath, dbusInterface, QStringLiteral("notifyRemove"), this, SLOT(slotNotifyRemove(QString,QString,QDBusMessage)));
0048 
0049     QString filename = "closeditems/" + KonqMisc::encodeFilename(dbus.baseService());
0050     QString file = QDir::tempPath() + QLatin1Char('/') +  filename;
0051     QFile::remove(file);
0052 
0053     KConfigGroup configGroup(KSharedConfig::openConfig(), "Undo");
0054     m_numUndoClosedItems = configGroup.readEntry("Number of Closed Windows", 0);
0055 
0056     m_konqClosedItemsConfig = nullptr;
0057     m_blockClosedItems = false;
0058     m_konqClosedItemsStore = new KConfig(file, KConfig::SimpleConfig);
0059 }
0060 
0061 KonqClosedWindowsManager::~KonqClosedWindowsManager()
0062 {
0063     // Do some file cleaning
0064     removeClosedItemsConfigFiles();
0065 
0066     qDeleteAll(m_closedWindowItemList); // must be done before deleting the kconfigs
0067     delete m_konqClosedItemsConfig;
0068     delete m_konqClosedItemsStore;
0069 }
0070 
0071 KConfig *KonqClosedWindowsManager::memoryStore()
0072 {
0073     return m_konqClosedItemsStore;
0074 }
0075 
0076 KonqClosedWindowsManager *KonqClosedWindowsManager::self()
0077 {
0078     if (!myKonqClosedWindowsManagerPrivate) {
0079         myKonqClosedWindowsManagerPrivate = new KonqClosedWindowsManagerPrivate;
0080     }
0081     return &myKonqClosedWindowsManagerPrivate->instance;
0082 }
0083 
0084 void KonqClosedWindowsManager::destroy()
0085 {
0086     delete myKonqClosedWindowsManagerPrivate;
0087     myKonqClosedWindowsManagerPrivate = nullptr;
0088 }
0089 
0090 void KonqClosedWindowsManager::addClosedWindowItem(KonqUndoManager
0091         *real_sender, KonqClosedWindowItem *closedWindowItem, bool propagate)
0092 {
0093     readConfig();
0094     // If we are off the limit, remove the last closed window item
0095     if (m_closedWindowItemList.size() >=
0096             KonqSettings::maxNumClosedItems()) {
0097         KonqClosedWindowItem *last = m_closedWindowItemList.last();
0098 
0099         emit removeWindowInOtherInstances(nullptr, last);
0100         emitNotifyRemove(last);
0101 
0102         m_closedWindowItemList.removeLast();
0103         delete last;
0104     }
0105 
0106     if (!m_blockClosedItems) {
0107         m_numUndoClosedItems++;
0108         emit addWindowInOtherInstances(real_sender, closedWindowItem);
0109     }
0110 
0111     // The prepend goes after emit addWindowInOtherInstances() because otherwise
0112     // the first time addWindowInOtherInstances() is emitted, KonqUndoManager
0113     // will catch it and it will call to its private populate() function which
0114     // will add to the undo list closedWindowItem, and then it will add it again
0115     // but we want it to be added only once.
0116     m_closedWindowItemList.prepend(closedWindowItem);
0117 
0118     if (propagate) {
0119         // if it needs to be propagated means that it's a local window and thus
0120         // we need to call to saveConfig() to keep updated the kconfig file, so
0121         // that new konqueror instances can read it correctly updated.
0122         saveConfig();
0123 
0124         // Once saved, tell to other konqi processes
0125         emitNotifyClosedWindowItem(closedWindowItem);
0126     }
0127 }
0128 
0129 void KonqClosedWindowsManager::removeClosedWindowItem(KonqUndoManager
0130         *real_sender, const KonqClosedWindowItem *closedWindowItem, bool propagate)
0131 {
0132     readConfig();
0133     auto it = std::find(m_closedWindowItemList.begin(), m_closedWindowItemList.end(), closedWindowItem);
0134 
0135     // If the item was found, remove it from the list
0136     if (it != m_closedWindowItemList.end()) {
0137         m_closedWindowItemList.erase(it);
0138         m_numUndoClosedItems--;
0139     }
0140     emit removeWindowInOtherInstances(real_sender, closedWindowItem);
0141 
0142     if (propagate) {
0143         emitNotifyRemove(closedWindowItem);
0144     }
0145 }
0146 
0147 const QList<KonqClosedWindowItem *> &KonqClosedWindowsManager::closedWindowItemList()
0148 {
0149     readConfig();
0150     return m_closedWindowItemList;
0151 }
0152 
0153 void KonqClosedWindowsManager::readSettings()
0154 {
0155     readConfig();
0156 }
0157 
0158 static QString dbusService()
0159 {
0160     return QDBusConnection::sessionBus().baseService();
0161 }
0162 
0163 /**
0164  * Returns whether the DBUS call we are handling was a call from us self
0165  */
0166 bool isSenderOfSignal(const QDBusMessage &msg)
0167 {
0168     return dbusService() == msg.service();
0169 }
0170 
0171 bool isSenderOfSignal(const QString &service)
0172 {
0173     return dbusService() == service;
0174 }
0175 
0176 void KonqClosedWindowsManager::emitNotifyClosedWindowItem(
0177     const KonqClosedWindowItem *closedWindowItem)
0178 {
0179     emit notifyClosedWindowItem(closedWindowItem->title(),
0180                                 closedWindowItem->numTabs(),
0181                                 m_konqClosedItemsStore->name(),
0182                                 closedWindowItem->configGroup().name());
0183 }
0184 
0185 void KonqClosedWindowsManager::emitNotifyRemove(
0186     const KonqClosedWindowItem *closedWindowItem)
0187 {
0188     const KonqClosedRemoteWindowItem *closedRemoteWindowItem =
0189         dynamic_cast<const KonqClosedRemoteWindowItem *>(closedWindowItem);
0190 
0191     // Here we do this because there's no need to call to configGroup() if it's
0192     // a remote window item, and it would be error prone to be so, because
0193     // it could give us a null pointer and konqueror would crash
0194     if (closedRemoteWindowItem)
0195         emit notifyRemove(closedRemoteWindowItem->remoteConfigFileName(),
0196                           closedRemoteWindowItem->remoteGroupName());
0197     else
0198         emit notifyRemove(closedWindowItem->configGroup().config()->name(),
0199                           closedWindowItem->configGroup().name());
0200 }
0201 
0202 void KonqClosedWindowsManager::slotNotifyClosedWindowItem(
0203     const QString &title, int numTabs, const QString &configFileName,
0204     const QString &configGroup, const QString &service)
0205 {
0206     if (isSenderOfSignal(service)) {
0207         return;
0208     }
0209 
0210     // Create a new ClosedWindowItem and add it to the list
0211     KonqClosedWindowItem *closedWindowItem = new KonqClosedRemoteWindowItem(
0212         title, memoryStore(), configGroup, configFileName,
0213         KIO::FileUndoManager::self()->newCommandSerialNumber(), numTabs,
0214         service);
0215 
0216     // Add it to all the windows but don't propagate over dbus,
0217     // as it already comes from dbus)
0218     addClosedWindowItem(nullptr, closedWindowItem, false);
0219 }
0220 
0221 void KonqClosedWindowsManager::slotNotifyClosedWindowItem(
0222     const QString &title, int numTabs, const QString &configFileName,
0223     const QString &configGroup, const QDBusMessage &msg)
0224 {
0225     slotNotifyClosedWindowItem(title, numTabs, configFileName, configGroup,
0226                                msg.service());
0227 }
0228 
0229 void KonqClosedWindowsManager::slotNotifyRemove(
0230     const QString &configFileName, const QString &configGroup,
0231     const QDBusMessage &msg)
0232 {
0233     if (isSenderOfSignal(msg)) {
0234         return;
0235     }
0236 
0237     // Find the window item. It can be either remote or local
0238     KonqClosedWindowItem *closedWindowItem =
0239         findClosedRemoteWindowItem(configFileName, configGroup);
0240     if (!closedWindowItem) {
0241         closedWindowItem = findClosedLocalWindowItem(configFileName, configGroup);
0242         if (!closedWindowItem) {
0243             return;
0244         }
0245     }
0246 
0247     // Remove it in all the windows but don't propagate over dbus,
0248     // as it already comes from dbus)
0249     removeClosedWindowItem(nullptr, closedWindowItem, false);
0250 }
0251 
0252 KonqClosedRemoteWindowItem *KonqClosedWindowsManager::findClosedRemoteWindowItem(
0253     const QString &configFileName,
0254     const QString &configGroup)
0255 {
0256     readConfig();
0257 
0258     KonqClosedRemoteWindowItem *closedRemoteWindowItem = nullptr;
0259     for (QList<KonqClosedWindowItem *>::const_iterator it = m_closedWindowItemList.constBegin();
0260             it != m_closedWindowItemList.constEnd(); ++it) {
0261         closedRemoteWindowItem = dynamic_cast<KonqClosedRemoteWindowItem *>(*it);
0262 
0263         if (closedRemoteWindowItem &&
0264                 closedRemoteWindowItem->equalsTo(configFileName, configGroup)) {
0265             return closedRemoteWindowItem;
0266         }
0267     }
0268 
0269     return closedRemoteWindowItem;
0270 }
0271 
0272 KonqClosedWindowItem *KonqClosedWindowsManager::findClosedLocalWindowItem(
0273     const QString &configFileName,
0274     const QString &configGroup)
0275 {
0276     readConfig();
0277     KonqClosedWindowItem *closedWindowItem = nullptr;
0278     for (QList<KonqClosedWindowItem *>::const_iterator it = m_closedWindowItemList.constBegin();
0279             it != m_closedWindowItemList.constEnd(); ++it) {
0280         closedWindowItem = *it;
0281         KonqClosedRemoteWindowItem *closedRemoteWindowItem =
0282             dynamic_cast<KonqClosedRemoteWindowItem *>(closedWindowItem);
0283 
0284         if (!closedRemoteWindowItem && closedWindowItem &&
0285                 closedWindowItem->configGroup().config()->name() == configFileName &&
0286                 closedWindowItem->configGroup().name() == configGroup) {
0287             return closedWindowItem;
0288         }
0289     }
0290 
0291     return closedWindowItem;
0292 }
0293 
0294 /**
0295  * @returns the number of konqueror processes by counting the number of
0296  * org.kde.konqueror services in dbus.
0297  *
0298  * If dbus fails it returns -1.
0299  */
0300 static int numberOfKonquerorProcesses()
0301 {
0302     QDBusConnection dbus = QDBusConnection::sessionBus();
0303     QDBusReply<QStringList> reply = dbus.interface()->registeredServiceNames();
0304     if (!reply.isValid()) {
0305         return -1;
0306     }
0307 
0308     const QStringList allServices = reply;
0309     int count = 0; // count the number of running konqueror processes. Should be at least one, us.
0310     for (QStringList::const_iterator it = allServices.begin(), end = allServices.end(); it != end; ++it) {
0311         const QString service = *it;
0312         if (service.startsWith(QLatin1String("org.kde.konqueror"))) {
0313             count++;
0314         }
0315     }
0316     return count;
0317 }
0318 
0319 void KonqClosedWindowsManager::removeClosedItemsConfigFiles()
0320 {
0321     // We'll only remove closed items config files if we are the only process
0322     // left or if dbus fails (just in case there is any other konqi process
0323     // but we couldn't see it).
0324     int count = numberOfKonquerorProcesses();
0325     if (count > 1 || count == -1) {
0326         return;
0327     }
0328 
0329     // We are the only instance of konqueror left and thus we can safely remove
0330     // all those temporary files.
0331     QString dir = QDir::tempPath() + QLatin1Char('/') +  "closeditems/";
0332     QDBusConnectionInterface *idbus = QDBusConnection::sessionBus().interface();
0333     QDirIterator it(dir, QDir::Writable | QDir::Files);
0334     while (it.hasNext()) {
0335         // Only remove the files for those konqueror instances not running anymore
0336         QString filename = it.next();
0337         if (!idbus->isServiceRegistered(KonqMisc::decodeFilename(it.fileName()))) {
0338             QFile::remove(filename);
0339         }
0340     }
0341 }
0342 
0343 void KonqClosedWindowsManager::saveConfig()
0344 {
0345     readConfig();
0346 
0347     // Create / overwrite the saved closed windows list
0348     QString filename = QStringLiteral("closeditems_saved");
0349     QString file = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QLatin1Char('/') + filename;
0350     QFile::remove(file);
0351 
0352     KConfig *config = new KConfig(file, KConfig::SimpleConfig);
0353 
0354     // Populate the config file
0355     KonqClosedWindowItem *closedWindowItem = nullptr;
0356     uint counter = m_closedWindowItemList.size() - 1;
0357     for (QList<KonqClosedWindowItem *>::const_iterator it = m_closedWindowItemList.constBegin();
0358             it != m_closedWindowItemList.constEnd(); ++it, --counter) {
0359         closedWindowItem = *it;
0360         KConfigGroup configGroup(config, "Closed_Window" + QString::number(counter));
0361         configGroup.writeEntry("title", closedWindowItem->title());
0362         configGroup.writeEntry("numTabs", closedWindowItem->numTabs());
0363         closedWindowItem->configGroup().copyTo(&configGroup);
0364     }
0365 
0366     KConfigGroup configGroup(KSharedConfig::openConfig(), "Undo");
0367     configGroup.writeEntry("Number of Closed Windows", m_closedWindowItemList.size());
0368     configGroup.sync();
0369 
0370     // Finally the most important thing, which is to save the store config
0371     // so that other konqi processes can reopen windows closed in this process.
0372     m_konqClosedItemsStore->sync();
0373 
0374     delete config;
0375 }
0376 
0377 void KonqClosedWindowsManager ::readConfig()
0378 {
0379     if (m_konqClosedItemsConfig) {
0380         return;
0381     }
0382 
0383     QString filename = QStringLiteral("closeditems_saved");
0384     QString file = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QLatin1Char('/') + filename;
0385 
0386     m_konqClosedItemsConfig = new KConfig(file, KConfig::SimpleConfig);
0387 
0388     // If the config file doesn't exists, there's nothing to read
0389     if (!QFile::exists(file)) {
0390         return;
0391     }
0392 
0393     m_blockClosedItems = true;
0394     for (int i = 0; i < m_numUndoClosedItems; i++) {
0395         // For each item, create a new ClosedWindowItem
0396         KConfigGroup configGroup(m_konqClosedItemsConfig, "Closed_Window" +
0397                                  QString::number(i));
0398 
0399         // The number of closed items was not correctly set, fix it and save the
0400         // correct number.
0401         if (!configGroup.exists()) {
0402             m_numUndoClosedItems = i;
0403             KConfigGroup configGroup(KSharedConfig::openConfig(), "Undo");
0404             configGroup.writeEntry("Number of Closed Windows",
0405                                    m_closedWindowItemList.size());
0406             configGroup.sync();
0407             break;
0408         }
0409 
0410         QString title = configGroup.readEntry("title", i18n("no name"));
0411         int numTabs = configGroup.readEntry("numTabs", 0);
0412 
0413         KonqClosedWindowItem *closedWindowItem = new KonqClosedWindowItem(
0414             title, memoryStore(), i, numTabs);
0415         configGroup.copyTo(&closedWindowItem->configGroup());
0416         configGroup.writeEntry("foo", 0);
0417 
0418         // Add the item only to this window
0419         addClosedWindowItem(nullptr, closedWindowItem, false);
0420     }
0421 
0422     m_blockClosedItems = false;
0423 }
0424 
0425 bool KonqClosedWindowsManager::undoAvailable() const
0426 {
0427     return m_numUndoClosedItems > 0;
0428 }