kdelibs/kio/bookmarks/kbookmark.cc
Ivailo Monev 47d0b5a297 kdecore: remove unused KUrl metadata
was used to pass the referrer and while it is still possible to
reimplement it realisticly speaking it is impossible to preserve the
referrer (and other HTTP(S) header metadata) because it will be lost in
transition in some cicumstances such as when drag-n-drop is done to
application unaware of application/x-kio-metadata

Signed-off-by: Ivailo Monev <xakepa10@gmail.com>
2023-06-25 21:13:59 +03:00

684 lines
19 KiB
C++

// -*- c-basic-offset:4; indent-tabs-mode:nil -*-
// vim: set ts=4 sts=4 sw=4 et:
/* This file is part of the KDE libraries
Copyright (C) 2000 David Faure <faure@kde.org>
Copyright (C) 2003 Alexander Kellett <lypanov@kde.org>
Copyright (C) 2008 Norbert Frese <nf2@scheinwelt.at>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License version 2 as published by the Free Software Foundation.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "kbookmark.h"
#include <QStack>
#include <QDateTime>
#include <kdebug.h>
#include <kmimetype.h>
#include <kstringhandler.h>
#include <kglobal.h>
#include <klocale.h>
#include <kbookmarkmanager.h>
#include <assert.h>
#define METADATA_KDE_OWNER "http://www.kde.org"
#define METADATA_FREEDESKTOP_OWNER "http://freedesktop.org"
#define METADATA_MIME_OWNER "http://www.freedesktop.org/standards/shared-mime-info"
////// utility functions
static QDomNode cd(QDomNode node, const QString &name, bool create)
{
QDomNode subnode = node.namedItem(name);
if (create && subnode.isNull()) {
subnode = node.ownerDocument().createElement(name);
node.appendChild(subnode);
}
return subnode;
}
static QDomNode cd_or_create(QDomNode node, const QString &name)
{
return cd(node, name, true);
}
static QDomText get_or_create_text(QDomNode node)
{
QDomNode subnode = node.firstChild();
if (subnode.isNull()) {
subnode = node.ownerDocument().createTextNode("");
node.appendChild(subnode);
}
return subnode.toText();
}
static QDomNode findMetadata(const QString &forOwner, QDomNode &parent, bool create)
{
bool forOwnerIsKDE = forOwner == METADATA_KDE_OWNER;
QDomElement metadataElement;
for (QDomNode _node = parent.firstChild(); !_node.isNull(); _node = _node.nextSibling()) {
QDomElement elem = _node.toElement();
if (!elem.isNull() && elem.tagName() == "metadata") {
const QString owner = elem.attribute("owner");
if (owner == forOwner) {
return elem;
}
if (owner.isEmpty() && forOwnerIsKDE) {
metadataElement = elem;
}
}
}
if (create && metadataElement.isNull()) {
metadataElement = parent.ownerDocument().createElement("metadata");
parent.appendChild(metadataElement);
metadataElement.setAttribute("owner", forOwner);
} else if (!metadataElement.isNull() && forOwnerIsKDE) {
// i'm not sure if this is good, we shouln't take over foreign metatdata
metadataElement.setAttribute("owner", METADATA_KDE_OWNER);
}
return metadataElement;
}
//////
KBookmarkGroup::KBookmarkGroup()
: KBookmark(QDomElement())
{
}
KBookmarkGroup::KBookmarkGroup(const QDomElement &elem)
: KBookmark(elem)
{
}
bool KBookmarkGroup::isOpen() const
{
return element.attribute("folded") == "no"; // default is: folded
}
KBookmark KBookmarkGroup::first() const
{
return KBookmark(nextKnownTag(element.firstChildElement(), true));
}
KBookmark KBookmarkGroup::previous(const KBookmark &current) const
{
return KBookmark(nextKnownTag(current.element.previousSiblingElement(), false));
}
KBookmark KBookmarkGroup::next(const KBookmark &current) const
{
return KBookmark(nextKnownTag(current.element.nextSiblingElement(), true));
}
int KBookmarkGroup::indexOf(const KBookmark &child) const
{
uint counter = 0;
for (KBookmark bk = first(); !bk.isNull(); bk = next(bk), ++counter) {
if (bk.element == child.element) {
return counter;
}
}
return -1;
}
QDomElement KBookmarkGroup::nextKnownTag(const QDomElement &start, bool goNext) const
{
static const QString bookmark = QString::fromLatin1("bookmark");
static const QString folder = QString::fromLatin1("folder");
static const QString separator = QString::fromLatin1("separator");
for(QDomElement elem = start; !elem.isNull();) {
const QString tag = elem.tagName();
if (tag == folder || tag == bookmark || tag == separator) {
return elem;
}
if (goNext) {
elem = elem.nextSiblingElement();
} else {
elem = elem.previousSiblingElement();
}
}
return QDomElement();
}
KBookmarkGroup KBookmarkGroup::createNewFolder(const QString &text)
{
if (isNull()) {
return KBookmarkGroup();
}
QDomDocument doc = element.ownerDocument();
QDomElement groupElem = doc.createElement("folder");
element.appendChild(groupElem);
QDomElement textElem = doc.createElement("title");
groupElem.appendChild(textElem);
textElem.appendChild(doc.createTextNode(text));
return KBookmarkGroup(groupElem);
}
KBookmark KBookmarkGroup::createNewSeparator()
{
if (isNull()) {
return KBookmark();
}
QDomDocument doc = element.ownerDocument();
Q_ASSERT(!doc.isNull());
QDomElement sepElem = doc.createElement("separator");
element.appendChild( sepElem );
return KBookmark(sepElem);
}
bool KBookmarkGroup::moveBookmark(const KBookmark &item, const KBookmark &after)
{
QDomNode n;
if (!after.isNull()) {
n = element.insertAfter(item.element, after.element);
} else {
// first child
if (element.firstChild().isNull()) {
// Empty element -> set as real first child
n = element.insertBefore( item.element, QDomElement() );
}
// we have to skip everything up to the first valid child
QDomElement firstChild = nextKnownTag(element.firstChild().toElement(), true);
if (!firstChild.isNull()) {
n = element.insertBefore(item.element, firstChild);
} else {
// No real first child -> append after the <title> etc.
n = element.appendChild(item.element);
}
}
return (!n.isNull());
}
KBookmark KBookmarkGroup::addBookmark(const KBookmark &bm)
{
element.appendChild(bm.internalElement());
return bm;
}
KBookmark KBookmarkGroup::addBookmark(const QString &text, const KUrl &url, const QString &icon)
{
if (isNull()) {
return KBookmark();
}
QDomDocument doc = element.ownerDocument();
QDomElement elem = doc.createElement("bookmark");
elem.setAttribute("href", url.url()); // gives us utf8
QDomElement textElem = doc.createElement("title");
elem.appendChild(textElem);
textElem.appendChild(doc.createTextNode(text));
KBookmark newBookmark = addBookmark(KBookmark(elem));
// as icons are moved to metadata, we have to use the KBookmark API for this
newBookmark.setIcon(icon.isEmpty() ? KMimeType::iconNameForUrl(url) : icon);
return newBookmark;
}
void KBookmarkGroup::deleteBookmark(const KBookmark &bk)
{
element.removeChild(bk.element);
}
QList<KUrl> KBookmarkGroup::groupUrlList() const
{
QList<KUrl> urlList;
for (KBookmark bm = first(); !bm.isNull(); bm = next(bm)) {
if (bm.isSeparator() || bm.isGroup()) {
continue;
}
urlList << bm.url();
}
return urlList;
}
//////
KBookmark::KBookmark()
{
}
KBookmark::KBookmark(const QDomElement &elem)
: element(elem)
{
}
bool KBookmark::isGroup() const
{
QString tag = element.tagName();
// don't forget the toplevel group
return (tag == "folder" || tag == "xbel" );
}
bool KBookmark::isSeparator() const
{
return (element.tagName() == "separator");
}
bool KBookmark::isNull() const
{
return element.isNull();
}
bool KBookmark::hasParent() const
{
QDomElement parent = element.parentNode().toElement();
return !parent.isNull();
}
QString KBookmark::text() const
{
return KStringHandler::csqueeze(fullText());
}
QString KBookmark::fullText() const
{
if (isSeparator()) {
return i18n("--- separator ---");
}
QString text = element.namedItem("title").toElement().text();
text.replace('\n', ' '); // #140673
return text;
}
void KBookmark::setFullText(const QString &fullText)
{
QDomNode titleNode = element.namedItem("title");
if (titleNode.isNull()) {
titleNode = element.ownerDocument().createElement("title");
element.appendChild(titleNode);
}
if (titleNode.firstChild().isNull()) {
QDomText domtext = titleNode.ownerDocument().createTextNode("");
titleNode.appendChild(domtext);
}
QDomText domtext = titleNode.firstChild().toText();
domtext.setData(fullText);
}
KUrl KBookmark::url() const
{
return KUrl(element.attribute("href").toLatin1()); // Decodes it from utf8
}
void KBookmark::setUrl(const KUrl &url)
{
element.setAttribute("href", url.url());
}
QString KBookmark::icon() const
{
QDomNode metaDataNode = metaData(METADATA_FREEDESKTOP_OWNER, false);
QDomElement iconElement = cd(metaDataNode, "bookmark:icon", false).toElement();
QString icon = iconElement.attribute("name");
if (icon == "bookmark_folder") {
return "folder-bookmarks";
}
if (icon.isEmpty()) {
// Default icon depends on URL for bookmarks, and is default directory
// icon for groups.
if (isGroup()) {
icon = "folder-bookmarks";
} else {
if (isSeparator()) {
icon = "edit-clear"; // whatever
} else {
// get icon from mimeType
QString _mimeType = mimeType();
if (!_mimeType.isEmpty()) {
KMimeType::Ptr mime = KMimeType::mimeType(_mimeType, KMimeType::ResolveAliases);
if (mime) {
return mime->iconName();
}
}
// get icon from URL
icon = KMimeType::iconNameForUrl(url());
}
}
}
return icon;
}
void KBookmark::setIcon(const QString &icon)
{
QDomNode metaDataNode = metaData(METADATA_FREEDESKTOP_OWNER, true);
QDomElement iconElement = cd_or_create(metaDataNode, "bookmark:icon").toElement();
iconElement.setAttribute("name", icon);
// migration code
if (!element.attribute("icon").isEmpty()) {
element.removeAttribute("icon");
}
}
QString KBookmark::description() const
{
if (isSeparator()) {
return QString();
}
QString description = element.namedItem("desc").toElement().text();
description.replace('\n', ' '); // #140673
return description;
}
void KBookmark::setDescription(const QString &description)
{
QDomNode descNode = element.namedItem("desc");
if (descNode.isNull()) {
descNode = element.ownerDocument().createElement("desc");
element.appendChild(descNode);
}
if (descNode.firstChild().isNull()) {
QDomText domtext = descNode.ownerDocument().createTextNode(QString());
descNode.appendChild(domtext);
}
QDomText domtext = descNode.firstChild().toText();
domtext.setData(description);
}
QString KBookmark::mimeType() const
{
QDomNode metaDataNode = metaData(METADATA_MIME_OWNER, false);
QDomElement mimeTypeElement = cd(metaDataNode, "mime:mime-type", false).toElement();
return mimeTypeElement.attribute("type");
}
void KBookmark::setMimeType(const QString &mimeType)
{
QDomNode metaDataNode = metaData(METADATA_MIME_OWNER, true);
QDomElement iconElement = cd_or_create(metaDataNode, "mime:mime-type").toElement();
iconElement.setAttribute("type", mimeType);
}
KBookmarkGroup KBookmark::parentGroup() const
{
return KBookmarkGroup(element.parentNode().toElement());
}
KBookmarkGroup KBookmark::toGroup() const
{
Q_ASSERT(isGroup());
return KBookmarkGroup(element);
}
QString KBookmark::address() const
{
if (element.tagName() == "xbel") {
return ""; // not QString() !
}
// Use keditbookmarks's DEBUG_ADDRESSES flag to debug this code :)
if (element.parentNode().isNull()) {
Q_ASSERT(false);
return "ERROR"; // Avoid an infinite loop
}
KBookmarkGroup group = parentGroup();
QString parentAddress = group.address();
int pos = group.indexOf(*this);
Q_ASSERT(pos != -1);
return parentAddress + '/' + QString::number(pos);
}
int KBookmark::positionInParent() const
{
return parentGroup().indexOf(*this);
}
QDomElement KBookmark::internalElement() const
{
return element;
}
KBookmark KBookmark::standaloneBookmark( const QString &text, const KUrl &url, const QString &icon)
{
QDomDocument doc("xbel");
QDomElement elem = doc.createElement("xbel");
doc.appendChild(elem);
KBookmarkGroup grp(elem);
grp.addBookmark(text, url, icon);
return grp.first();
}
QString KBookmark::commonParent(const QString &first, const QString &second)
{
QString A = first;
QString B = second;
QString error("ERROR");
if (A == error || B == error) {
return error;
}
A += '/';
B += '/';
uint lastCommonSlash = 0;
uint lastPos = A.length() < B.length() ? A.length() : B.length();
for(uint i = 0; i < lastPos; ++i) {
if (A[i] != B[i]) {
return A.left(lastCommonSlash);
}
if (A[i] == '/') {
lastCommonSlash = i;
}
}
return A.left(lastCommonSlash);
}
void KBookmark::updateAccessMetadata()
{
kDebug(7043) << "updateAccessMetadata" << address() << url().prettyUrl();
const uint timet = QDateTime::currentDateTime().toTime_t();
setMetaDataItem("time_added", QString::number(timet), DontOverwriteMetaData);
setMetaDataItem("time_visited", QString::number(timet));
QString countStr = metaDataItem("visit_count"); // TODO: use spec'ed name
bool ok = false;
int currentCount = countStr.toInt(&ok);
if (!ok) {
currentCount = 0;
}
currentCount++;
setMetaDataItem("visit_count", QString::number(currentCount));
// TODO: time_modified
}
QString KBookmark::parentAddress(const QString &address)
{
return address.left(address.lastIndexOf(QLatin1Char('/')));
}
uint KBookmark::positionInParent(const QString &address)
{
return address.mid(address.lastIndexOf(QLatin1Char('/')) + 1).toInt();
}
QString KBookmark::previousAddress( const QString & address )
{
uint pp = positionInParent(address);
if (pp > 0) {
return parentAddress(address) + QLatin1Char('/') + QString::number(pp - 1);
}
return QString();
}
QString KBookmark::nextAddress(const QString &address)
{
return parentAddress(address) + QLatin1Char('/') + QString::number(positionInParent(address) + 1);
}
QDomNode KBookmark::metaData(const QString &owner, bool create) const
{
QDomNode infoNode = cd(internalElement(), "info", create);
if (infoNode.isNull()) {
return QDomNode();
}
return findMetadata(owner, infoNode , create);
}
QString KBookmark::metaDataItem(const QString &key) const
{
QDomNode metaDataNode = metaData(METADATA_KDE_OWNER, false);
for ( QDomElement e = metaDataNode.firstChildElement(); !e.isNull(); e = e.nextSiblingElement() )
{
if (e.tagName() == key) {
return e.text();
}
}
return QString();
}
void KBookmark::setMetaDataItem(const QString &key, const QString &value, MetaDataOverwriteMode mode)
{
QDomNode metaDataNode = metaData(METADATA_KDE_OWNER, true);
QDomNode item = cd_or_create( metaDataNode, key );
QDomText text = get_or_create_text( item );
if (mode == DontOverwriteMetaData && !text.data().isEmpty()) {
return;
}
text.setData(value);
}
bool KBookmark::operator==(const KBookmark &rhs) const
{
return element == rhs.element;
}
////
KBookmarkGroupTraverser::~KBookmarkGroupTraverser()
{
}
void KBookmarkGroupTraverser::traverse(const KBookmarkGroup &root)
{
QStack<KBookmarkGroup> stack;
stack.push(root);
KBookmark bk = root.first();
for(;;) {
if (bk.isNull()) {
if (stack.count() == 1) {
// only root is on the stack
return;
}
if (stack.count() > 0) {
visitLeave(stack.top());
bk = stack.pop();
}
bk = stack.top().next(bk);
} else if (bk.isGroup()) {
KBookmarkGroup gp = bk.toGroup();
visitEnter(gp);
bk = gp.first();
stack.push(gp);
} else {
visit(bk);
bk = stack.top().next(bk);
}
}
}
void KBookmarkGroupTraverser::visit(const KBookmark &)
{
}
void KBookmarkGroupTraverser::visitEnter(const KBookmarkGroup &)
{
}
void KBookmarkGroupTraverser::visitLeave(const KBookmarkGroup &)
{
}
void KBookmark::populateMimeData(QMimeData *mimeData) const
{
KBookmark::List bookmarkList;
bookmarkList.append(*this);
bookmarkList.populateMimeData(mimeData);
}
KBookmark::List::List()
: QList<KBookmark>()
{
}
void KBookmark::List::populateMimeData(QMimeData *mimeData) const
{
KUrl::List urls;
QDomDocument doc("xbel");
QDomElement elem = doc.createElement("xbel");
doc.appendChild(elem);
for ( const_iterator it = begin(), end = this->end() ; it != end ; ++it ) {
urls.append((*it).url());
elem.appendChild((*it).internalElement().cloneNode(true /* deep */));
}
// This sets text/uri-list and text/plain into the mimedata
urls.populateMimeData(mimeData);
mimeData->setData("application/x-xbel", doc.toByteArray());
}
bool KBookmark::List::canDecode(const QMimeData *mimeData)
{
return mimeData->hasFormat("application/x-xbel") || KUrl::List::canDecode(mimeData);
}
QStringList KBookmark::List::mimeDataTypes()
{
return QStringList() << QString::fromLatin1("application/x-xbel") << KUrl::List::mimeDataTypes();
}
KBookmark::List KBookmark::List::fromMimeData(const QMimeData *mimeData, QDomDocument &doc)
{
KBookmark::List bookmarks;
QByteArray payload = mimeData->data("application/x-xbel");
if (!payload.isEmpty()) {
doc.setContent(payload);
QDomElement elem = doc.documentElement();
const QDomNodeList children = elem.childNodes();
for (int childno = 0; childno < children.count(); childno++) {
bookmarks.append(KBookmark(children.item(childno).toElement()));
}
return bookmarks;
}
const KUrl::List urls = KUrl::List::fromMimeData(mimeData);
if (!urls.isEmpty()) {
KUrl::List::ConstIterator uit = urls.begin();
KUrl::List::ConstIterator uEnd = urls.end();
for ( ; uit != uEnd ; ++uit ) {
// kDebug(7043) << "url=" << (*uit);
bookmarks.append(KBookmark::standaloneBookmark((*uit).prettyUrl(), (*uit)));
}
}
return bookmarks;
}