Merged da shit.
This commit is contained in:
commit
3735d3a105
40 changed files with 736 additions and 180 deletions
|
@ -2,7 +2,7 @@ RSS Guard
|
||||||
=========
|
=========
|
||||||
Welcome to RSS Guard website. You can find here basic information.
|
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
|
Contacts
|
||||||
|
@ -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:
|
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,
|
* multiplatformity,
|
||||||
* support for all feed formats,
|
* support for all feed formats,
|
||||||
* simplicity,
|
* simplicity,
|
||||||
|
|
|
@ -36,7 +36,7 @@ CREATE TABLE IF NOT EXISTS Categories (
|
||||||
parent_id INTEGER NOT NULL,
|
parent_id INTEGER NOT NULL,
|
||||||
title VARCHAR(100) NOT NULL CHECK (title != ''),
|
title VARCHAR(100) NOT NULL CHECK (title != ''),
|
||||||
description TEXT,
|
description TEXT,
|
||||||
date_created BIGINT NOT NULL CHECK (date_created != 0),
|
date_created BIGINT,
|
||||||
icon BLOB,
|
icon BLOB,
|
||||||
account_id INTEGER NOT NULL,
|
account_id INTEGER NOT NULL,
|
||||||
custom_id TEXT,
|
custom_id TEXT,
|
||||||
|
@ -50,17 +50,17 @@ CREATE TABLE IF NOT EXISTS Feeds (
|
||||||
id INTEGER AUTO_INCREMENT PRIMARY KEY,
|
id INTEGER AUTO_INCREMENT PRIMARY KEY,
|
||||||
title TEXT NOT NULL CHECK (title != ''),
|
title TEXT NOT NULL CHECK (title != ''),
|
||||||
description TEXT,
|
description TEXT,
|
||||||
date_created BIGINT NOT NULL CHECK (date_created != 0),
|
date_created BIGINT,
|
||||||
icon BLOB,
|
icon BLOB,
|
||||||
category INTEGER NOT NULL CHECK (category >= -1),
|
category INTEGER NOT NULL CHECK (category >= -1),
|
||||||
encoding TEXT NOT NULL CHECK (encoding != ''),
|
encoding TEXT,
|
||||||
url VARCHAR(100) NOT NULL CHECK (url != ''),
|
url VARCHAR(100),
|
||||||
protected INTEGER(1) NOT NULL CHECK (protected >= 0 AND protected <= 1),
|
protected INTEGER(1) NOT NULL CHECK (protected >= 0 AND protected <= 1),
|
||||||
username TEXT,
|
username TEXT,
|
||||||
password TEXT,
|
password TEXT,
|
||||||
update_type INTEGER(1) NOT NULL CHECK (update_type >= 0),
|
update_type INTEGER(1) NOT NULL CHECK (update_type >= 0),
|
||||||
update_interval INTEGER NOT NULL DEFAULT 15 CHECK (update_interval >= 5),
|
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,
|
account_id INTEGER NOT NULL,
|
||||||
custom_id TEXT,
|
custom_id TEXT,
|
||||||
|
|
||||||
|
|
|
@ -31,7 +31,7 @@ CREATE TABLE IF NOT EXISTS Categories (
|
||||||
parent_id INTEGER NOT NULL,
|
parent_id INTEGER NOT NULL,
|
||||||
title TEXT NOT NULL CHECK (title != ''),
|
title TEXT NOT NULL CHECK (title != ''),
|
||||||
description TEXT,
|
description TEXT,
|
||||||
date_created INTEGER NOT NULL CHECK (date_created != 0),
|
date_created INTEGER,
|
||||||
icon BLOB,
|
icon BLOB,
|
||||||
account_id INTEGER NOT NULL,
|
account_id INTEGER NOT NULL,
|
||||||
custom_id TEXT,
|
custom_id TEXT,
|
||||||
|
@ -45,17 +45,17 @@ CREATE TABLE IF NOT EXISTS Feeds (
|
||||||
id INTEGER PRIMARY KEY,
|
id INTEGER PRIMARY KEY,
|
||||||
title TEXT NOT NULL CHECK (title != ''),
|
title TEXT NOT NULL CHECK (title != ''),
|
||||||
description TEXT,
|
description TEXT,
|
||||||
date_created INTEGER NOT NULL CHECK (date_created != 0),
|
date_created INTEGER,
|
||||||
icon BLOB,
|
icon BLOB,
|
||||||
category INTEGER NOT NULL CHECK (category >= -1),
|
category INTEGER NOT NULL CHECK (category >= -1),
|
||||||
encoding TEXT NOT NULL CHECK (encoding != ''),
|
encoding TEXT,
|
||||||
url TEXT NOT NULL CHECK (url != ''),
|
url TEXT,
|
||||||
protected INTEGER(1) NOT NULL CHECK (protected >= 0 AND protected <= 1),
|
protected INTEGER(1) NOT NULL CHECK (protected >= 0 AND protected <= 1),
|
||||||
username TEXT,
|
username TEXT,
|
||||||
password TEXT,
|
password TEXT,
|
||||||
update_type INTEGER(1) NOT NULL CHECK (update_type >= 0),
|
update_type INTEGER(1) NOT NULL CHECK (update_type >= 0),
|
||||||
update_interval INTEGER NOT NULL CHECK (update_interval >= 5) DEFAULT 15,
|
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,
|
account_id INTEGER NOT NULL,
|
||||||
custom_id TEXT,
|
custom_id TEXT,
|
||||||
|
|
||||||
|
|
|
@ -40,4 +40,19 @@ DROP FOREIGN KEY feed;
|
||||||
ALTER TABLE Messages
|
ALTER TABLE Messages
|
||||||
MODIFY Feeds TEXT;
|
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';
|
UPDATE Information SET inf_value = '4' WHERE inf_key = 'schema_version';
|
|
@ -61,4 +61,54 @@ INSERT INTO Messages SELECT * FROM backup_Messages;
|
||||||
-- !
|
-- !
|
||||||
DROP TABLE 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';
|
UPDATE Information SET inf_value = '4' WHERE inf_key = 'schema_version';
|
|
@ -18,6 +18,7 @@
|
||||||
<ul>
|
<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 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 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>
|
<li>Added ability to go to next <font style="font-weight: bold;">unread</font> message. (partially bug #112)</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
|
|
@ -70,7 +70,7 @@ FeedsModel::FeedsModel(QObject *parent)
|
||||||
|
|
||||||
connect(m_autoUpdateTimer, SIGNAL(timeout()), this, SLOT(executeNextAutoUpdate()));
|
connect(m_autoUpdateTimer, SIGNAL(timeout()), this, SLOT(executeNextAutoUpdate()));
|
||||||
|
|
||||||
loadActivatedServiceAccounts();
|
//loadActivatedServiceAccounts();
|
||||||
updateAutoUpdateStatus();
|
updateAutoUpdateStatus();
|
||||||
|
|
||||||
if (qApp->settings()->value(GROUP(Feeds), SETTING(Feeds::FeedsUpdateOnStartup)).toBool()) {
|
if (qApp->settings()->value(GROUP(Feeds), SETTING(Feeds::FeedsUpdateOnStartup)).toBool()) {
|
||||||
|
@ -82,6 +82,10 @@ FeedsModel::FeedsModel(QObject *parent)
|
||||||
FeedsModel::~FeedsModel() {
|
FeedsModel::~FeedsModel() {
|
||||||
qDebug("Destroying FeedsModel instance.");
|
qDebug("Destroying FeedsModel instance.");
|
||||||
|
|
||||||
|
foreach (ServiceRoot *account, serviceRoots()) {
|
||||||
|
account->stop();
|
||||||
|
}
|
||||||
|
|
||||||
// Delete all model items.
|
// Delete all model items.
|
||||||
delete m_rootItem;
|
delete m_rootItem;
|
||||||
}
|
}
|
||||||
|
@ -697,6 +701,7 @@ bool FeedsModel::addServiceAccount(ServiceRoot *root) {
|
||||||
connect(root, SIGNAL(readFeedsFilterInvalidationRequested()), this, SIGNAL(readFeedsFilterInvalidationRequested()));
|
connect(root, SIGNAL(readFeedsFilterInvalidationRequested()), this, SIGNAL(readFeedsFilterInvalidationRequested()));
|
||||||
connect(root, SIGNAL(dataChanged(QList<RootItem*>)), this, SLOT(onItemDataChanged(QList<RootItem*>)));
|
connect(root, SIGNAL(dataChanged(QList<RootItem*>)), this, SLOT(onItemDataChanged(QList<RootItem*>)));
|
||||||
connect(root, SIGNAL(reloadMessageListRequested(bool)), this, SIGNAL(reloadMessageListRequested(bool)));
|
connect(root, SIGNAL(reloadMessageListRequested(bool)), this, SIGNAL(reloadMessageListRequested(bool)));
|
||||||
|
connect(root, SIGNAL(itemExpandRequested(QList<RootItem*>,bool)), this, SIGNAL(itemExpandRequested(QList<RootItem*>,bool)));
|
||||||
|
|
||||||
root->start();
|
root->start();
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -151,6 +151,9 @@ class FeedsModel : public QAbstractItemModel {
|
||||||
// Adds given service root account.
|
// Adds given service root account.
|
||||||
bool addServiceAccount(ServiceRoot *root);
|
bool addServiceAccount(ServiceRoot *root);
|
||||||
|
|
||||||
|
// Loads feed/categories from the database.
|
||||||
|
void loadActivatedServiceAccounts();
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
// Checks if new parent node is different from one used by original node.
|
// Checks if new parent node is different from one used by original node.
|
||||||
// If it is, then it reassigns original_node to new parent.
|
// If it is, then it reassigns original_node to new parent.
|
||||||
|
@ -206,6 +209,9 @@ class FeedsModel : public QAbstractItemModel {
|
||||||
// Emitted if counts of messages are changed.
|
// Emitted if counts of messages are changed.
|
||||||
void messageCountsChanged(int unread_messages, int total_messages, bool any_feed_has_unread_messages);
|
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.
|
// Emitted when there is a need of reloading of displayed messages.
|
||||||
void reloadMessageListRequested(bool mark_selected_messages_read);
|
void reloadMessageListRequested(bool mark_selected_messages_read);
|
||||||
|
|
||||||
|
@ -218,9 +224,6 @@ class FeedsModel : public QAbstractItemModel {
|
||||||
// which are suitable as IN clause for SQL queries.
|
// which are suitable as IN clause for SQL queries.
|
||||||
QStringList textualFeedIds(const QList<Feed*> &feeds);
|
QStringList textualFeedIds(const QList<Feed*> &feeds);
|
||||||
|
|
||||||
// Loads feed/categories from the database.
|
|
||||||
void loadActivatedServiceAccounts();
|
|
||||||
|
|
||||||
RootItem *m_rootItem;
|
RootItem *m_rootItem;
|
||||||
QList<QString> m_headerData;
|
QList<QString> m_headerData;
|
||||||
QList<QString> m_tooltipData;
|
QList<QString> m_tooltipData;
|
||||||
|
|
|
@ -61,7 +61,7 @@ QString Enclosures::encodeEnclosuresToString(const QList<Enclosure> &enclosures)
|
||||||
}
|
}
|
||||||
|
|
||||||
Message::Message() {
|
Message::Message() {
|
||||||
m_title = m_url = m_author = m_contents = m_customId = "";
|
m_title = m_url = m_author = m_contents = m_feedId = m_customId = "";
|
||||||
m_feedId = 0;
|
|
||||||
m_enclosures = QList<Enclosure>();
|
m_enclosures = QList<Enclosure>();
|
||||||
|
m_isRead = m_isImportant = false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,9 +49,12 @@ class Message {
|
||||||
QString m_author;
|
QString m_author;
|
||||||
QString m_contents;
|
QString m_contents;
|
||||||
QDateTime m_created;
|
QDateTime m_created;
|
||||||
int m_feedId;
|
QString m_feedId;
|
||||||
QString m_customId;
|
QString m_customId;
|
||||||
|
|
||||||
|
bool m_isRead;
|
||||||
|
bool m_isImportant;
|
||||||
|
|
||||||
QList<Enclosure> m_enclosures;
|
QList<Enclosure> m_enclosures;
|
||||||
|
|
||||||
// Is true if "created" date was obtained directly
|
// Is true if "created" date was obtained directly
|
||||||
|
|
|
@ -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_enclosures = Enclosures::decodeEnclosuresFromString(rec.value(MSG_DB_ENCLOSURES_INDEX).toString());
|
||||||
message.m_title = rec.value(MSG_DB_TITLE_INDEX).toString();
|
message.m_title = rec.value(MSG_DB_TITLE_INDEX).toString();
|
||||||
message.m_url = rec.value(MSG_DB_URL_INDEX).toString();
|
message.m_url = rec.value(MSG_DB_URL_INDEX).toString();
|
||||||
message.m_feedId = rec.value(MSG_DB_FEED_INDEX).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();
|
message.m_created = TextFactory::parseDateTime(rec.value(MSG_DB_DCREATED_INDEX).value<qint64>()).toLocalTime();
|
||||||
|
|
||||||
return message;
|
return message;
|
||||||
|
|
|
@ -281,6 +281,26 @@ QList<Category*> RootItem::getSubTreeCategories() {
|
||||||
return children;
|
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*> RootItem::getSubTreeFeeds() {
|
||||||
QList<Feed*> children;
|
QList<Feed*> children;
|
||||||
QList<RootItem*> traversable_items;
|
QList<RootItem*> traversable_items;
|
||||||
|
|
|
@ -177,6 +177,7 @@ class RootItem : public QObject {
|
||||||
QList<RootItem*> getSubTree();
|
QList<RootItem*> getSubTree();
|
||||||
QList<RootItem*> getSubTree(RootItemKind::Kind kind_of_item);
|
QList<RootItem*> getSubTree(RootItemKind::Kind kind_of_item);
|
||||||
QList<Category*> getSubTreeCategories();
|
QList<Category*> getSubTreeCategories();
|
||||||
|
QHash<int,Category*> getHashedSubTreeCategories();
|
||||||
QList<Feed*> getSubTreeFeeds();
|
QList<Feed*> getSubTreeFeeds();
|
||||||
|
|
||||||
// Returns the service root node which is direct or indirect parent of current item.
|
// Returns the service root node which is direct or indirect parent of current item.
|
||||||
|
|
|
@ -105,6 +105,7 @@ QList<QAction*> FormMain::allActions() {
|
||||||
actions << m_ui->m_actionSwitchMainMenu;
|
actions << m_ui->m_actionSwitchMainMenu;
|
||||||
actions << m_ui->m_actionSwitchToolBars;
|
actions << m_ui->m_actionSwitchToolBars;
|
||||||
actions << m_ui->m_actionSwitchListHeaders;
|
actions << m_ui->m_actionSwitchListHeaders;
|
||||||
|
actions << m_ui->m_actionSwitchStatusBar;
|
||||||
actions << m_ui->m_actionSwitchMessageListOrientation;
|
actions << m_ui->m_actionSwitchMessageListOrientation;
|
||||||
|
|
||||||
// Add web browser actions
|
// Add web browser actions
|
||||||
|
@ -172,10 +173,6 @@ void FormMain::switchFullscreenMode() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void FormMain::switchMainMenu() {
|
|
||||||
m_ui->m_menuBar->setVisible(m_ui->m_actionSwitchMainMenu->isChecked());
|
|
||||||
}
|
|
||||||
|
|
||||||
void FormMain::updateAddItemMenu() {
|
void FormMain::updateAddItemMenu() {
|
||||||
// NOTE: Clear here deletes items from memory but only those OWNED by the menu.
|
// NOTE: Clear here deletes items from memory but only those OWNED by the menu.
|
||||||
m_ui->m_menuAddItem->clear();
|
m_ui->m_menuAddItem->clear();
|
||||||
|
@ -326,6 +323,7 @@ void FormMain::setupIcons() {
|
||||||
m_ui->m_actionSwitchMainMenu->setIcon(icon_theme_factory->fromTheme(QSL("view-switch-menu")));
|
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_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_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_actionSwitchMessageListOrientation->setIcon(icon_theme_factory->fromTheme(QSL("view-switch-layout-direction")));
|
||||||
m_ui->m_menuShowHide->setIcon(icon_theme_factory->fromTheme(QSL("view-switch")));
|
m_ui->m_menuShowHide->setIcon(icon_theme_factory->fromTheme(QSL("view-switch")));
|
||||||
|
|
||||||
|
@ -408,6 +406,7 @@ void FormMain::loadSize() {
|
||||||
m_ui->m_tabWidget->feedMessageViewer()->loadSize();
|
m_ui->m_tabWidget->feedMessageViewer()->loadSize();
|
||||||
m_ui->m_actionSwitchToolBars->setChecked(settings->value(GROUP(GUI), SETTING(GUI::ToolbarsVisible)).toBool());
|
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_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.
|
// 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());
|
m_ui->m_actionShowOnlyUnreadItems->setChecked(settings->value(GROUP(Feeds), SETTING(Feeds::ShowOnlyUnreadFeeds)).toBool());
|
||||||
|
@ -431,6 +430,7 @@ void FormMain::saveSize() {
|
||||||
settings->setValue(GROUP(GUI), GUI::MainWindowInitialSize, size());
|
settings->setValue(GROUP(GUI), GUI::MainWindowInitialSize, size());
|
||||||
settings->setValue(GROUP(GUI), GUI::MainWindowStartsMaximized, is_maximized);
|
settings->setValue(GROUP(GUI), GUI::MainWindowStartsMaximized, is_maximized);
|
||||||
settings->setValue(GROUP(GUI), GUI::MainWindowStartsFullscreen, is_fullscreen);
|
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();
|
m_ui->m_tabWidget->feedMessageViewer()->saveSize();
|
||||||
}
|
}
|
||||||
|
@ -452,8 +452,9 @@ void FormMain::createConnections() {
|
||||||
|
|
||||||
// Menu "View" connections.
|
// Menu "View" connections.
|
||||||
connect(m_ui->m_actionFullscreen, SIGNAL(toggled(bool)), this, SLOT(switchFullscreenMode()));
|
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_actionSwitchMainWindow, SIGNAL(triggered()), this, SLOT(switchVisibility()));
|
||||||
|
connect(m_ui->m_actionSwitchStatusBar, SIGNAL(toggled(bool)), statusBar(), SLOT(setVisible(bool)));
|
||||||
|
|
||||||
// Menu "Tools" connections.
|
// Menu "Tools" connections.
|
||||||
connect(m_ui->m_actionSettings, SIGNAL(triggered()), this, SLOT(showSettings()));
|
connect(m_ui->m_actionSettings, SIGNAL(triggered()), this, SLOT(showSettings()));
|
||||||
|
|
|
@ -73,9 +73,6 @@ class FormMain : public QMainWindow {
|
||||||
// Turns on/off fullscreen mode
|
// Turns on/off fullscreen mode
|
||||||
void switchFullscreenMode();
|
void switchFullscreenMode();
|
||||||
|
|
||||||
// Switches visibility of main menu.
|
|
||||||
void switchMainMenu();
|
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void updateAddItemMenu();
|
void updateAddItemMenu();
|
||||||
void updateRecycleBinMenu();
|
void updateRecycleBinMenu();
|
||||||
|
|
|
@ -85,6 +85,7 @@
|
||||||
<addaction name="m_actionSwitchMainMenu"/>
|
<addaction name="m_actionSwitchMainMenu"/>
|
||||||
<addaction name="m_actionSwitchToolBars"/>
|
<addaction name="m_actionSwitchToolBars"/>
|
||||||
<addaction name="m_actionSwitchListHeaders"/>
|
<addaction name="m_actionSwitchListHeaders"/>
|
||||||
|
<addaction name="m_actionSwitchStatusBar"/>
|
||||||
</widget>
|
</widget>
|
||||||
<addaction name="m_menuShowHide"/>
|
<addaction name="m_menuShowHide"/>
|
||||||
<addaction name="m_actionSwitchMainWindow"/>
|
<addaction name="m_actionSwitchMainWindow"/>
|
||||||
|
@ -742,6 +743,20 @@
|
||||||
<string notr="true"/>
|
<string notr="true"/>
|
||||||
</property>
|
</property>
|
||||||
</action>
|
</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>
|
||||||
|
<property name="shortcut">
|
||||||
|
<string notr="true"/>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
</widget>
|
</widget>
|
||||||
<customwidgets>
|
<customwidgets>
|
||||||
<customwidget>
|
<customwidget>
|
||||||
|
|
|
@ -103,8 +103,6 @@ void FeedMessageViewer::loadSize() {
|
||||||
Settings *settings = qApp->settings();
|
Settings *settings = qApp->settings();
|
||||||
int default_msg_section_size = m_messagesView->header()->defaultSectionSize();
|
int default_msg_section_size = m_messagesView->header()->defaultSectionSize();
|
||||||
|
|
||||||
m_feedsView->loadExpandedStates();
|
|
||||||
|
|
||||||
// Restore offsets of splitters.
|
// Restore offsets of splitters.
|
||||||
m_feedSplitter->restoreState(QByteArray::fromBase64(settings->value(GROUP(GUI), SETTING(GUI::SplitterFeeds)).toString().toLocal8Bit()));
|
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()));
|
m_messageSplitter->restoreState(QByteArray::fromBase64(settings->value(GROUP(GUI), SETTING(GUI::SplitterMessages)).toString().toLocal8Bit()));
|
||||||
|
|
|
@ -55,6 +55,7 @@ FeedsView::FeedsView(QWidget *parent)
|
||||||
|
|
||||||
// Connections.
|
// Connections.
|
||||||
connect(m_sourceModel, SIGNAL(requireItemValidationAfterDragDrop(QModelIndex)), this, SLOT(validateItemAfterDragDrop(QModelIndex)));
|
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)));
|
connect(header(), SIGNAL(sortIndicatorChanged(int,Qt::SortOrder)), this, SLOT(saveSortState(int,Qt::SortOrder)));
|
||||||
|
|
||||||
setModel(m_proxyModel);
|
setModel(m_proxyModel);
|
||||||
|
@ -102,11 +103,11 @@ void FeedsView::saveExpandedStates() {
|
||||||
Settings *settings = qApp->settings();
|
Settings *settings = qApp->settings();
|
||||||
QList<RootItem*> expandable_items;
|
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.
|
// Iterate all categories and save their expand statuses.
|
||||||
foreach (RootItem *item, expandable_items) {
|
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),
|
settings->setValue(GROUP(Categories),
|
||||||
setting_name,
|
setting_name,
|
||||||
|
@ -122,10 +123,10 @@ void FeedsView::loadExpandedStates() {
|
||||||
|
|
||||||
// Iterate all categories and save their expand statuses.
|
// Iterate all categories and save their expand statuses.
|
||||||
foreach (RootItem *item, expandable_items) {
|
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)),
|
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);
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -104,6 +104,7 @@ class FeedsView : public QTreeView {
|
||||||
|
|
||||||
void saveSortState(int column, Qt::SortOrder order);
|
void saveSortState(int column, Qt::SortOrder order);
|
||||||
void validateItemAfterDragDrop(const QModelIndex &source_index);
|
void validateItemAfterDragDrop(const QModelIndex &source_index);
|
||||||
|
void onItemExpandRequested(const QList<RootItem*> &items, bool exp);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Initializes context menus.
|
// Initializes context menus.
|
||||||
|
|
|
@ -121,6 +121,10 @@ int main(int argc, char *argv[]) {
|
||||||
QTimer::singleShot(STARTUP_UPDATE_DELAY, application.system(), SLOT(checkForUpdatesOnStartup()));
|
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.
|
// Setup single-instance behavior.
|
||||||
QObject::connect(&application, SIGNAL(messageReceived(QString)), &application, SLOT(processExecutionMessage(QString)));
|
QObject::connect(&application, SIGNAL(messageReceived(QString)), &application, SLOT(processExecutionMessage(QString)));
|
||||||
|
|
||||||
|
|
|
@ -105,6 +105,9 @@ DVALUE(bool) GUI::ToolbarsVisibleDef = true;
|
||||||
DKEY GUI::ListHeadersVisible = "enable_list_headers";
|
DKEY GUI::ListHeadersVisible = "enable_list_headers";
|
||||||
DVALUE(bool) GUI::ListHeadersVisibleDef = true;
|
DVALUE(bool) GUI::ListHeadersVisibleDef = true;
|
||||||
|
|
||||||
|
DKEY GUI::StatusBarVisible = "enable_status_bar";
|
||||||
|
DVALUE(bool) GUI::StatusBarVisibleDef = true;
|
||||||
|
|
||||||
DKEY GUI::HideMainWindowWhenMinimized = "hide_when_minimized";
|
DKEY GUI::HideMainWindowWhenMinimized = "hide_when_minimized";
|
||||||
DVALUE(bool) GUI::HideMainWindowWhenMinimizedDef = false;
|
DVALUE(bool) GUI::HideMainWindowWhenMinimizedDef = false;
|
||||||
|
|
||||||
|
|
|
@ -120,6 +120,9 @@ namespace GUI {
|
||||||
KEY ListHeadersVisible;
|
KEY ListHeadersVisible;
|
||||||
VALUE(bool) ListHeadersVisibleDef;
|
VALUE(bool) ListHeadersVisibleDef;
|
||||||
|
|
||||||
|
KEY StatusBarVisible;
|
||||||
|
VALUE(bool) StatusBarVisibleDef;
|
||||||
|
|
||||||
KEY HideMainWindowWhenMinimized;
|
KEY HideMainWindowWhenMinimized;
|
||||||
VALUE(bool) HideMainWindowWhenMinimizedDef;
|
VALUE(bool) HideMainWindowWhenMinimizedDef;
|
||||||
|
|
||||||
|
|
|
@ -55,6 +55,7 @@ class Feed : public RootItem {
|
||||||
/////////////////////////////////////////
|
/////////////////////////////////////////
|
||||||
|
|
||||||
// Performs synchronous update and returns number of newly updated messages.
|
// 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
|
// NOTE: This should COMPLETELY download ALL messages from online source
|
||||||
// into locale "Messages" table, INCLUDING contents (or excerpts) of those
|
// into locale "Messages" table, INCLUDING contents (or excerpts) of those
|
||||||
// messages.
|
// messages.
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
|
|
||||||
#include "core/feedsmodel.h"
|
#include "core/feedsmodel.h"
|
||||||
#include "miscellaneous/application.h"
|
#include "miscellaneous/application.h"
|
||||||
|
#include "services/abstract/category.h"
|
||||||
|
|
||||||
#include <QSqlQuery>
|
#include <QSqlQuery>
|
||||||
|
|
||||||
|
@ -58,7 +59,7 @@ bool ServiceRoot::deleteViaGui() {
|
||||||
return data_removed;
|
return data_removed;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ServiceRoot::itemChanged(QList<RootItem*> items) {
|
void ServiceRoot::itemChanged(const QList<RootItem *> &items) {
|
||||||
emit dataChanged(items);
|
emit dataChanged(items);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,6 +71,10 @@ void ServiceRoot::requestFeedReadFilterReload() {
|
||||||
emit readFeedsFilterInvalidationRequested();
|
emit readFeedsFilterInvalidationRequested();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ServiceRoot::requestItemExpand(const QList<RootItem *> &items, bool expand) {
|
||||||
|
emit itemExpandRequested(items, expand);
|
||||||
|
}
|
||||||
|
|
||||||
void ServiceRoot::requestItemReassignment(RootItem *item, RootItem *new_parent) {
|
void ServiceRoot::requestItemReassignment(RootItem *item, RootItem *new_parent) {
|
||||||
emit itemReassignmentRequested(item, new_parent);
|
emit itemReassignmentRequested(item, new_parent);
|
||||||
}
|
}
|
||||||
|
@ -85,3 +90,47 @@ int ServiceRoot::accountId() const {
|
||||||
void ServiceRoot::setAccountId(int account_id) {
|
void ServiceRoot::setAccountId(int account_id) {
|
||||||
m_accountId = 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--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -30,6 +30,10 @@ class RecycleBin;
|
||||||
class QAction;
|
class QAction;
|
||||||
class QSqlTableModel;
|
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.
|
// THIS IS the root node of the service.
|
||||||
// NOTE: The root usually contains some core functionality of the
|
// NOTE: The root usually contains some core functionality of the
|
||||||
// service like service account username/password etc.
|
// service like service account username/password etc.
|
||||||
|
@ -64,6 +68,8 @@ class ServiceRoot : public RootItem {
|
||||||
|
|
||||||
// Start/stop services.
|
// Start/stop services.
|
||||||
// Start method is called when feed model gets initialized OR after user adds new service.
|
// 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
|
// Stop method is called just before application exits OR when
|
||||||
// user explicitly deletes existing service instance.
|
// user explicitly deletes existing service instance.
|
||||||
|
@ -79,8 +85,6 @@ class ServiceRoot : public RootItem {
|
||||||
// and then use method QSqlTableModel::setFilter(....).
|
// and then use method QSqlTableModel::setFilter(....).
|
||||||
// NOTE: It would be more preferable if all messages are downloaded
|
// NOTE: It would be more preferable if all messages are downloaded
|
||||||
// right when feeds are updated.
|
// 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;
|
virtual bool loadMessagesForItem(RootItem *item, QSqlTableModel *model) = 0;
|
||||||
|
|
||||||
// Called BEFORE this read status update (triggered by user in message list) is stored in DB,
|
// Called BEFORE this read status update (triggered by user in message list) is stored in DB,
|
||||||
|
@ -138,10 +142,10 @@ class ServiceRoot : public RootItem {
|
||||||
/////////////////////////////////////////
|
/////////////////////////////////////////
|
||||||
|
|
||||||
// Obvious methods to wrap signals.
|
// Obvious methods to wrap signals.
|
||||||
void itemChanged(QList<RootItem*> items);
|
void itemChanged(const QList<RootItem*> &items);
|
||||||
void requestReloadMessageList(bool mark_selected_messages_read);
|
void requestReloadMessageList(bool mark_selected_messages_read);
|
||||||
void requestFeedReadFilterReload();
|
void requestFeedReadFilterReload();
|
||||||
|
void requestItemExpand(const QList<RootItem*> &items, bool expand);
|
||||||
void requestItemReassignment(RootItem *item, RootItem *new_parent);
|
void requestItemReassignment(RootItem *item, RootItem *new_parent);
|
||||||
void requestItemRemoval(RootItem *item);
|
void requestItemRemoval(RootItem *item);
|
||||||
|
|
||||||
|
@ -149,11 +153,17 @@ class ServiceRoot : public RootItem {
|
||||||
int accountId() const;
|
int accountId() const;
|
||||||
void setAccountId(int account_id);
|
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:
|
signals:
|
||||||
// Emitted if data in any item belonging to this root are changed.
|
// Emitted if data in any item belonging to this root are changed.
|
||||||
void dataChanged(QList<RootItem*> items);
|
void dataChanged(QList<RootItem*> items);
|
||||||
void readFeedsFilterInvalidationRequested();
|
void readFeedsFilterInvalidationRequested();
|
||||||
void reloadMessageListRequested(bool mark_selected_messages_read);
|
void reloadMessageListRequested(bool mark_selected_messages_read);
|
||||||
|
void itemExpandRequested(QList<RootItem*> items, bool expand);
|
||||||
|
|
||||||
void itemReassignmentRequested(RootItem *item, RootItem *new_parent);
|
void itemReassignmentRequested(RootItem *item, RootItem *new_parent);
|
||||||
void itemRemovalRequested(RootItem *item);
|
void itemRemovalRequested(RootItem *item);
|
||||||
|
|
|
@ -233,6 +233,7 @@ void FormStandardImportExport::importFeeds() {
|
||||||
QString output_message;
|
QString output_message;
|
||||||
|
|
||||||
if (m_serviceRoot->mergeImportExportModel(m_model, 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);
|
m_ui->m_lblResult->setStatus(WidgetWithStatus::Ok, output_message, output_message);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|
|
@ -135,7 +135,7 @@ QList<Message> StandardFeed::undeletedMessages() const {
|
||||||
QSqlDatabase database = qApp->database()->connection(metaObject()->className(), DatabaseFactory::FromSettings);
|
QSqlDatabase database = qApp->database()->connection(metaObject()->className(), DatabaseFactory::FromSettings);
|
||||||
QSqlQuery query_read_msg(database);
|
QSqlQuery query_read_msg(database);
|
||||||
query_read_msg.setForwardOnly(true);
|
query_read_msg.setForwardOnly(true);
|
||||||
query_read_msg.prepare("SELECT title, url, author, date_created, contents "
|
query_read_msg.prepare("SELECT title, url, author, date_created, contents, enclosures "
|
||||||
"FROM Messages "
|
"FROM Messages "
|
||||||
"WHERE is_deleted = 0 AND feed = :feed AND account_id = :account_id;");
|
"WHERE is_deleted = 0 AND feed = :feed AND account_id = :account_id;");
|
||||||
|
|
||||||
|
@ -154,6 +154,7 @@ QList<Message> StandardFeed::undeletedMessages() const {
|
||||||
message.m_author = query_read_msg.value(2).toString();
|
message.m_author = query_read_msg.value(2).toString();
|
||||||
message.m_created = TextFactory::parseDateTime(query_read_msg.value(3).value<qint64>());
|
message.m_created = TextFactory::parseDateTime(query_read_msg.value(3).value<qint64>());
|
||||||
message.m_contents = query_read_msg.value(4).toString();
|
message.m_contents = query_read_msg.value(4).toString();
|
||||||
|
message.m_enclosures = Enclosures::decodeEnclosuresFromString(query_read_msg.value(5).toString());
|
||||||
|
|
||||||
messages.append(message);
|
messages.append(message);
|
||||||
}
|
}
|
||||||
|
@ -763,7 +764,6 @@ QNetworkReply::NetworkError StandardFeed::networkError() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
StandardFeed::StandardFeed(const QSqlRecord &record) : Feed(NULL) {
|
StandardFeed::StandardFeed(const QSqlRecord &record) : Feed(NULL) {
|
||||||
setKind(RootItemKind::Feed);
|
|
||||||
setTitle(record.value(FDS_DB_TITLE_INDEX).toString());
|
setTitle(record.value(FDS_DB_TITLE_INDEX).toString());
|
||||||
setId(record.value(FDS_DB_ID_INDEX).toInt());
|
setId(record.value(FDS_DB_ID_INDEX).toInt());
|
||||||
setDescription(record.value(FDS_DB_DESCRIPTION_INDEX).toString());
|
setDescription(record.value(FDS_DB_DESCRIPTION_INDEX).toString());
|
||||||
|
@ -782,6 +782,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());
|
setAutoUpdateInitialInterval(record.value(FDS_DB_UPDATE_INTERVAL_INDEX).toInt());
|
||||||
}
|
}
|
||||||
|
|
|
@ -75,7 +75,6 @@ ServiceRoot *StandardServiceEntryPoint::createNewRoot() {
|
||||||
SERVICE_CODE_STD_RSS))) {
|
SERVICE_CODE_STD_RSS))) {
|
||||||
StandardServiceRoot *root = new StandardServiceRoot();
|
StandardServiceRoot *root = new StandardServiceRoot();
|
||||||
root->setAccountId(id_to_assing);
|
root->setAccountId(id_to_assing);
|
||||||
root->loadFromDatabase();
|
|
||||||
return root;
|
return root;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@ -93,7 +92,6 @@ QList<ServiceRoot*> StandardServiceEntryPoint::initializeSubtree() {
|
||||||
while (query.next()) {
|
while (query.next()) {
|
||||||
StandardServiceRoot *root = new StandardServiceRoot();
|
StandardServiceRoot *root = new StandardServiceRoot();
|
||||||
root->setAccountId(query.value(0).toInt());
|
root->setAccountId(query.value(0).toInt());
|
||||||
root->loadFromDatabase();
|
|
||||||
roots.append(root);
|
roots.append(root);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,6 +61,8 @@ StandardServiceRoot::~StandardServiceRoot() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void StandardServiceRoot::start() {
|
void StandardServiceRoot::start() {
|
||||||
|
loadFromDatabase();
|
||||||
|
|
||||||
if (qApp->isFirstRun()) {
|
if (qApp->isFirstRun()) {
|
||||||
if (MessageBox::show(qApp->mainForm(), QMessageBox::Question, QObject::tr("Load initial set of feeds"),
|
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),
|
tr("You started %1 for the first time, now you can load initial set of feeds.").arg(APP_NAME),
|
||||||
|
@ -83,7 +85,10 @@ void StandardServiceRoot::start() {
|
||||||
try {
|
try {
|
||||||
model.importAsOPML20(IOFactory::readTextFile(file_to_load));
|
model.importAsOPML20(IOFactory::readTextFile(file_to_load));
|
||||||
model.checkAllItems();
|
model.checkAllItems();
|
||||||
mergeImportExportModel(&model, output_msg);
|
|
||||||
|
if (mergeImportExportModel(&model, output_msg)) {
|
||||||
|
requestItemExpand(getSubTree(), true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (ApplicationException &ex) {
|
catch (ApplicationException &ex) {
|
||||||
MessageBox::show(qApp->mainForm(), QMessageBox::Critical, tr("Error when loading initial feeds"), ex.message());
|
MessageBox::show(qApp->mainForm(), QMessageBox::Critical, tr("Error when loading initial feeds"), ex.message());
|
||||||
|
@ -326,8 +331,8 @@ bool StandardServiceRoot::emptyBin() {
|
||||||
|
|
||||||
void StandardServiceRoot::loadFromDatabase(){
|
void StandardServiceRoot::loadFromDatabase(){
|
||||||
QSqlDatabase database = qApp->database()->connection(metaObject()->className(), DatabaseFactory::FromSettings);
|
QSqlDatabase database = qApp->database()->connection(metaObject()->className(), DatabaseFactory::FromSettings);
|
||||||
CategoryAssignment categories;
|
Assignment categories;
|
||||||
FeedAssignment feeds;
|
Assignment feeds;
|
||||||
|
|
||||||
// Obtain data for categories from the database.
|
// Obtain data for categories from the database.
|
||||||
QSqlQuery query_categories(database);
|
QSqlQuery query_categories(database);
|
||||||
|
@ -339,7 +344,7 @@ void StandardServiceRoot::loadFromDatabase(){
|
||||||
}
|
}
|
||||||
|
|
||||||
while (query_categories.next()) {
|
while (query_categories.next()) {
|
||||||
CategoryAssignmentItem pair;
|
AssignmentItem pair;
|
||||||
pair.first = query_categories.value(CAT_DB_PARENT_ID_INDEX).toInt();
|
pair.first = query_categories.value(CAT_DB_PARENT_ID_INDEX).toInt();
|
||||||
pair.second = new StandardCategory(query_categories.record());
|
pair.second = new StandardCategory(query_categories.record());
|
||||||
|
|
||||||
|
@ -364,10 +369,10 @@ void StandardServiceRoot::loadFromDatabase(){
|
||||||
case StandardFeed::Rdf:
|
case StandardFeed::Rdf:
|
||||||
case StandardFeed::Rss0X:
|
case StandardFeed::Rss0X:
|
||||||
case StandardFeed::Rss2X: {
|
case StandardFeed::Rss2X: {
|
||||||
FeedAssignmentItem pair;
|
AssignmentItem pair;
|
||||||
pair.first = query_feeds.value(FDS_DB_CATEGORY_INDEX).toInt();
|
pair.first = query_feeds.value(FDS_DB_CATEGORY_INDEX).toInt();
|
||||||
pair.second = new StandardFeed(query_feeds.record());
|
pair.second = new StandardFeed(query_feeds.record());
|
||||||
pair.second->setType(type);
|
qobject_cast<StandardFeed*>(pair.second)->setType(type);
|
||||||
|
|
||||||
feeds << pair;
|
feeds << pair;
|
||||||
break;
|
break;
|
||||||
|
@ -414,6 +419,7 @@ QHash<int,StandardCategory*> StandardServiceRoot::categoriesForItem(RootItem *ro
|
||||||
}
|
}
|
||||||
|
|
||||||
QHash<int,StandardCategory*> StandardServiceRoot::allCategories() {
|
QHash<int,StandardCategory*> StandardServiceRoot::allCategories() {
|
||||||
|
// TODO: změnit na qlist, použít getsubtree možná
|
||||||
return categoriesForItem(this);
|
return categoriesForItem(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -431,26 +437,6 @@ QList<QAction*> StandardServiceRoot::getContextMenuForFeed(StandardFeed *feed) {
|
||||||
return m_feedContextMenu;
|
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) {
|
bool StandardServiceRoot::mergeImportExportModel(FeedsImportExportModel *model, QString &output_message) {
|
||||||
QStack<RootItem*> original_parents; original_parents.push(this);
|
QStack<RootItem*> original_parents; original_parents.push(this);
|
||||||
QStack<RootItem*> new_parents; new_parents.push(model->rootItem());
|
QStack<RootItem*> new_parents; new_parents.push(model->rootItem());
|
||||||
|
@ -691,26 +677,3 @@ bool StandardServiceRoot::onAfterMessagesRestoredFromBin(RootItem *selected_item
|
||||||
requestFeedReadFilterReload();
|
requestFeedReadFilterReload();
|
||||||
return true;
|
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--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -30,12 +30,6 @@ class StandardFeed;
|
||||||
class FeedsImportExportModel;
|
class FeedsImportExportModel;
|
||||||
class QMenu;
|
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 {
|
class StandardServiceRoot : public ServiceRoot {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
|
@ -117,11 +111,6 @@ class StandardServiceRoot : public ServiceRoot {
|
||||||
// which are suitable as IN clause for SQL queries.
|
// which are suitable as IN clause for SQL queries.
|
||||||
QStringList textualFeedIds(const QList<Feed *> &feeds);
|
QStringList textualFeedIds(const QList<Feed *> &feeds);
|
||||||
|
|
||||||
// Takes lists of feeds/categories and assembles
|
|
||||||
// them into the tree structure.
|
|
||||||
void assembleCategories(CategoryAssignment categories);
|
|
||||||
void assembleFeeds(FeedAssignment feeds);
|
|
||||||
|
|
||||||
StandardRecycleBin *m_recycleBin;
|
StandardRecycleBin *m_recycleBin;
|
||||||
|
|
||||||
// Menus.
|
// Menus.
|
||||||
|
|
|
@ -11,6 +11,9 @@
|
||||||
#define UNKNOWN_METHOD "UNKNOWN_METHOD" // Given "op" is not recognized.
|
#define UNKNOWN_METHOD "UNKNOWN_METHOD" // Given "op" is not recognized.
|
||||||
#define INCORRECT_USAGE "INCORRECT_USAGE" // Given "op" was used with bad parameters.
|
#define INCORRECT_USAGE "INCORRECT_USAGE" // Given "op" was used with bad parameters.
|
||||||
|
|
||||||
|
// Limitations
|
||||||
|
#define MAX_MESSAGES 200
|
||||||
|
|
||||||
// General return status codes.
|
// General return status codes.
|
||||||
#define API_STATUS_OK 0
|
#define API_STATUS_OK 0
|
||||||
#define API_STATUS_ERR 1
|
#define API_STATUS_ERR 1
|
||||||
|
|
|
@ -185,6 +185,9 @@ void FormEditAccount::onUrlChanged() {
|
||||||
if (url.isEmpty()) {
|
if (url.isEmpty()) {
|
||||||
m_ui->m_txtUrl->setStatus(WidgetWithStatus::Error, tr("URL cannot be empty."));
|
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 {
|
else {
|
||||||
m_ui->m_txtUrl->setStatus(WidgetWithStatus::Ok, tr("URL is okay."));
|
m_ui->m_txtUrl->setStatus(WidgetWithStatus::Ok, tr("URL is okay."));
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
#include "services/tt-rss/ttrsscategory.h"
|
#include "services/tt-rss/ttrsscategory.h"
|
||||||
#include "miscellaneous/application.h"
|
#include "miscellaneous/application.h"
|
||||||
#include "miscellaneous/iconfactory.h"
|
#include "miscellaneous/iconfactory.h"
|
||||||
|
#include "miscellaneous/textfactory.h"
|
||||||
#include "network-web/networkfactory.h"
|
#include "network-web/networkfactory.h"
|
||||||
|
|
||||||
#include <QPair>
|
#include <QPair>
|
||||||
|
@ -92,18 +93,25 @@ TtRssLoginResponse TtRssNetworkFactory::login(QNetworkReply::NetworkError &error
|
||||||
}
|
}
|
||||||
|
|
||||||
TtRssResponse TtRssNetworkFactory::logout(QNetworkReply::NetworkError &error) {
|
TtRssResponse TtRssNetworkFactory::logout(QNetworkReply::NetworkError &error) {
|
||||||
QtJson::JsonObject json;
|
if (!m_sessionId.isEmpty()) {
|
||||||
json["op"] = "logout";
|
|
||||||
json["sid"] = m_sessionId;
|
|
||||||
|
|
||||||
QByteArray result_raw;
|
QtJson::JsonObject json;
|
||||||
NetworkResult network_reply = NetworkFactory::uploadData(m_url, DOWNLOAD_TIMEOUT, QtJson::serialize(json), CONTENT_TYPE, result_raw);
|
json["op"] = "logout";
|
||||||
|
json["sid"] = m_sessionId;
|
||||||
|
|
||||||
error = network_reply.first;
|
QByteArray result_raw;
|
||||||
return TtRssResponse(QString::fromUtf8(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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TtRssGetFeedsTreeResponse TtRssNetworkFactory::getFeedsTree(QNetworkReply::NetworkError &error) {
|
TtRssGetFeedsCategoriesResponse TtRssNetworkFactory::getFeedsCategories(QNetworkReply::NetworkError &error) {
|
||||||
QtJson::JsonObject json;
|
QtJson::JsonObject json;
|
||||||
json["op"] = "getFeedTree";
|
json["op"] = "getFeedTree";
|
||||||
json["sid"] = m_sessionId;
|
json["sid"] = m_sessionId;
|
||||||
|
@ -111,7 +119,7 @@ TtRssGetFeedsTreeResponse TtRssNetworkFactory::getFeedsTree(QNetworkReply::Netwo
|
||||||
|
|
||||||
QByteArray result_raw;
|
QByteArray result_raw;
|
||||||
NetworkResult network_reply = NetworkFactory::uploadData(m_url, DOWNLOAD_TIMEOUT, QtJson::serialize(json), CONTENT_TYPE, result_raw);
|
NetworkResult network_reply = NetworkFactory::uploadData(m_url, DOWNLOAD_TIMEOUT, QtJson::serialize(json), CONTENT_TYPE, result_raw);
|
||||||
TtRssGetFeedsTreeResponse result(QString::fromUtf8(result_raw));
|
TtRssGetFeedsCategoriesResponse result(QString::fromUtf8(result_raw));
|
||||||
|
|
||||||
if (result.isNotLoggedIn()) {
|
if (result.isNotLoggedIn()) {
|
||||||
// We are not logged in.
|
// We are not logged in.
|
||||||
|
@ -119,7 +127,38 @@ TtRssGetFeedsTreeResponse TtRssNetworkFactory::getFeedsTree(QNetworkReply::Netwo
|
||||||
json["sid"] = m_sessionId;
|
json["sid"] = m_sessionId;
|
||||||
|
|
||||||
network_reply = NetworkFactory::uploadData(m_url, DOWNLOAD_TIMEOUT, QtJson::serialize(json), CONTENT_TYPE, result_raw);
|
network_reply = NetworkFactory::uploadData(m_url, DOWNLOAD_TIMEOUT, QtJson::serialize(json), CONTENT_TYPE, result_raw);
|
||||||
result = TtRssGetFeedsTreeResponse(QString::fromUtf8(result_raw));
|
result = TtRssGetFeedsCategoriesResponse(QString::fromUtf8(result_raw));
|
||||||
|
}
|
||||||
|
|
||||||
|
error = network_reply.first;
|
||||||
|
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;
|
error = network_reply.first;
|
||||||
|
@ -203,15 +242,19 @@ bool TtRssResponse::hasError() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
TtRssGetFeedsTreeResponse::TtRssGetFeedsTreeResponse(const QString &raw_content) : TtRssResponse(raw_content) {
|
TtRssGetFeedsCategoriesResponse::TtRssGetFeedsCategoriesResponse(const QString &raw_content) : TtRssResponse(raw_content) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TtRssGetFeedsTreeResponse::~TtRssGetFeedsTreeResponse() {
|
TtRssGetFeedsCategoriesResponse::~TtRssGetFeedsCategoriesResponse() {
|
||||||
}
|
}
|
||||||
|
|
||||||
RootItem *TtRssGetFeedsTreeResponse::feedsTree() {
|
RootItem *TtRssGetFeedsCategoriesResponse::feedsCategories(bool obtain_icons, QString base_address) {
|
||||||
RootItem *parent = new RootItem();
|
RootItem *parent = new RootItem();
|
||||||
|
|
||||||
|
// Chop the "api/" from the end of the address.
|
||||||
|
base_address.chop(4);
|
||||||
|
|
||||||
if (status() == API_STATUS_OK) {
|
if (status() == API_STATUS_OK) {
|
||||||
// We have data, construct object tree according to data.
|
// We have data, construct object tree according to data.
|
||||||
QList<QVariant> items_to_process = m_rawContent["content"].toMap()["categories"].toMap()["items"].toList();
|
QList<QVariant> items_to_process = m_rawContent["content"].toMap()["categories"].toMap()["items"].toList();
|
||||||
|
@ -226,51 +269,105 @@ RootItem *TtRssGetFeedsTreeResponse::feedsTree() {
|
||||||
RootItem *act_parent = pair.first;
|
RootItem *act_parent = pair.first;
|
||||||
QMap<QString,QVariant> item = pair.second.toMap();
|
QMap<QString,QVariant> item = pair.second.toMap();
|
||||||
|
|
||||||
if (item.contains("type") && item["type"].toString() == GFT_TYPE_CATEGORY) {
|
int item_id = item["bare_id"].toInt();
|
||||||
// Add category to the parent, go through children.
|
bool is_category = item.contains("type") && item["type"].toString() == GFT_TYPE_CATEGORY;
|
||||||
int item_bare_id = item["bare_id"].toInt();
|
|
||||||
|
|
||||||
if (item_bare_id < 0) {
|
if (item_id >= 0) {
|
||||||
// Ignore virtual categories or feeds.
|
if (is_category) {
|
||||||
continue;
|
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) {
|
category->setTitle(item["name"].toString());
|
||||||
// This is "Uncategorized" category, all its feeds belong to total parent.
|
category->setCustomId(item_id);
|
||||||
if (item.contains("items")) {
|
act_parent->appendChild(category);
|
||||||
foreach (QVariant child_feed, item["items"].toList()) {
|
|
||||||
pairs.append(QPair<RootItem*,QVariant>(parent, child_feed));
|
if (item.contains("items")) {
|
||||||
|
foreach (QVariant child, item["items"].toList()) {
|
||||||
|
pairs.append(QPair<RootItem*,QVariant>(category, child));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (item_bare_id > 0) {
|
else {
|
||||||
TtRssCategory *category = new TtRssCategory();
|
// We have feed.
|
||||||
|
TtRssFeed *feed = new TtRssFeed();
|
||||||
|
|
||||||
category->setIcon(qApp->icons()->fromTheme(QSL("folder-category")));
|
if (obtain_icons) {
|
||||||
category->setTitle(item["name"].toString());
|
QString icon_path = item["icon"].type() == QVariant::String ? item["icon"].toString() : QString();
|
||||||
category->setCustomId(item_bare_id);
|
|
||||||
act_parent->appendChild(category);
|
|
||||||
|
|
||||||
if (item.contains("items")) {
|
if (!icon_path.isEmpty()) {
|
||||||
foreach (QVariant child, item["items"].toList()) {
|
// Chop the "api/" suffix out and append
|
||||||
pairs.append(QPair<RootItem*,QVariant>(category, child));
|
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return parent;
|
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;
|
||||||
|
}
|
||||||
|
|
|
@ -20,6 +20,8 @@
|
||||||
|
|
||||||
#include "qt-json/json.h"
|
#include "qt-json/json.h"
|
||||||
|
|
||||||
|
#include "core/message.h"
|
||||||
|
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QPair>
|
#include <QPair>
|
||||||
#include <QNetworkReply>
|
#include <QNetworkReply>
|
||||||
|
@ -53,12 +55,23 @@ class TtRssLoginResponse : public TtRssResponse {
|
||||||
QString sessionId() const;
|
QString sessionId() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
class TtRssGetFeedsTreeResponse : public TtRssResponse {
|
class TtRssGetFeedsCategoriesResponse : public TtRssResponse {
|
||||||
public:
|
public:
|
||||||
explicit TtRssGetFeedsTreeResponse(const QString &raw_content = QString());
|
explicit TtRssGetFeedsCategoriesResponse(const QString &raw_content = QString());
|
||||||
virtual ~TtRssGetFeedsTreeResponse();
|
virtual ~TtRssGetFeedsCategoriesResponse();
|
||||||
|
|
||||||
RootItem *feedsTree();
|
// 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 TtRssGetHeadlinesResponse : public TtRssResponse {
|
||||||
|
public:
|
||||||
|
explicit TtRssGetHeadlinesResponse(const QString &raw_content = QString());
|
||||||
|
virtual ~TtRssGetHeadlinesResponse();
|
||||||
|
|
||||||
|
QList<Message> messages();
|
||||||
};
|
};
|
||||||
|
|
||||||
class TtRssNetworkFactory {
|
class TtRssNetworkFactory {
|
||||||
|
@ -84,7 +97,12 @@ class TtRssNetworkFactory {
|
||||||
TtRssResponse logout(QNetworkReply::NetworkError &error);
|
TtRssResponse logout(QNetworkReply::NetworkError &error);
|
||||||
|
|
||||||
// Gets feeds from the server.
|
// Gets feeds from the server.
|
||||||
TtRssGetFeedsTreeResponse getFeedsTree(QNetworkReply::NetworkError &error);
|
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:
|
private:
|
||||||
QString m_url;
|
QString m_url;
|
||||||
|
|
12
src/services/tt-rss/ttrsscategory.cpp
Normal file → Executable file
12
src/services/tt-rss/ttrsscategory.cpp
Normal file → Executable file
|
@ -18,9 +18,21 @@
|
||||||
#include "services/tt-rss/ttrsscategory.h"
|
#include "services/tt-rss/ttrsscategory.h"
|
||||||
|
|
||||||
#include "definitions/definitions.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(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());
|
||||||
|
setCustomId(record.value(CAT_DB_CUSTOM_ID_INDEX).toInt());
|
||||||
}
|
}
|
||||||
|
|
||||||
TtRssCategory::~TtRssCategory() {
|
TtRssCategory::~TtRssCategory() {
|
||||||
|
|
3
src/services/tt-rss/ttrsscategory.h
Normal file → Executable file
3
src/services/tt-rss/ttrsscategory.h
Normal file → Executable file
|
@ -20,10 +20,13 @@
|
||||||
|
|
||||||
#include "services/abstract/category.h"
|
#include "services/abstract/category.h"
|
||||||
|
|
||||||
|
#include <QSqlRecord>
|
||||||
|
|
||||||
|
|
||||||
class TtRssCategory : public Category {
|
class TtRssCategory : public Category {
|
||||||
public:
|
public:
|
||||||
explicit TtRssCategory(RootItem *parent = NULL);
|
explicit TtRssCategory(RootItem *parent = NULL);
|
||||||
|
explicit TtRssCategory(const QSqlRecord &record);
|
||||||
virtual ~TtRssCategory();
|
virtual ~TtRssCategory();
|
||||||
|
|
||||||
int customId() const;
|
int customId() const;
|
||||||
|
|
125
src/services/tt-rss/ttrssfeed.cpp
Normal file → Executable file
125
src/services/tt-rss/ttrssfeed.cpp
Normal file → Executable file
|
@ -18,20 +18,133 @@
|
||||||
#include "services/tt-rss/ttrssfeed.h"
|
#include "services/tt-rss/ttrssfeed.h"
|
||||||
|
|
||||||
#include "definitions/definitions.h"
|
#include "definitions/definitions.h"
|
||||||
|
#include "miscellaneous/application.h"
|
||||||
|
#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>
|
||||||
|
|
||||||
|
|
||||||
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(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() {
|
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() {
|
int TtRssFeed::update() {
|
||||||
return 0;
|
// TODO: přes getHeadlines provede stažení kompletnich zprav.
|
||||||
|
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);
|
||||||
|
newly_added_messages = new_messages.size();
|
||||||
|
skip += newly_added_messages;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (newly_added_messages > 0);
|
||||||
|
|
||||||
|
return updateMessages(messages);
|
||||||
}
|
}
|
||||||
|
|
||||||
QList<Message> TtRssFeed::undeletedMessages() const {
|
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, custom_id "
|
||||||
|
"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 = 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return messages;
|
||||||
}
|
}
|
||||||
|
|
||||||
int TtRssFeed::customId() const {
|
int TtRssFeed::customId() const {
|
||||||
|
@ -41,3 +154,9 @@ int TtRssFeed::customId() const {
|
||||||
void TtRssFeed::setCustomId(int custom_id) {
|
void TtRssFeed::setCustomId(int custom_id) {
|
||||||
m_customId = custom_id;
|
m_customId = custom_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int TtRssFeed::updateMessages(const QList<Message> &messages) {
|
||||||
|
// TODO: pokračovat tady
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
16
src/services/tt-rss/ttrssfeed.h
Normal file → Executable file
16
src/services/tt-rss/ttrssfeed.h
Normal file → Executable file
|
@ -20,12 +20,24 @@
|
||||||
|
|
||||||
#include "services/abstract/feed.h"
|
#include "services/abstract/feed.h"
|
||||||
|
|
||||||
|
#include <QSqlRecord>
|
||||||
|
|
||||||
|
|
||||||
|
class TtRssServiceRoot;
|
||||||
|
|
||||||
class TtRssFeed : public Feed {
|
class TtRssFeed : public Feed {
|
||||||
public:
|
public:
|
||||||
explicit TtRssFeed(RootItem *parent = NULL);
|
explicit TtRssFeed(RootItem *parent = NULL);
|
||||||
|
explicit TtRssFeed(const QSqlRecord &record);
|
||||||
virtual ~TtRssFeed();
|
virtual ~TtRssFeed();
|
||||||
|
|
||||||
|
TtRssServiceRoot *serviceRoot();
|
||||||
|
|
||||||
|
void updateCounts(bool including_total_count);
|
||||||
|
|
||||||
|
int countOfAllMessages();
|
||||||
|
int countOfUnreadMessages();
|
||||||
|
|
||||||
int update();
|
int update();
|
||||||
QList<Message> undeletedMessages() const;
|
QList<Message> undeletedMessages() const;
|
||||||
|
|
||||||
|
@ -33,7 +45,11 @@ class TtRssFeed : public Feed {
|
||||||
void setCustomId(int custom_id);
|
void setCustomId(int custom_id);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
int updateMessages(const QList<Message> &messages);
|
||||||
|
|
||||||
int m_customId;
|
int m_customId;
|
||||||
|
int m_totalCount;
|
||||||
|
int m_unreadCount;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // TTRSSFEED_H
|
#endif // TTRSSFEED_H
|
||||||
|
|
|
@ -20,13 +20,18 @@
|
||||||
#include "miscellaneous/application.h"
|
#include "miscellaneous/application.h"
|
||||||
#include "miscellaneous/settings.h"
|
#include "miscellaneous/settings.h"
|
||||||
#include "gui/dialogs/formmain.h"
|
#include "gui/dialogs/formmain.h"
|
||||||
|
#include "network-web/networkfactory.h"
|
||||||
#include "services/tt-rss/ttrssserviceentrypoint.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/network/ttrssnetworkfactory.h"
|
||||||
#include "services/tt-rss/gui/formeditaccount.h"
|
#include "services/tt-rss/gui/formeditaccount.h"
|
||||||
|
|
||||||
|
#include <QSqlTableModel>
|
||||||
#include <QSqlQuery>
|
#include <QSqlQuery>
|
||||||
#include <QSqlError>
|
#include <QSqlError>
|
||||||
#include <QPointer>
|
#include <QPointer>
|
||||||
|
#include <QPair>
|
||||||
|
|
||||||
|
|
||||||
TtRssServiceRoot::TtRssServiceRoot(RootItem *parent)
|
TtRssServiceRoot::TtRssServiceRoot(RootItem *parent)
|
||||||
|
@ -42,15 +47,18 @@ TtRssServiceRoot::~TtRssServiceRoot() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void TtRssServiceRoot::start() {
|
void TtRssServiceRoot::start() {
|
||||||
loadFeedTreeFromDatabase();
|
loadFromDatabase();
|
||||||
|
|
||||||
if (childItems().isEmpty()) {
|
if (childCount() == 0) {
|
||||||
syncIn();
|
syncIn();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void TtRssServiceRoot::stop() {
|
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() {
|
QString TtRssServiceRoot::code() {
|
||||||
|
@ -111,7 +119,13 @@ RecycleBin *TtRssServiceRoot::recycleBin() {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TtRssServiceRoot::loadMessagesForItem(RootItem *item, QSqlTableModel *model) {
|
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() {
|
QList<QAction*> TtRssServiceRoot::serviceMenu() {
|
||||||
|
@ -211,6 +225,52 @@ 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() {
|
void TtRssServiceRoot::updateTitle() {
|
||||||
QString host = QUrl(m_network->url()).host();
|
QString host = QUrl(m_network->url()).host();
|
||||||
|
|
||||||
|
@ -222,37 +282,110 @@ void TtRssServiceRoot::updateTitle() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void TtRssServiceRoot::syncIn() {
|
void TtRssServiceRoot::syncIn() {
|
||||||
// TODO: provede stažení kanálů/kategorií
|
QNetworkReply::NetworkError err;
|
||||||
// ze serveru, a sloučení s aktuálními
|
TtRssGetFeedsCategoriesResponse feed_cats_response = m_network->getFeedsCategories(err);
|
||||||
// neprovádí aktualizace kanálů ani stažení počtu nepřečtených zpráv
|
|
||||||
QNetworkReply::NetworkError error;
|
|
||||||
RootItem *new_feeds = m_network->getFeedsTree(error).feedsTree();
|
|
||||||
|
|
||||||
if (error == QNetworkReply::NoError) {
|
if (err == QNetworkReply::NoError) {
|
||||||
// We have new feeds, purge old and set new to DB.
|
RootItem *new_tree = feed_cats_response.feedsCategories(true, m_network->url());
|
||||||
|
|
||||||
|
// Purge old data from SQL and clean all model items.
|
||||||
removeOldFeedTree();
|
removeOldFeedTree();
|
||||||
|
cleanAllItems();
|
||||||
|
|
||||||
foreach (RootItem *child, childItems()) {
|
// Model is clean, now store new tree into DB and
|
||||||
requestItemRemoval(child);
|
// set primary IDs of the items.
|
||||||
|
storeNewFeedTree(new_tree);
|
||||||
|
|
||||||
|
foreach (RootItem *top_level_item, new_tree->childItems()) {
|
||||||
|
appendChild(top_level_item);
|
||||||
}
|
}
|
||||||
|
|
||||||
clearChildren();
|
updateCounts(true);
|
||||||
|
|
||||||
// Old stuff is gone.
|
new_tree->clearChildren();
|
||||||
storeNewFeedTree(new_feeds);
|
new_tree->deleteLater();
|
||||||
loadFeedTreeFromDatabase();
|
|
||||||
//itemChanged(QList<RootItem*>() << this);
|
itemChanged(QList<RootItem*>() << this);
|
||||||
|
requestFeedReadFilterReload();
|
||||||
|
requestReloadMessageList(true);
|
||||||
|
requestItemExpand(getSubTree(), true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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() {
|
void TtRssServiceRoot::removeOldFeedTree() {
|
||||||
// TODO: vymazat kanaly a kategorie.
|
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::storeNewFeedTree(RootItem *tree_root) {
|
void TtRssServiceRoot::cleanAllItems() {
|
||||||
// TODO: ulozit do db.
|
foreach (RootItem *top_level_item, childItems()) {
|
||||||
|
requestItemRemoval(top_level_item);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void TtRssServiceRoot::loadFeedTreeFromDatabase() {
|
void TtRssServiceRoot::storeNewFeedTree(RootItem *root) {
|
||||||
// TODO: nacist kanaly a kategorie z db
|
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());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// TODO: logovat.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,8 @@
|
||||||
#include <QCoreApplication>
|
#include <QCoreApplication>
|
||||||
|
|
||||||
|
|
||||||
|
class TtRssCategory;
|
||||||
|
class TtRssFeed;
|
||||||
class TtRssNetworkFactory;
|
class TtRssNetworkFactory;
|
||||||
|
|
||||||
class TtRssServiceRoot : public ServiceRoot {
|
class TtRssServiceRoot : public ServiceRoot {
|
||||||
|
@ -73,9 +75,14 @@ class TtRssServiceRoot : public ServiceRoot {
|
||||||
void syncIn();
|
void syncIn();
|
||||||
|
|
||||||
private:
|
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 removeOldFeedTree();
|
||||||
void storeNewFeedTree(RootItem *tree_root);
|
void cleanAllItems();
|
||||||
void loadFeedTreeFromDatabase();
|
void storeNewFeedTree(RootItem *root);
|
||||||
|
void loadFromDatabase();
|
||||||
|
|
||||||
QAction *m_actionSyncIn;
|
QAction *m_actionSyncIn;
|
||||||
QList<QAction*> m_serviceMenu;
|
QList<QAction*> m_serviceMenu;
|
||||||
|
|
Loading…
Add table
Reference in a new issue