Work on labels, huge changes in plugin-base code, also working sync-in common logic, working label obtaining for TT-RSS.

This commit is contained in:
Martin Rotter 2020-10-18 14:27:22 +02:00
parent 93721ac9e0
commit ed56e7581b
12 changed files with 209 additions and 33 deletions

View File

@ -205,7 +205,7 @@ void MessagePreviewer::updateLabels(bool only_clear) {
return;
}
if (m_root.data() != nullptr) {
if (m_root.data() != nullptr && m_root.data()->getParentServiceRoot()->labelsNode()->labels().size() > 0) {
m_separator = m_toolBar->addSeparator();
QSqlDatabase database = qApp->database()->connection(metaObject()->className());
@ -216,7 +216,7 @@ void MessagePreviewer::updateLabels(bool only_clear) {
btn_label->setCheckable(true);
btn_label->setIcon(Label::generateIcon(label->color()));
btn_label->setAutoRaise(false);
btn_label->setText(label->title());
btn_label->setText(QSL(" ") + label->title());
btn_label->setToolButtonStyle(Qt::ToolButtonStyle::ToolButtonTextBesideIcon);
btn_label->setChecked(DatabaseQueries::isLabelAssignedToMessage(database, label, m_message));

View File

@ -152,7 +152,7 @@ void MessagesView::setupAppearance() {
header()->setDefaultSectionSize(MESSAGES_VIEW_DEFAULT_COL);
header()->setMinimumSectionSize(MESSAGES_VIEW_MINIMUM_COL);
header()->setCascadingSectionResizes(false);
header()->setStretchLastSection(true);
header()->setStretchLastSection(false);
}
void MessagesView::focusInEvent(QFocusEvent* event) {

View File

@ -60,6 +60,8 @@ void DatabaseCleaner::purgeDatabaseData(const CleanerOrders& which_data) {
emit purgeProgress(progress, tr("Starred messages purged..."));
}
result &= DatabaseQueries::purgeLeftoverLabelAssignments(database);
if (which_data.m_shrinkDatabase) {
progress += difference;
emit purgeProgress(progress, tr("Shrinking database file..."));

View File

@ -156,7 +156,9 @@ bool DatabaseQueries::createLabel(const QSqlDatabase& db, Label* label, int acco
// NOTE: This custom ID in this object will be probably
// overwritten in online-synchronized labels.
label->setCustomId(QString::number(label->id()));
if (label->customId().isEmpty()) {
label->setCustomId(QString::number(label->id()));
}
}
// Fixup missing custom IDs.
@ -988,7 +990,9 @@ bool DatabaseQueries::deleteAccount(const QSqlDatabase& db, int account_id) {
<< QSL("DELETE FROM Feeds WHERE account_id = :account_id;")
<< QSL("DELETE FROM Categories WHERE account_id = :account_id;")
<< QSL("DELETE FROM MessageFiltersInFeeds WHERE account_id = :account_id;")
<< QSL("DELETE FROM Accounts WHERE id = :account_id;");
<< QSL("DELETE FROM Accounts WHERE id = :account_id;")
<< QSL("DELETE FROM LabelsInMessages WHERE account_id = :account_id;")
<< QSL("DELETE FROM Labels WHERE account_id = :account_id;");
for (const QString& q : queries) {
query.prepare(q);
@ -1029,6 +1033,17 @@ bool DatabaseQueries::deleteAccountData(const QSqlDatabase& db, int account_id,
q.bindValue(QSL(":account_id"), account_id);
result &= q.exec();
if (delete_messages_too) {
// If we delete message, make sure to delete message/label assignments too.
q.prepare(QSL("DELETE FROM LabelsInMessages WHERE account_id = :account_id;"));
q.bindValue(QSL(":account_id"), account_id);
result &= q.exec();
}
q.prepare(QSL("DELETE FROM Labels WHERE account_id = :account_id;"));
q.bindValue(QSL(":account_id"), account_id);
result &= q.exec();
return result;
}
@ -1118,8 +1133,8 @@ bool DatabaseQueries::purgeLeftoverMessages(const QSqlDatabase& db, int account_
QSqlQuery q(db);
q.setForwardOnly(true);
q.prepare(
QSL("DELETE FROM Messages WHERE account_id = :account_id AND feed NOT IN (SELECT custom_id FROM Feeds WHERE account_id = :account_id);"));
q.prepare(QSL("DELETE FROM Messages "
"WHERE account_id = :account_id AND feed NOT IN (SELECT custom_id FROM Feeds WHERE account_id = :account_id);"));
q.bindValue(QSL(":account_id"), account_id);
if (!q.exec()) {
@ -1134,6 +1149,52 @@ bool DatabaseQueries::purgeLeftoverMessages(const QSqlDatabase& db, int account_
}
}
bool DatabaseQueries::purgeLeftoverLabelAssignments(const QSqlDatabase& db, int account_id) {
QSqlQuery q(db);
bool succ = false;
if (account_id <= 0) {
succ = q.exec(QSL("DELETE FROM LabelsInMessages "
"WHERE NOT EXISTS (SELECT * FROM Messages WHERE Messages.account_id = LabelsInMessages.account_id AND Messages.custom_id = LabelsInMessages.message);"))
&&
q.exec(QSL("DELETE FROM LabelsInMessages "
"WHERE NOT EXISTS (SELECT * FROM Labels WHERE Labels.account_id = LabelsInMessages.account_id AND Labels.custom_id = LabelsInMessages.label);"));
}
else {
q.prepare(QSL("DELETE FROM LabelsInMessages "
"WHERE account_id = :account_id AND "
" (message NOT IN (SELECT custom_id FROM Messages WHERE account_id = :account_id) OR "
" label NOT IN (SELECT custom_id FROM Labels WHERE account_id = :account_id));"));
q.bindValue(QSL(":account_id"), account_id);
succ = q.exec();
}
if (!succ) {
auto xx = q.lastError().text();
qWarningNN << LOGSEC_DB
<< "Removing of leftover label assignments failed: '"
<< q.lastError().text()
<< "'.";
}
return succ;
}
bool DatabaseQueries::purgeLabelsAndLabelAssignments(const QSqlDatabase& db, int account_id) {
QSqlQuery q(db);
q.prepare(QSL("DELETE FROM LabelsInMessages WHERE account_id = :account_id;"));
q.bindValue(QSL(":account_id"), account_id);
auto succ = q.exec();
q.prepare(QSL("DELETE FROM Labels WHERE account_id = :account_id;"));
q.bindValue(QSL(":account_id"), account_id);
succ &= q.exec();
return succ;
}
bool DatabaseQueries::storeAccountTree(const QSqlDatabase& db, RootItem* tree_root, int account_id) {
QSqlQuery query_category(db);
QSqlQuery query_feed(db);
@ -1179,6 +1240,16 @@ bool DatabaseQueries::storeAccountTree(const QSqlDatabase& db, RootItem* tree_ro
return false;
}
}
else if (child->kind() == RootItem::Kind::Labels) {
// Add all labels.
for (RootItem* lbl : child->childItems()) {
Label* label = lbl->toLabel();
if (!createLabel(db, label, account_id)) {
return false;
}
}
}
}
return true;

View File

@ -47,6 +47,11 @@ class DatabaseQueries {
static bool purgeMessagesFromBin(const QSqlDatabase& db, bool clear_only_read, int account_id);
static bool purgeLeftoverMessages(const QSqlDatabase& db, int account_id);
// Purges message/label assignments where source message or label does not exist.
// If account ID smaller than 0 is passed, then do this for all accounts.
static bool purgeLeftoverLabelAssignments(const QSqlDatabase& db, int account_id = -1);
static bool purgeLabelsAndLabelAssignments(const QSqlDatabase& db, int account_id);
// Counts of unread/all messages.
static QMap<QString, QPair<int, int>> getMessageCountsForCategory(const QSqlDatabase& db, const QString& custom_id,
int account_id, bool only_total_counts,

View File

@ -7,6 +7,7 @@
#include "miscellaneous/iconfactory.h"
#include "services/abstract/category.h"
#include "services/abstract/feed.h"
#include "services/abstract/label.h"
#include "services/abstract/recyclebin.h"
#include "services/abstract/serviceroot.h"
@ -14,7 +15,7 @@
RootItem::RootItem(RootItem* parent_item)
: QObject(nullptr), m_kind(RootItem::Kind::Root), m_id(NO_PARENT_CATEGORY), m_customId(QL1S("")),
m_title(QString()), m_description(QString()), m_keepOnTop(false), m_parentItem(parent_item) {}
m_title(QString()), m_description(QString()), m_keepOnTop(false), m_childItems(QList<RootItem*>()), m_parentItem(parent_item) {}
RootItem::RootItem(const RootItem& other) : RootItem(nullptr) {
setTitle(other.title());
@ -479,6 +480,10 @@ Feed* RootItem::toFeed() const {
return dynamic_cast<Feed*>(const_cast<RootItem*>(this));
}
Label* RootItem::toLabel() const {
return dynamic_cast<Label*>(const_cast<RootItem*>(this));
}
ServiceRoot* RootItem::toServiceRoot() const {
return dynamic_cast<ServiceRoot*>(const_cast<RootItem*>(this));
}

View File

@ -11,6 +11,7 @@
class Category;
class Feed;
class Label;
class ServiceRoot;
class QAction;
@ -173,6 +174,7 @@ class RSSGUARD_DLLSPEC RootItem : public QObject {
// Converters
Category* toCategory() const;
Feed* toFeed() const;
Label* toLabel() const;
ServiceRoot* toServiceRoot() const;
bool keepOnTop() const;

View File

@ -141,8 +141,8 @@ void ServiceRoot::updateCounts(bool including_total_count) {
void ServiceRoot::completelyRemoveAllData() {
// Purge old data from SQL and clean all model items.
removeOldAccountFromDatabase(true);
cleanAllItemsFromModel();
removeOldAccountFromDatabase(true);
updateCounts(true);
itemChanged(QList<RootItem*>() << this);
requestReloadMessageList(true);
@ -156,10 +156,18 @@ void ServiceRoot::removeOldAccountFromDatabase(bool including_messages) {
void ServiceRoot::cleanAllItemsFromModel() {
for (RootItem* top_level_item : childItems()) {
if (top_level_item->kind() != RootItem::Kind::Bin && top_level_item->kind() != RootItem::Kind::Important) {
if (top_level_item->kind() != RootItem::Kind::Bin &&
top_level_item->kind() != RootItem::Kind::Important &&
top_level_item->kind() != RootItem::Kind::Labels) {
requestItemRemoval(top_level_item);
}
}
if (labelsNode() != nullptr) {
for (RootItem* lbl : labelsNode()->childItems()) {
requestItemRemoval(lbl);
}
}
}
bool ServiceRoot::cleanFeeds(QList<Feed*> items, bool clean_read_only) {
@ -199,23 +207,6 @@ bool ServiceRoot::cleanFeeds(QList<Feed*> items, bool clean_read_only) {
void ServiceRoot::storeNewFeedTree(RootItem* root) {
DatabaseQueries::storeAccountTree(qApp->database()->connection(metaObject()->className()), root, accountId());
/*if (DatabaseQueries::storeAccountTree(database, root, accountId())) {
RecycleBin* bin = recycleBin();
if (bin != nullptr && !childItems().contains(bin)) {
// As the last item, add recycle bin, which is needed.
appendChild(bin);
bin->updateCounts(true);
}
ImportantNode* imp = importantNode();
if (imp != nullptr && !childItems().contains(imp)) {
appendChild(imp);
imp->updateCounts(true);
}
}*/
}
void ServiceRoot::removeLeftOverMessages() {
@ -230,6 +221,12 @@ void ServiceRoot::removeLeftOverMessageFilterAssignments() {
DatabaseQueries::purgeLeftoverMessageFilterAssignments(database, accountId());
}
void ServiceRoot::removeLeftOverMessageLabelAssignments() {
QSqlDatabase database = qApp->database()->connection(metaObject()->className());
DatabaseQueries::purgeLeftoverLabelAssignments(database, accountId());
}
QList<Message> ServiceRoot::undeletedMessages() const {
QSqlDatabase database = qApp->database()->connection(metaObject()->className());
@ -345,10 +342,22 @@ void ServiceRoot::syncIn() {
// so remove left over messages and filter assignments.
removeLeftOverMessages();
removeLeftOverMessageFilterAssignments();
removeLeftOverMessageLabelAssignments();
for (RootItem* top_level_item : new_tree->childItems()) {
top_level_item->setParent(nullptr);
requestItemReassignment(top_level_item, this);
if (top_level_item->kind() != Kind::Labels) {
top_level_item->setParent(nullptr);
requestItemReassignment(top_level_item, this);
}
else {
// It seems that some labels got synced-in.
if (labelsNode() != nullptr) {
for (RootItem* new_lbl : top_level_item->childItems()) {
new_lbl->setParent(nullptr);
requestItemReassignment(new_lbl, labelsNode());
}
}
}
}
new_tree->clearChildren();

View File

@ -191,6 +191,11 @@ class ServiceRoot : public RootItem {
// from another machine and then performs sync-in on this machine.
void removeLeftOverMessageFilterAssignments();
// Removes all labels/message assignments which are
// assigned to non-existing messages or which are
// assigned from non-existing labels.
void removeLeftOverMessageLabelAssignments();
QStringList textualFeedUrls(const QList<Feed*>& feeds) const;
QStringList textualFeedIds(const QList<Feed*>& feeds) const;
QStringList customIDsOfMessages(const QList<ImportanceChange>& changes);

View File

@ -8,6 +8,7 @@
#include "miscellaneous/textfactory.h"
#include "network-web/networkfactory.h"
#include "services/abstract/category.h"
#include "services/abstract/label.h"
#include "services/abstract/rootitem.h"
#include "services/tt-rss/definitions.h"
#include "services/tt-rss/ttrssfeed.h"
@ -150,6 +151,47 @@ TtRssResponse TtRssNetworkFactory::logout() {
}
}
TtRssGetLabelsResponse TtRssNetworkFactory::getLabels() {
QJsonObject json;
json["op"] = QSL("getLabels");
json["sid"] = m_sessionId;
const int timeout = qApp->settings()->value(GROUP(Feeds), SETTING(Feeds::UpdateTimeout)).toInt();
QByteArray result_raw;
QList<QPair<QByteArray, QByteArray>> headers;
headers << QPair<QByteArray, QByteArray>(HTTP_HEADERS_CONTENT_TYPE, TTRSS_CONTENT_TYPE_JSON);
headers << NetworkFactory::generateBasicAuthHeader(m_authUsername, m_authPassword);
NetworkResult network_reply = NetworkFactory::performNetworkOperation(m_fullUrl, timeout,
QJsonDocument(json).toJson(QJsonDocument::Compact),
result_raw,
QNetworkAccessManager::PostOperation,
headers);
TtRssGetLabelsResponse result(QString::fromUtf8(result_raw));
if (result.isNotLoggedIn()) {
// We are not logged in.
login();
json["sid"] = m_sessionId;
network_reply = NetworkFactory::performNetworkOperation(m_fullUrl, timeout, QJsonDocument(json).toJson(QJsonDocument::Compact),
result_raw,
QNetworkAccessManager::PostOperation,
headers);
result = TtRssGetLabelsResponse(QString::fromUtf8(result_raw));
}
if (network_reply.first != QNetworkReply::NoError) {
qWarningNN << LOGSEC_TTRSS
<< "getLabels failed with error:"
<< QUOTE_W_SPACE_DOT(network_reply.first);
}
m_lastError = network_reply.first;
return result;
}
TtRssGetFeedsCategoriesResponse TtRssNetworkFactory::getFeedsCategories() {
QJsonObject json;
@ -497,6 +539,7 @@ bool TtRssResponse::hasError() const {
TtRssGetFeedsCategoriesResponse::TtRssGetFeedsCategoriesResponse(const QString& raw_content) : TtRssResponse(raw_content) {}
TtRssGetFeedsCategoriesResponse::~TtRssGetFeedsCategoriesResponse() = default;
RootItem* TtRssGetFeedsCategoriesResponse::feedsCategories(bool obtain_icons, QString base_address) const {
auto* parent = new RootItem();
@ -675,3 +718,19 @@ QString TtRssUnsubscribeFeedResponse::code() const {
return QString();
}
TtRssGetLabelsResponse::TtRssGetLabelsResponse(const QString& raw_content) : TtRssResponse(raw_content) {}
QList<RootItem*> TtRssGetLabelsResponse::labels() const {
QList<RootItem*> labels;
for (const QJsonValue& lbl_val : m_rawContent["content"].toArray()) {
QJsonObject lbl_obj = lbl_val.toObject();
Label* lbl = new Label(lbl_obj["caption"].toString(), QColor(lbl_obj["fg_color"].toString()));
lbl->setCustomId(QString::number(lbl_obj["id"].toInt()));
labels.append(lbl);
}
return labels;
}

View File

@ -11,8 +11,8 @@
#include <QString>
class RootItem;
class TtRssFeed;
class Label;
class TtRssResponse {
public:
@ -41,6 +41,13 @@ class TtRssLoginResponse : public TtRssResponse {
QString sessionId() const;
};
class TtRssGetLabelsResponse : public TtRssResponse {
public:
explicit TtRssGetLabelsResponse(const QString& raw_content = QString());
QList<RootItem*> labels() const;
};
class TtRssGetFeedsCategoriesResponse : public TtRssResponse {
public:
explicit TtRssGetFeedsCategoriesResponse(const QString& raw_content = QString());
@ -139,6 +146,9 @@ class TtRssNetworkFactory {
// Logs user out.
TtRssResponse logout();
// Gets list of labels from the server.
TtRssGetLabelsResponse getLabels();
// Gets feeds from the server.
TtRssGetFeedsCategoriesResponse getFeedsCategories();

View File

@ -10,6 +10,7 @@
#include "miscellaneous/textfactory.h"
#include "network-web/networkfactory.h"
#include "services/abstract/importantnode.h"
#include "services/abstract/labelsnode.h"
#include "services/abstract/recyclebin.h"
#include "services/tt-rss/definitions.h"
#include "services/tt-rss/gui/formeditttrssaccount.h"
@ -36,7 +37,7 @@ void TtRssServiceRoot::start(bool freshly_activated) {
loadFromDatabase();
loadCacheFromFile(accountId());
if (childCount() <= 2) {
if (childCount() <= 3) {
syncIn();
}
}
@ -222,10 +223,17 @@ void TtRssServiceRoot::updateTitle() {
}
RootItem* TtRssServiceRoot::obtainNewTreeForSyncIn() const {
TtRssGetFeedsCategoriesResponse feed_cats_response = m_network->getFeedsCategories();
TtRssGetFeedsCategoriesResponse feed_cats = m_network->getFeedsCategories();
TtRssGetLabelsResponse labels = m_network->getLabels();
if (m_network->lastError() == QNetworkReply::NoError) {
return feed_cats_response.feedsCategories(true, m_network->url());
auto* tree = feed_cats.feedsCategories(true, m_network->url());
auto* lblroot = new LabelsNode(tree);
lblroot->setChildItems(labels.labels());
tree->appendChild(lblroot);
return tree;
}
else {
return nullptr;