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