File indexing completed on 2024-04-28 05:10:45

0001 /*
0002  * This file is part of akregatorstorageexporter
0003  *
0004  * SPDX-FileCopyrightText: 2009 Frank Osterfeld <osterfeld@kde.org>
0005  *
0006  * SPDX-License-Identifier: LGPL-2.0-or-later
0007  *
0008  */
0009 #include "storage/feedstorage.h"
0010 #include "storage/storage.h"
0011 #include <KLocalizedString>
0012 #include <Syndication/Atom/Atom>
0013 #include <Syndication/Constants>
0014 
0015 #include <QCoreApplication>
0016 #include <QDateTime>
0017 #include <QFile>
0018 #include <QIODevice>
0019 #include <QVariant>
0020 #include <QXmlStreamWriter>
0021 
0022 #include <QDebug>
0023 #include <QUrl>
0024 
0025 #include <iostream>
0026 
0027 #include <KPluginFactory>
0028 #include <cassert>
0029 
0030 using namespace Akregator;
0031 using namespace Akregator::Backend;
0032 
0033 namespace
0034 {
0035 static QString akregatorNamespace()
0036 {
0037     return QStringLiteral("http://akregator.kde.org/StorageExporter#");
0038 }
0039 
0040 enum TextMode {
0041     PlainText,
0042     Html,
0043 };
0044 
0045 enum Status {
0046     Deleted = 0x01,
0047     Trash = 0x02,
0048     New = 0x04,
0049     Read = 0x08,
0050     Keep = 0x10,
0051 };
0052 
0053 class Element
0054 {
0055 public:
0056     Element(const QString &ns_, const QString &name_)
0057         : ns(ns_)
0058         , name(name_)
0059         , qualifiedName(ns + QLatin1Char(':') + name)
0060     {
0061     }
0062 
0063     const QString ns;
0064     const QString name;
0065     const QString qualifiedName;
0066 
0067     void writeStartElement(QXmlStreamWriter &writer) const
0068     {
0069         if (!ns.isNull()) {
0070             writer.writeStartElement(ns, name);
0071         } else {
0072             writer.writeStartElement(name);
0073         }
0074     }
0075 
0076     void write(const QVariant &value, QXmlStreamWriter &writer, TextMode mode = PlainText) const
0077     {
0078         const QVariant qv(value);
0079         Q_ASSERT(qv.canConvert<QString>());
0080         const QString str = qv.toString();
0081         if (str.isEmpty()) {
0082             return;
0083         }
0084 
0085         if (ns.isEmpty()) {
0086             writer.writeStartElement(name);
0087         } else {
0088             writer.writeStartElement(ns, name);
0089         }
0090         if (mode == Html) {
0091             writer.writeAttribute(QStringLiteral("type"), QStringLiteral("html"));
0092         }
0093         writer.writeCharacters(str);
0094         writer.writeEndElement();
0095     }
0096 };
0097 
0098 struct Elements {
0099     Elements()
0100         : atomNS(Syndication::Atom::atom1Namespace())
0101         , akregatorNS(akregatorNamespace())
0102         , commentNS(Syndication::commentApiNamespace())
0103         , title(atomNS, QStringLiteral("title"))
0104         , summary(atomNS, QStringLiteral("summary"))
0105         , content(atomNS, QStringLiteral("content"))
0106         , link(atomNS, QStringLiteral("link"))
0107         , language(atomNS, QStringLiteral("language"))
0108         , feed(atomNS, QStringLiteral("feed"))
0109         , guid(atomNS, QStringLiteral("id"))
0110         , published(atomNS, QStringLiteral("published"))
0111         , updated(atomNS, QStringLiteral("updated"))
0112         , commentsCount(Syndication::slashNamespace(), QStringLiteral("comments"))
0113         , commentsFeed(commentNS, QStringLiteral("commentRss"))
0114         , commentPostUri(commentNS, QStringLiteral("comment"))
0115         , commentsLink(akregatorNS, QStringLiteral("commentsLink"))
0116         , hash(akregatorNS, QStringLiteral("hash"))
0117         , guidIsHash(akregatorNS, QStringLiteral("idIsHash"))
0118         , name(atomNS, QStringLiteral("name"))
0119         , uri(atomNS, QStringLiteral("uri"))
0120         , email(atomNS, QStringLiteral("email"))
0121         , author(atomNS, QStringLiteral("author"))
0122         , category(atomNS, QStringLiteral("category"))
0123         , entry(atomNS, QStringLiteral("entry"))
0124         , itemProperties(akregatorNS, QStringLiteral("itemProperties"))
0125         , readStatus(akregatorNS, QStringLiteral("readStatus"))
0126         , deleted(akregatorNS, QStringLiteral("deleted"))
0127         , important(akregatorNS, QStringLiteral("important"))
0128     {
0129     }
0130 
0131     const QString atomNS;
0132     const QString akregatorNS;
0133     const QString commentNS;
0134     const Element title;
0135     const Element summary;
0136     const Element content;
0137     const Element link;
0138     const Element language;
0139     const Element feed;
0140     const Element guid;
0141     const Element published;
0142     const Element updated;
0143     const Element commentsCount;
0144     const Element commentsFeed;
0145     const Element commentPostUri;
0146     const Element commentsLink;
0147     const Element hash;
0148     const Element guidIsHash;
0149     const Element name;
0150     const Element uri;
0151     const Element email;
0152     const Element author;
0153     const Element category;
0154     const Element entry;
0155     const Element itemProperties;
0156     const Element readStatus;
0157     const Element deleted;
0158     const Element important;
0159     static const Elements instance;
0160 };
0161 
0162 const Elements Elements::instance;
0163 
0164 void writeAttributeIfNotEmpty(const QString &element, const QVariant &value, QXmlStreamWriter &writer)
0165 {
0166     const QString text = value.toString();
0167     if (text.isEmpty()) {
0168         return;
0169     }
0170     writer.writeAttribute(element, text);
0171 }
0172 
0173 void writeEnclosure(const QString &url, const QString &type, int length, QXmlStreamWriter &writer)
0174 {
0175     Elements::instance.link.writeStartElement(writer);
0176     writer.writeAttribute(QStringLiteral("rel"), QStringLiteral("enclosure"));
0177     writeAttributeIfNotEmpty(QStringLiteral("href"), url, writer);
0178     writeAttributeIfNotEmpty(QStringLiteral("type"), type, writer);
0179     if (length > 0) {
0180         writer.writeAttribute(QStringLiteral("length"), QString::number(length));
0181     }
0182     writer.writeEndElement();
0183 }
0184 
0185 void writeLink(const QString &url, QXmlStreamWriter &writer)
0186 {
0187     if (url.isEmpty()) {
0188         return;
0189     }
0190     Elements::instance.link.writeStartElement(writer);
0191     writer.writeAttribute(QStringLiteral("rel"), QStringLiteral("alternate"));
0192     writeAttributeIfNotEmpty(QStringLiteral("href"), url, writer);
0193     writer.writeEndElement();
0194 }
0195 
0196 void writeAuthor(const QString &name, const QString &uri, const QString &email, QXmlStreamWriter &writer)
0197 {
0198     if (name.isEmpty() && uri.isEmpty() && email.isEmpty()) {
0199         return;
0200     }
0201 
0202     const QString atomNS = Syndication::Atom::atom1Namespace();
0203     Elements::instance.author.writeStartElement(writer);
0204     Elements::instance.name.write(name, writer);
0205     Elements::instance.uri.write(uri, writer);
0206     Elements::instance.email.write(email, writer);
0207     writer.writeEndElement(); // </author>
0208 }
0209 
0210 static void writeItem(FeedStorage *storage, const QString &guid, QXmlStreamWriter &writer)
0211 {
0212     Elements::instance.entry.writeStartElement(writer);
0213     Elements::instance.guid.write(guid, writer);
0214 
0215     const QDateTime published = storage->pubDate(guid);
0216     if (published.isValid()) {
0217         const QString pdStr = published.toString(Qt::ISODate);
0218         Elements::instance.published.write(pdStr, writer);
0219     }
0220 
0221     const int status = storage->status(guid);
0222 
0223     Elements::instance.itemProperties.writeStartElement(writer);
0224 
0225     if (status & Deleted) {
0226         Elements::instance.deleted.write(QStringLiteral("true"), writer);
0227         writer.writeEndElement(); // </itemProperties>
0228         writer.writeEndElement(); // </item>
0229         return;
0230     }
0231 
0232     Elements::instance.hash.write(QString::number(storage->hash(guid)), writer);
0233     if (storage->guidIsHash(guid)) {
0234         Elements::instance.guidIsHash.write(QStringLiteral("true"), writer);
0235     }
0236     if (status & New) {
0237         Elements::instance.readStatus.write(QStringLiteral("new"), writer);
0238     } else if ((status & Read) == 0) {
0239         Elements::instance.readStatus.write(QStringLiteral("unread"), writer);
0240     }
0241     if (status & Keep) {
0242         Elements::instance.important.write(QStringLiteral("true"), writer);
0243     }
0244     writer.writeEndElement(); // </itemProperties>
0245 
0246     Elements::instance.title.write(storage->title(guid), writer, Html);
0247     writeLink(storage->guidIsPermaLink(guid) ? guid : storage->link(guid), writer);
0248 
0249     Elements::instance.summary.write(storage->description(guid), writer, Html);
0250     Elements::instance.content.write(storage->content(guid), writer, Html);
0251     writeAuthor(storage->authorName(guid), storage->authorUri(guid), storage->authorEMail(guid), writer);
0252 
0253     bool hasEnc = false;
0254     QString encUrl;
0255     QString encType;
0256     int encLength = 0;
0257     storage->enclosure(guid, hasEnc, encUrl, encType, encLength);
0258     if (hasEnc) {
0259         writeEnclosure(encUrl, encType, encLength, writer);
0260     }
0261     writer.writeEndElement(); // </item>
0262 }
0263 
0264 static void serialize(FeedStorage *storage, const QString &url, QIODevice *device)
0265 {
0266     Q_ASSERT(storage);
0267     Q_ASSERT(device);
0268     QXmlStreamWriter writer(device);
0269     writer.setAutoFormatting(true);
0270     writer.setAutoFormattingIndent(2);
0271     writer.writeStartDocument();
0272 
0273     Elements::instance.feed.writeStartElement(writer);
0274 
0275     writer.writeDefaultNamespace(Syndication::Atom::atom1Namespace());
0276     writer.writeNamespace(Syndication::commentApiNamespace(), QStringLiteral("comment"));
0277     writer.writeNamespace(akregatorNamespace(), QStringLiteral("akregator"));
0278     writer.writeNamespace(Syndication::itunesNamespace(), QStringLiteral("itunes"));
0279 
0280     Elements::instance.title.write(i18n("Akregator Export for %1", url), writer, Html);
0281 
0282     const auto articles = storage->articles();
0283     for (const QString &i : articles) {
0284         writeItem(storage, i, writer);
0285     }
0286     writer.writeEndElement(); // </feed>
0287     writer.writeEndDocument();
0288 }
0289 
0290 static void serialize(Storage *storage, const QString &url, QIODevice *device)
0291 {
0292     serialize(storage->archiveFor(url), url, device);
0293 }
0294 
0295 static void printUsage()
0296 {
0297     std::cout << "akregatorstorageexporter [--base64] url" << std::endl;
0298 }
0299 }
0300 
0301 int main(int argc, char *argv[])
0302 {
0303     QCoreApplication app(argc, argv);
0304 
0305     if (argc < 2) {
0306         printUsage();
0307         return 1;
0308     }
0309 
0310     const bool base64 = qstrcmp(argv[1], "--base64") == 0;
0311 
0312     if (base64 && argc < 3) {
0313         printUsage();
0314         return 1;
0315     }
0316 
0317     const int pos = base64 ? 2 : 1;
0318     const QString url = QUrl::fromEncoded(base64 ? QByteArray::fromBase64(argv[pos]) : QByteArray(argv[pos])).toString();
0319 
0320     Storage storage;
0321 
0322     QFile out;
0323     if (!out.open(stdout, QIODevice::WriteOnly)) {
0324         qCritical() << "Could not open stdout for writing: " << qPrintable(out.errorString());
0325         return 1;
0326     }
0327 
0328     serialize(&storage, url, &out);
0329 
0330     return 0;
0331 }