From 683517948d84c229ba2201dfaf82aae03daabfeb Mon Sep 17 00:00:00 2001 From: Martin Rotter <rotter.martinos@gmail.com> Date: Tue, 8 Dec 2015 09:11:55 +0100 Subject: [PATCH 01/11] Many changes, working Sync in, fixed some problems with item expanding, items now can inform model that it wants some items expanded or not. --- resources/misc/db_init_mysql.sql | 10 +- resources/misc/db_init_sqlite.sql | 10 +- resources/misc/db_update_mysql_3_4.sql | 15 +++ resources/misc/db_update_sqlite_3_4.sql | 50 ++++++++ src/core/feedsmodel.cpp | 1 + src/core/feedsmodel.h | 3 + src/gui/feedsview.cpp | 19 +++- src/gui/feedsview.h | 1 + src/services/abstract/serviceroot.cpp | 6 +- src/services/abstract/serviceroot.h | 5 +- .../standard/standardserviceentrypoint.cpp | 2 - src/services/standard/standardserviceroot.cpp | 2 + src/services/tt-rss/gui/formeditaccount.cpp | 5 +- .../tt-rss/network/ttrssnetworkfactory.cpp | 102 ++++++++++------- .../tt-rss/network/ttrssnetworkfactory.h | 5 +- .../tt-rss/ttrssserviceentrypoint.cpp | 1 - src/services/tt-rss/ttrssserviceroot.cpp | 107 +++++++++++++++++- src/services/tt-rss/ttrssserviceroot.h | 8 +- 18 files changed, 282 insertions(+), 70 deletions(-) diff --git a/resources/misc/db_init_mysql.sql b/resources/misc/db_init_mysql.sql index b3eb5e2cd..d1bbd370d 100644 --- a/resources/misc/db_init_mysql.sql +++ b/resources/misc/db_init_mysql.sql @@ -36,7 +36,7 @@ CREATE TABLE IF NOT EXISTS Categories ( parent_id INTEGER NOT NULL, title VARCHAR(100) NOT NULL CHECK (title != ''), description TEXT, - date_created BIGINT NOT NULL CHECK (date_created != 0), + date_created BIGINT, icon BLOB, account_id INTEGER NOT NULL, custom_id TEXT, @@ -50,17 +50,17 @@ CREATE TABLE IF NOT EXISTS Feeds ( id INTEGER AUTO_INCREMENT PRIMARY KEY, title TEXT NOT NULL CHECK (title != ''), description TEXT, - date_created BIGINT NOT NULL CHECK (date_created != 0), + date_created BIGINT, icon BLOB, category INTEGER NOT NULL CHECK (category >= -1), - encoding TEXT NOT NULL CHECK (encoding != ''), - url VARCHAR(100) NOT NULL CHECK (url != ''), + encoding TEXT, + url VARCHAR(100), protected INTEGER(1) NOT NULL CHECK (protected >= 0 AND protected <= 1), username TEXT, password TEXT, update_type INTEGER(1) NOT NULL CHECK (update_type >= 0), update_interval INTEGER NOT NULL DEFAULT 15 CHECK (update_interval >= 5), - type INTEGER NOT NULL CHECK (type >= 0), + type INTEGER, account_id INTEGER NOT NULL, custom_id TEXT, diff --git a/resources/misc/db_init_sqlite.sql b/resources/misc/db_init_sqlite.sql index 80a091205..a0e357296 100644 --- a/resources/misc/db_init_sqlite.sql +++ b/resources/misc/db_init_sqlite.sql @@ -31,7 +31,7 @@ CREATE TABLE IF NOT EXISTS Categories ( parent_id INTEGER NOT NULL, title TEXT NOT NULL CHECK (title != ''), description TEXT, - date_created INTEGER NOT NULL CHECK (date_created != 0), + date_created INTEGER, icon BLOB, account_id INTEGER NOT NULL, custom_id TEXT, @@ -45,17 +45,17 @@ CREATE TABLE IF NOT EXISTS Feeds ( id INTEGER PRIMARY KEY, title TEXT NOT NULL CHECK (title != ''), description TEXT, - date_created INTEGER NOT NULL CHECK (date_created != 0), + date_created INTEGER, icon BLOB, category INTEGER NOT NULL CHECK (category >= -1), - encoding TEXT NOT NULL CHECK (encoding != ''), - url TEXT NOT NULL CHECK (url != ''), + encoding TEXT, + url TEXT, protected INTEGER(1) NOT NULL CHECK (protected >= 0 AND protected <= 1), username TEXT, password TEXT, update_type INTEGER(1) NOT NULL CHECK (update_type >= 0), update_interval INTEGER NOT NULL CHECK (update_interval >= 5) DEFAULT 15, - type INTEGER NOT NULL CHECK (type >= 0), + type INTEGER, account_id INTEGER NOT NULL, custom_id TEXT, diff --git a/resources/misc/db_update_mysql_3_4.sql b/resources/misc/db_update_mysql_3_4.sql index 892d54579..25038fd1e 100644 --- a/resources/misc/db_update_mysql_3_4.sql +++ b/resources/misc/db_update_mysql_3_4.sql @@ -40,4 +40,19 @@ DROP FOREIGN KEY feed; ALTER TABLE Messages MODIFY Feeds TEXT; -- ! +ALTER TABLE Feeds +MODIFY date_created BIGINT; +-- ! +ALTER TABLE Feeds +MODIFY encoding TEXT; +-- ! +ALTER TABLE Feeds +MODIFY url VARCHAR(100); +-- ! +ALTER TABLE Feeds +MODIFY type INTEGER; +-- ! +ALTER TABLE Categories +MODIFY date_created BIGINT; +-- ! UPDATE Information SET inf_value = '4' WHERE inf_key = 'schema_version'; \ No newline at end of file diff --git a/resources/misc/db_update_sqlite_3_4.sql b/resources/misc/db_update_sqlite_3_4.sql index 6a6433312..6765c6491 100644 --- a/resources/misc/db_update_sqlite_3_4.sql +++ b/resources/misc/db_update_sqlite_3_4.sql @@ -61,4 +61,54 @@ INSERT INTO Messages SELECT * FROM backup_Messages; -- ! DROP TABLE backup_Messages; -- ! +CREATE TABLE backup_Feeds AS SELECT * FROM Feeds; +-- ! +DROP TABLE Feeds; +-- ! +CREATE TABLE Feeds ( + id INTEGER PRIMARY KEY, + title TEXT NOT NULL CHECK (title != ''), + description TEXT, + date_created INTEGER, + icon BLOB, + category INTEGER NOT NULL CHECK (category >= -1), + encoding TEXT, + url TEXT, + protected INTEGER(1) NOT NULL CHECK (protected >= 0 AND protected <= 1), + username TEXT, + password TEXT, + update_type INTEGER(1) NOT NULL CHECK (update_type >= 0), + update_interval INTEGER NOT NULL CHECK (update_interval >= 5) DEFAULT 15, + type INTEGER, + account_id INTEGER NOT NULL, + custom_id TEXT, + + FOREIGN KEY (account_id) REFERENCES Accounts (id) +); +-- ! +INSERT INTO Feeds SELECT * FROM backup_Feeds; +-- ! +DROP TABLE backup_Feeds; +-- ! +CREATE TABLE backup_Categories AS SELECT * FROM Categories; +-- ! +DROP TABLE Categories; +-- ! +CREATE TABLE Categories ( + id INTEGER PRIMARY KEY, + parent_id INTEGER NOT NULL, + title TEXT NOT NULL CHECK (title != ''), + description TEXT, + date_created INTEGER, + icon BLOB, + account_id INTEGER NOT NULL, + custom_id TEXT, + + FOREIGN KEY (account_id) REFERENCES Accounts (id) +); +-- ! +INSERT INTO Categories SELECT * FROM backup_Categories; +-- ! +DROP TABLE backup_Categories; +-- ! UPDATE Information SET inf_value = '4' WHERE inf_key = 'schema_version'; \ No newline at end of file diff --git a/src/core/feedsmodel.cpp b/src/core/feedsmodel.cpp index ce322eb5e..b6040476e 100755 --- a/src/core/feedsmodel.cpp +++ b/src/core/feedsmodel.cpp @@ -697,6 +697,7 @@ bool FeedsModel::addServiceAccount(ServiceRoot *root) { connect(root, SIGNAL(readFeedsFilterInvalidationRequested()), this, SIGNAL(readFeedsFilterInvalidationRequested())); connect(root, SIGNAL(dataChanged(QList<RootItem*>)), this, SLOT(onItemDataChanged(QList<RootItem*>))); connect(root, SIGNAL(reloadMessageListRequested(bool)), this, SIGNAL(reloadMessageListRequested(bool))); + connect(root, SIGNAL(itemExpandRequested(QList<RootItem*>,bool)), this, SIGNAL(itemExpandRequested(QList<RootItem*>,bool))); root->start(); return true; diff --git a/src/core/feedsmodel.h b/src/core/feedsmodel.h index 83df548e5..1e94efc93 100755 --- a/src/core/feedsmodel.h +++ b/src/core/feedsmodel.h @@ -206,6 +206,9 @@ class FeedsModel : public QAbstractItemModel { // Emitted if counts of messages are changed. void messageCountsChanged(int unread_messages, int total_messages, bool any_feed_has_unread_messages); + // Emitted if any item requested that any view should expand it. + void itemExpandRequested(QList<RootItem*> items, bool expand); + // Emitted when there is a need of reloading of displayed messages. void reloadMessageListRequested(bool mark_selected_messages_read); diff --git a/src/gui/feedsview.cpp b/src/gui/feedsview.cpp index d976b6e84..39a48d211 100755 --- a/src/gui/feedsview.cpp +++ b/src/gui/feedsview.cpp @@ -55,6 +55,7 @@ FeedsView::FeedsView(QWidget *parent) // Connections. connect(m_sourceModel, SIGNAL(requireItemValidationAfterDragDrop(QModelIndex)), this, SLOT(validateItemAfterDragDrop(QModelIndex))); + connect(m_sourceModel, SIGNAL(itemExpandRequested(QList<RootItem*>,bool)), this, SLOT(onItemExpandRequested(QList<RootItem*>,bool))); connect(header(), SIGNAL(sortIndicatorChanged(int,Qt::SortOrder)), this, SLOT(saveSortState(int,Qt::SortOrder))); setModel(m_proxyModel); @@ -102,11 +103,11 @@ void FeedsView::saveExpandedStates() { Settings *settings = qApp->settings(); QList<RootItem*> expandable_items; - expandable_items.append(sourceModel()->rootItem()->getSubTree(RootItemKind::Category)); + expandable_items.append(sourceModel()->rootItem()->getSubTree(RootItemKind::Category | RootItemKind::ServiceRoot)); // Iterate all categories and save their expand statuses. foreach (RootItem *item, expandable_items) { - QString setting_name = QString::number(qHash(item->title())) + QL1S("-") + QString::number(item->id()); + QString setting_name = QString::number(item->kind()) + QL1S("-") + QString::number(qHash(item->title())) + QL1S("-") + QString::number(item->id()); settings->setValue(GROUP(Categories), setting_name, @@ -122,10 +123,10 @@ void FeedsView::loadExpandedStates() { // Iterate all categories and save their expand statuses. foreach (RootItem *item, expandable_items) { - QString setting_name = QString::number(qHash(item->title())) + QL1S("-") + QString::number(item->id()); + QString setting_name = QString::number(item->kind()) + QL1S("-") + QString::number(qHash(item->title())) + QL1S("-") + QString::number(item->id()); setExpanded(model()->mapFromSource(sourceModel()->indexForItem(item)), - settings->value(GROUP(Categories), setting_name, true).toBool()); + settings->value(GROUP(Categories), setting_name, item->childCount() > 0).toBool()); } } @@ -466,3 +467,13 @@ void FeedsView::validateItemAfterDragDrop(const QModelIndex &source_index) { setCurrentIndex(mapped); } } + +void FeedsView::onItemExpandRequested(const QList<RootItem*> &items, bool exp) { + foreach (RootItem *item, items) { + QModelIndex source_index = m_sourceModel->indexForItem(item); + QModelIndex proxy_index = m_proxyModel->mapFromSource(source_index); + + setExpanded(proxy_index, !exp); + setExpanded(proxy_index, exp); + } +} diff --git a/src/gui/feedsview.h b/src/gui/feedsview.h index 05f9f8a31..f80a9a250 100755 --- a/src/gui/feedsview.h +++ b/src/gui/feedsview.h @@ -104,6 +104,7 @@ class FeedsView : public QTreeView { void saveSortState(int column, Qt::SortOrder order); void validateItemAfterDragDrop(const QModelIndex &source_index); + void onItemExpandRequested(const QList<RootItem*> &items, bool exp); private: // Initializes context menus. diff --git a/src/services/abstract/serviceroot.cpp b/src/services/abstract/serviceroot.cpp index 2a804ce18..cd25632b1 100755 --- a/src/services/abstract/serviceroot.cpp +++ b/src/services/abstract/serviceroot.cpp @@ -58,7 +58,7 @@ bool ServiceRoot::deleteViaGui() { return data_removed; } -void ServiceRoot::itemChanged(QList<RootItem*> items) { +void ServiceRoot::itemChanged(const QList<RootItem *> &items) { emit dataChanged(items); } @@ -70,6 +70,10 @@ void ServiceRoot::requestFeedReadFilterReload() { emit readFeedsFilterInvalidationRequested(); } +void ServiceRoot::requestItemExpand(const QList<RootItem *> &items, bool expand) { + emit itemExpandRequested(items, expand); +} + void ServiceRoot::requestItemReassignment(RootItem *item, RootItem *new_parent) { emit itemReassignmentRequested(item, new_parent); } diff --git a/src/services/abstract/serviceroot.h b/src/services/abstract/serviceroot.h index 3dbd13475..30498fc73 100755 --- a/src/services/abstract/serviceroot.h +++ b/src/services/abstract/serviceroot.h @@ -138,10 +138,10 @@ class ServiceRoot : public RootItem { ///////////////////////////////////////// // Obvious methods to wrap signals. - void itemChanged(QList<RootItem*> items); + void itemChanged(const QList<RootItem*> &items); void requestReloadMessageList(bool mark_selected_messages_read); void requestFeedReadFilterReload(); - + void requestItemExpand(const QList<RootItem*> &items, bool expand); void requestItemReassignment(RootItem *item, RootItem *new_parent); void requestItemRemoval(RootItem *item); @@ -154,6 +154,7 @@ class ServiceRoot : public RootItem { void dataChanged(QList<RootItem*> items); void readFeedsFilterInvalidationRequested(); void reloadMessageListRequested(bool mark_selected_messages_read); + void itemExpandRequested(QList<RootItem*> items, bool expand); void itemReassignmentRequested(RootItem *item, RootItem *new_parent); void itemRemovalRequested(RootItem *item); diff --git a/src/services/standard/standardserviceentrypoint.cpp b/src/services/standard/standardserviceentrypoint.cpp index ca3615619..f852f6bb5 100755 --- a/src/services/standard/standardserviceentrypoint.cpp +++ b/src/services/standard/standardserviceentrypoint.cpp @@ -75,7 +75,6 @@ ServiceRoot *StandardServiceEntryPoint::createNewRoot() { SERVICE_CODE_STD_RSS))) { StandardServiceRoot *root = new StandardServiceRoot(); root->setAccountId(id_to_assing); - root->loadFromDatabase(); return root; } else { @@ -93,7 +92,6 @@ QList<ServiceRoot*> StandardServiceEntryPoint::initializeSubtree() { while (query.next()) { StandardServiceRoot *root = new StandardServiceRoot(); root->setAccountId(query.value(0).toInt()); - root->loadFromDatabase(); roots.append(root); } } diff --git a/src/services/standard/standardserviceroot.cpp b/src/services/standard/standardserviceroot.cpp index ecebe24bf..c261155f0 100755 --- a/src/services/standard/standardserviceroot.cpp +++ b/src/services/standard/standardserviceroot.cpp @@ -61,6 +61,8 @@ StandardServiceRoot::~StandardServiceRoot() { } void StandardServiceRoot::start() { + loadFromDatabase(); + if (qApp->isFirstRun()) { if (MessageBox::show(qApp->mainForm(), QMessageBox::Question, QObject::tr("Load initial set of feeds"), tr("You started %1 for the first time, now you can load initial set of feeds.").arg(APP_NAME), diff --git a/src/services/tt-rss/gui/formeditaccount.cpp b/src/services/tt-rss/gui/formeditaccount.cpp index 43068321c..865e2df0e 100755 --- a/src/services/tt-rss/gui/formeditaccount.cpp +++ b/src/services/tt-rss/gui/formeditaccount.cpp @@ -148,7 +148,7 @@ void FormEditAccount::onClickedOk() { m_editableRoot->network()->setUrl(m_ui->m_txtUrl->lineEdit()->text()); m_editableRoot->network()->setUsername(m_ui->m_txtUsername->lineEdit()->text()); m_editableRoot->network()->setPassword(m_ui->m_txtPassword->lineEdit()->text()); - m_editableRoot->saveToDatabase(); + m_editableRoot->saveAccountDataToDatabase(); accept(); } @@ -185,6 +185,9 @@ void FormEditAccount::onUrlChanged() { if (url.isEmpty()) { m_ui->m_txtUrl->setStatus(WidgetWithStatus::Error, tr("URL cannot be empty.")); } + else if (!url.endsWith(QL1S("api/"))) { + m_ui->m_txtUrl->setStatus(WidgetWithStatus::Error, tr("URL must end with \"api/\".")); + } else { m_ui->m_txtUrl->setStatus(WidgetWithStatus::Ok, tr("URL is okay.")); } diff --git a/src/services/tt-rss/network/ttrssnetworkfactory.cpp b/src/services/tt-rss/network/ttrssnetworkfactory.cpp index 6576b9c08..5915f545e 100755 --- a/src/services/tt-rss/network/ttrssnetworkfactory.cpp +++ b/src/services/tt-rss/network/ttrssnetworkfactory.cpp @@ -92,15 +92,22 @@ TtRssLoginResponse TtRssNetworkFactory::login(QNetworkReply::NetworkError &error } TtRssResponse TtRssNetworkFactory::logout(QNetworkReply::NetworkError &error) { - QtJson::JsonObject json; - json["op"] = "logout"; - json["sid"] = m_sessionId; + if (!m_sessionId.isEmpty()) { - QByteArray result_raw; - NetworkResult network_reply = NetworkFactory::uploadData(m_url, DOWNLOAD_TIMEOUT, QtJson::serialize(json), CONTENT_TYPE, result_raw); + QtJson::JsonObject json; + json["op"] = "logout"; + json["sid"] = m_sessionId; - error = network_reply.first; - return TtRssResponse(QString::fromUtf8(result_raw)); + QByteArray result_raw; + NetworkResult network_reply = NetworkFactory::uploadData(m_url, DOWNLOAD_TIMEOUT, QtJson::serialize(json), CONTENT_TYPE, result_raw); + + error = network_reply.first; + return TtRssResponse(QString::fromUtf8(result_raw)); + } + else { + error = QNetworkReply::NoError; + return TtRssResponse(); + } } TtRssGetFeedsCategoriesResponse TtRssNetworkFactory::getFeedsCategories(QNetworkReply::NetworkError &error) { @@ -210,9 +217,12 @@ TtRssGetFeedsCategoriesResponse::TtRssGetFeedsCategoriesResponse(const QString & TtRssGetFeedsCategoriesResponse::~TtRssGetFeedsCategoriesResponse() { } -RootItem *TtRssGetFeedsCategoriesResponse::feedsCategories() { +RootItem *TtRssGetFeedsCategoriesResponse::feedsCategories(bool obtain_icons, QString base_address) { RootItem *parent = new RootItem(); + // Chop the "api/" from the end of the address. + base_address.chop(4); + if (status() == API_STATUS_OK) { // We have data, construct object tree according to data. QList<QVariant> items_to_process = m_rawContent["content"].toMap()["categories"].toMap()["items"].toList(); @@ -227,49 +237,61 @@ RootItem *TtRssGetFeedsCategoriesResponse::feedsCategories() { RootItem *act_parent = pair.first; QMap<QString,QVariant> item = pair.second.toMap(); - if (item.contains("type") && item["type"].toString() == GFT_TYPE_CATEGORY) { - // Add category to the parent, go through children. - int item_bare_id = item["bare_id"].toInt(); + int item_id = item["bare_id"].toInt(); + bool is_category = item.contains("type") && item["type"].toString() == GFT_TYPE_CATEGORY; - if (item_bare_id < 0) { - // Ignore virtual categories or feeds. - continue; - } + if (item_id >= 0) { + if (is_category) { + if (item_id == 0) { + // This is "Uncategorized" category, all its feeds belong to top-level root. + if (item.contains("items")) { + foreach (QVariant child_feed, item["items"].toList()) { + pairs.append(QPair<RootItem*,QVariant>(parent, child_feed)); + } + } + } + else { + TtRssCategory *category = new TtRssCategory(); - if (item_bare_id == 0) { - // This is "Uncategorized" category, all its feeds belong to total parent. - if (item.contains("items")) { - foreach (QVariant child_feed, item["items"].toList()) { - pairs.append(QPair<RootItem*,QVariant>(parent, child_feed)); + category->setIcon(qApp->icons()->fromTheme(QSL("folder-category"))); + category->setTitle(item["name"].toString()); + category->setCustomId(item_id); + act_parent->appendChild(category); + + if (item.contains("items")) { + foreach (QVariant child, item["items"].toList()) { + pairs.append(QPair<RootItem*,QVariant>(category, child)); + } } } } - else if (item_bare_id > 0) { - TtRssCategory *category = new TtRssCategory(); + else { + // We have feed. + TtRssFeed *feed = new TtRssFeed(); - category->setIcon(qApp->icons()->fromTheme(QSL("folder-category"))); - category->setTitle(item["name"].toString()); - category->setCustomId(item_bare_id); - act_parent->appendChild(category); + if (obtain_icons) { + QString icon_path = item["icon"].type() == QVariant::String ? item["icon"].toString() : QString(); - if (item.contains("items")) { - foreach (QVariant child, item["items"].toList()) { - pairs.append(QPair<RootItem*,QVariant>(category, child)); + if (!icon_path.isEmpty()) { + // Chop the "api/" suffix out and append + QString full_icon_address = base_address + QL1C('/') + icon_path; + QByteArray icon_data; + + if (NetworkFactory::downloadFile(full_icon_address, DOWNLOAD_TIMEOUT, icon_data).first == QNetworkReply::NoError) { + // Icon downloaded, set it up. + QPixmap icon_pixmap; + icon_pixmap.loadFromData(icon_data); + feed->setIcon(QIcon(icon_pixmap)); + } } } + + // TODO: stahnout a nastavit ikonu + feed->setTitle(item["name"].toString()); + feed->setCustomId(item_id); + act_parent->appendChild(feed); } } - else { - // We have feed. - int item_bare_id = item["bare_id"].toInt(); - TtRssFeed *feed = new TtRssFeed(); - - // TODO: stahnout a nastavit ikonu - feed->setTitle(item["name"].toString()); - feed->setCustomId(item_bare_id); - act_parent->appendChild(feed); - } - } } diff --git a/src/services/tt-rss/network/ttrssnetworkfactory.h b/src/services/tt-rss/network/ttrssnetworkfactory.h index 5bc259209..8e76f3b09 100755 --- a/src/services/tt-rss/network/ttrssnetworkfactory.h +++ b/src/services/tt-rss/network/ttrssnetworkfactory.h @@ -58,7 +58,10 @@ class TtRssGetFeedsCategoriesResponse : public TtRssResponse { explicit TtRssGetFeedsCategoriesResponse(const QString &raw_content = QString()); virtual ~TtRssGetFeedsCategoriesResponse(); - RootItem *feedsCategories(); + // 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()); }; class TtRssNetworkFactory { diff --git a/src/services/tt-rss/ttrssserviceentrypoint.cpp b/src/services/tt-rss/ttrssserviceentrypoint.cpp index 02c03c88a..6af16e2a8 100755 --- a/src/services/tt-rss/ttrssserviceentrypoint.cpp +++ b/src/services/tt-rss/ttrssserviceentrypoint.cpp @@ -87,7 +87,6 @@ QList<ServiceRoot*> TtRssServiceEntryPoint::initializeSubtree() { root->network()->setPassword(query.value(2).toString()); root->network()->setUrl(query.value(3).toString()); root->updateTitle(); - root->loadFromDatabase(); roots.append(root); } } diff --git a/src/services/tt-rss/ttrssserviceroot.cpp b/src/services/tt-rss/ttrssserviceroot.cpp index 48575f313..bffdd96e0 100755 --- a/src/services/tt-rss/ttrssserviceroot.cpp +++ b/src/services/tt-rss/ttrssserviceroot.cpp @@ -21,6 +21,8 @@ #include "miscellaneous/settings.h" #include "gui/dialogs/formmain.h" #include "services/tt-rss/ttrssserviceentrypoint.h" +#include "services/tt-rss/ttrssfeed.h" +#include "services/tt-rss/ttrsscategory.h" #include "services/tt-rss/network/ttrssnetworkfactory.h" #include "services/tt-rss/gui/formeditaccount.h" @@ -42,13 +44,16 @@ TtRssServiceRoot::~TtRssServiceRoot() { } void TtRssServiceRoot::start() { - if (childItems().isEmpty()) { - syncIn(); - } + // TODO: posunout starty rootů až je okno uděláno + loadFromDatabase(); + + // TODO: pokud tady není nic načteno, tak + // syncIn } void TtRssServiceRoot::stop() { - + QNetworkReply::NetworkError error; + m_network->logout(error); } QString TtRssServiceRoot::code() { @@ -164,7 +169,7 @@ TtRssNetworkFactory *TtRssServiceRoot::network() const { return m_network; } -void TtRssServiceRoot::saveToDatabase() { +void TtRssServiceRoot::saveAccountDataToDatabase() { if (accountId() != NO_PARENT_CATEGORY) { // We are overwritting previously saved data. QSqlDatabase database = qApp->database()->connection(metaObject()->className(), DatabaseFactory::FromSettings); @@ -228,7 +233,97 @@ void TtRssServiceRoot::syncIn() { // ze serveru, a sloučení s aktuálními // neprovádí aktualizace kanálů ani stažení počtu nepřečtených zpráv QNetworkReply::NetworkError err; + TtRssGetFeedsCategoriesResponse feed_cats_response = m_network->getFeedsCategories(err); + if (err == QNetworkReply::NoError) { + RootItem *new_tree = feed_cats_response.feedsCategories(true, m_network->url()); - RootItem *aa = m_network->getFeedsCategories(err).feedsCategories(); + // Purge old data from SQL and clean all model items. + removeOldFeedTree(); + cleanAllItems(); + + // Model is clean, now store new tree into DB and + // set primary IDs of the items. + storeNewFeedTree(new_tree); + + foreach (RootItem *top_level_item, new_tree->childItems()) { + appendChild(top_level_item); + } + + new_tree->clearChildren(); + new_tree->deleteLater(); + itemChanged(QList<RootItem*>() << this); + requestFeedReadFilterReload(); + requestReloadMessageList(true); + requestItemExpand(getSubTree(), true); + } +} + +void TtRssServiceRoot::removeOldFeedTree() { + QSqlDatabase database = qApp->database()->connection(metaObject()->className(), DatabaseFactory::FromSettings); + QSqlQuery query(database); + query.setForwardOnly(true); + + query.prepare(QSL("DELETE FROM Feeds WHERE account_id = :account_id;")); + query.bindValue(QSL(":account_id"), accountId()); + query.exec(); + + query.prepare(QSL("DELETE FROM Categories WHERE account_id = :account_id;")); + query.bindValue(QSL(":account_id"), accountId()); + query.exec(); +} + +void TtRssServiceRoot::cleanAllItems() { + foreach (RootItem *top_level_item, childItems()) { + requestItemRemoval(top_level_item); + } +} + +void TtRssServiceRoot::storeNewFeedTree(RootItem *root) { + QSqlDatabase database = qApp->database()->connection(metaObject()->className(), DatabaseFactory::FromSettings); + QSqlQuery query_category(database); + QSqlQuery query_feed(database); + + query_category.prepare("INSERT INTO Categories (parent_id, title, account_id, custom_id) " + "VALUES (:parent_id, :title, :account_id, :custom_id);"); + query_feed.prepare("INSERT INTO Feeds (title, icon, category, protected, update_type, update_interval, account_id, custom_id) " + "VALUES (:title, :icon, :category, :protected, :update_type, :update_interval, :account_id, :custom_id);"); + + // Iterate all children. + foreach (RootItem *child, root->getSubTree()) { + if (child->kind() == RootItemKind::Category) { + query_category.bindValue(QSL(":parent_id"), child->parent()->id()); + query_category.bindValue(QSL(":title"), child->title()); + query_category.bindValue(QSL(":account_id"), accountId()); + query_category.bindValue(QSL(":custom_id"), QString::number(static_cast<TtRssCategory*>(child)->customId())); + + if (query_category.exec()) { + child->setId(query_category.lastInsertId().toInt()); + } + else { + // TODO: logovat + } + } + else if (child->kind() == RootItemKind::Feed) { + TtRssFeed *feed = static_cast<TtRssFeed*>(child); + + query_feed.bindValue(QSL(":title"), feed->title()); + query_feed.bindValue(QSL(":icon"), qApp->icons()->toByteArray(feed->icon())); + query_feed.bindValue(QSL(":category"), feed->parent()->id()); + query_feed.bindValue(QSL(":protected"), 0); + query_feed.bindValue(QSL(":update_type"), (int) feed->autoUpdateType()); + query_feed.bindValue(QSL(":update_interval"), feed->autoUpdateInitialInterval()); + query_feed.bindValue(QSL(":account_id"), accountId()); + query_feed.bindValue(QSL(":custom_id"), feed->customId()); + + if (query_feed.exec()) { + feed->setId(query_feed.lastInsertId().toInt()); + + // TODO: updatecounts; + } + else { + // TODO: logovat. + } + } + } } diff --git a/src/services/tt-rss/ttrssserviceroot.h b/src/services/tt-rss/ttrssserviceroot.h index 00ba6446e..a549d1374 100755 --- a/src/services/tt-rss/ttrssserviceroot.h +++ b/src/services/tt-rss/ttrssserviceroot.h @@ -66,14 +66,18 @@ class TtRssServiceRoot : public ServiceRoot { TtRssNetworkFactory *network() const; - void saveToDatabase(); - void loadFromDatabase(); + void saveAccountDataToDatabase(); void updateTitle(); private slots: void syncIn(); private: + void removeOldFeedTree(); + void cleanAllItems(); + void storeNewFeedTree(RootItem *root); + void loadFromDatabase(); + QAction *m_actionSyncIn; QList<QAction*> m_serviceMenu; From 11dfe67b4a7a0bcfb9123ffa31d6edfbf5c414b1 Mon Sep 17 00:00:00 2001 From: Martin Rotter <rotter.martinos@gmail.com> Date: Tue, 8 Dec 2015 09:34:14 +0100 Subject: [PATCH 02/11] Account initial starting is now delayed after main form construction, better now. Also some more stuff. --- src/core/feedsmodel.cpp | 6 +++++- src/core/feedsmodel.h | 6 +++--- src/gui/feedmessageviewer.cpp | 2 -- src/main.cpp | 4 ++++ src/services/abstract/serviceroot.h | 4 ++-- src/services/tt-rss/ttrssserviceroot.cpp | 10 +++++++--- 6 files changed, 21 insertions(+), 11 deletions(-) diff --git a/src/core/feedsmodel.cpp b/src/core/feedsmodel.cpp index b6040476e..6b4ee2a30 100755 --- a/src/core/feedsmodel.cpp +++ b/src/core/feedsmodel.cpp @@ -70,7 +70,7 @@ FeedsModel::FeedsModel(QObject *parent) connect(m_autoUpdateTimer, SIGNAL(timeout()), this, SLOT(executeNextAutoUpdate())); - loadActivatedServiceAccounts(); + //loadActivatedServiceAccounts(); updateAutoUpdateStatus(); if (qApp->settings()->value(GROUP(Feeds), SETTING(Feeds::FeedsUpdateOnStartup)).toBool()) { @@ -82,6 +82,10 @@ FeedsModel::FeedsModel(QObject *parent) FeedsModel::~FeedsModel() { qDebug("Destroying FeedsModel instance."); + foreach (ServiceRoot *account, serviceRoots()) { + account->stop(); + } + // Delete all model items. delete m_rootItem; } diff --git a/src/core/feedsmodel.h b/src/core/feedsmodel.h index 1e94efc93..b561aa506 100755 --- a/src/core/feedsmodel.h +++ b/src/core/feedsmodel.h @@ -151,6 +151,9 @@ class FeedsModel : public QAbstractItemModel { // Adds given service root account. bool addServiceAccount(ServiceRoot *root); + // Loads feed/categories from the database. + void loadActivatedServiceAccounts(); + public slots: // Checks if new parent node is different from one used by original node. // If it is, then it reassigns original_node to new parent. @@ -221,9 +224,6 @@ class FeedsModel : public QAbstractItemModel { // which are suitable as IN clause for SQL queries. QStringList textualFeedIds(const QList<Feed*> &feeds); - // Loads feed/categories from the database. - void loadActivatedServiceAccounts(); - RootItem *m_rootItem; QList<QString> m_headerData; QList<QString> m_tooltipData; diff --git a/src/gui/feedmessageviewer.cpp b/src/gui/feedmessageviewer.cpp index 69a978993..c772e7548 100755 --- a/src/gui/feedmessageviewer.cpp +++ b/src/gui/feedmessageviewer.cpp @@ -103,8 +103,6 @@ void FeedMessageViewer::loadSize() { Settings *settings = qApp->settings(); int default_msg_section_size = m_messagesView->header()->defaultSectionSize(); - m_feedsView->loadExpandedStates(); - // Restore offsets of splitters. m_feedSplitter->restoreState(QByteArray::fromBase64(settings->value(GROUP(GUI), SETTING(GUI::SplitterFeeds)).toString().toLocal8Bit())); m_messageSplitter->restoreState(QByteArray::fromBase64(settings->value(GROUP(GUI), SETTING(GUI::SplitterMessages)).toString().toLocal8Bit())); diff --git a/src/main.cpp b/src/main.cpp index f78478c22..94736dd08 100755 --- a/src/main.cpp +++ b/src/main.cpp @@ -121,6 +121,10 @@ int main(int argc, char *argv[]) { QTimer::singleShot(STARTUP_UPDATE_DELAY, application.system(), SLOT(checkForUpdatesOnStartup())); } + // Load activated accounts. + qApp->mainForm()->tabWidget()->feedMessageViewer()->feedsView()->sourceModel()->loadActivatedServiceAccounts(); + qApp->mainForm()->tabWidget()->feedMessageViewer()->feedsView()->loadExpandedStates(); + // Setup single-instance behavior. QObject::connect(&application, SIGNAL(messageReceived(QString)), &application, SLOT(processExecutionMessage(QString))); diff --git a/src/services/abstract/serviceroot.h b/src/services/abstract/serviceroot.h index 30498fc73..ed0295daa 100755 --- a/src/services/abstract/serviceroot.h +++ b/src/services/abstract/serviceroot.h @@ -64,6 +64,8 @@ class ServiceRoot : public RootItem { // Start/stop services. // Start method is called when feed model gets initialized OR after user adds new service. + // Account should synchronously initialize its children (load them from DB is recommended + // here). // // Stop method is called just before application exits OR when // user explicitly deletes existing service instance. @@ -79,8 +81,6 @@ class ServiceRoot : public RootItem { // and then use method QSqlTableModel::setFilter(....). // NOTE: It would be more preferable if all messages are downloaded // right when feeds are updated. - // TODO: toto možná udělat asynchronně, zobrazit - // "loading" dialog přes view a toto zavolat, nasledně signalovat virtual bool loadMessagesForItem(RootItem *item, QSqlTableModel *model) = 0; // Called BEFORE this read status update (triggered by user in message list) is stored in DB, diff --git a/src/services/tt-rss/ttrssserviceroot.cpp b/src/services/tt-rss/ttrssserviceroot.cpp index bffdd96e0..0f6680779 100755 --- a/src/services/tt-rss/ttrssserviceroot.cpp +++ b/src/services/tt-rss/ttrssserviceroot.cpp @@ -20,6 +20,7 @@ #include "miscellaneous/application.h" #include "miscellaneous/settings.h" #include "gui/dialogs/formmain.h" +#include "network-web/networkfactory.h" #include "services/tt-rss/ttrssserviceentrypoint.h" #include "services/tt-rss/ttrssfeed.h" #include "services/tt-rss/ttrsscategory.h" @@ -44,16 +45,19 @@ TtRssServiceRoot::~TtRssServiceRoot() { } void TtRssServiceRoot::start() { - // TODO: posunout starty rootů až je okno uděláno loadFromDatabase(); - // TODO: pokud tady není nic načteno, tak - // syncIn + if (childCount() == 0) { + // TODO: pokud tady není nic načteno, tak + // syncIn + } } void TtRssServiceRoot::stop() { QNetworkReply::NetworkError error; m_network->logout(error); + + qDebug("Stopping Tiny Tiny RSS account, logging out with result '%d'.", (int) error); } QString TtRssServiceRoot::code() { From 04028ef6bb8bc41fb7796dc938f3ccceea6326fa Mon Sep 17 00:00:00 2001 From: Martin Rotter <rotter.martinos@gmail.com> Date: Tue, 8 Dec 2015 09:58:05 +0100 Subject: [PATCH 03/11] Fixed expanding when importing, newer readme. --- README.md | 2 + src/gui/dialogs/formmain.cpp | 2 + .../standard/gui/formstandardimportexport.cpp | 1 + src/services/standard/standardserviceroot.cpp | 5 +- src/services/tt-rss/ttrssfeed.cpp | 46 ++++++++++++++++++- src/services/tt-rss/ttrssfeed.h | 11 +++++ src/services/tt-rss/ttrssserviceroot.cpp | 5 +- 7 files changed, 68 insertions(+), 4 deletions(-) mode change 100644 => 100755 src/services/tt-rss/ttrssfeed.cpp mode change 100644 => 100755 src/services/tt-rss/ttrssfeed.h diff --git a/README.md b/README.md index 716888781..b3cfb76d5 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,8 @@ RSS Guard is simple (yet powerful) feed reader. It is able to fetch the most kno RSS Guard is written in C++. It is pretty fast even with tons of messages loaded. The core features are: +* support for online feed synchronization via plugins, + * Tiny Tiny RSS (from RSS Guard 3.0.0). * multiplatformity, * support for all feed formats, * simplicity, diff --git a/src/gui/dialogs/formmain.cpp b/src/gui/dialogs/formmain.cpp index 278a791c8..ce207f599 100755 --- a/src/gui/dialogs/formmain.cpp +++ b/src/gui/dialogs/formmain.cpp @@ -80,6 +80,8 @@ FormMain::FormMain(QWidget *parent, Qt::WindowFlags f) setupIcons(); loadSize(); + statusBar()->setVisible(false); + // Initialize the web factory. WebFactory::instance()->loadState(); } diff --git a/src/services/standard/gui/formstandardimportexport.cpp b/src/services/standard/gui/formstandardimportexport.cpp index 9c10ac444..6e7f869cd 100755 --- a/src/services/standard/gui/formstandardimportexport.cpp +++ b/src/services/standard/gui/formstandardimportexport.cpp @@ -233,6 +233,7 @@ void FormStandardImportExport::importFeeds() { QString output_message; if (m_serviceRoot->mergeImportExportModel(m_model, output_message)) { + m_serviceRoot->requestItemExpand(m_serviceRoot->getSubTree(), true); m_ui->m_lblResult->setStatus(WidgetWithStatus::Ok, output_message, output_message); } else { diff --git a/src/services/standard/standardserviceroot.cpp b/src/services/standard/standardserviceroot.cpp index c261155f0..e86be599b 100755 --- a/src/services/standard/standardserviceroot.cpp +++ b/src/services/standard/standardserviceroot.cpp @@ -85,7 +85,10 @@ void StandardServiceRoot::start() { try { model.importAsOPML20(IOFactory::readTextFile(file_to_load)); model.checkAllItems(); - mergeImportExportModel(&model, output_msg); + + if (mergeImportExportModel(&model, output_msg)) { + requestItemExpand(getSubTree(), true); + } } catch (ApplicationException &ex) { MessageBox::show(qApp->mainForm(), QMessageBox::Critical, tr("Error when loading initial feeds"), ex.message()); diff --git a/src/services/tt-rss/ttrssfeed.cpp b/src/services/tt-rss/ttrssfeed.cpp old mode 100644 new mode 100755 index 45d90dc31..5c8dc0810 --- a/src/services/tt-rss/ttrssfeed.cpp +++ b/src/services/tt-rss/ttrssfeed.cpp @@ -18,14 +18,58 @@ #include "services/tt-rss/ttrssfeed.h" #include "definitions/definitions.h" +#include "miscellaneous/application.h" +#include "miscellaneous/databasefactory.h" +#include "services/tt-rss/ttrssserviceroot.h" + +#include <QSqlQuery> -TtRssFeed::TtRssFeed(RootItem *parent) : Feed(parent), m_customId(NO_PARENT_CATEGORY) { +TtRssFeed::TtRssFeed(RootItem *parent) + : Feed(parent), m_customId(NO_PARENT_CATEGORY), m_totalCount(0), m_unreadCount(0) { } TtRssFeed::~TtRssFeed() { } +TtRssServiceRoot *TtRssFeed::serviceRoot() { + return qobject_cast<TtRssServiceRoot*>(getParentServiceRoot()); +} + +void TtRssFeed::updateCounts(bool including_total_count) { + QSqlDatabase database = qApp->database()->connection(metaObject()->className(), DatabaseFactory::FromSettings); + QSqlQuery query_all(database); + + query_all.setForwardOnly(true); + + if (including_total_count) { + if (query_all.exec(QString("SELECT count(*) FROM Messages WHERE feed = '%1' AND is_deleted = 0 AND account_id = %2;").arg(QString::number(customId()), + QString::number(serviceRoot()->accountId()))) && query_all.next()) { + m_totalCount = query_all.value(0).toInt(); + } + } + + // Obtain count of unread messages. + if (query_all.exec(QString("SELECT count(*) FROM Messages WHERE feed = '%1' AND is_deleted = 0 AND is_read = 0 AND account_id = %2;").arg(QString::number(customId()), + QString::number(serviceRoot()->accountId()))) && query_all.next()) { + int new_unread_count = query_all.value(0).toInt(); + + if (status() == NewMessages && new_unread_count < m_unreadCount) { + setStatus(Normal); + } + + m_unreadCount = new_unread_count; + } +} + +int TtRssFeed::countOfAllMessages() { + return m_totalCount; +} + +int TtRssFeed::countOfUnreadMessages() { + return m_unreadCount; +} + int TtRssFeed::update() { return 0; } diff --git a/src/services/tt-rss/ttrssfeed.h b/src/services/tt-rss/ttrssfeed.h old mode 100644 new mode 100755 index 314ad880d..a66335781 --- a/src/services/tt-rss/ttrssfeed.h +++ b/src/services/tt-rss/ttrssfeed.h @@ -21,11 +21,20 @@ #include "services/abstract/feed.h" +class TtRssServiceRoot; + class TtRssFeed : public Feed { public: explicit TtRssFeed(RootItem *parent = NULL); virtual ~TtRssFeed(); + TtRssServiceRoot *serviceRoot(); + + void updateCounts(bool including_total_count); + + int countOfAllMessages(); + int countOfUnreadMessages(); + int update(); QList<Message> undeletedMessages() const; @@ -34,6 +43,8 @@ class TtRssFeed : public Feed { private: int m_customId; + int m_totalCount; + int m_unreadCount; }; #endif // TTRSSFEED_H diff --git a/src/services/tt-rss/ttrssserviceroot.cpp b/src/services/tt-rss/ttrssserviceroot.cpp index 0f6680779..50eeb2bd3 100755 --- a/src/services/tt-rss/ttrssserviceroot.cpp +++ b/src/services/tt-rss/ttrssserviceroot.cpp @@ -254,8 +254,11 @@ void TtRssServiceRoot::syncIn() { appendChild(top_level_item); } + updateCounts(true); + new_tree->clearChildren(); new_tree->deleteLater(); + itemChanged(QList<RootItem*>() << this); requestFeedReadFilterReload(); requestReloadMessageList(true); @@ -322,8 +325,6 @@ void TtRssServiceRoot::storeNewFeedTree(RootItem *root) { if (query_feed.exec()) { feed->setId(query_feed.lastInsertId().toInt()); - - // TODO: updatecounts; } else { // TODO: logovat. From a66c9055fd5f1687eb3906ed3b8dfc66a2e86422 Mon Sep 17 00:00:00 2001 From: Martin Rotter <rotter.martinos@gmail.com> Date: Tue, 8 Dec 2015 09:59:51 +0100 Subject: [PATCH 04/11] README. --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b3cfb76d5..6ec89a9b8 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ RSS Guard ========= Welcome to RSS Guard website. You can find here basic information. -RSS Guard is simple and easy-to-use RSS/ATOM feed aggregator developed using Qt framework. +RSS Guard is simple and easy-to-use RSS/ATOM feed aggregator developed using Qt framework which supports online feed synchronization. - - - Contacts @@ -66,7 +66,7 @@ RSS Guard is simple (yet powerful) feed reader. It is able to fetch the most kno RSS Guard is written in C++. It is pretty fast even with tons of messages loaded. The core features are: -* support for online feed synchronization via plugins, +* **support for online feed synchronization via plugins**, * Tiny Tiny RSS (from RSS Guard 3.0.0). * multiplatformity, * support for all feed formats, From cac3aee503c04744044b1cd928c3fa9887be41e4 Mon Sep 17 00:00:00 2001 From: Martin Rotter <rotter.martinos@gmail.com> Date: Tue, 8 Dec 2015 10:13:05 +0100 Subject: [PATCH 05/11] Added ability to hide status bar. --- resources/text/CHANGELOG | 1 + src/gui/dialogs/formmain.cpp | 13 ++++++------- src/gui/dialogs/formmain.h | 3 --- src/gui/dialogs/formmain.ui | 12 ++++++++++++ src/miscellaneous/settings.cpp | 3 +++ src/miscellaneous/settings.h | 3 +++ 6 files changed, 25 insertions(+), 10 deletions(-) diff --git a/resources/text/CHANGELOG b/resources/text/CHANGELOG index 8718de143..1ad0bd8f0 100644 --- a/resources/text/CHANGELOG +++ b/resources/text/CHANGELOG @@ -18,6 +18,7 @@ <ul> <li style="color: red;">Brand new "service plugin system" - HIGHLY EXPERIMENTAL and REWRITTEN from scratch. Expect bugs and misunderstandings now! Major parts of RSS Guard were completely rewritten. Note that some functionality might be TEMPORARILY removed.</li> <li>Added ability to completely disable notifications (bug #128).</li> + <li>Added ability to hide status bar.</li> <li>Added ability to go to next <font style="font-weight: bold;">unread</font> message. (partially bug #112)</li> </ul> diff --git a/src/gui/dialogs/formmain.cpp b/src/gui/dialogs/formmain.cpp index ce207f599..94801af62 100755 --- a/src/gui/dialogs/formmain.cpp +++ b/src/gui/dialogs/formmain.cpp @@ -80,8 +80,6 @@ FormMain::FormMain(QWidget *parent, Qt::WindowFlags f) setupIcons(); loadSize(); - statusBar()->setVisible(false); - // Initialize the web factory. WebFactory::instance()->loadState(); } @@ -107,6 +105,7 @@ QList<QAction*> FormMain::allActions() { actions << m_ui->m_actionSwitchMainMenu; actions << m_ui->m_actionSwitchToolBars; actions << m_ui->m_actionSwitchListHeaders; + actions << m_ui->m_actionSwitchStatusBar; actions << m_ui->m_actionSwitchMessageListOrientation; // Add web browser actions @@ -174,10 +173,6 @@ void FormMain::switchFullscreenMode() { } } -void FormMain::switchMainMenu() { - m_ui->m_menuBar->setVisible(m_ui->m_actionSwitchMainMenu->isChecked()); -} - void FormMain::updateAddItemMenu() { // NOTE: Clear here deletes items from memory but only those OWNED by the menu. m_ui->m_menuAddItem->clear(); @@ -328,6 +323,7 @@ void FormMain::setupIcons() { m_ui->m_actionSwitchMainMenu->setIcon(icon_theme_factory->fromTheme(QSL("view-switch-menu"))); m_ui->m_actionSwitchToolBars->setIcon(icon_theme_factory->fromTheme(QSL("view-switch-list"))); m_ui->m_actionSwitchListHeaders->setIcon(icon_theme_factory->fromTheme(QSL("view-switch-list"))); + m_ui->m_actionSwitchStatusBar->setIcon(icon_theme_factory->fromTheme(QSL("dialog-information"))); m_ui->m_actionSwitchMessageListOrientation->setIcon(icon_theme_factory->fromTheme(QSL("view-switch-layout-direction"))); m_ui->m_menuShowHide->setIcon(icon_theme_factory->fromTheme(QSL("view-switch"))); @@ -410,6 +406,7 @@ void FormMain::loadSize() { m_ui->m_tabWidget->feedMessageViewer()->loadSize(); m_ui->m_actionSwitchToolBars->setChecked(settings->value(GROUP(GUI), SETTING(GUI::ToolbarsVisible)).toBool()); m_ui->m_actionSwitchListHeaders->setChecked(settings->value(GROUP(GUI), SETTING(GUI::ListHeadersVisible)).toBool()); + m_ui->m_actionSwitchStatusBar->setChecked(settings->value(GROUP(GUI), SETTING(GUI::StatusBarVisible)).toBool()); // Make sure that only unread feeds are shown if user has that feature set on. m_ui->m_actionShowOnlyUnreadItems->setChecked(settings->value(GROUP(Feeds), SETTING(Feeds::ShowOnlyUnreadFeeds)).toBool()); @@ -433,6 +430,7 @@ void FormMain::saveSize() { settings->setValue(GROUP(GUI), GUI::MainWindowInitialSize, size()); settings->setValue(GROUP(GUI), GUI::MainWindowStartsMaximized, is_maximized); settings->setValue(GROUP(GUI), GUI::MainWindowStartsFullscreen, is_fullscreen); + settings->setValue(GROUP(GUI), GUI::StatusBarVisible, m_ui->m_actionSwitchStatusBar->isChecked()); m_ui->m_tabWidget->feedMessageViewer()->saveSize(); } @@ -454,8 +452,9 @@ void FormMain::createConnections() { // Menu "View" connections. connect(m_ui->m_actionFullscreen, SIGNAL(toggled(bool)), this, SLOT(switchFullscreenMode())); - connect(m_ui->m_actionSwitchMainMenu, SIGNAL(toggled(bool)), this, SLOT(switchMainMenu())); + connect(m_ui->m_actionSwitchMainMenu, SIGNAL(toggled(bool)), m_ui->m_menuBar, SLOT(setVisible(bool))); connect(m_ui->m_actionSwitchMainWindow, SIGNAL(triggered()), this, SLOT(switchVisibility())); + connect(m_ui->m_actionSwitchStatusBar, SIGNAL(toggled(bool)), statusBar(), SLOT(setVisible(bool))); // Menu "Tools" connections. connect(m_ui->m_actionSettings, SIGNAL(triggered()), this, SLOT(showSettings())); diff --git a/src/gui/dialogs/formmain.h b/src/gui/dialogs/formmain.h index 1320a832f..140d9ad79 100755 --- a/src/gui/dialogs/formmain.h +++ b/src/gui/dialogs/formmain.h @@ -73,9 +73,6 @@ class FormMain : public QMainWindow { // Turns on/off fullscreen mode void switchFullscreenMode(); - // Switches visibility of main menu. - void switchMainMenu(); - private slots: void updateAddItemMenu(); void updateRecycleBinMenu(); diff --git a/src/gui/dialogs/formmain.ui b/src/gui/dialogs/formmain.ui index fe4f645ce..b392a808b 100755 --- a/src/gui/dialogs/formmain.ui +++ b/src/gui/dialogs/formmain.ui @@ -85,6 +85,7 @@ <addaction name="m_actionSwitchMainMenu"/> <addaction name="m_actionSwitchToolBars"/> <addaction name="m_actionSwitchListHeaders"/> + <addaction name="m_actionSwitchStatusBar"/> </widget> <addaction name="m_menuShowHide"/> <addaction name="m_actionSwitchMainWindow"/> @@ -742,6 +743,17 @@ <string notr="true"/> </property> </action> + <action name="m_actionSwitchStatusBar"> + <property name="checkable"> + <bool>true</bool> + </property> + <property name="checked"> + <bool>true</bool> + </property> + <property name="text"> + <string>Status bar</string> + </property> + </action> </widget> <customwidgets> <customwidget> diff --git a/src/miscellaneous/settings.cpp b/src/miscellaneous/settings.cpp index bc040936e..bf1179f07 100755 --- a/src/miscellaneous/settings.cpp +++ b/src/miscellaneous/settings.cpp @@ -105,6 +105,9 @@ DVALUE(bool) GUI::ToolbarsVisibleDef = true; DKEY GUI::ListHeadersVisible = "enable_list_headers"; DVALUE(bool) GUI::ListHeadersVisibleDef = true; +DKEY GUI::StatusBarVisible = "enable_status_bar"; +DVALUE(bool) GUI::StatusBarVisibleDef = true; + DKEY GUI::HideMainWindowWhenMinimized = "hide_when_minimized"; DVALUE(bool) GUI::HideMainWindowWhenMinimizedDef = false; diff --git a/src/miscellaneous/settings.h b/src/miscellaneous/settings.h index b3a977520..35746dd5b 100755 --- a/src/miscellaneous/settings.h +++ b/src/miscellaneous/settings.h @@ -120,6 +120,9 @@ namespace GUI { KEY ListHeadersVisible; VALUE(bool) ListHeadersVisibleDef; + KEY StatusBarVisible; + VALUE(bool) StatusBarVisibleDef; + KEY HideMainWindowWhenMinimized; VALUE(bool) HideMainWindowWhenMinimizedDef; From c4a8497471315608e80e98b22048a25c4c06bc63 Mon Sep 17 00:00:00 2001 From: Martin Rotter <rotter.martinos@gmail.com> Date: Tue, 8 Dec 2015 10:55:03 +0100 Subject: [PATCH 06/11] Refactored assembling methods, TT-RSS is now able to load stored feeds. --- src/core/rootitem.cpp | 20 +++++++ src/core/rootitem.h | 1 + src/gui/dialogs/formmain.ui | 3 ++ src/services/abstract/serviceroot.cpp | 45 ++++++++++++++++ src/services/abstract/serviceroot.h | 9 ++++ src/services/standard/standardfeed.cpp | 3 +- src/services/standard/standardserviceroot.cpp | 54 +++---------------- src/services/standard/standardserviceroot.h | 11 ---- src/services/tt-rss/ttrsscategory.cpp | 11 ++++ src/services/tt-rss/ttrsscategory.h | 3 ++ src/services/tt-rss/ttrssfeed.cpp | 10 ++++ src/services/tt-rss/ttrssfeed.h | 3 ++ src/services/tt-rss/ttrssserviceroot.cpp | 43 +++++++++++++++ src/services/tt-rss/ttrssserviceroot.h | 2 + 14 files changed, 157 insertions(+), 61 deletions(-) mode change 100644 => 100755 src/services/tt-rss/ttrsscategory.cpp mode change 100644 => 100755 src/services/tt-rss/ttrsscategory.h diff --git a/src/core/rootitem.cpp b/src/core/rootitem.cpp index 879ad8839..60f7482ed 100755 --- a/src/core/rootitem.cpp +++ b/src/core/rootitem.cpp @@ -281,6 +281,26 @@ QList<Category*> RootItem::getSubTreeCategories() { return children; } +QHash<int,Category*> RootItem::getHashedSubTreeCategories() { + QHash<int,Category*> children; + QList<RootItem*> traversable_items; + + traversable_items.append(this); + + // Iterate all nested items. + while (!traversable_items.isEmpty()) { + RootItem *active_item = traversable_items.takeFirst(); + + if (active_item->kind() == RootItemKind::Category && !children.contains(active_item->id())) { + children.insert(active_item->id(), active_item->toCategory()); + } + + traversable_items.append(active_item->childItems()); + } + + return children; +} + QList<Feed*> RootItem::getSubTreeFeeds() { QList<Feed*> children; QList<RootItem*> traversable_items; diff --git a/src/core/rootitem.h b/src/core/rootitem.h index 800bbc3cf..9ad382c32 100755 --- a/src/core/rootitem.h +++ b/src/core/rootitem.h @@ -177,6 +177,7 @@ class RootItem : public QObject { QList<RootItem*> getSubTree(); QList<RootItem*> getSubTree(RootItemKind::Kind kind_of_item); QList<Category*> getSubTreeCategories(); + QHash<int,Category*> getHashedSubTreeCategories(); QList<Feed*> getSubTreeFeeds(); // Returns the service root node which is direct or indirect parent of current item. diff --git a/src/gui/dialogs/formmain.ui b/src/gui/dialogs/formmain.ui index b392a808b..7e9a1ef3c 100755 --- a/src/gui/dialogs/formmain.ui +++ b/src/gui/dialogs/formmain.ui @@ -753,6 +753,9 @@ <property name="text"> <string>Status bar</string> </property> + <property name="shortcut"> + <string notr="true"/> + </property> </action> </widget> <customwidgets> diff --git a/src/services/abstract/serviceroot.cpp b/src/services/abstract/serviceroot.cpp index cd25632b1..c3c9413a3 100755 --- a/src/services/abstract/serviceroot.cpp +++ b/src/services/abstract/serviceroot.cpp @@ -19,6 +19,7 @@ #include "core/feedsmodel.h" #include "miscellaneous/application.h" +#include "services/abstract/category.h" #include <QSqlQuery> @@ -89,3 +90,47 @@ int ServiceRoot::accountId() const { void ServiceRoot::setAccountId(int account_id) { m_accountId = account_id; } + +void ServiceRoot::assembleFeeds(Assignment feeds) { + QHash<int,Category*> categories = getHashedSubTreeCategories(); + + foreach (const AssignmentItem &feed, feeds) { + if (feed.first == NO_PARENT_CATEGORY) { + // This is top-level feed, add it to the root item. + appendChild(feed.second); + feed.second->updateCounts(true); + } + else if (categories.contains(feed.first)) { + // This feed belongs to this category. + categories.value(feed.first)->appendChild(feed.second); + feed.second->updateCounts(true); + } + else { + qWarning("Feed '%s' is loose, skipping it.", qPrintable(feed.second->title())); + } + } +} + +void ServiceRoot::assembleCategories(Assignment categories) { + QHash<int,RootItem*> assignments; + assignments.insert(NO_PARENT_CATEGORY, this); + + // Add top-level categories. + while (!categories.isEmpty()) { + for (int i = 0; i < categories.size(); i++) { + if (assignments.contains(categories.at(i).first)) { + // Parent category of this category is already added. + assignments.value(categories.at(i).first)->appendChild(categories.at(i).second); + + // Now, added category can be parent for another categories, add it. + assignments.insert(categories.at(i).second->id(), categories.at(i).second); + + // Remove the category from the list, because it was + // added to the final collection. + categories.removeAt(i); + i--; + } + } + } +} + diff --git a/src/services/abstract/serviceroot.h b/src/services/abstract/serviceroot.h index ed0295daa..aca550619 100755 --- a/src/services/abstract/serviceroot.h +++ b/src/services/abstract/serviceroot.h @@ -30,6 +30,10 @@ class RecycleBin; class QAction; class QSqlTableModel; +// Car here represents ID of the item. +typedef QList<QPair<int,RootItem*> > Assignment; +typedef QPair<int,RootItem*> AssignmentItem; + // THIS IS the root node of the service. // NOTE: The root usually contains some core functionality of the // service like service account username/password etc. @@ -149,6 +153,11 @@ class ServiceRoot : public RootItem { int accountId() const; void setAccountId(int account_id); + protected: + // Takes lists of feeds/categories and assembles them into the tree structure. + void assembleCategories(Assignment categories); + void assembleFeeds(Assignment feeds); + signals: // Emitted if data in any item belonging to this root are changed. void dataChanged(QList<RootItem*> items); diff --git a/src/services/standard/standardfeed.cpp b/src/services/standard/standardfeed.cpp index a0f4332f3..0b22d15a9 100755 --- a/src/services/standard/standardfeed.cpp +++ b/src/services/standard/standardfeed.cpp @@ -763,7 +763,6 @@ QNetworkReply::NetworkError StandardFeed::networkError() const { } StandardFeed::StandardFeed(const QSqlRecord &record) : Feed(NULL) { - setKind(RootItemKind::Feed); setTitle(record.value(FDS_DB_TITLE_INDEX).toString()); setId(record.value(FDS_DB_ID_INDEX).toInt()); setDescription(record.value(FDS_DB_DESCRIPTION_INDEX).toString()); @@ -782,6 +781,6 @@ StandardFeed::StandardFeed(const QSqlRecord &record) : Feed(NULL) { } - setAutoUpdateType(static_cast<StandardFeed::AutoUpdateType>(record.value(FDS_DB_UPDATE_TYPE_INDEX).toInt())); + setAutoUpdateType(static_cast<Feed::AutoUpdateType>(record.value(FDS_DB_UPDATE_TYPE_INDEX).toInt())); setAutoUpdateInitialInterval(record.value(FDS_DB_UPDATE_INTERVAL_INDEX).toInt()); } diff --git a/src/services/standard/standardserviceroot.cpp b/src/services/standard/standardserviceroot.cpp index e86be599b..6a6c44884 100755 --- a/src/services/standard/standardserviceroot.cpp +++ b/src/services/standard/standardserviceroot.cpp @@ -331,8 +331,8 @@ bool StandardServiceRoot::emptyBin() { void StandardServiceRoot::loadFromDatabase(){ QSqlDatabase database = qApp->database()->connection(metaObject()->className(), DatabaseFactory::FromSettings); - CategoryAssignment categories; - FeedAssignment feeds; + Assignment categories; + Assignment feeds; // Obtain data for categories from the database. QSqlQuery query_categories(database); @@ -344,7 +344,7 @@ void StandardServiceRoot::loadFromDatabase(){ } while (query_categories.next()) { - CategoryAssignmentItem pair; + AssignmentItem pair; pair.first = query_categories.value(CAT_DB_PARENT_ID_INDEX).toInt(); pair.second = new StandardCategory(query_categories.record()); @@ -369,10 +369,10 @@ void StandardServiceRoot::loadFromDatabase(){ case StandardFeed::Rdf: case StandardFeed::Rss0X: case StandardFeed::Rss2X: { - FeedAssignmentItem pair; + AssignmentItem pair; pair.first = query_feeds.value(FDS_DB_CATEGORY_INDEX).toInt(); pair.second = new StandardFeed(query_feeds.record()); - pair.second->setType(type); + qobject_cast<StandardFeed*>(pair.second)->setType(type); feeds << pair; break; @@ -419,6 +419,7 @@ QHash<int,StandardCategory*> StandardServiceRoot::categoriesForItem(RootItem *ro } QHash<int,StandardCategory*> StandardServiceRoot::allCategories() { + // TODO: změnit na qlist, použít getsubtree možná return categoriesForItem(this); } @@ -436,26 +437,6 @@ QList<QAction*> StandardServiceRoot::getContextMenuForFeed(StandardFeed *feed) { return m_feedContextMenu; } -void StandardServiceRoot::assembleFeeds(FeedAssignment feeds) { - QHash<int,StandardCategory*> categories = categoriesForItem(this); - - foreach (const FeedAssignmentItem &feed, feeds) { - if (feed.first == NO_PARENT_CATEGORY) { - // This is top-level feed, add it to the root item. - appendChild(feed.second); - feed.second->updateCounts(true); - } - else if (categories.contains(feed.first)) { - // This feed belongs to this category. - categories.value(feed.first)->appendChild(feed.second); - feed.second->updateCounts(true); - } - else { - qWarning("Feed '%s' is loose, skipping it.", qPrintable(feed.second->title())); - } - } -} - bool StandardServiceRoot::mergeImportExportModel(FeedsImportExportModel *model, QString &output_message) { QStack<RootItem*> original_parents; original_parents.push(this); QStack<RootItem*> new_parents; new_parents.push(model->rootItem()); @@ -696,26 +677,3 @@ bool StandardServiceRoot::onAfterMessagesRestoredFromBin(RootItem *selected_item requestFeedReadFilterReload(); return true; } - -void StandardServiceRoot::assembleCategories(CategoryAssignment categories) { - QHash<int,RootItem*> assignments; - assignments.insert(NO_PARENT_CATEGORY, this); - - // Add top-level categories. - while (!categories.isEmpty()) { - for (int i = 0; i < categories.size(); i++) { - if (assignments.contains(categories.at(i).first)) { - // Parent category of this category is already added. - assignments.value(categories.at(i).first)->appendChild(categories.at(i).second); - - // Now, added category can be parent for another categories, add it. - assignments.insert(categories.at(i).second->id(), categories.at(i).second); - - // Remove the category from the list, because it was - // added to the final collection. - categories.removeAt(i); - i--; - } - } - } -} diff --git a/src/services/standard/standardserviceroot.h b/src/services/standard/standardserviceroot.h index 1a54f7b02..55552ff54 100755 --- a/src/services/standard/standardserviceroot.h +++ b/src/services/standard/standardserviceroot.h @@ -30,12 +30,6 @@ class StandardFeed; class FeedsImportExportModel; class QMenu; -typedef QList<QPair<int, StandardCategory*> > CategoryAssignment; -typedef QPair<int, StandardCategory*> CategoryAssignmentItem; - -typedef QList<QPair<int, StandardFeed*> > FeedAssignment; -typedef QPair<int, StandardFeed*> FeedAssignmentItem; - class StandardServiceRoot : public ServiceRoot { Q_OBJECT @@ -117,11 +111,6 @@ class StandardServiceRoot : public ServiceRoot { // which are suitable as IN clause for SQL queries. QStringList textualFeedIds(const QList<Feed *> &feeds); - // Takes lists of feeds/categories and assembles - // them into the tree structure. - void assembleCategories(CategoryAssignment categories); - void assembleFeeds(FeedAssignment feeds); - StandardRecycleBin *m_recycleBin; // Menus. diff --git a/src/services/tt-rss/ttrsscategory.cpp b/src/services/tt-rss/ttrsscategory.cpp old mode 100644 new mode 100755 index 7b485288e..9334a347d --- a/src/services/tt-rss/ttrsscategory.cpp +++ b/src/services/tt-rss/ttrsscategory.cpp @@ -18,11 +18,22 @@ #include "services/tt-rss/ttrsscategory.h" #include "definitions/definitions.h" +#include "miscellaneous/application.h" +#include "miscellaneous/iconfactory.h" + +#include <QVariant> TtRssCategory::TtRssCategory(RootItem *parent) : Category(parent), m_customId(NO_PARENT_CATEGORY) { } +TtRssCategory::TtRssCategory(const QSqlRecord &record) : Category(NULL) { + setId(record.value(CAT_DB_ID_INDEX).toInt()); + setTitle(record.value(CAT_DB_TITLE_INDEX).toString()); + setIcon(qApp->icons()->fromByteArray(record.value(CAT_DB_ICON_INDEX).toByteArray())); + setCustomId(record.value(CAT_DB_CUSTOM_ID_INDEX).toInt()); +} + TtRssCategory::~TtRssCategory() { } diff --git a/src/services/tt-rss/ttrsscategory.h b/src/services/tt-rss/ttrsscategory.h old mode 100644 new mode 100755 index fb7df9a24..c029d3435 --- a/src/services/tt-rss/ttrsscategory.h +++ b/src/services/tt-rss/ttrsscategory.h @@ -20,10 +20,13 @@ #include "services/abstract/category.h" +#include <QSqlRecord> + class TtRssCategory : public Category { public: explicit TtRssCategory(RootItem *parent = NULL); + explicit TtRssCategory(const QSqlRecord &record); virtual ~TtRssCategory(); int customId() const; diff --git a/src/services/tt-rss/ttrssfeed.cpp b/src/services/tt-rss/ttrssfeed.cpp index 5c8dc0810..f45cd427f 100755 --- a/src/services/tt-rss/ttrssfeed.cpp +++ b/src/services/tt-rss/ttrssfeed.cpp @@ -20,6 +20,7 @@ #include "definitions/definitions.h" #include "miscellaneous/application.h" #include "miscellaneous/databasefactory.h" +#include "miscellaneous/iconfactory.h" #include "services/tt-rss/ttrssserviceroot.h" #include <QSqlQuery> @@ -29,6 +30,15 @@ TtRssFeed::TtRssFeed(RootItem *parent) : Feed(parent), m_customId(NO_PARENT_CATEGORY), m_totalCount(0), m_unreadCount(0) { } +TtRssFeed::TtRssFeed(const QSqlRecord &record) : Feed(NULL), m_totalCount(0), m_unreadCount(0) { + setTitle(record.value(FDS_DB_TITLE_INDEX).toString()); + setId(record.value(FDS_DB_ID_INDEX).toInt()); + setIcon(qApp->icons()->fromByteArray(record.value(FDS_DB_ICON_INDEX).toByteArray())); + setAutoUpdateType(static_cast<Feed::AutoUpdateType>(record.value(FDS_DB_UPDATE_TYPE_INDEX).toInt())); + setAutoUpdateInitialInterval(record.value(FDS_DB_UPDATE_INTERVAL_INDEX).toInt()); + setCustomId(record.value(FDS_DB_CUSTOM_ID_INDEX).toInt()); +} + TtRssFeed::~TtRssFeed() { } diff --git a/src/services/tt-rss/ttrssfeed.h b/src/services/tt-rss/ttrssfeed.h index a66335781..a934f9743 100755 --- a/src/services/tt-rss/ttrssfeed.h +++ b/src/services/tt-rss/ttrssfeed.h @@ -20,12 +20,15 @@ #include "services/abstract/feed.h" +#include <QSqlRecord> + class TtRssServiceRoot; class TtRssFeed : public Feed { public: explicit TtRssFeed(RootItem *parent = NULL); + explicit TtRssFeed(const QSqlRecord &record); virtual ~TtRssFeed(); TtRssServiceRoot *serviceRoot(); diff --git a/src/services/tt-rss/ttrssserviceroot.cpp b/src/services/tt-rss/ttrssserviceroot.cpp index 50eeb2bd3..32882d7c6 100755 --- a/src/services/tt-rss/ttrssserviceroot.cpp +++ b/src/services/tt-rss/ttrssserviceroot.cpp @@ -30,6 +30,7 @@ #include <QSqlQuery> #include <QSqlError> #include <QPointer> +#include <QPair> TtRssServiceRoot::TtRssServiceRoot(RootItem *parent) @@ -220,6 +221,48 @@ void TtRssServiceRoot::saveAccountDataToDatabase() { void TtRssServiceRoot::loadFromDatabase() { // TODO: Load feeds/categories from DB. + + QSqlDatabase database = qApp->database()->connection(metaObject()->className(), DatabaseFactory::FromSettings); + Assignment categories; + Assignment feeds; + + // Obtain data for categories from the database. + QSqlQuery query_categories(database); + query_categories.setForwardOnly(true); + + if (!query_categories.exec(QString("SELECT * FROM Categories WHERE account_id = %1;").arg(accountId())) || query_categories.lastError().isValid()) { + qFatal("Query for obtaining categories failed. Error message: '%s'.", + qPrintable(query_categories.lastError().text())); + } + + while (query_categories.next()) { + AssignmentItem pair; + pair.first = query_categories.value(CAT_DB_PARENT_ID_INDEX).toInt(); + pair.second = new TtRssCategory(query_categories.record()); + + categories << pair; + } + + // All categories are now loaded. + QSqlQuery query_feeds(database); + query_feeds.setForwardOnly(true); + + if (!query_feeds.exec(QString("SELECT * FROM Feeds WHERE account_id = %1;").arg(accountId())) || query_feeds.lastError().isValid()) { + qFatal("Query for obtaining feeds failed. Error message: '%s'.", + qPrintable(query_feeds.lastError().text())); + } + + while (query_feeds.next()) { + AssignmentItem pair; + pair.first = query_feeds.value(FDS_DB_CATEGORY_INDEX).toInt(); + pair.second = new TtRssFeed(query_feeds.record()); + + feeds << pair; + } + + // All data are now obtained, lets create the hierarchy. + assembleCategories(categories); + assembleFeeds(feeds); } void TtRssServiceRoot::updateTitle() { diff --git a/src/services/tt-rss/ttrssserviceroot.h b/src/services/tt-rss/ttrssserviceroot.h index a549d1374..5ff7cffb6 100755 --- a/src/services/tt-rss/ttrssserviceroot.h +++ b/src/services/tt-rss/ttrssserviceroot.h @@ -23,6 +23,8 @@ #include <QCoreApplication> +class TtRssCategory; +class TtRssFeed; class TtRssNetworkFactory; class TtRssServiceRoot : public ServiceRoot { From 523adcf4b7d39d93544dec3a900262658620d4b3 Mon Sep 17 00:00:00 2001 From: Martin Rotter <rotter.martinos@gmail.com> Date: Tue, 8 Dec 2015 11:00:43 +0100 Subject: [PATCH 07/11] Fixed icons. --- src/services/tt-rss/network/ttrssnetworkfactory.cpp | 1 - src/services/tt-rss/ttrsscategory.cpp | 3 ++- src/services/tt-rss/ttrssfeed.cpp | 1 + src/services/tt-rss/ttrssserviceroot.cpp | 6 +----- 4 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/services/tt-rss/network/ttrssnetworkfactory.cpp b/src/services/tt-rss/network/ttrssnetworkfactory.cpp index 5915f545e..5dcfb7b8b 100755 --- a/src/services/tt-rss/network/ttrssnetworkfactory.cpp +++ b/src/services/tt-rss/network/ttrssnetworkfactory.cpp @@ -253,7 +253,6 @@ RootItem *TtRssGetFeedsCategoriesResponse::feedsCategories(bool obtain_icons, QS else { TtRssCategory *category = new TtRssCategory(); - category->setIcon(qApp->icons()->fromTheme(QSL("folder-category"))); category->setTitle(item["name"].toString()); category->setCustomId(item_id); act_parent->appendChild(category); diff --git a/src/services/tt-rss/ttrsscategory.cpp b/src/services/tt-rss/ttrsscategory.cpp index 9334a347d..7dc127ea6 100755 --- a/src/services/tt-rss/ttrsscategory.cpp +++ b/src/services/tt-rss/ttrsscategory.cpp @@ -25,12 +25,13 @@ TtRssCategory::TtRssCategory(RootItem *parent) : Category(parent), m_customId(NO_PARENT_CATEGORY) { + setIcon(qApp->icons()->fromTheme(QSL("folder-category"))); } TtRssCategory::TtRssCategory(const QSqlRecord &record) : Category(NULL) { + setIcon(qApp->icons()->fromTheme(QSL("folder-category"))); setId(record.value(CAT_DB_ID_INDEX).toInt()); setTitle(record.value(CAT_DB_TITLE_INDEX).toString()); - setIcon(qApp->icons()->fromByteArray(record.value(CAT_DB_ICON_INDEX).toByteArray())); setCustomId(record.value(CAT_DB_CUSTOM_ID_INDEX).toInt()); } diff --git a/src/services/tt-rss/ttrssfeed.cpp b/src/services/tt-rss/ttrssfeed.cpp index f45cd427f..9b6916c86 100755 --- a/src/services/tt-rss/ttrssfeed.cpp +++ b/src/services/tt-rss/ttrssfeed.cpp @@ -81,6 +81,7 @@ int TtRssFeed::countOfUnreadMessages() { } int TtRssFeed::update() { + // TODO: přes getHeadlines provede stažení kompletnich zprav. return 0; } diff --git a/src/services/tt-rss/ttrssserviceroot.cpp b/src/services/tt-rss/ttrssserviceroot.cpp index 32882d7c6..532610aa7 100755 --- a/src/services/tt-rss/ttrssserviceroot.cpp +++ b/src/services/tt-rss/ttrssserviceroot.cpp @@ -49,8 +49,7 @@ void TtRssServiceRoot::start() { loadFromDatabase(); if (childCount() == 0) { - // TODO: pokud tady není nic načteno, tak - // syncIn + syncIn(); } } @@ -276,9 +275,6 @@ void TtRssServiceRoot::updateTitle() { } void TtRssServiceRoot::syncIn() { - // TODO: provede stažení kanálů/kategorií - // ze serveru, a sloučení s aktuálními - // neprovádí aktualizace kanálů ani stažení počtu nepřečtených zpráv QNetworkReply::NetworkError err; TtRssGetFeedsCategoriesResponse feed_cats_response = m_network->getFeedsCategories(err); From eb1eef3a504afe4ebe185816fc5242a4d489b28a Mon Sep 17 00:00:00 2001 From: Martin Rotter <rotter.martinos@gmail.com> Date: Tue, 8 Dec 2015 11:13:35 +0100 Subject: [PATCH 08/11] Added ability to load messages. --- src/services/tt-rss/ttrssserviceroot.cpp | 20 +++++++++++++++++++- src/services/tt-rss/ttrssserviceroot.h | 4 ++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/services/tt-rss/ttrssserviceroot.cpp b/src/services/tt-rss/ttrssserviceroot.cpp index 532610aa7..5ac50a600 100755 --- a/src/services/tt-rss/ttrssserviceroot.cpp +++ b/src/services/tt-rss/ttrssserviceroot.cpp @@ -27,6 +27,7 @@ #include "services/tt-rss/network/ttrssnetworkfactory.h" #include "services/tt-rss/gui/formeditaccount.h" +#include <QSqlTableModel> #include <QSqlQuery> #include <QSqlError> #include <QPointer> @@ -118,7 +119,13 @@ RecycleBin *TtRssServiceRoot::recycleBin() { } bool TtRssServiceRoot::loadMessagesForItem(RootItem *item, QSqlTableModel *model) { - return false; + QList<Feed*> children = item->getSubTreeFeeds(); + QString filter_clause = textualFeedIds(children).join(QSL(", ")); + + model->setFilter(QString(QSL("feed IN (%1) AND is_deleted = 0 AND is_pdeleted = 0")).arg(filter_clause)); + qDebug("Loading messages from feeds: %s.", qPrintable(filter_clause)); + + return true; } QList<QAction*> TtRssServiceRoot::serviceMenu() { @@ -305,6 +312,17 @@ void TtRssServiceRoot::syncIn() { } } +QStringList TtRssServiceRoot::textualFeedIds(const QList<Feed*> &feeds) { + QStringList stringy_ids; + stringy_ids.reserve(feeds.size()); + + foreach (Feed *feed, feeds) { + stringy_ids.append(QString("'%1'").arg(QString::number(static_cast<TtRssFeed*>(feed)->customId()))); + } + + return stringy_ids; +} + void TtRssServiceRoot::removeOldFeedTree() { QSqlDatabase database = qApp->database()->connection(metaObject()->className(), DatabaseFactory::FromSettings); QSqlQuery query(database); diff --git a/src/services/tt-rss/ttrssserviceroot.h b/src/services/tt-rss/ttrssserviceroot.h index 5ff7cffb6..56519886f 100755 --- a/src/services/tt-rss/ttrssserviceroot.h +++ b/src/services/tt-rss/ttrssserviceroot.h @@ -75,6 +75,10 @@ class TtRssServiceRoot : public ServiceRoot { void syncIn(); private: + // Returns converted ids of given feeds + // which are suitable as IN clause for SQL queries. + QStringList textualFeedIds(const QList<Feed*> &feeds); + void removeOldFeedTree(); void cleanAllItems(); void storeNewFeedTree(RootItem *root); From c9bb406a509cd88f0a46be20ff2f27656fdef85a Mon Sep 17 00:00:00 2001 From: Martin Rotter <rotter.martinos@gmail.com> Date: Tue, 8 Dec 2015 12:12:39 +0100 Subject: [PATCH 09/11] Added undeletedMessages() implementation. --- src/services/standard/standardfeed.cpp | 3 ++- src/services/tt-rss/ttrssfeed.cpp | 33 +++++++++++++++++++++++++- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/src/services/standard/standardfeed.cpp b/src/services/standard/standardfeed.cpp index 0b22d15a9..c7ea6fecc 100755 --- a/src/services/standard/standardfeed.cpp +++ b/src/services/standard/standardfeed.cpp @@ -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 " + query_read_msg.prepare("SELECT title, url, author, date_created, contents, enclosures " "FROM Messages " "WHERE is_deleted = 0 AND feed = :feed AND account_id = :account_id;"); @@ -154,6 +154,7 @@ QList<Message> StandardFeed::undeletedMessages() const { message.m_author = query_read_msg.value(2).toString(); message.m_created = TextFactory::parseDateTime(query_read_msg.value(3).value<qint64>()); message.m_contents = query_read_msg.value(4).toString(); + message.m_enclosures = Enclosures::decodeEnclosuresFromString(query_read_msg.value(5).toString()); messages.append(message); } diff --git a/src/services/tt-rss/ttrssfeed.cpp b/src/services/tt-rss/ttrssfeed.cpp index 9b6916c86..849b67ace 100755 --- a/src/services/tt-rss/ttrssfeed.cpp +++ b/src/services/tt-rss/ttrssfeed.cpp @@ -21,6 +21,7 @@ #include "miscellaneous/application.h" #include "miscellaneous/databasefactory.h" #include "miscellaneous/iconfactory.h" +#include "miscellaneous/textfactory.h" #include "services/tt-rss/ttrssserviceroot.h" #include <QSqlQuery> @@ -86,7 +87,37 @@ int TtRssFeed::update() { } QList<Message> TtRssFeed::undeletedMessages() const { - return QList<Message>(); + QList<Message> messages; + int account_id = const_cast<TtRssFeed*>(this)->serviceRoot()->accountId(); + 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 " + "FROM Messages " + "WHERE is_deleted = 0 AND feed = :feed AND account_id = :account_id;"); + + query_read_msg.bindValue(QSL(":feed"), id()); + query_read_msg.bindValue(QSL(":account_id"), account_id); + + // FIXME: Fix those const functions, this is fucking ugly. + + if (query_read_msg.exec()) { + while (query_read_msg.next()) { + Message message; + + message.m_feedId = account_id; + message.m_title = query_read_msg.value(0).toString(); + message.m_url = query_read_msg.value(1).toString(); + message.m_author = query_read_msg.value(2).toString(); + message.m_created = TextFactory::parseDateTime(query_read_msg.value(3).value<qint64>()); + message.m_contents = query_read_msg.value(4).toString(); + message.m_enclosures = Enclosures::decodeEnclosuresFromString(query_read_msg.value(5).toString()); + + messages.append(message); + } + } + + return messages; } int TtRssFeed::customId() const { From 34e2f702190b1c58389f01e329f08bf557e43e25 Mon Sep 17 00:00:00 2001 From: Martin Rotter <rotter.martinos@gmail.com> Date: Tue, 8 Dec 2015 13:37:26 +0100 Subject: [PATCH 10/11] Working on TT-RSS messages obtaining. --- src/core/message.cpp | 4 +- src/core/message.h | 5 +- src/core/messagesmodel.cpp | 3 +- src/services/abstract/feed.h | 1 + src/services/tt-rss/definitions.h | 3 + .../tt-rss/network/ttrssnetworkfactory.cpp | 75 +++++++++++++++++++ .../tt-rss/network/ttrssnetworkfactory.h | 15 ++++ src/services/tt-rss/ttrssfeed.cpp | 36 ++++++++- src/services/tt-rss/ttrssfeed.h | 2 + 9 files changed, 137 insertions(+), 7 deletions(-) diff --git a/src/core/message.cpp b/src/core/message.cpp index 0338a5477..6c8833400 100755 --- a/src/core/message.cpp +++ b/src/core/message.cpp @@ -61,7 +61,7 @@ QString Enclosures::encodeEnclosuresToString(const QList<Enclosure> &enclosures) } Message::Message() { - m_title = m_url = m_author = m_contents = m_customId = ""; - m_feedId = 0; + m_title = m_url = m_author = m_contents = m_feedId = m_customId = ""; m_enclosures = QList<Enclosure>(); + m_isImportant = m_isImportant = false; } diff --git a/src/core/message.h b/src/core/message.h index f0cd4ab3b..eab142d15 100755 --- a/src/core/message.h +++ b/src/core/message.h @@ -49,9 +49,12 @@ class Message { QString m_author; QString m_contents; QDateTime m_created; - int m_feedId; + QString m_feedId; QString m_customId; + bool m_isRead; + bool m_isImportant; + QList<Enclosure> m_enclosures; // Is true if "created" date was obtained directly diff --git a/src/core/messagesmodel.cpp b/src/core/messagesmodel.cpp index 62af8ae12..ba0968bbe 100755 --- a/src/core/messagesmodel.cpp +++ b/src/core/messagesmodel.cpp @@ -139,7 +139,8 @@ Message MessagesModel::messageAt(int row_index) const { message.m_enclosures = Enclosures::decodeEnclosuresFromString(rec.value(MSG_DB_ENCLOSURES_INDEX).toString()); message.m_title = rec.value(MSG_DB_TITLE_INDEX).toString(); message.m_url = rec.value(MSG_DB_URL_INDEX).toString(); - message.m_feedId = rec.value(MSG_DB_FEED_INDEX).toInt(); + message.m_feedId = rec.value(MSG_DB_FEED_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(); return message; diff --git a/src/services/abstract/feed.h b/src/services/abstract/feed.h index 0ab9671e5..aaed9266a 100755 --- a/src/services/abstract/feed.h +++ b/src/services/abstract/feed.h @@ -55,6 +55,7 @@ class Feed : public RootItem { ///////////////////////////////////////// // Performs synchronous update and returns number of newly updated messages. + // NOTE: This is called from worker thread, not from main UI thread. // NOTE: This should COMPLETELY download ALL messages from online source // into locale "Messages" table, INCLUDING contents (or excerpts) of those // messages. diff --git a/src/services/tt-rss/definitions.h b/src/services/tt-rss/definitions.h index 6aae31bea..391188c76 100755 --- a/src/services/tt-rss/definitions.h +++ b/src/services/tt-rss/definitions.h @@ -11,6 +11,9 @@ #define UNKNOWN_METHOD "UNKNOWN_METHOD" // Given "op" is not recognized. #define INCORRECT_USAGE "INCORRECT_USAGE" // Given "op" was used with bad parameters. +// Limitations +#define MAX_MESSAGES 200 + // General return status codes. #define API_STATUS_OK 0 #define API_STATUS_ERR 1 diff --git a/src/services/tt-rss/network/ttrssnetworkfactory.cpp b/src/services/tt-rss/network/ttrssnetworkfactory.cpp index 5dcfb7b8b..4d5c8ec03 100755 --- a/src/services/tt-rss/network/ttrssnetworkfactory.cpp +++ b/src/services/tt-rss/network/ttrssnetworkfactory.cpp @@ -24,6 +24,7 @@ #include "services/tt-rss/ttrsscategory.h" #include "miscellaneous/application.h" #include "miscellaneous/iconfactory.h" +#include "miscellaneous/textfactory.h" #include "network-web/networkfactory.h" #include <QPair> @@ -133,6 +134,37 @@ TtRssGetFeedsCategoriesResponse TtRssNetworkFactory::getFeedsCategories(QNetwork return result; } +TtRssGetHeadlinesResponse TtRssNetworkFactory::getHeadlines(int feed_id, bool force_update, int limit, int skip, + bool show_content, bool include_attachments, + bool sanitize, QNetworkReply::NetworkError &error) { + QtJson::JsonObject json; + json["op"] = "getHeadlines"; + json["sid"] = m_sessionId; + json["feed_id"] = feed_id; + json["force_update"] = force_update; + json["limit"] = limit; + json["skip"] = skip; + json["show_content"] = show_content; + json["include_attachments"] = include_attachments; + json["sanitize"] = sanitize; + + QByteArray result_raw; + NetworkResult network_reply = NetworkFactory::uploadData(m_url, DOWNLOAD_TIMEOUT, QtJson::serialize(json), CONTENT_TYPE, result_raw); + TtRssGetHeadlinesResponse 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 = TtRssGetHeadlinesResponse(QString::fromUtf8(result_raw)); + } + + error = network_reply.first; + return result; +} + TtRssResponse::TtRssResponse(const QString &raw_content) { m_rawContent = QtJson::parse(raw_content).toMap(); } @@ -296,3 +328,46 @@ RootItem *TtRssGetFeedsCategoriesResponse::feedsCategories(bool obtain_icons, QS return parent; } + + +TtRssGetHeadlinesResponse::TtRssGetHeadlinesResponse(const QString &raw_content) : TtRssResponse(raw_content) { +} + +TtRssGetHeadlinesResponse::~TtRssGetHeadlinesResponse() { +} + +QList<Message> TtRssGetHeadlinesResponse::messages() { + QList<Message> messages; + + foreach (QVariant item, m_rawContent["content"].toList()) { + QMap<QString,QVariant> mapped = item.toMap(); + Message message; + + message.m_author = mapped["author"].toString(); + message.m_isRead = !mapped["unread"].toBool(); + message.m_isImportant = mapped["marked"].toBool(); + message.m_contents = mapped["content"].toString(); + message.m_created = TextFactory::parseDateTime(mapped["updated"].value<qint64>()); + message.m_createdFromFeed = true; + message.m_customId = mapped["id"].toString(); + message.m_feedId = mapped["feed_id"].toString(); + message.m_title = mapped["title"].toString(); + message.m_url = mapped["link"].toString(); + + if (mapped.contains(QSL("attachments"))) { + // Process enclosures. + foreach (QVariant attachment, mapped["attachments"].toList()) { + QMap<QString,QVariant> mapped_attachemnt = attachment.toMap(); + Enclosure enclosure; + + enclosure.m_mimeType = mapped_attachemnt["content_type"].toString(); + enclosure.m_url = mapped_attachemnt["content_url"].toString(); + message.m_enclosures.append(enclosure); + } + } + + messages.append(message); + } + + return messages; +} diff --git a/src/services/tt-rss/network/ttrssnetworkfactory.h b/src/services/tt-rss/network/ttrssnetworkfactory.h index 8e76f3b09..8d9374f20 100755 --- a/src/services/tt-rss/network/ttrssnetworkfactory.h +++ b/src/services/tt-rss/network/ttrssnetworkfactory.h @@ -20,6 +20,8 @@ #include "qt-json/json.h" +#include "core/message.h" + #include <QString> #include <QPair> #include <QNetworkReply> @@ -64,6 +66,14 @@ class TtRssGetFeedsCategoriesResponse : public TtRssResponse { RootItem *feedsCategories(bool obtain_icons, QString base_address = QString()); }; +class TtRssGetHeadlinesResponse : public TtRssResponse { + public: + explicit TtRssGetHeadlinesResponse(const QString &raw_content = QString()); + virtual ~TtRssGetHeadlinesResponse(); + + QList<Message> messages(); +}; + class TtRssNetworkFactory { public: explicit TtRssNetworkFactory(); @@ -89,6 +99,11 @@ class TtRssNetworkFactory { // Gets feeds from the server. TtRssGetFeedsCategoriesResponse getFeedsCategories(QNetworkReply::NetworkError &error); + // Gets headlines (messages) from the server. + TtRssGetHeadlinesResponse getHeadlines(int feed_id, bool force_update, int limit, int skip, + bool show_content, bool include_attachments, + bool sanitize, QNetworkReply::NetworkError &error); + private: QString m_url; QString m_username; diff --git a/src/services/tt-rss/ttrssfeed.cpp b/src/services/tt-rss/ttrssfeed.cpp index 849b67ace..7a8c64383 100755 --- a/src/services/tt-rss/ttrssfeed.cpp +++ b/src/services/tt-rss/ttrssfeed.cpp @@ -22,7 +22,9 @@ #include "miscellaneous/databasefactory.h" #include "miscellaneous/iconfactory.h" #include "miscellaneous/textfactory.h" +#include "services/tt-rss/definitions.h" #include "services/tt-rss/ttrssserviceroot.h" +#include "services/tt-rss/network/ttrssnetworkfactory.h" #include <QSqlQuery> @@ -83,7 +85,30 @@ int TtRssFeed::countOfUnreadMessages() { int TtRssFeed::update() { // TODO: přes getHeadlines provede stažení kompletnich zprav. - return 0; + QNetworkReply::NetworkError error; + QList<Message> messages; + int newly_added_messages = 0; + int limit = MAX_MESSAGES; + int skip = 0; + + do { + TtRssGetHeadlinesResponse headlines = serviceRoot()->network()->getHeadlines(customId(), true, limit, skip, + true, true, false, error); + + if (error != QNetworkReply::NoError) { + setStatus(Feed::NetworkError); + return 0; + } + else { + QList<Message> new_messages = headlines.messages(); + + messages.append(new_messages); + skip += new_messages.size(); + } + } + while (newly_added_messages > 0); + + return updateMessages(messages); } QList<Message> TtRssFeed::undeletedMessages() const { @@ -92,7 +117,7 @@ QList<Message> TtRssFeed::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, custom_id " "FROM Messages " "WHERE is_deleted = 0 AND feed = :feed AND account_id = :account_id;"); @@ -105,13 +130,14 @@ QList<Message> TtRssFeed::undeletedMessages() const { while (query_read_msg.next()) { Message message; - message.m_feedId = account_id; + message.m_feedId = QString::number(account_id); message.m_title = query_read_msg.value(0).toString(); message.m_url = query_read_msg.value(1).toString(); message.m_author = query_read_msg.value(2).toString(); message.m_created = TextFactory::parseDateTime(query_read_msg.value(3).value<qint64>()); message.m_contents = query_read_msg.value(4).toString(); message.m_enclosures = Enclosures::decodeEnclosuresFromString(query_read_msg.value(5).toString()); + message.m_customId = query_read_msg.value(6).toString(); messages.append(message); } @@ -127,3 +153,7 @@ int TtRssFeed::customId() const { void TtRssFeed::setCustomId(int custom_id) { m_customId = custom_id; } + +int TtRssFeed::updateMessages(const QList<Message> &messages) { + return 0; +} diff --git a/src/services/tt-rss/ttrssfeed.h b/src/services/tt-rss/ttrssfeed.h index a934f9743..92fae6ef2 100755 --- a/src/services/tt-rss/ttrssfeed.h +++ b/src/services/tt-rss/ttrssfeed.h @@ -45,6 +45,8 @@ class TtRssFeed : public Feed { void setCustomId(int custom_id); private: + int updateMessages(const QList<Message> &messages); + int m_customId; int m_totalCount; int m_unreadCount; From 44528a79d751f5df0f846f3d846f750dae926411 Mon Sep 17 00:00:00 2001 From: Martin Rotter <rotter.martinos@gmail.com> Date: Tue, 8 Dec 2015 13:44:57 +0100 Subject: [PATCH 11/11] Work on msgs gettttttttttttttttttttting. --- src/services/tt-rss/ttrssfeed.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/services/tt-rss/ttrssfeed.cpp b/src/services/tt-rss/ttrssfeed.cpp index 7a8c64383..33c7f1bc6 100755 --- a/src/services/tt-rss/ttrssfeed.cpp +++ b/src/services/tt-rss/ttrssfeed.cpp @@ -103,7 +103,8 @@ int TtRssFeed::update() { QList<Message> new_messages = headlines.messages(); messages.append(new_messages); - skip += new_messages.size(); + newly_added_messages = new_messages.size(); + skip += newly_added_messages; } } while (newly_added_messages > 0); @@ -155,5 +156,7 @@ void TtRssFeed::setCustomId(int custom_id) { } int TtRssFeed::updateMessages(const QList<Message> &messages) { + // TODO: pokračovat tady + return 0; }