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:
parent
93721ac9e0
commit
ed56e7581b
@ -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));
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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..."));
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
|
@ -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));
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user