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() {
m_title = m_url = m_author = m_contents = m_feedId = m_customId = "";
m_enclosures = QList<Enclosure>();
m_accountId = 0;
m_accountId = m_id = 0;
m_isRead = m_isImportant = false;
}

View File

@ -51,6 +51,7 @@ class Message {
QDateTime m_created;
QString m_feedId;
int m_accountId;
int m_id;
QString m_customId;
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_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<qint64>()).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<int>() << message_id, read)) {
if (!m_selectedItem->getParentServiceRoot()->onBeforeSetMessagesRead(m_selectedItem, QList<Message>() << 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<int>() << message_id, read);
return m_selectedItem->getParentServiceRoot()->onAfterSetMessagesRead(m_selectedItem, QList<Message>() << 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<int> message_ids_num;
QList<Message> 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;

View File

@ -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.

View File

@ -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();

View File

@ -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();

View File

@ -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<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,
// 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<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,
// 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);
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<Message> StandardFeed::undeletedMessages() const {
message.m_contents = query_read_msg.value(4).toString();
message.m_accountId = const_cast<StandardFeed*>(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);
}

View File

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

View File

@ -63,8 +63,8 @@ class StandardServiceRoot : public ServiceRoot {
// Message stuff.
bool loadMessagesForItem(RootItem *item, QSqlTableModel *model);
bool onBeforeSetMessagesRead(RootItem *selected_item, QList<int> message_db_ids, ReadStatus read);
bool onAfterSetMessagesRead(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, const QList<Message> &messages, ReadStatus read);
bool onBeforeSwitchMessageImportance(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;
}
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) {
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<Message> TtRssGetHeadlinesResponse::messages() {
QList<Message> TtRssGetHeadlinesResponse::messages() const {
QList<Message> messages;
foreach (QVariant item, m_rawContent["content"].toList()) {
@ -380,3 +418,28 @@ QList<Message> 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;
}
}

View File

@ -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<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 {
public:
explicit TtRssNetworkFactory();
@ -107,7 +130,12 @@ class TtRssNetworkFactory {
bool show_content, bool include_attachments,
bool sanitize, QNetworkReply::NetworkError &error);
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_username;
QString m_password;

View File

@ -118,7 +118,7 @@ QList<Message> 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<Message> 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);
}

View File

@ -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<QAction*> TtRssServiceRoot::contextMenu() {
return serviceMenu();
}
bool TtRssServiceRoot::onBeforeSetMessagesRead(RootItem *selected_item, QList<int> 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<Message> &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<int> message_db_ids, RootItem::ReadStatus read) {
Q_UNUSED(message_db_ids)
bool TtRssServiceRoot::onAfterSetMessagesRead(RootItem *selected_item, const QList<Message> &messages, RootItem::ReadStatus read) {
Q_UNUSED(messages)
Q_UNUSED(read)
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 stringy_ids;
stringy_ids.reserve(feeds.size());

View File

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