Experimental ability to update read/unread status of messages.

This commit is contained in:
Martin Rotter 2015-12-10 11:30:10 +01:00
parent 0b5b75d3bd
commit c8d4819270
15 changed files with 154 additions and 40 deletions

View file

@ -63,6 +63,6 @@ QString Enclosures::encodeEnclosuresToString(const QList<Enclosure> &enclosures)
Message::Message() { Message::Message() {
m_title = m_url = m_author = m_contents = m_feedId = m_customId = ""; m_title = m_url = m_author = m_contents = m_feedId = m_customId = "";
m_enclosures = QList<Enclosure>(); m_enclosures = QList<Enclosure>();
m_accountId = 0; m_accountId = m_id = 0;
m_isRead = m_isImportant = false; m_isRead = m_isImportant = false;
} }

View file

@ -51,6 +51,7 @@ class Message {
QDateTime m_created; QDateTime m_created;
QString m_feedId; QString m_feedId;
int m_accountId; int m_accountId;
int m_id;
QString m_customId; QString m_customId;
bool m_isRead; bool m_isRead;

View file

@ -141,6 +141,7 @@ Message MessagesModel::messageAt(int row_index) const {
message.m_url = rec.value(MSG_DB_URL_INDEX).toString(); message.m_url = rec.value(MSG_DB_URL_INDEX).toString();
message.m_feedId = rec.value(MSG_DB_FEED_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_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_customId = rec.value(MSG_DB_CUSTOM_ID_INDEX).toString();
message.m_created = TextFactory::parseDateTime(rec.value(MSG_DB_DCREATED_INDEX).value<qint64>()).toLocalTime(); message.m_created = TextFactory::parseDateTime(rec.value(MSG_DB_DCREATED_INDEX).value<qint64>()).toLocalTime();
@ -260,9 +261,9 @@ bool MessagesModel::setMessageRead(int row_index, RootItem::ReadStatus read) {
return true; return true;
} }
int message_id = messageId(row_index); Message message = messageAt(row_index);
if (!m_selectedItem->getParentServiceRoot()->onBeforeSetMessagesRead(m_selectedItem, QList<int>() << message_id, read)) { if (!m_selectedItem->getParentServiceRoot()->onBeforeSetMessagesRead(m_selectedItem, QList<Message>() << message, read)) {
// Cannot change read status of the item. Abort. // Cannot change read status of the item. Abort.
return false; return false;
} }
@ -284,11 +285,11 @@ bool MessagesModel::setMessageRead(int row_index, RootItem::ReadStatus read) {
return false; 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); query_read_msg.bindValue(QSL(":read"), (int) read);
if (query_read_msg.exec()) { if (query_read_msg.exec()) {
return m_selectedItem->getParentServiceRoot()->onAfterSetMessagesRead(m_selectedItem, QList<int>() << message_id, read); return m_selectedItem->getParentServiceRoot()->onAfterSetMessagesRead(m_selectedItem, QList<Message>() << message, read);
} }
else { else {
return false; return false;
@ -408,17 +409,17 @@ bool MessagesModel::setBatchMessagesDeleted(const QModelIndexList &messages) {
bool MessagesModel::setBatchMessagesRead(const QModelIndexList &messages, RootItem::ReadStatus read) { bool MessagesModel::setBatchMessagesRead(const QModelIndexList &messages, RootItem::ReadStatus read) {
QStringList message_ids; QStringList message_ids;
QList<int> message_ids_num; QList<Message> msgs;
// Obtain IDs of all desired messages. // Obtain IDs of all desired messages.
foreach (const QModelIndex &message, messages) { foreach (const QModelIndex &message, messages) {
int message_id = messageId(message.row()); Message msg = messageAt(message.row());
message_ids_num.append(message_id); msgs.append(msg);
message_ids.append(QString::number(message_id)); 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; 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")))) { .arg(message_ids.join(QSL(", ")), read == RootItem::Read ? QSL("1") : QSL("0")))) {
fetchAllData(); fetchAllData();
return m_selectedItem->getParentServiceRoot()->onAfterSetMessagesRead(m_selectedItem, message_ids_num, read); return m_selectedItem->getParentServiceRoot()->onAfterSetMessagesRead(m_selectedItem, msgs, read);
} }
else { else {
return false; return false;

View file

@ -233,7 +233,7 @@ void FeedMessageViewer::createConnections() {
connect(qApp->feedUpdateLock(), SIGNAL(unlocked()), this, SLOT(updateFeedButtonsAvailability())); connect(qApp->feedUpdateLock(), SIGNAL(unlocked()), this, SLOT(updateFeedButtonsAvailability()));
// If user selects feeds, load their messages. // 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 // State of many messages is changed, then we need
// to reload selections. // to reload selections.

View file

@ -227,7 +227,7 @@ void MessagesView::selectionChanged(const QItemSelection &selected, const QItemS
QTreeView::selectionChanged(selected, deselected); QTreeView::selectionChanged(selected, deselected);
} }
void MessagesView::loadFeeds(RootItem *item) { void MessagesView::loadItem(RootItem *item) {
m_sourceModel->loadMessages(item); m_sourceModel->loadMessages(item);
int col = qApp->settings()->value(GROUP(GUI), SETTING(GUI::DefaultSortColumnMessages)).toInt(); int col = qApp->settings()->value(GROUP(GUI), SETTING(GUI::DefaultSortColumnMessages)).toInt();

View file

@ -57,7 +57,7 @@ class MessagesView : public QTreeView {
void reloadSelections(bool mark_current_index_read); void reloadSelections(bool mark_current_index_read);
// Loads un-deleted messages from selected feeds. // Loads un-deleted messages from selected feeds.
void loadFeeds(RootItem *item); void loadItem(RootItem *item);
// Message manipulators. // Message manipulators.
void openSelectedSourceMessagesExternally(); void openSelectedSourceMessagesExternally();

View file

@ -93,7 +93,7 @@ class ServiceRoot : public RootItem {
// some ONLINE service or something. // some ONLINE service or something.
// //
// "read" is status which is ABOUT TO BE SET. // "read" is status which is ABOUT TO BE SET.
virtual bool onBeforeSetMessagesRead(RootItem *selected_item, QList<int> message_db_ids, ReadStatus read) = 0; virtual bool onBeforeSetMessagesRead(RootItem *selected_item, const QList<Message> &messages, ReadStatus read) = 0;
// Called AFTER this read status update (triggered by user in message list) is stored in DB, // Called AFTER this read status update (triggered by user in message list) is stored in DB,
// when false is returned, change is aborted. // when false is returned, change is aborted.
@ -101,7 +101,7 @@ class ServiceRoot : public RootItem {
// which items are actually changed. // which items are actually changed.
// //
// "read" is status which is ABOUT TO BE SET. // "read" is status which is ABOUT TO BE SET.
virtual bool onAfterSetMessagesRead(RootItem *selected_item, QList<int> message_db_ids, ReadStatus read) = 0; virtual bool onAfterSetMessagesRead(RootItem *selected_item, const QList<Message> &messages, ReadStatus read) = 0;
// Called BEFORE this importance switch update is stored in DB, // Called BEFORE this importance switch update is stored in DB,
// when false is returned, change is aborted. // when false is returned, change is aborted.

View file

@ -135,7 +135,7 @@ QList<Message> StandardFeed::undeletedMessages() const {
QSqlDatabase database = qApp->database()->connection(metaObject()->className(), DatabaseFactory::FromSettings); QSqlDatabase database = qApp->database()->connection(metaObject()->className(), DatabaseFactory::FromSettings);
QSqlQuery query_read_msg(database); QSqlQuery query_read_msg(database);
query_read_msg.setForwardOnly(true); 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 " "FROM Messages "
"WHERE is_deleted = 0 AND feed = :feed AND account_id = :account_id;"); "WHERE is_deleted = 0 AND feed = :feed AND account_id = :account_id;");
@ -156,6 +156,7 @@ QList<Message> StandardFeed::undeletedMessages() const {
message.m_contents = query_read_msg.value(4).toString(); message.m_contents = query_read_msg.value(4).toString();
message.m_accountId = const_cast<StandardFeed*>(this)->serviceRoot()->accountId(); message.m_accountId = const_cast<StandardFeed*>(this)->serviceRoot()->accountId();
message.m_enclosures = Enclosures::decodeEnclosuresFromString(query_read_msg.value(5).toString()); message.m_enclosures = Enclosures::decodeEnclosuresFromString(query_read_msg.value(5).toString());
message.m_id = query_read_msg.value(6).toInt();
messages.append(message); messages.append(message);
} }

View file

@ -504,16 +504,16 @@ bool StandardServiceRoot::loadMessagesForItem(RootItem *item, QSqlTableModel *mo
return true; return true;
} }
bool StandardServiceRoot::onBeforeSetMessagesRead(RootItem *selected_item, QList<int> message_db_ids, RootItem::ReadStatus read) { bool StandardServiceRoot::onBeforeSetMessagesRead(RootItem *selected_item, const QList<Message> &messages, RootItem::ReadStatus read) {
Q_UNUSED(message_db_ids) Q_UNUSED(messages)
Q_UNUSED(read) Q_UNUSED(read)
Q_UNUSED(selected_item) Q_UNUSED(selected_item)
return true; return true;
} }
bool StandardServiceRoot::onAfterSetMessagesRead(RootItem *selected_item, QList<int> message_db_ids, RootItem::ReadStatus read) { bool StandardServiceRoot::onAfterSetMessagesRead(RootItem *selected_item, const QList<Message> &messages, RootItem::ReadStatus read) {
Q_UNUSED(message_db_ids) Q_UNUSED(messages)
Q_UNUSED(read) Q_UNUSED(read)
selected_item->updateCounts(false); selected_item->updateCounts(false);

View file

@ -63,8 +63,8 @@ class StandardServiceRoot : public ServiceRoot {
// Message stuff. // Message stuff.
bool loadMessagesForItem(RootItem *item, QSqlTableModel *model); bool loadMessagesForItem(RootItem *item, QSqlTableModel *model);
bool onBeforeSetMessagesRead(RootItem *selected_item, QList<int> message_db_ids, ReadStatus read); bool onBeforeSetMessagesRead(RootItem *selected_item, const QList<Message> &messages, ReadStatus read);
bool onAfterSetMessagesRead(RootItem *selected_item, QList<int> message_db_ids, ReadStatus read); bool onAfterSetMessagesRead(RootItem *selected_item, const QList<Message> &messages, ReadStatus read);
bool onBeforeSwitchMessageImportance(RootItem *selected_item, QList<QPair<int,RootItem::Importance> > changes); bool onBeforeSwitchMessageImportance(RootItem *selected_item, QList<QPair<int,RootItem::Importance> > changes);
bool onAfterSwitchMessageImportance(RootItem *selected_item, QList<QPair<int,RootItem::Importance> > changes); bool onAfterSwitchMessageImportance(RootItem *selected_item, QList<QPair<int,RootItem::Importance> > changes);

View file

@ -171,6 +171,44 @@ TtRssGetHeadlinesResponse TtRssNetworkFactory::getHeadlines(int feed_id, bool fo
return result; return result;
} }
TtRssUpdateArticleResponse TtRssNetworkFactory::updateArticles(const QList<int> &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<int> &ids) {
QStringList strings;
foreach (int id, ids) {
strings.append(QString::number(id));
}
return strings.join(QL1C(','));
}
TtRssResponse::TtRssResponse(const QString &raw_content) { TtRssResponse::TtRssResponse(const QString &raw_content) {
m_rawContent = QtJson::parse(raw_content).toMap(); m_rawContent = QtJson::parse(raw_content).toMap();
} }
@ -255,7 +293,7 @@ TtRssGetFeedsCategoriesResponse::TtRssGetFeedsCategoriesResponse(const QString &
TtRssGetFeedsCategoriesResponse::~TtRssGetFeedsCategoriesResponse() { 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(); RootItem *parent = new RootItem();
// Chop the "api/" from the end of the address. // Chop the "api/" from the end of the address.
@ -342,7 +380,7 @@ TtRssGetHeadlinesResponse::TtRssGetHeadlinesResponse(const QString &raw_content)
TtRssGetHeadlinesResponse::~TtRssGetHeadlinesResponse() { TtRssGetHeadlinesResponse::~TtRssGetHeadlinesResponse() {
} }
QList<Message> TtRssGetHeadlinesResponse::messages() { QList<Message> TtRssGetHeadlinesResponse::messages() const {
QList<Message> messages; QList<Message> messages;
foreach (QVariant item, m_rawContent["content"].toList()) { foreach (QVariant item, m_rawContent["content"].toList()) {
@ -380,3 +418,28 @@ QList<Message> TtRssGetHeadlinesResponse::messages() {
return 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;
}
}

View file

@ -63,7 +63,7 @@ class TtRssGetFeedsCategoriesResponse : public TtRssResponse {
// Returns tree of feeds/categories. // Returns tree of feeds/categories.
// Top-level root of the tree is not needed here. // Top-level root of the tree is not needed here.
// Returned items do not have primary IDs assigned. // 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 { class TtRssGetHeadlinesResponse : public TtRssResponse {
@ -71,9 +71,32 @@ class TtRssGetHeadlinesResponse : public TtRssResponse {
explicit TtRssGetHeadlinesResponse(const QString &raw_content = QString()); explicit TtRssGetHeadlinesResponse(const QString &raw_content = QString());
virtual ~TtRssGetHeadlinesResponse(); virtual ~TtRssGetHeadlinesResponse();
QList<Message> messages(); QList<Message> 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 { class TtRssNetworkFactory {
public: public:
explicit TtRssNetworkFactory(); explicit TtRssNetworkFactory();
@ -107,7 +130,12 @@ class TtRssNetworkFactory {
bool show_content, bool include_attachments, bool show_content, bool include_attachments,
bool sanitize, QNetworkReply::NetworkError &error); bool sanitize, QNetworkReply::NetworkError &error);
private: TtRssUpdateArticleResponse updateArticles(const QList<int> &ids, UpdateArticle::OperatingField field,
UpdateArticle::Mode mode, QNetworkReply::NetworkError &error);
private:
QString encodeArticleIds(const QList<int> &ids);
QString m_url; QString m_url;
QString m_username; QString m_username;
QString m_password; QString m_password;

View file

@ -118,7 +118,7 @@ QList<Message> TtRssFeed::undeletedMessages() const {
QSqlQuery query_read_msg(database); QSqlQuery query_read_msg(database);
query_read_msg.setForwardOnly(true); 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 " "FROM Messages "
"WHERE is_deleted = 0 AND feed = :feed AND account_id = :account_id;"); "WHERE is_deleted = 0 AND feed = :feed AND account_id = :account_id;");
@ -140,6 +140,7 @@ QList<Message> TtRssFeed::undeletedMessages() const {
message.m_enclosures = Enclosures::decodeEnclosuresFromString(query_read_msg.value(5).toString()); message.m_enclosures = Enclosures::decodeEnclosuresFromString(query_read_msg.value(5).toString());
message.m_accountId = account_id; message.m_accountId = account_id;
message.m_customId = query_read_msg.value(6).toString(); message.m_customId = query_read_msg.value(6).toString();
message.m_id = query_read_msg.value(7).toInt();
messages.append(message); messages.append(message);
} }

View file

@ -24,6 +24,7 @@
#include "services/tt-rss/ttrssserviceentrypoint.h" #include "services/tt-rss/ttrssserviceentrypoint.h"
#include "services/tt-rss/ttrssfeed.h" #include "services/tt-rss/ttrssfeed.h"
#include "services/tt-rss/ttrsscategory.h" #include "services/tt-rss/ttrsscategory.h"
#include "services/tt-rss/definitions.h"
#include "services/tt-rss/network/ttrssnetworkfactory.h" #include "services/tt-rss/network/ttrssnetworkfactory.h"
#include "services/tt-rss/gui/formeditaccount.h" #include "services/tt-rss/gui/formeditaccount.h"
@ -145,19 +146,25 @@ QList<QAction*> TtRssServiceRoot::contextMenu() {
return serviceMenu(); return serviceMenu();
} }
bool TtRssServiceRoot::onBeforeSetMessagesRead(RootItem *selected_item, QList<int> message_db_ids, RootItem::ReadStatus read) { bool TtRssServiceRoot::onBeforeSetMessagesRead(RootItem *selected_item, const QList<Message> &messages, RootItem::ReadStatus read) {
// TODO: misto čísel primarnich zprav, vracet cele objekty zprav Q_UNUSED(selected_item)
// tedy včetně custom ID, nemusi se tak znova tahat z DB asi?s
// 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. if (error == QNetworkReply::NoError && response.updateStatus() == STATUS_OK && response.articlesUpdated() == messages.size()) {
return true;
return false; }
else {
return false;
}
} }
bool TtRssServiceRoot::onAfterSetMessagesRead(RootItem *selected_item, QList<int> message_db_ids, RootItem::ReadStatus read) { bool TtRssServiceRoot::onAfterSetMessagesRead(RootItem *selected_item, const QList<Message> &messages, RootItem::ReadStatus read) {
Q_UNUSED(message_db_ids) Q_UNUSED(messages)
Q_UNUSED(read) Q_UNUSED(read)
selected_item->updateCounts(false); selected_item->updateCounts(false);
@ -338,6 +345,16 @@ void TtRssServiceRoot::syncIn() {
} }
} }
QList<int> TtRssServiceRoot::customIDsOfMessages(const QList<Message> &messages) {
QList<int> list;
foreach (const Message &message, messages) {
list.append(message.m_customId.toInt());
}
return list;
}
QStringList TtRssServiceRoot::textualFeedIds(const QList<Feed*> &feeds) { QStringList TtRssServiceRoot::textualFeedIds(const QList<Feed*> &feeds) {
QStringList stringy_ids; QStringList stringy_ids;
stringy_ids.reserve(feeds.size()); stringy_ids.reserve(feeds.size());

View file

@ -54,8 +54,8 @@ class TtRssServiceRoot : public ServiceRoot {
bool loadMessagesForItem(RootItem *item, QSqlTableModel *model); bool loadMessagesForItem(RootItem *item, QSqlTableModel *model);
bool onBeforeSetMessagesRead(RootItem *selected_item, QList<int> message_db_ids, ReadStatus read); bool onBeforeSetMessagesRead(RootItem *selected_item, const QList<Message> &messages, ReadStatus read);
bool onAfterSetMessagesRead(RootItem *selected_item, QList<int> message_db_ids, ReadStatus read); bool onAfterSetMessagesRead(RootItem *selected_item, const QList<Message> &messages, ReadStatus read);
bool onBeforeSwitchMessageImportance(RootItem *selected_item, QList<QPair<int,RootItem::Importance> > changes); bool onBeforeSwitchMessageImportance(RootItem *selected_item, QList<QPair<int,RootItem::Importance> > changes);
bool onAfterSwitchMessageImportance(RootItem *selected_item, QList<QPair<int,RootItem::Importance> > changes); bool onAfterSwitchMessageImportance(RootItem *selected_item, QList<QPair<int,RootItem::Importance> > changes);
@ -77,6 +77,8 @@ class TtRssServiceRoot : public ServiceRoot {
void syncIn(); void syncIn();
private: private:
QList<int> customIDsOfMessages(const QList<Message> &messages);
// Returns converted ids of given feeds // Returns converted ids of given feeds
// which are suitable as IN clause for SQL queries. // which are suitable as IN clause for SQL queries.
QStringList textualFeedIds(const QList<Feed*> &feeds); QStringList textualFeedIds(const QList<Feed*> &feeds);