summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNikita Kostovsky <nikita@kostovsky.me>2025-08-13 19:24:57 +0200
committerNikita Kostovsky <nikita@kostovsky.me>2025-08-13 19:24:57 +0200
commit6d7eaf0c724b976325f4c46b2ff4c452c09a24ea (patch)
treec28e24742a9711ecc5ad467460b32bc03862c5b5
parentb91bddfbc588dedffda4b129c5e2c2fd9afe7bfd (diff)
refactorHEADmaster
-rw-r--r--src/atomchannel.cpp214
-rw-r--r--src/atomchannel.h46
-rw-r--r--src/main.cpp8
-rw-r--r--src/playground.cpp6
-rw-r--r--src/sql/db.sqlitebin110592 -> 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
index aefa4d5..1a03b14 100644
--- a/src/sql/db.sqlite
+++ b/src/sql/db.sqlite
Binary files differ