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 }