diff options
| author | Nikita Kostovsky <nikita@kostovsky.me> | 2025-08-13 19:24:57 +0200 |
|---|---|---|
| committer | Nikita Kostovsky <nikita@kostovsky.me> | 2025-08-13 19:24:57 +0200 |
| commit | 6d7eaf0c724b976325f4c46b2ff4c452c09a24ea (patch) | |
| tree | c28e24742a9711ecc5ad467460b32bc03862c5b5 | |
| parent | b91bddfbc588dedffda4b129c5e2c2fd9afe7bfd (diff) | |
| -rw-r--r-- | src/atomchannel.cpp | 214 | ||||
| -rw-r--r-- | src/atomchannel.h | 46 | ||||
| -rw-r--r-- | src/main.cpp | 8 | ||||
| -rw-r--r-- | src/playground.cpp | 6 | ||||
| -rw-r--r-- | src/sql/db.sqlite | bin | 110592 -> 110592 bytes |
5 files changed, 237 insertions, 37 deletions
diff --git a/src/atomchannel.cpp b/src/atomchannel.cpp index d3f3c3f..0f3bca2 100644 --- a/src/atomchannel.cpp +++ b/src/atomchannel.cpp @@ -2,6 +2,7 @@ #include <QSqlError> #include <QSqlQuery> +#include <QSqlRecord> #include <QXmlStreamReader> #include "constants.h" @@ -185,21 +186,21 @@ QList<int> AtomChannel::syncDbItems() return result; } -QDebug operator<<(QDebug debug, const AtomChannel &channel) +QDebug operator<<(QDebug debug, const AtomChannel &feed) { 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; + PRINT_ATOM_FIELD(feed, title); + PRINT_ATOM_FIELD(feed, link); + PRINT_ATOM_FIELD(feed, description); + PRINT_ATOM_FIELD(feed, lastBuildDate); + PRINT_ATOM_FIELD(feed, language); + debug << "\timage:\n" << feed.image << Qt::endl; + debug << "\titems count:" << feed.items.size() << Qt::endl; - for (const auto &item : channel.items) + for (const auto &item : feed.items) debug << item << Qt::endl; debug.nospace() << "}"; @@ -207,17 +208,18 @@ QDebug operator<<(QDebug debug, const AtomChannel &channel) return debug; } -QDebug operator<<(QDebug debug, const Channel &channel) +QDebug operator<<(QDebug debug, const Feed &feed) { QDebugStateSaver saver{debug}; - const auto &mo = Channel::staticMetaObject; + // const auto &mo = Channel::staticMetaObject; + const auto &mo = *feed.metaObject(); debug.nospace() << mo.className() << " {" << Qt::endl; - for (int i = 0; i < mo.propertyCount(); ++i) { + for (int i = mo.propertyOffset(); i < mo.propertyCount(); ++i) { auto prop = mo.property(i); - debug.nospace() << "\t" << prop.name() << ": " << prop.readOnGadget(&channel) << Qt::endl; + debug.nospace() << "\t" << prop.name() << ": " << prop.readOnGadget(&feed) << Qt::endl; } debug.nospace() << "}"; @@ -229,11 +231,12 @@ QDebug operator<<(QDebug debug, const Item &item) { QDebugStateSaver saver{debug}; - const auto &mo = Item::staticMetaObject; + // const auto &mo = Item::staticMetaObject; + const auto &mo = *item.metaObject(); debug.nospace() << mo.className() << " {" << Qt::endl; - for (int i = 0; i < mo.propertyCount(); ++i) { + for (int i = mo.propertyOffset(); i < mo.propertyCount(); ++i) { auto prop = mo.property(i); debug.nospace() << "\t" << prop.name() << ": " << prop.readOnGadget(&item) << Qt::endl; } @@ -243,10 +246,10 @@ QDebug operator<<(QDebug debug, const Item &item) return debug; } -Channel::Channel(const QDomNode &node) +Feed::Feed(const QDomNode &node) : DbItem{node} { - init(staticMetaObject, node); + init(node); // use 'atom:link href="LINK"' if any const QString atomLinkTag = QStringLiteral("atom:link"); @@ -258,22 +261,27 @@ Channel::Channel(const QDomNode &node) .attribute(hrefTag, get_link().toString())); } -bool Channel::isValid() const +bool Feed::isValid() const { - staticMetaObject; return !m_title.isEmpty() && m_link.isValid(); } +const QStringList &Feed::uniqueFields() const +{ + // TODO: validate once that this class has corresponding fields + return m_uniqueFields; +} + DbItem::DbItem(const QDomNode &node) { - init(staticMetaObject, node); + init(node); } -void DbItem::init(const QMetaObject &mo, const QDomNode &node) +void DbItem::init(const QDomNode &node) { // for (int i = mo.propertyOffset(); i < mo.propertyCount(); ++i) { - for (int i = staticMetaObject.propertyOffset(); i < staticMetaObject.propertyCount(); ++i) { - const auto prop = staticMetaObject.property(i); + for (int i = metaObject()->propertyOffset(); i < metaObject()->propertyCount(); ++i) { + const auto prop = metaObject()->property(i); const auto propName = prop.name(); const auto propElement = node.toElement().elementsByTagName(propName).at(0); const auto propText = propElement.toElement().text(); @@ -306,13 +314,171 @@ void DbItem::init(const QMetaObject &mo, const QDomNode &node) } } +QStringList DbItem::propertyNames() const +{ + // TODO: init once to static var + const auto propOffset = QObject::staticMetaObject.propertyCount(); + const auto propCount = metaObject()->propertyCount(); + + QStringList result; + result.reserve(propOffset - propCount); + + for (int i = propOffset; i < propCount; ++i) { + result << metaObject()->property(i).name(); + } + + return result; +} + +QList<QMetaProperty> DbItem::properties() const +{ + // TODO: init once to static var + const auto propOffset = QObject::staticMetaObject.propertyCount(); + const auto propCount = metaObject()->propertyCount(); + + QList<QMetaProperty> result; + result.reserve(propOffset - propCount); + + for (int i = propOffset; i < propCount; ++i) { + result << metaObject()->property(i); + } + + return result; +} + +DbItem::DbId DbItem::syncWithDb() +{ + // const auto propOffset = QObject::staticMetaObject.propertyCount(); + // const auto propCount = metaObject()->propertyCount(); + // const auto propNames = propertyNames(); + + // "Item" -> "items" + QString tableName{metaObject()->className()}; + tableName[0] = tableName.at(0).toLower(); + tableName.append('s'); + + qDebug() << Q_FUNC_INFO << "table name:" << tableName; + + // exists in db, update data + if (m_id.has_value()) { + // TODO: what to do if item was removed from db? + return m_id; + } + // select id from db or insert + else { + QSqlQuery q; + + // TODO: validate once statically + if (uniqueFields().isEmpty()) { + qFatal() << metaObject()->className() << "has no unique fields, cannot select"; + + return {}; + } + + // TODO: init once + QStringList whereConditions; + + // for (const auto &name : propertyNames()) { + for (const auto &name : uniqueFields()) { + whereConditions << name + QStringLiteral("=:") + name; + } + + const QString where{QStringLiteral(" where (") + + whereConditions.join(QStringLiteral(" and ")) + ')'}; + // const QString queryTemplate{QStringLiteral("select * from ") + tableName + ' ' + where}; + qDebug() << "where:" << where; + const QString queryTemplate{QStringLiteral("select * from ") + tableName + where}; + qDebug().noquote() << "template:" << queryTemplate; + + const bool prepared = q.prepare(queryTemplate); + + if (!prepared) { + qCritical() << tableName + ": cannot prepare select query:" << q.lastError().text(); + qCritical().noquote() << "template: " << queryTemplate; + + return {}; + } + + for (const auto &prop : properties()) { + // FIXME: create uniqueProperties + if (!uniqueFields().contains(prop.name())) + continue; + q.addBindValue(prop.read(this).toString()); + // q.addBindValue("'asdfsdf'"); + } + + if (!q.exec()) { + qCritical() << "cannot select" << tableName << "from db:" << q.lastError().text(); + qCritical().noquote() << "template: " << queryTemplate; + + return {}; + } + + if (!q.next()) { + qCritical() << "no results for" << tableName; + // TODO: inset this shit. always inset it + return {}; + } + + const auto rec = q.record(); + + if (rec.isEmpty()) { + qCritical() << "got empty record for" << tableName; + return {}; + } + + // qDebug() << "rec:" << rec; + + bool dbNeedsUpdate{false}; + + for (const auto &prop : properties()) { + const auto val = rec.value(prop.name()); + + if (val.isNull() || !val.isValid()) { + qCritical() << "got invalid value for table" << tableName << "fieldl" + << prop.name(); + return {}; + } + + // if (prop.read(this) != val) { + // // TODO: don't use this shit with tmp vars, call update here and break + // dbNeedsUpdate = true; + // break; + // } + + prop.write(this, val); + } + + if (dbNeedsUpdate) { + } + + qDebug() << "finished reading from" << tableName; + } + + // AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + // FIXME: implement + return {}; +} + Item::Item(const QDomNode &node) : DbItem{node} { - init(staticMetaObject, node); + init(node); if (m_content.isEmpty()) { const QString contentEncodedTag = QStringLiteral("content:encoded"); set_content(node.toElement().elementsByTagName(contentEncodedTag).at(0).toElement().text()); } } + +bool Item::isValid() const +{ + // TODO: check if isNull is needed + return !m_pubDate.isNull() && m_pubDate.isValid() && !m_title.isEmpty() && !m_link.isEmpty() + && !m_description.isEmpty(); +} + +const QStringList &Item::uniqueFields() const +{ + return m_uniqueFields; +} diff --git a/src/atomchannel.h b/src/atomchannel.h index e52048a..e88c05f 100644 --- a/src/atomchannel.h +++ b/src/atomchannel.h @@ -32,11 +32,14 @@ public: \ protected: \ type m_##name -class DbItem +class DbItem : public QObject { - Q_GADGET + // Q_GADGET + Q_OBJECT - GADGET_PROPERTY(std::optional<qlonglong>, id); +public: + using DbId = std::optional<qlonglong>; + GADGET_PROPERTY(DbId, id); static constexpr auto dateFormat = Qt::DateFormat::RFC2822Date; @@ -44,28 +47,44 @@ public: explicit DbItem(const QDomNode &node); public: - void init(const QMetaObject &mo, const QDomNode &node); + virtual bool isValid() const = 0; + + virtual void init(const QDomNode &node); + + // TODO: sync list of items, thus minimizing amount of db queries + virtual DbId syncWithDb(); + + virtual QStringList propertyNames() const; + virtual QList<QMetaProperty> properties() const; + + virtual const QStringList &uniqueFields() const = 0; }; -class Channel : public DbItem +class Feed : public DbItem { - Q_GADGET + Q_OBJECT GADGET_PROPERTY(QString, title); GADGET_PROPERTY(QUrl, link); + GADGET_PROPERTY(QUrl, image_url); public: - explicit Channel(const QDomNode &node); + explicit Feed(const QDomNode &node); public: - bool isValid() const; + bool isValid() const override; + + const QStringList &uniqueFields() const override; + +private: + static inline QStringList m_uniqueFields{{"link"}}; }; // TODO: <image> class Item : public DbItem { - Q_GADGET + Q_OBJECT GADGET_PROPERTY(QString, title); GADGET_PROPERTY(QUrl, link); @@ -79,7 +98,12 @@ public: explicit Item(const QDomNode &node); public: - bool isValid() const; + bool isValid() const override; + + const QStringList &uniqueFields() const override; + +private: + static inline QStringList m_uniqueFields{{"link"}}; }; class AtomChannel @@ -140,5 +164,5 @@ public: }; QDebug operator<<(QDebug debug, const AtomChannel &channel); -QDebug operator<<(QDebug debug, const Channel &channel); +QDebug operator<<(QDebug debug, const Feed &feed); QDebug operator<<(QDebug debug, const Item &item); diff --git a/src/main.cpp b/src/main.cpp index 843d66a..7585d3b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -26,7 +26,7 @@ int main(int argc, char *argv[]) const QString newDbFilepath{dataLocation + '/' + QFileInfo{dbSqliteResource}.fileName()}; -#define RECREATE_DB_FILE + // #define RECREATE_DB_FILE #ifdef RECREATE_DB_FILE if (!QFile::remove(newDbFilepath)) { @@ -42,6 +42,7 @@ int main(int argc, char *argv[]) #endif QFile newDbFile{newDbFilepath}; + qDebug() << "db filepath:" << newDbFile.fileName(); if (!newDbFile.setPermissions(QFileDevice::Permission::ReadUser | QFileDevice::Permission::WriteUser)) { @@ -79,6 +80,11 @@ int main(int argc, char *argv[]) const auto channel = playground.parseFeed(&f); + if (!channel) { + qDebug() << "no channel"; + return; + } + if (!applyToDb(channel)) { qWarning() << "cannot apply channel to db"; diff --git a/src/playground.cpp b/src/playground.cpp index 73eb84a..567223e 100644 --- a/src/playground.cpp +++ b/src/playground.cpp @@ -60,9 +60,13 @@ std::shared_ptr<AtomChannel> Playground::parseFeed(QIODevice *ioDevice) const auto channelNode = documentElement.firstChildElement(); VERIFY_CRIT_RETURN_VAL(!channelNode.isNull(), {}); - Channel tmpChannel{channelNode}; + Feed tmpChannel{channelNode}; qDebug() << tmpChannel; + tmpChannel.syncWithDb(); + qDebug() << tmpChannel; + return {}; + const auto itemNodes = channelNode.elementsByTagName(itemTag); for (const auto &itemNode : itemNodes) { diff --git a/src/sql/db.sqlite b/src/sql/db.sqlite Binary files differindex aefa4d5..1a03b14 100644 --- a/src/sql/db.sqlite +++ b/src/sql/db.sqlite |
