From c8d4819270c9083f5a24df4b8c146d59bba6ae54 Mon Sep 17 00:00:00 2001 From: Martin Rotter Date: Thu, 10 Dec 2015 11:30:10 +0100 Subject: [PATCH] Experimental ability to update read/unread status of messages. --- src/core/message.cpp | 2 +- src/core/message.h | 1 + src/core/messagesmodel.cpp | 21 +++--- src/gui/feedmessageviewer.cpp | 2 +- src/gui/messagesview.cpp | 2 +- src/gui/messagesview.h | 2 +- src/services/abstract/serviceroot.h | 4 +- src/services/standard/standardfeed.cpp | 3 +- src/services/standard/standardserviceroot.cpp | 8 +-- src/services/standard/standardserviceroot.h | 4 +- .../tt-rss/network/ttrssnetworkfactory.cpp | 67 ++++++++++++++++++- .../tt-rss/network/ttrssnetworkfactory.h | 34 +++++++++- src/services/tt-rss/ttrssfeed.cpp | 3 +- src/services/tt-rss/ttrssserviceroot.cpp | 35 +++++++--- src/services/tt-rss/ttrssserviceroot.h | 6 +- 15 files changed, 154 insertions(+), 40 deletions(-) diff --git a/src/core/message.cpp b/src/core/message.cpp index a8e5b22ce..e47791322 100755 --- a/src/core/message.cpp +++ b/src/core/message.cpp @@ -63,6 +63,6 @@ QString Enclosures::encodeEnclosuresToString(const QList &enclosures) Message::Message() { m_title = m_url = m_author = m_contents = m_feedId = m_customId = ""; m_enclosures = QList(); - m_accountId = 0; + m_accountId = m_id = 0; m_isRead = m_isImportant = false; } diff --git a/src/core/message.h b/src/core/message.h index 7459eb0be..a9f9769fb 100755 --- a/src/core/message.h +++ b/src/core/message.h @@ -51,6 +51,7 @@ class Message { QDateTime m_created; QString m_feedId; int m_accountId; + int m_id; QString m_customId; bool m_isRead; diff --git a/src/core/messagesmodel.cpp b/src/core/messagesmodel.cpp index cd1fad544..05a22d9eb 100755 --- a/src/core/messagesmodel.cpp +++ b/src/core/messagesmodel.cpp @@ -141,6 +141,7 @@ Message MessagesModel::messageAt(int row_index) const { message.m_url = rec.value(MSG_DB_URL_INDEX).toString(); message.m_feedId = rec.value(MSG_DB_FEED_INDEX).toString(); message.m_accountId = rec.value(MSG_DB_ACCOUNT_ID_INDEX).toInt(); + message.m_id = rec.value(MSG_DB_ID_INDEX).toInt(); message.m_customId = rec.value(MSG_DB_CUSTOM_ID_INDEX).toString(); message.m_created = TextFactory::parseDateTime(rec.value(MSG_DB_DCREATED_INDEX).value()).toLocalTime(); @@ -260,9 +261,9 @@ bool MessagesModel::setMessageRead(int row_index, RootItem::ReadStatus read) { return true; } - int message_id = messageId(row_index); + Message message = messageAt(row_index); - if (!m_selectedItem->getParentServiceRoot()->onBeforeSetMessagesRead(m_selectedItem, QList() << message_id, read)) { + if (!m_selectedItem->getParentServiceRoot()->onBeforeSetMessagesRead(m_selectedItem, QList() << message, read)) { // Cannot change read status of the item. Abort. return false; } @@ -284,11 +285,11 @@ bool MessagesModel::setMessageRead(int row_index, RootItem::ReadStatus read) { return false; } - query_read_msg.bindValue(QSL(":id"), message_id); + query_read_msg.bindValue(QSL(":id"), message.m_id); query_read_msg.bindValue(QSL(":read"), (int) read); if (query_read_msg.exec()) { - return m_selectedItem->getParentServiceRoot()->onAfterSetMessagesRead(m_selectedItem, QList() << message_id, read); + return m_selectedItem->getParentServiceRoot()->onAfterSetMessagesRead(m_selectedItem, QList() << message, read); } else { return false; @@ -408,17 +409,17 @@ bool MessagesModel::setBatchMessagesDeleted(const QModelIndexList &messages) { bool MessagesModel::setBatchMessagesRead(const QModelIndexList &messages, RootItem::ReadStatus read) { QStringList message_ids; - QList message_ids_num; + QList msgs; // Obtain IDs of all desired messages. foreach (const QModelIndex &message, messages) { - int message_id = messageId(message.row()); + Message msg = messageAt(message.row()); - message_ids_num.append(message_id); - message_ids.append(QString::number(message_id)); + msgs.append(msg); + message_ids.append(QString::number(msg.m_id)); } - if (!m_selectedItem->getParentServiceRoot()->onBeforeSetMessagesRead(m_selectedItem, message_ids_num, read)) { + if (!m_selectedItem->getParentServiceRoot()->onBeforeSetMessagesRead(m_selectedItem, msgs, read)) { return false; } @@ -429,7 +430,7 @@ bool MessagesModel::setBatchMessagesRead(const QModelIndexList &messages, RootIt .arg(message_ids.join(QSL(", ")), read == RootItem::Read ? QSL("1") : QSL("0")))) { fetchAllData(); - return m_selectedItem->getParentServiceRoot()->onAfterSetMessagesRead(m_selectedItem, message_ids_num, read); + return m_selectedItem->getParentServiceRoot()->onAfterSetMessagesRead(m_selectedItem, msgs, read); } else { return false; diff --git a/src/gui/feedmessageviewer.cpp b/src/gui/feedmessageviewer.cpp index c772e7548..75f2e75c1 100755 --- a/src/gui/feedmessageviewer.cpp +++ b/src/gui/feedmessageviewer.cpp @@ -233,7 +233,7 @@ void FeedMessageViewer::createConnections() { connect(qApp->feedUpdateLock(), SIGNAL(unlocked()), this, SLOT(updateFeedButtonsAvailability())); // If user selects feeds, load their messages. - connect(m_feedsView, SIGNAL(itemSelected(RootItem*)), m_messagesView, SLOT(loadFeeds(RootItem*))); + connect(m_feedsView, SIGNAL(itemSelected(RootItem*)), m_messagesView, SLOT(loadItem(RootItem*))); // State of many messages is changed, then we need // to reload selections. diff --git a/src/gui/messagesview.cpp b/src/gui/messagesview.cpp index e6522062c..0d8ecbf97 100755 --- a/src/gui/messagesview.cpp +++ b/src/gui/messagesview.cpp @@ -227,7 +227,7 @@ void MessagesView::selectionChanged(const QItemSelection &selected, const QItemS QTreeView::selectionChanged(selected, deselected); } -void MessagesView::loadFeeds(RootItem *item) { +void MessagesView::loadItem(RootItem *item) { m_sourceModel->loadMessages(item); int col = qApp->settings()->value(GROUP(GUI), SETTING(GUI::DefaultSortColumnMessages)).toInt(); diff --git a/src/gui/messagesview.h b/src/gui/messagesview.h index 2d4b6b107..27ea31bf3 100755 --- a/src/gui/messagesview.h +++ b/src/gui/messagesview.h @@ -57,7 +57,7 @@ class MessagesView : public QTreeView { void reloadSelections(bool mark_current_index_read); // Loads un-deleted messages from selected feeds. - void loadFeeds(RootItem *item); + void loadItem(RootItem *item); // Message manipulators. void openSelectedSourceMessagesExternally(); diff --git a/src/services/abstract/serviceroot.h b/src/services/abstract/serviceroot.h index aca550619..5c3212c04 100755 --- a/src/services/abstract/serviceroot.h +++ b/src/services/abstract/serviceroot.h @@ -93,7 +93,7 @@ class ServiceRoot : public RootItem { // some ONLINE service or something. // // "read" is status which is ABOUT TO BE SET. - virtual bool onBeforeSetMessagesRead(RootItem *selected_item, QList message_db_ids, ReadStatus read) = 0; + virtual bool onBeforeSetMessagesRead(RootItem *selected_item, const QList &messages, ReadStatus read) = 0; // Called AFTER this read status update (triggered by user in message list) is stored in DB, // when false is returned, change is aborted. @@ -101,7 +101,7 @@ class ServiceRoot : public RootItem { // which items are actually changed. // // "read" is status which is ABOUT TO BE SET. - virtual bool onAfterSetMessagesRead(RootItem *selected_item, QList message_db_ids, ReadStatus read) = 0; + virtual bool onAfterSetMessagesRead(RootItem *selected_item, const QList &messages, ReadStatus read) = 0; // Called BEFORE this importance switch update is stored in DB, // when false is returned, change is aborted. diff --git a/src/services/standard/standardfeed.cpp b/src/services/standard/standardfeed.cpp index 321884f87..0eeb522b0 100755 --- a/src/services/standard/standardfeed.cpp +++ b/src/services/standard/standardfeed.cpp @@ -135,7 +135,7 @@ QList StandardFeed::undeletedMessages() const { QSqlDatabase database = qApp->database()->connection(metaObject()->className(), DatabaseFactory::FromSettings); QSqlQuery query_read_msg(database); query_read_msg.setForwardOnly(true); - query_read_msg.prepare("SELECT title, url, author, date_created, contents, enclosures " + query_read_msg.prepare("SELECT title, url, author, date_created, contents, enclosures, id " "FROM Messages " "WHERE is_deleted = 0 AND feed = :feed AND account_id = :account_id;"); @@ -156,6 +156,7 @@ QList StandardFeed::undeletedMessages() const { message.m_contents = query_read_msg.value(4).toString(); message.m_accountId = const_cast(this)->serviceRoot()->accountId(); message.m_enclosures = Enclosures::decodeEnclosuresFromString(query_read_msg.value(5).toString()); + message.m_id = query_read_msg.value(6).toInt(); messages.append(message); } diff --git a/src/services/standard/standardserviceroot.cpp b/src/services/standard/standardserviceroot.cpp index bb5d9b6e2..985f93c62 100755 --- a/src/services/standard/standardserviceroot.cpp +++ b/src/services/standard/standardserviceroot.cpp @@ -504,16 +504,16 @@ bool StandardServiceRoot::loadMessagesForItem(RootItem *item, QSqlTableModel *mo return true; } -bool StandardServiceRoot::onBeforeSetMessagesRead(RootItem *selected_item, QList message_db_ids, RootItem::ReadStatus read) { - Q_UNUSED(message_db_ids) +bool StandardServiceRoot::onBeforeSetMessagesRead(RootItem *selected_item, const QList &messages, RootItem::ReadStatus read) { + Q_UNUSED(messages) Q_UNUSED(read) Q_UNUSED(selected_item) return true; } -bool StandardServiceRoot::onAfterSetMessagesRead(RootItem *selected_item, QList message_db_ids, RootItem::ReadStatus read) { - Q_UNUSED(message_db_ids) +bool StandardServiceRoot::onAfterSetMessagesRead(RootItem *selected_item, const QList &messages, RootItem::ReadStatus read) { + Q_UNUSED(messages) Q_UNUSED(read) selected_item->updateCounts(false); diff --git a/src/services/standard/standardserviceroot.h b/src/services/standard/standardserviceroot.h index 35d5e0eca..ca782b283 100755 --- a/src/services/standard/standardserviceroot.h +++ b/src/services/standard/standardserviceroot.h @@ -63,8 +63,8 @@ class StandardServiceRoot : public ServiceRoot { // Message stuff. bool loadMessagesForItem(RootItem *item, QSqlTableModel *model); - bool onBeforeSetMessagesRead(RootItem *selected_item, QList message_db_ids, ReadStatus read); - bool onAfterSetMessagesRead(RootItem *selected_item, QList message_db_ids, ReadStatus read); + bool onBeforeSetMessagesRead(RootItem *selected_item, const QList &messages, ReadStatus read); + bool onAfterSetMessagesRead(RootItem *selected_item, const QList &messages, ReadStatus read); bool onBeforeSwitchMessageImportance(RootItem *selected_item, QList > changes); bool onAfterSwitchMessageImportance(RootItem *selected_item, QList > changes); diff --git a/src/services/tt-rss/network/ttrssnetworkfactory.cpp b/src/services/tt-rss/network/ttrssnetworkfactory.cpp index bfc02ab33..f6cb1159d 100755 --- a/src/services/tt-rss/network/ttrssnetworkfactory.cpp +++ b/src/services/tt-rss/network/ttrssnetworkfactory.cpp @@ -171,6 +171,44 @@ TtRssGetHeadlinesResponse TtRssNetworkFactory::getHeadlines(int feed_id, bool fo return result; } +TtRssUpdateArticleResponse TtRssNetworkFactory::updateArticles(const QList &ids, + UpdateArticle::OperatingField field, + UpdateArticle::Mode mode, + QNetworkReply::NetworkError &error) { + QtJson::JsonObject json; + json["op"] = "updateArticle"; + json["sid"] = m_sessionId; + json["article_ids"] = encodeArticleIds(ids); + json["mode"] = (int) mode; + json["field"] = (int) field; + + QByteArray result_raw; + NetworkResult network_reply = NetworkFactory::uploadData(m_url, DOWNLOAD_TIMEOUT, QtJson::serialize(json), CONTENT_TYPE, result_raw); + TtRssUpdateArticleResponse result(QString::fromUtf8(result_raw)); + + if (result.isNotLoggedIn()) { + // We are not logged in. + login(error); + json["sid"] = m_sessionId; + + network_reply = NetworkFactory::uploadData(m_url, DOWNLOAD_TIMEOUT, QtJson::serialize(json), CONTENT_TYPE, result_raw); + result = TtRssUpdateArticleResponse(QString::fromUtf8(result_raw)); + } + + error = network_reply.first; + return result; +} + +QString TtRssNetworkFactory::encodeArticleIds(const QList &ids) { + QStringList strings; + + foreach (int id, ids) { + strings.append(QString::number(id)); + } + + return strings.join(QL1C(',')); +} + TtRssResponse::TtRssResponse(const QString &raw_content) { m_rawContent = QtJson::parse(raw_content).toMap(); } @@ -255,7 +293,7 @@ TtRssGetFeedsCategoriesResponse::TtRssGetFeedsCategoriesResponse(const QString & TtRssGetFeedsCategoriesResponse::~TtRssGetFeedsCategoriesResponse() { } -RootItem *TtRssGetFeedsCategoriesResponse::feedsCategories(bool obtain_icons, QString base_address) { +RootItem *TtRssGetFeedsCategoriesResponse::feedsCategories(bool obtain_icons, QString base_address) const { RootItem *parent = new RootItem(); // Chop the "api/" from the end of the address. @@ -342,7 +380,7 @@ TtRssGetHeadlinesResponse::TtRssGetHeadlinesResponse(const QString &raw_content) TtRssGetHeadlinesResponse::~TtRssGetHeadlinesResponse() { } -QList TtRssGetHeadlinesResponse::messages() { +QList TtRssGetHeadlinesResponse::messages() const { QList messages; foreach (QVariant item, m_rawContent["content"].toList()) { @@ -380,3 +418,28 @@ QList TtRssGetHeadlinesResponse::messages() { return messages; } + + +TtRssUpdateArticleResponse::TtRssUpdateArticleResponse(const QString &raw_content) : TtRssResponse(raw_content) { +} + +TtRssUpdateArticleResponse::~TtRssUpdateArticleResponse() { +} + +QString TtRssUpdateArticleResponse::updateStatus() const { + if (m_rawContent.contains(QSL("content"))) { + return m_rawContent["content"].toMap()["status"].toString(); + } + else { + return QString(); + } +} + +int TtRssUpdateArticleResponse::articlesUpdated() const { + if (m_rawContent.contains(QSL("content"))) { + return m_rawContent["content"].toMap()["updated"].toInt(); + } + else { + return 0; + } +} diff --git a/src/services/tt-rss/network/ttrssnetworkfactory.h b/src/services/tt-rss/network/ttrssnetworkfactory.h index ca74fc11e..b7bf159fa 100755 --- a/src/services/tt-rss/network/ttrssnetworkfactory.h +++ b/src/services/tt-rss/network/ttrssnetworkfactory.h @@ -63,7 +63,7 @@ class TtRssGetFeedsCategoriesResponse : public TtRssResponse { // Returns tree of feeds/categories. // Top-level root of the tree is not needed here. // Returned items do not have primary IDs assigned. - RootItem *feedsCategories(bool obtain_icons, QString base_address = QString()); + RootItem *feedsCategories(bool obtain_icons, QString base_address = QString()) const; }; class TtRssGetHeadlinesResponse : public TtRssResponse { @@ -71,9 +71,32 @@ class TtRssGetHeadlinesResponse : public TtRssResponse { explicit TtRssGetHeadlinesResponse(const QString &raw_content = QString()); virtual ~TtRssGetHeadlinesResponse(); - QList messages(); + QList messages() const; }; +class TtRssUpdateArticleResponse : public TtRssResponse { + public: + explicit TtRssUpdateArticleResponse(const QString &raw_content = QString()); + virtual ~TtRssUpdateArticleResponse(); + + QString updateStatus() const; + int articlesUpdated() const; +}; + +namespace UpdateArticle { + enum Mode { + SetToFalse = 0, + SetToTrue = 1, + Togggle = 2 + }; + + enum OperatingField { + Starred = 0, + Published = 1, + Unread = 2 + }; +} + class TtRssNetworkFactory { public: explicit TtRssNetworkFactory(); @@ -107,7 +130,12 @@ class TtRssNetworkFactory { bool show_content, bool include_attachments, bool sanitize, QNetworkReply::NetworkError &error); - private: + TtRssUpdateArticleResponse updateArticles(const QList &ids, UpdateArticle::OperatingField field, + UpdateArticle::Mode mode, QNetworkReply::NetworkError &error); + + private: + QString encodeArticleIds(const QList &ids); + QString m_url; QString m_username; QString m_password; diff --git a/src/services/tt-rss/ttrssfeed.cpp b/src/services/tt-rss/ttrssfeed.cpp index 0579cf0fb..2dd8ccc48 100755 --- a/src/services/tt-rss/ttrssfeed.cpp +++ b/src/services/tt-rss/ttrssfeed.cpp @@ -118,7 +118,7 @@ QList TtRssFeed::undeletedMessages() const { QSqlQuery query_read_msg(database); query_read_msg.setForwardOnly(true); - query_read_msg.prepare("SELECT title, url, author, date_created, contents, enclosures, custom_id " + query_read_msg.prepare("SELECT title, url, author, date_created, contents, enclosures, custom_id, id " "FROM Messages " "WHERE is_deleted = 0 AND feed = :feed AND account_id = :account_id;"); @@ -140,6 +140,7 @@ QList TtRssFeed::undeletedMessages() const { message.m_enclosures = Enclosures::decodeEnclosuresFromString(query_read_msg.value(5).toString()); message.m_accountId = account_id; message.m_customId = query_read_msg.value(6).toString(); + message.m_id = query_read_msg.value(7).toInt(); messages.append(message); } diff --git a/src/services/tt-rss/ttrssserviceroot.cpp b/src/services/tt-rss/ttrssserviceroot.cpp index e076a2b43..02a842b0c 100755 --- a/src/services/tt-rss/ttrssserviceroot.cpp +++ b/src/services/tt-rss/ttrssserviceroot.cpp @@ -24,6 +24,7 @@ #include "services/tt-rss/ttrssserviceentrypoint.h" #include "services/tt-rss/ttrssfeed.h" #include "services/tt-rss/ttrsscategory.h" +#include "services/tt-rss/definitions.h" #include "services/tt-rss/network/ttrssnetworkfactory.h" #include "services/tt-rss/gui/formeditaccount.h" @@ -145,19 +146,25 @@ QList TtRssServiceRoot::contextMenu() { return serviceMenu(); } -bool TtRssServiceRoot::onBeforeSetMessagesRead(RootItem *selected_item, QList message_db_ids, RootItem::ReadStatus read) { - // TODO: misto čísel primarnich zprav, vracet cele objekty zprav - // tedy včetně custom ID, nemusi se tak znova tahat z DB asi?s +bool TtRssServiceRoot::onBeforeSetMessagesRead(RootItem *selected_item, const QList &messages, RootItem::ReadStatus read) { + Q_UNUSED(selected_item) - // OK, update the messages status online. + QNetworkReply::NetworkError error; + TtRssUpdateArticleResponse response = m_network->updateArticles(customIDsOfMessages(messages), + UpdateArticle::Unread, + read == RootItem::Unread ? UpdateArticle::SetToTrue : UpdateArticle::SetToFalse, + error); - // First obtain, custom IDs of messages. - - return false; + if (error == QNetworkReply::NoError && response.updateStatus() == STATUS_OK && response.articlesUpdated() == messages.size()) { + return true; + } + else { + return false; + } } -bool TtRssServiceRoot::onAfterSetMessagesRead(RootItem *selected_item, QList message_db_ids, RootItem::ReadStatus read) { - Q_UNUSED(message_db_ids) +bool TtRssServiceRoot::onAfterSetMessagesRead(RootItem *selected_item, const QList &messages, RootItem::ReadStatus read) { + Q_UNUSED(messages) Q_UNUSED(read) selected_item->updateCounts(false); @@ -338,6 +345,16 @@ void TtRssServiceRoot::syncIn() { } } +QList TtRssServiceRoot::customIDsOfMessages(const QList &messages) { + QList list; + + foreach (const Message &message, messages) { + list.append(message.m_customId.toInt()); + } + + return list; +} + QStringList TtRssServiceRoot::textualFeedIds(const QList &feeds) { QStringList stringy_ids; stringy_ids.reserve(feeds.size()); diff --git a/src/services/tt-rss/ttrssserviceroot.h b/src/services/tt-rss/ttrssserviceroot.h index 96d424b06..4d4b896e3 100755 --- a/src/services/tt-rss/ttrssserviceroot.h +++ b/src/services/tt-rss/ttrssserviceroot.h @@ -54,8 +54,8 @@ class TtRssServiceRoot : public ServiceRoot { bool loadMessagesForItem(RootItem *item, QSqlTableModel *model); - bool onBeforeSetMessagesRead(RootItem *selected_item, QList message_db_ids, ReadStatus read); - bool onAfterSetMessagesRead(RootItem *selected_item, QList message_db_ids, ReadStatus read); + bool onBeforeSetMessagesRead(RootItem *selected_item, const QList &messages, ReadStatus read); + bool onAfterSetMessagesRead(RootItem *selected_item, const QList &messages, ReadStatus read); bool onBeforeSwitchMessageImportance(RootItem *selected_item, QList > changes); bool onAfterSwitchMessageImportance(RootItem *selected_item, QList > changes); @@ -77,6 +77,8 @@ class TtRssServiceRoot : public ServiceRoot { void syncIn(); private: + QList customIDsOfMessages(const QList &messages); + // Returns converted ids of given feeds // which are suitable as IN clause for SQL queries. QStringList textualFeedIds(const QList &feeds);