File indexing completed on 2024-04-28 04:58:11

0001 /* This file is part of the KDE project
0002     SPDX-FileCopyrightText: 2000, 2001 Carsten Pfeiffer <pfeiffer@kde.org>
0003     SPDX-FileCopyrightText: 2007-2009 David Faure <faure@kde.org>
0004 
0005     SPDX-License-Identifier: LGPL-2.0-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
0006 */
0007 
0008 #include "konq_historyprovider.h"
0009 
0010 #include <kconfiggroup.h>
0011 #include <ksharedconfig.h>
0012 #include "konq_historyloader_p.h"
0013 #include <KSharedConfig>
0014 
0015 #include <QDBusConnection>
0016 #include <QDBusContext>
0017 #include <QDBusMessage>
0018 #include <QDataStream>
0019 #include <QDir>
0020 #include <QSaveFile>
0021 #include <QStandardPaths>
0022 
0023 #include <zlib.h> // for crc32
0024 
0025 #include "libkonq_debug.h"
0026 
0027 class KonqHistoryProviderPrivate : public QObject, QDBusContext
0028 {
0029     Q_OBJECT
0030     Q_CLASSINFO("D-Bus Interface", "org.kde.Konqueror.HistoryManager")
0031 public:
0032     KonqHistoryProviderPrivate(KonqHistoryProvider *qq);
0033 
0034     /**
0035      * Resizes the history list to contain less or equal than m_maxCount
0036      * entries. The first (oldest) entries are removed.
0037      */
0038     void adjustSize();
0039 
0040     /**
0041      * Saves the entire history.
0042      */
0043     bool saveHistory();
0044 
0045 Q_SIGNALS: // DBUS methods/signals,  they have to match org.kde.Konqueror.HistoryManager.xml
0046     friend class KonqHistoryProvider;
0047     /**
0048      * Every konqueror instance broadcasts new history entries to the other
0049      * konqueror instances. Those add the entry to their list, but don't
0050      * save the list, because the sender saves the list.
0051      *
0052      * @param e the new history entry
0053      * @param saveId is the dbus service of the sender so that
0054      * only the sender saves the new history.
0055      */
0056     void notifyHistoryEntry(const QByteArray &historyEntry);
0057 
0058     /**
0059      * Called when the configuration of the maximum count changed.
0060      * Called via DBUS by some config-module
0061      */
0062     void notifyMaxCount(int count);
0063 
0064     /**
0065      * Called when the configuration of the maximum age of history-entries
0066      * changed. Called via DBUS by some config-module
0067      */
0068     void notifyMaxAge(int days);
0069 
0070     /**
0071      * Clears the history completely. Called via DBUS by some config-module
0072      */
0073     void notifyClear();
0074 
0075     /**
0076      * Notifes about a url that has to be removed from the history.
0077      * The sender instance has to save the history.
0078      */
0079     void notifyRemove(const QString &url);
0080 
0081     /**
0082      * Notifes about a list of urls that has to be removed from the history.
0083      * The sender instance has to save the history.
0084      */
0085     void notifyRemoveList(const QStringList &urls);
0086 
0087 private Q_SLOTS: // connected to DBUS signals
0088     void slotNotifyHistoryEntry(const QByteArray &historyEntry);
0089     void slotNotifyMaxCount(int count);
0090     void slotNotifyMaxAge(int days);
0091     void slotNotifyClear();
0092     void slotNotifyRemove(const QString &url);
0093     void slotNotifyRemoveList(const QStringList &urls);
0094 
0095 public:
0096     KSharedConfig::Ptr konqConfig()
0097     {
0098         // We want to use konquerorrc even when this class isn't used in konqueror,
0099         // this is why this doesn't say KSharedConfig::openConfig().
0100         return KSharedConfig::openConfig(QStringLiteral("konquerorrc"));
0101     }
0102 
0103     KonqHistoryList m_history;
0104     int m_maxCount;   // maximum of history entries
0105     int m_maxAgeDays; // maximum age of a history entry
0106     KonqHistoryProvider *q;
0107 };
0108 
0109 KonqHistoryProviderPrivate::KonqHistoryProviderPrivate(KonqHistoryProvider *qq)
0110     : QObject(), QDBusContext(), q(qq)
0111 {
0112     // defaults
0113     KConfigGroup cs(konqConfig(), "HistorySettings");
0114     m_maxCount = cs.readEntry("Maximum of History entries", 500);
0115     m_maxCount = qMax(1, m_maxCount);
0116     m_maxAgeDays = cs.readEntry("Maximum age of History entries", 90);
0117 
0118     const QString dbusPath = QStringLiteral("/KonqHistoryManager");
0119     const QString dbusInterface = QStringLiteral("org.kde.Konqueror.HistoryManager");
0120 
0121     QDBusConnection dbus = QDBusConnection::sessionBus();
0122     dbus.registerObject(dbusPath, this, QDBusConnection::ExportAllSignals);
0123     dbus.connect(QString(), dbusPath, dbusInterface, QStringLiteral("notifyClear"), this, SLOT(slotNotifyClear()));
0124     dbus.connect(QString(), dbusPath, dbusInterface, QStringLiteral("notifyHistoryEntry"), this, SLOT(slotNotifyHistoryEntry(QByteArray)));
0125     dbus.connect(QString(), dbusPath, dbusInterface, QStringLiteral("notifyMaxAge"), this, SLOT(slotNotifyMaxAge(int)));
0126     dbus.connect(QString(), dbusPath, dbusInterface, QStringLiteral("notifyMaxCount"), this, SLOT(slotNotifyMaxCount(int)));
0127     dbus.connect(QString(), dbusPath, dbusInterface, QStringLiteral("notifyRemove"), this, SLOT(slotNotifyRemove(QString)));
0128     dbus.connect(QString(), dbusPath, dbusInterface, QStringLiteral("notifyRemoveList"), this, SLOT(slotNotifyRemoveList(QStringList)));
0129 }
0130 
0131 ////
0132 
0133 KonqHistoryProvider::KonqHistoryProvider(QObject *parent)
0134     : HistoryProvider(parent), d(new KonqHistoryProviderPrivate(this))
0135 {
0136 }
0137 
0138 KonqHistoryProvider::~KonqHistoryProvider()
0139 {
0140     delete d;
0141 }
0142 
0143 const KonqHistoryList &KonqHistoryProvider::entries() const
0144 {
0145     return d->m_history;
0146 }
0147 
0148 bool KonqHistoryProvider::loadHistory()
0149 {
0150     KonqHistoryLoader loader;
0151     if (!loader.loadHistory()) {
0152         return false;
0153     }
0154 
0155     d->m_history = loader.entries();
0156 
0157     d->adjustSize();
0158 
0159     QListIterator<KonqHistoryEntry> it(d->m_history);
0160     while (it.hasNext()) {
0161         const KonqHistoryEntry &entry = it.next();
0162 
0163         // Fill the entries into HistoryProvider.
0164         const QString urlString = entry.url.url();
0165         HistoryProvider::insert(urlString);
0166         // DF: also insert the "pretty" version if different
0167         // This helps getting 'visited' links on websites which don't use fully-escaped urls.
0168         const QString prettyUrlString = entry.url.toDisplayString();
0169         if (urlString != prettyUrlString) {
0170             HistoryProvider::insert(prettyUrlString);
0171         }
0172     }
0173 
0174     return true;
0175 }
0176 
0177 void KonqHistoryProviderPrivate::adjustSize()
0178 {
0179     if (m_history.isEmpty()) {
0180         return;
0181     }
0182 
0183     KonqHistoryEntry entry = m_history.first();
0184     const QDateTime expirationDate(QDate::currentDate().addDays(-m_maxAgeDays).startOfDay());
0185 
0186     while (m_history.count() > qint32(m_maxCount) ||
0187             (m_maxAgeDays > 0 && entry.lastVisited.isValid() && entry.lastVisited < expirationDate)) { // i.e. entry is expired
0188         q->removeEntry(m_history.begin());
0189 
0190         if (m_history.isEmpty()) {
0191             break;
0192         }
0193         entry = m_history.first();
0194     }
0195 }
0196 
0197 static QString dbusService()
0198 {
0199     return QDBusConnection::sessionBus().baseService();
0200 }
0201 
0202 void KonqHistoryProvider::emitAddToHistory(const KonqHistoryEntry &entry)
0203 {
0204     QByteArray data;
0205     QDataStream stream(&data, QIODevice::WriteOnly);
0206     entry.save(stream, KonqHistoryEntry::MarshalUrlAsStrings);
0207     stream << dbusService();
0208     // Protection against very long urls (like data:)
0209     if (data.size() > 4096) {
0210         return;
0211     }
0212     emit d->notifyHistoryEntry(data);
0213 }
0214 
0215 void KonqHistoryProvider::emitRemoveFromHistory(const QUrl &url)
0216 {
0217     emit d->notifyRemove(url.url());
0218 }
0219 
0220 void KonqHistoryProvider::emitRemoveListFromHistory(const QList<QUrl> &urls)
0221 {
0222     QStringList result;
0223     for (const QUrl &url: urls) {
0224         result << url.url();
0225     }
0226     emit d->notifyRemoveList(result);
0227 }
0228 
0229 void KonqHistoryProvider::emitClear()
0230 {
0231     emit d->notifyClear();
0232 }
0233 
0234 void KonqHistoryProvider::emitSetMaxCount(int count)
0235 {
0236     emit d->notifyMaxCount(count);
0237 }
0238 
0239 void KonqHistoryProvider::emitSetMaxAge(int days)
0240 {
0241     emit d->notifyMaxAge(days);
0242 }
0243 
0244 /**
0245  * Returns whether the D-Bus call we are handling was a call from us self
0246  */
0247 static bool isSenderOfSignal(const QDBusMessage &msg)
0248 {
0249     return dbusService() == msg.service();
0250 }
0251 
0252 void KonqHistoryProviderPrivate::slotNotifyHistoryEntry(const QByteArray &data)
0253 {
0254     KonqHistoryEntry e;
0255     QDataStream stream(const_cast<QByteArray *>(&data), QIODevice::ReadOnly);
0256 
0257     //This is important - we need to switch to a consistent marshalling format for
0258     //communicating between different konqueror instances. Since during an upgrade
0259     //some "old" copies may still running, we use the old format for the DBUS transfers.
0260     //This doesn't make that much difference performance-wise for single entries anyway.
0261     e.load(stream, KonqHistoryEntry::MarshalUrlAsStrings);
0262     //qCDebug(LIBKONQ_LOG) << "Got new entry from Broadcast:" << e.url;
0263 
0264     KonqHistoryList::iterator existingEntry = q->findEntry(e.url);
0265     QString urlString = e.url.url();
0266     const bool newEntry = existingEntry == m_history.end();
0267 
0268     KonqHistoryEntry entry;
0269 
0270     if (!newEntry) {
0271         entry = *existingEntry;
0272     } else { // create a new history entry
0273         entry.url = e.url;
0274         entry.firstVisited = e.firstVisited;
0275         entry.numberOfTimesVisited = 0; // will get set to 1 below
0276         q->HistoryProvider::insert(urlString);
0277     }
0278 
0279     if (!e.typedUrl.isEmpty()) {
0280         entry.typedUrl = e.typedUrl;
0281     }
0282     if (!e.title.isEmpty()) {
0283         entry.title = e.title;
0284     }
0285     entry.numberOfTimesVisited += e.numberOfTimesVisited;
0286     entry.lastVisited = e.lastVisited;
0287 
0288     if (newEntry) {
0289         m_history.append(entry);
0290     } else {
0291         *existingEntry = entry;
0292     }
0293 
0294     adjustSize();
0295 
0296     q->finishAddingEntry(entry, isSenderOfSignal(message()));
0297 
0298     emit q->entryAdded(entry);
0299 }
0300 
0301 void KonqHistoryProviderPrivate::slotNotifyMaxCount(int count)
0302 {
0303     m_maxCount = count;
0304     // TODO clearPending();
0305     adjustSize();
0306 
0307     KConfigGroup cs(konqConfig(), "HistorySettings");
0308     cs.writeEntry("Maximum of History entries", m_maxCount);
0309 
0310     if (isSenderOfSignal(message())) {
0311         saveHistory();
0312         cs.sync();
0313     }
0314 }
0315 
0316 void KonqHistoryProviderPrivate::slotNotifyMaxAge(int days)
0317 {
0318     m_maxAgeDays = days;
0319     // TODO clearPending();
0320     adjustSize();
0321 
0322     KConfigGroup cs(konqConfig(), "HistorySettings");
0323     cs.writeEntry("Maximum age of History entries", m_maxAgeDays);
0324 
0325     if (isSenderOfSignal(message())) {
0326         saveHistory();
0327         cs.sync();
0328     }
0329 }
0330 
0331 void KonqHistoryProviderPrivate::slotNotifyClear()
0332 {
0333     m_history.clear();
0334 
0335     if (isSenderOfSignal(message())) {
0336         saveHistory();
0337     }
0338 
0339     q->HistoryProvider::clear(); // also emits the cleared() signal
0340 }
0341 
0342 void KonqHistoryProviderPrivate::slotNotifyRemove(const QString &urlStr)
0343 {
0344     QUrl url(urlStr);
0345 
0346     KonqHistoryList::iterator existingEntry = q->findEntry(url);
0347     if (existingEntry != m_history.end()) {
0348         q->removeEntry(existingEntry);
0349         if (isSenderOfSignal(message())) {
0350             saveHistory();
0351         }
0352     }
0353 }
0354 
0355 void KonqHistoryProviderPrivate::slotNotifyRemoveList(const QStringList &urls)
0356 {
0357     bool doSave = false;
0358     QStringList::const_iterator it = urls.begin();
0359     for (; it != urls.end(); ++it) {
0360         QUrl url(*it);
0361         KonqHistoryList::iterator existingEntry = m_history.findEntry(url);
0362         if (existingEntry != m_history.end()) {
0363             q->removeEntry(existingEntry);
0364             doSave = true;
0365         }
0366     }
0367 
0368     if (doSave && isSenderOfSignal(message())) {
0369         saveHistory();
0370     }
0371 }
0372 
0373 void KonqHistoryProvider::removeEntry(KonqHistoryList::iterator existingEntry)
0374 {
0375     const KonqHistoryEntry entry = *existingEntry; // make copy, due to erase call below
0376     const QString urlString = entry.url.url();
0377 
0378     HistoryProvider::remove(urlString);
0379 
0380     d->m_history.erase(existingEntry);
0381     emit entryRemoved(entry);
0382 }
0383 
0384 int KonqHistoryProvider::maxCount() const
0385 {
0386     return d->m_maxCount;
0387 }
0388 
0389 int KonqHistoryProvider::maxAge() const
0390 {
0391     return d->m_maxAgeDays;
0392 }
0393 
0394 bool KonqHistoryProviderPrivate::saveHistory()
0395 {
0396     const QString dir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/konqueror");
0397     QDir().mkpath(dir);
0398     const QString filename = dir + QLatin1String("/konq_history");
0399     QSaveFile file(filename);
0400     if (!file.open(QIODevice::WriteOnly)) {
0401         qCWarning(LIBKONQ_LOG) << "Can't open" << file.fileName() << "for saving history";
0402         return false;
0403     }
0404 
0405     QDataStream fileStream(&file);
0406     fileStream << KonqHistoryLoader::historyVersion();
0407 
0408     QByteArray data;
0409     QDataStream stream(&data, QIODevice::WriteOnly);
0410 
0411     QListIterator<KonqHistoryEntry> it(m_history);
0412     while (it.hasNext()) {
0413         //We use QUrl for marshalling URLs in entries in the V4
0414         //file format
0415         it.next().save(stream, KonqHistoryEntry::NoFlags);
0416     }
0417 
0418     quint32 crc = crc32(0, reinterpret_cast<unsigned char *>(data.data()), data.size());
0419     fileStream << crc << data;
0420 
0421     return file.commit();
0422 }
0423 
0424 KonqHistoryList::iterator KonqHistoryProvider::findEntry(const QUrl &url)
0425 {
0426     // small optimization (dict lookup) for items _not_ in our history
0427     if (!HistoryProvider::contains(url.url())) {
0428         return d->m_history.end();
0429     }
0430     return d->m_history.findEntry(url);
0431 }
0432 
0433 KonqHistoryList::const_iterator KonqHistoryProvider::constFindEntry(const QUrl &url) const
0434 {
0435     // small optimization (dict lookup) for items _not_ in our history
0436     if (!HistoryProvider::contains(url.url())) {
0437         return d->m_history.constEnd();
0438     }
0439     return d->m_history.constFindEntry(url);
0440 }
0441 
0442 void KonqHistoryProvider::finishAddingEntry(const KonqHistoryEntry &entry, bool isSender)
0443 {
0444     Q_UNUSED(entry); // this arg is used by konq's reimplementation
0445     if (isSender) {
0446         // we are the sender of the broadcast, so we save
0447         d->saveHistory();
0448     }
0449 }
0450 
0451 #include "konq_historyprovider.moc"
0452