#include "atomchannel.h" #include #include #include #include "constants.h" #include "macros.h" #include "rsshit_db.h" AtomChannel::AtomChannel(QXmlStreamReader *xmlReader) { Q_ASSERT(xmlReader != nullptr); const QString titleTag{"title"}; const QString linkTag{"link"}; const QString descriptionTag{"description"}; const QString lastBuildDateTag{"lastBuildDate"}; const QString languageTag{"language"}; while (!xmlReader->atEnd() && !xmlReader->hasError()) { const auto itemNext = xmlReader->readNext(); switch (itemNext) { case QXmlStreamReader::TokenType::StartElement: { const auto name = xmlReader->name(); // qDebug() << __func__ << ": StartElement" << name; // qDebug() << __func__ << "namespaceUri:" << xmlReader->namespaceUri(); // qDebug() << __func__ << "prefix:" << xmlReader->prefix(); // qDebug() << __func__ << "qualifiedName:" << xmlReader->qualifiedName(); // const auto elementText = xmlReader->readElementText(); if (name == titleTag) title = xmlReader->readElementText(); else if (name == linkTag) link = xmlReader->readElementText(); else if (name == descriptionTag) description = xmlReader->readElementText(); else if (name == lastBuildDateTag) lastBuildDate = QDateTime::fromString(xmlReader->readElementText(), Qt::DateFormat::RFC2822Date); else if (name == languageTag) language = QLocale{xmlReader->readElementText()}.language(); else if (name == AtomChannelImage::tag) { // qDebug() << "got image tag"; image = AtomChannelImage{xmlReader}; // qDebug() << image; } else if (name == AtomItem::tag) { items << AtomItem{xmlReader}; // qDebug() << items.constLast(); // qDebug() << __func__ << "got feed item"; } else { // qDebug() << "exit" << __func__; // qDebug() << __func__ << "unknown tag:" << name; continue; } break; } case QXmlStreamReader::TokenType::EndElement: { // qDebug() << "EndElement: " << xmlReader->name(); if (xmlReader->name() == AtomChannel::xmlTag) return; } case QXmlStreamReader::TokenType::Characters: { const auto characters = xmlReader->text().toString().simplified(); if (characters.isEmpty()) break; qDebug() << "channel: characters: " << characters; break; } } } qDebug() << "exit " << __func__; } // TODO: move to a separate function, code is almost identical in all methods called `getDbId` std::optional AtomChannel::getDbId() { if (m_id) return m_id; const auto db = rsshit::db::open(); if (!db) return rsshit::db::IdNotFound; QSqlQuery selectQ{"select id from feeds where link=?"}; selectQ.addBindValue(link); if (!selectQ.exec()) { qWarning() << "cannot exec query" << selectQ.lastQuery() << ":" << selectQ.lastError().text(); return rsshit::db::IdNotFound; } if (!selectQ.next()) return rsshit::db::IdNotFound; const auto idVariant = selectQ.value(rsshit::db::idTag); if (!idVariant.isValid() || !idVariant.canConvert()) return rsshit::db::IdNotFound; bool ok{false}; const auto result = idVariant.toInt(&ok); if (!ok) { qWarning() << "got invalid id from db:" << idVariant; return rsshit::db::IdNotFound; } return result; } // TODO: move to a separate function, code is almost identical in all methods called `createInDb` std::optional AtomChannel::createInDb() { if (m_id) return m_id; const auto db = rsshit::db::open(); if (!db) { return rsshit::db::IdNotFound; } QSqlQuery insertQ; if (!insertQ.prepare("insert into feeds(link, title, image_url) values(?, ?, ?)")) { qCritical() << "cannot prepare query:" << insertQ.lastError().text(); return rsshit::db::IdNotFound; } insertQ.addBindValue(link); insertQ.addBindValue(title); insertQ.addBindValue(image.url); if (!insertQ.exec()) { qWarning() << "cannot exec query" << insertQ.lastQuery() << ":" << insertQ.lastError().text(); return rsshit::db::IdNotFound; } m_id = insertQ.lastInsertId().toInt(); return m_id; } // TODO: can be moved to IDbObject std::optional AtomChannel::getOrInsertDbId() { const auto id = getDbId(); if (id != rsshit::db::IdNotFound) return id; return createInDb(); } QList AtomChannel::syncDbItems() { if (!m_id) m_id = getOrInsertDbId(); if (!m_id) return {}; QList result; result.reserve(items.count()); for (auto &item : items) { auto id = item.getOrInsertDbId(*this->m_id); if (id) result << id; } return result; } QDebug operator<<(QDebug debug, const AtomChannel &channel) { QDebugStateSaver saver{debug}; debug.nospace() << typeid(AtomChannel).name() << " {" << Qt::endl; PRINT_ATOM_FIELD(channel, title); PRINT_ATOM_FIELD(channel, link); PRINT_ATOM_FIELD(channel, description); PRINT_ATOM_FIELD(channel, lastBuildDate); PRINT_ATOM_FIELD(channel, language); debug << "\timage:\n" << channel.image << Qt::endl; debug << "\titems count:" << channel.items.size() << Qt::endl; for (const auto &item : channel.items) debug << item << Qt::endl; debug.nospace() << "}"; return debug; } QDebug operator<<(QDebug debug, const Channel &channel) { QDebugStateSaver saver{debug}; const auto &mo = Channel::staticMetaObject; debug.nospace() << mo.className() << " {" << Qt::endl; for (int i = 0; i < mo.propertyCount(); ++i) { auto prop = mo.property(i); debug.nospace() << "\t" << prop.name() << ": " << prop.readOnGadget(&channel) << Qt::endl; } debug.nospace() << "}"; return debug; } QDebug operator<<(QDebug debug, const Item &item) { QDebugStateSaver saver{debug}; const auto &mo = Item::staticMetaObject; debug.nospace() << mo.className() << " {" << Qt::endl; for (int i = 0; i < mo.propertyCount(); ++i) { auto prop = mo.property(i); debug.nospace() << "\t" << prop.name() << ": " << prop.readOnGadget(&item) << Qt::endl; } debug.nospace() << "}"; return debug; } Channel::Channel(const QDomNode &node) : DbItem{node} { init(staticMetaObject, node); // use 'atom:link href="LINK"' if any const QString atomLinkTag = QStringLiteral("atom:link"); const QString hrefTag = QStringLiteral("href"); set_link(node.toElement() .elementsByTagName(atomLinkTag) .at(0) .toElement() .attribute(hrefTag, get_link().toString())); } bool Channel::isValid() const { staticMetaObject; return !m_title.isEmpty() && m_link.isValid(); } DbItem::DbItem(const QDomNode &node) { init(staticMetaObject, node); } void DbItem::init(const QMetaObject &mo, const QDomNode &node) { for (int i = mo.propertyOffset(); i < mo.propertyCount(); ++i) { auto prop = mo.property(i); QVariant variant{node.toElement().elementsByTagName(prop.name()).at(0).toElement().text()}; const bool converted = variant.convert(QMetaType{prop.typeId()}); prop.writeOnGadget(this, converted ? std::move(variant) : QVariant{}); } } Item::Item(const QDomNode &node) : DbItem{node} { init(staticMetaObject, node); }