diff options
author | Weblate <noreply@weblate.org> | 2017-01-04 21:47:56 +0000 |
---|---|---|
committer | Weblate <noreply@weblate.org> | 2017-01-04 21:47:56 +0000 |
commit | 5840eb59b26d1dab6f43b2e2f9e4ca6ce8b70e0f (patch) | |
tree | 931c2fe0927dcf25cac4031374db4b0c6dcdc727 /src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/rssreader | |
parent | Translated using Weblate (lumina_SEARCH@fr (generated)) (diff) | |
parent | Merge branch 'master' of github.com:trueos/lumina (diff) | |
download | lumina-5840eb59b26d1dab6f43b2e2f9e4ca6ce8b70e0f.tar.gz lumina-5840eb59b26d1dab6f43b2e2f9e4ca6ce8b70e0f.tar.bz2 lumina-5840eb59b26d1dab6f43b2e2f9e4ca6ce8b70e0f.zip |
Merge remote-tracking branch 'origin/master'
Diffstat (limited to 'src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/rssreader')
5 files changed, 1379 insertions, 0 deletions
diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/rssreader/RSSFeedPlugin.cpp b/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/rssreader/RSSFeedPlugin.cpp new file mode 100644 index 00000000..c330d6c0 --- /dev/null +++ b/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/rssreader/RSSFeedPlugin.cpp @@ -0,0 +1,363 @@ +//=========================================== +// Lumina-DE source code +// Copyright (c) 2016, Ken Moore +// Available under the 3-clause BSD license +// See the LICENSE file for full details +//=========================================== +#include "RSSFeedPlugin.h" +#include "ui_RSSFeedPlugin.h" + +#include <LuminaXDG.h> +#include "LSession.h" +#include <LUtils.h> +#include <QDir> +#include <QFileDialog> +#include <QInputDialog> +#include <QtConcurrent> + +RSSFeedPlugin::RSSFeedPlugin(QWidget* parent, QString ID) : LDPlugin(parent, ID), ui(new Ui::RSSFeedPlugin()){ + ui->setupUi(this); + //Load the global settings + setprefix = "rssreader/"; //this structure/prefix should be used for *all* plugins of this type + RSS = new RSSReader(this, setprefix); + ui->text_feed->setContextMenuPolicy(Qt::NoContextMenu); + //Create the options menu + optionsMenu = new QMenu(this); + ui->tool_options->setMenu(optionsMenu); + presetMenu = new QMenu(this); + ui->tool_add_preset->setMenu(presetMenu); + + //Setup any signal/slot connections + connect(ui->push_back1, SIGNAL(clicked()), this, SLOT(backToFeeds()) ); + connect(ui->push_back2, SIGNAL(clicked()), this, SLOT(backToFeeds()) ); + connect(ui->push_back3, SIGNAL(clicked()), this, SLOT(backToFeeds()) ); + connect(ui->push_save_settings, SIGNAL(clicked()), this, SLOT(saveSettings()) ); + connect(RSS, SIGNAL(rssChanged(QString)), this, SLOT(RSSItemChanged(QString)) ); + connect(RSS, SIGNAL(newChannelsAvailable()), this, SLOT(UpdateFeedList())); + connect(ui->tool_gotosite, SIGNAL(clicked()), this, SLOT(openFeedPage()) ); + connect(ui->push_rm_feed, SIGNAL(clicked()), this, SLOT(removeFeed()) ); + connect(ui->push_add_url, SIGNAL(clicked()), this, SLOT(addNewFeed()) ); + connect(ui->combo_feed, SIGNAL(currentIndexChanged(int)), this, SLOT(currentFeedChanged()) ); + + connect(presetMenu, SIGNAL(triggered(QAction*)), this, SLOT(loadPreset(QAction*)) ); + + updateOptionsMenu(); + QTimer::singleShot(0,this, SLOT(ThemeChange()) ); + //qDebug() << " - Done with init"; + QStringList feeds; + if( !LSession::handle()->DesktopPluginSettings()->contains(setprefix+"currentfeeds") ){ + //First-time run of the plugin - automatically load the default feeds + feeds = LOS::RSSFeeds(); + for(int i=0; i<feeds.length(); i++){ feeds[i] = feeds[i].section("::::",1,-1); } //just need url right now + feeds << "http://lumina-desktop.org/?feed=rss2"; //Lumina Desktop blog feed + LSession::handle()->DesktopPluginSettings()->setValue(setprefix+"currentfeeds", feeds); + }else{ + feeds = LSession::handle()->DesktopPluginSettings()->value(setprefix+"currentfeeds",QStringList()).toStringList(); + } + RSS->addUrls(feeds); + backToFeeds(); //always load the first page +} + +RSSFeedPlugin::~RSSFeedPlugin(){ + +} + +//================ +// PRIVATE +//================ +void RSSFeedPlugin::updateOptionsMenu(){ + optionsMenu->clear(); + optionsMenu->addAction(LXDG::findIcon("list-add",""), tr("Add RSS Feed"), this, SLOT(openFeedNew()) ); + optionsMenu->addAction(LXDG::findIcon("help-about",""), tr("View Feed Details"), this, SLOT(openFeedInfo()) ); + optionsMenu->addAction(LXDG::findIcon("configure",""), tr("Settings"), this, SLOT(openSettings()) ); + optionsMenu->addSeparator(); + optionsMenu->addAction(LXDG::findIcon("download",""), tr("Update Feeds Now"), this, SLOT(resyncFeeds()) ); + + presetMenu->clear(); + QStringList feeds = LOS::RSSFeeds(); + feeds << tr("Lumina Desktop RSS")+"::::http://lumina-desktop.org/?feed=rss2"; + feeds.sort(); + for(int i=0; i<feeds.length(); i++){ + QAction *tmp = presetMenu->addAction(feeds[i].section("::::",0,0) ); + tmp->setWhatsThis( feeds[i].section("::::",1,-1) ); + } +} + +void RSSFeedPlugin::checkFeedNotify(){ + bool notify = false; + for(int i=0; i<ui->combo_feed->count() && !notify; i++){ + if( !ui->combo_feed->itemData(i, Qt::WhatsThisRole).toString().isEmpty()){ notify = true; } + } + QString style; + if(notify){ style = "QComboBox{ background-color: rgba(255,0,0,120); }"; } + ui->combo_feed->setStyleSheet(style); +} + +//Simplification functions for loading feed info onto widgets +void RSSFeedPlugin::updateFeed(QString ID){ + //Now clear/update the feed viewer (HTML) + ui->text_feed->clear(); + if(ID.isEmpty()){ return; } //nothing to show + + //Save the datetime this feed was read + LSession::handle()->DesktopPluginSettings()->setValue(setprefix+"feedReads/"+ID, QDateTime::currentDateTime() ); + //Get the color to use for hyperlinks (need to specify in html) + QString color = ui->text_feed->palette().text().color().name(); //keep the hyperlinks the same color as the main text (different formatting still applies) + QString html; + RSSchannel data = RSS->dataForID(ID); + ui->label_lastupdate->setText( data.lastsync.toString(Qt::DefaultLocaleShortDate) ); + // - generate the html + // html.append("<ul style=\"margin-left: 3px;\">\n"); + for(int i=0; i<data.items.length(); i++){ + //html.append("<li>"); + html.append("<h4><a href=\""+data.items[i].link+"\" style=\"color: "+color+";\">"+data.items[i].title+"</a></h4>"); + if(!data.items[i].pubdate.isNull() || !data.items[i].author.isEmpty()){ + html.append("<i>("); + if(!data.items[i].pubdate.isNull()){ html.append(data.items[i].pubdate.toString(Qt::DefaultLocaleShortDate)); } + if(!data.items[i].author.isEmpty()){ + if(!html.endsWith("(")){ html.append(", "); } //spacing between date/author + if(!data.items[i].author_email.isEmpty()){ html.append("<a href=\"mailto:"+data.items[i].author_email+"\" style=\"color: "+color+";\">"+data.items[i].author+"</a>"); } + else{ html.append(data.items[i].author); } + } + html.append(")</i><br>"); + } + html.append(data.items[i].description); + //html.append("</li>\n"); + if(i+1 < data.items.length()){ html.append("<br>"); } + } + //html.append("</ul>\n"); + // - load that html into the viewer + ui->text_feed->setHtml(html); +} + +void RSSFeedPlugin::updateFeedInfo(QString ID){ + ui->page_feed_info->setWhatsThis(ID); + ui->text_feed_info->clear(); + if(ID.isEmpty()){ return; } + //Get the color to use for hyperlinks (need to specify in html) + QString color = ui->text_feed->palette().text().color().name(); //keep the hyperlinks the same color as the main text (different formatting still applies) + QString html; + RSSchannel data = RSS->dataForID(ID); + // - generate the html + // <a href=\""+LINK+"\" style=\"color: "+color+";\">"+TEXT+"</a> + html.append( QString(tr("Feed URL: %1")).arg("<a href=\""+data.originalURL+"\" style=\"color: "+color+";\">"+data.originalURL+"</a>") +"<br><hr>"); + html.append( QString(tr("Title: %1")).arg(data.title) +"<br>"); + html.append( QString(tr("Description: %1")).arg(data.description) +"<br>"); + html.append( QString(tr("Website: %1")).arg("<a href=\""+data.link+"\" style=\"color: "+color+";\">"+data.link+"</a>") +"<br><hr>"); + if(!data.lastBuildDate.isNull()){ html.append( QString(tr("Last Build Date: %1")).arg(data.lastBuildDate.toString(Qt::DefaultLocaleShortDate)) +"<br>"); } + html.append( QString(tr("Last Sync: %1")).arg(data.lastsync.toString(Qt::DefaultLocaleShortDate)) +"<br>"); + html.append( QString(tr("Next Sync: %1")).arg(data.nextsync.toString(Qt::DefaultLocaleShortDate)) +"<br>"); + // - load that html into the viewer + ui->text_feed_info->setHtml(html); +} + +//================ +// PRIVATE SLOTS +//================ +void RSSFeedPlugin::loadIcons(){ + ui->tool_options->setIcon( LXDG::findIcon("configure","") ); + ui->tool_gotosite->setIcon( LXDG::findIcon("applications-internet","") ); + ui->push_back1->setIcon( LXDG::findIcon("go-previous","") ); + ui->push_back2->setIcon( LXDG::findIcon("go-previous","") ); + ui->push_back3->setIcon( LXDG::findIcon("go-previous","") ); + ui->push_rm_feed->setIcon( LXDG::findIcon("list-remove","") ); + ui->push_add_url->setIcon( LXDG::findIcon("list-add","") ); + ui->push_save_settings->setIcon( LXDG::findIcon("document-save","") ); + ui->tool_add_preset->setIcon( LXDG::findIcon("bookmark-new-list","") ); +} + +//GUI slots +// - Page management +void RSSFeedPlugin::backToFeeds(){ + ui->stackedWidget->setCurrentWidget(ui->page_feed); +} + +void RSSFeedPlugin::openFeedInfo(){ + QString ID = ui->combo_feed->currentData().toString(); + if(ID.isEmpty()){ return; } + updateFeedInfo(ID); + ui->stackedWidget->setCurrentWidget(ui->page_feed_info); + +} + +void RSSFeedPlugin::openFeedNew(){ + ui->line_new_url->setText(""); + ui->stackedWidget->setCurrentWidget(ui->page_new_feed); +} + +void RSSFeedPlugin::openSettings(){ + //Sync the widget with the current settings + QSettings *set = LSession::handle()->DesktopPluginSettings(); + + ui->check_manual_sync->setChecked( set->value(setprefix+"manual_sync_only", false).toBool() ); + int DI = set->value(setprefix+"default_interval_minutes", 60).toInt(); + if(DI<1){ DI = 60; } + if( (DI%60) == 0 ){DI = DI/60; ui->combo_sync_units->setCurrentIndex(1); } //hourly setting + else{ ui->combo_sync_units->setCurrentIndex(1); } //minutes setting + ui->spin_synctime->setValue(DI); + + //Now show the page + ui->stackedWidget->setCurrentWidget(ui->page_settings); +} + +// - Feed Management +void RSSFeedPlugin::addNewFeed(){ + if(ui->line_new_url->text().isEmpty()){ return; } //nothing to add + //Validate the URL + QUrl url(ui->line_new_url->text()); + if(!url.isValid()){ + ui->line_new_url->setFocus(); + return; + } + //Add the URL to the settings file for next login + QStringList feeds = LSession::handle()->DesktopPluginSettings()->value(setprefix+"currentfeeds",QStringList()).toStringList(); + feeds << url.toString(); + LSession::handle()->DesktopPluginSettings()->setValue(setprefix+"currentfeeds", feeds); + + //Set this URL as the current selection + ui->combo_feed->setWhatsThis(url.toString()); //hidden field - will trigger an update in a moment + //Add the URL to the backend + RSS->addUrls(QStringList() << url.toString()); + //UpdateFeedList(); //now re-load the feeds which are available + + //Now go back to the main page + backToFeeds(); +} + +void RSSFeedPlugin::loadPreset(QAction *act){ + ui->line_new_url->setText( act->whatsThis() ); +} + +void RSSFeedPlugin::removeFeed(){ + QString ID = ui->page_feed_info->whatsThis(); + if(ID.isEmpty()){ return; } + //Remove from the RSS feed object + RSSchannel info = RSS->dataForID(ID); + RSS->removeUrl(ID); + //Remove the URL from the settings file for next login + QStringList feeds = LSession::handle()->DesktopPluginSettings()->value(setprefix+"currentfeeds",QStringList()).toStringList(); + feeds.removeAll(info.originalURL); + LSession::handle()->DesktopPluginSettings()->setValue(setprefix+"currentfeeds", feeds); + LSession::handle()->DesktopPluginSettings()->remove(setprefix+"feedReads/"+ID); + //Now go back to the main page + backToFeeds(); +} + +void RSSFeedPlugin::resyncFeeds(){ + RSS->addUrls( LSession::handle()->DesktopPluginSettings()->value(setprefix+"currentfeeds",QStringList()).toStringList() ); + RSS->syncNow(); +} + +// - Feed Interactions +void RSSFeedPlugin::currentFeedChanged(){ + QString ID = ui->combo_feed->currentData().toString(); + //Remove the "unread" color/flag from the feed + ui->combo_feed->setItemData( ui->combo_feed->currentIndex(), QBrush(Qt::transparent) , Qt::BackgroundRole); + ui->combo_feed->setItemData( ui->combo_feed->currentIndex(), "", Qt::WhatsThisRole); + checkFeedNotify(); + updateFeed(ID); +} + +void RSSFeedPlugin::openFeedPage(){ //Open main website for feed + QString ID = ui->combo_feed->currentData().toString(); + //Find the data associated with this feed + RSSchannel data = RSS->dataForID(ID); + QString url = data.link; + //qDebug() << "Open Feed Page:" << url; + //Now launch the browser + if(!url.isEmpty()){ + LSession::LaunchApplication("lumina-open \""+url+"\""); + } +} + +void RSSFeedPlugin::saveSettings(){ + QSettings *set = LSession::handle()->DesktopPluginSettings(); + set->setValue(setprefix+"manual_sync_only", ui->check_manual_sync->isChecked() ); + int DI = ui->spin_synctime->value(); + if(ui->combo_sync_units->currentIndex()==1){ DI = DI*60; } //convert from hours to minutes + set->setValue(setprefix+"default_interval_minutes", DI); + set->sync(); + + //Now go back to the feeds + backToFeeds(); +} + +//Feed Object interactions +void RSSFeedPlugin::UpdateFeedList(){ + + QString activate = ui->combo_feed->whatsThis(); + if(!activate.isEmpty()){ ui->combo_feed->setWhatsThis(""); } + if(activate.isEmpty()){ activate = ui->combo_feed->currentData().toString(); } // keep current item selected + //Now get/list all the available feeds + QStringList IDS = RSS->channels(); //this is pre-sorted by title of the feed + //qDebug() << "Update RSS Feed List:" << IDS << activate; + for(int i=0; i<IDS.length(); i++){ + bool newitem = false; + if(ui->combo_feed->count()<=i){ newitem = true; } + else{ + QString cid = ui->combo_feed->itemData(i).toString(); + if(IDS[i]!=cid){ + if(IDS.contains(cid)){ newitem = true; } //this item is just out of order + else{ ui->combo_feed->removeItem(i); } //item no longer is valid + } + } + if(newitem){ + //Need to add a new item at this point in the menu + RSSchannel info = RSS->dataForID(IDS[i]); + if(info.title.isEmpty()){ + //invalid/empty channel + ui->combo_feed->insertItem(i, IDS[i], IDS[i]); //just show the URL + }else{ + ui->combo_feed->insertItem(i, info.icon, info.title, IDS[i]); + } + } + } + //Remove any extra items on the end of the list + for(int i=IDS.length(); i<ui->combo_feed->count(); i++){ + ui->combo_feed->removeItem(i); i--; + } + //Now activate the proper item as needed + if(IDS.contains(activate)){ + ui->combo_feed->setCurrentIndex( IDS.indexOf(activate) ); + } + checkFeedNotify(); +} + +void RSSFeedPlugin::RSSItemChanged(QString ID){ + for(int i=0; i<ui->combo_feed->count(); i++){ + if(ui->combo_feed->itemData(i).toString()!=ID){ continue; } + RSSchannel info = RSS->dataForID(ID); + if(info.title.isEmpty()){ + ui->combo_feed->setItemText(i, ID); + ui->combo_feed->setItemIcon(i, LXDG::findIcon("dialog-cancel","") ); + }else{ + ui->combo_feed->setItemText(i, info.title); + ui->combo_feed->setItemIcon(i, info.icon ); + QColor color(Qt::transparent); + if( info.lastBuildDate > LSession::handle()->DesktopPluginSettings()->value(setprefix+"feedReads/"+ID,QDateTime()).toDateTime() ){ + color = QColor(255,10,10,100); //semi-transparent red + ui->combo_feed->setItemData(i, "notify", Qt::WhatsThisRole); + }else{ + ui->combo_feed->setItemData(i, "", Qt::WhatsThisRole); + } + ui->combo_feed->setItemData(i, QBrush(color) , Qt::BackgroundRole); + } + } + if(ID == ui->combo_feed->currentData().toString()){ + currentFeedChanged(); //re-load the current feed + }else{ + checkFeedNotify(); + } +} + +//================== +// PUBLIC SLOTS +//================== +void RSSFeedPlugin::LocaleChange(){ + ui->retranslateUi(this); + updateOptionsMenu(); +} +void RSSFeedPlugin::ThemeChange(){ + QTimer::singleShot(0,this, SLOT(loadIcons())); + updateOptionsMenu(); +} diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/rssreader/RSSFeedPlugin.h b/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/rssreader/RSSFeedPlugin.h new file mode 100644 index 00000000..68b36760 --- /dev/null +++ b/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/rssreader/RSSFeedPlugin.h @@ -0,0 +1,72 @@ +//=========================================== +// Lumina-DE source code +// Copyright (c) 2016, Ken Moore +// Available under the 3-clause BSD license +// See the LICENSE file for full details +//=========================================== +// This plugin is a simple RSS feed reader for the desktop +//=========================================== +#ifndef _LUMINA_DESKTOP_RSS_FEEDER_PLUGIN_H +#define _LUMINA_DESKTOP_RSS_FEEDER_PLUGIN_H + +#include <QTimer> +#include "../LDPlugin.h" + +#include "RSSObjects.h" + +namespace Ui{ + class RSSFeedPlugin; +}; + +class RSSFeedPlugin : public LDPlugin{ + Q_OBJECT +public: + RSSFeedPlugin(QWidget* parent, QString ID); + ~RSSFeedPlugin(); + + virtual QSize defaultPluginSize(){ + // The returned QSize is in grid points (typically 100 or 200 pixels square) + return QSize(3,3); + } +private: + Ui::RSSFeedPlugin *ui; + QMenu *optionsMenu, *presetMenu; + QString setprefix; //settings prefix + RSSReader *RSS; + + void updateOptionsMenu(); + void checkFeedNotify(); //check if unread feeds are available and change the styling a bit as needed + + //Simplification functions for loading feed info onto widgets + void updateFeed(QString ID); + void updateFeedInfo(QString ID); + +private slots: + void loadIcons(); + + //GUI slots + // - Page management + void backToFeeds(); + void openFeedInfo(); + void openFeedNew(); + void openSettings(); + // - Feed Management + void addNewFeed(); // the "add" button (current url in widget on page) + void loadPreset(QAction*); //the add-preset menu + void removeFeed(); // the "remove" button (current feed for page) + void resyncFeeds(); + // - Feed Interactions + void currentFeedChanged(); + void openFeedPage(); //Open the website in a browser + void saveSettings(); + + //Feed Object interactions + void UpdateFeedList(); + void RSSItemChanged(QString ID); + +public slots: + void LocaleChange(); + void ThemeChange(); + +}; +#endif diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/rssreader/RSSFeedPlugin.ui b/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/rssreader/RSSFeedPlugin.ui new file mode 100644 index 00000000..dc7acd0b --- /dev/null +++ b/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/rssreader/RSSFeedPlugin.ui @@ -0,0 +1,552 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>RSSFeedPlugin</class> + <widget class="QWidget" name="RSSFeedPlugin"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>238</width> + <height>278</height> + </rect> + </property> + <property name="windowTitle"> + <string>Form</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <property name="spacing"> + <number>0</number> + </property> + <property name="leftMargin"> + <number>3</number> + </property> + <property name="topMargin"> + <number>3</number> + </property> + <property name="rightMargin"> + <number>3</number> + </property> + <property name="bottomMargin"> + <number>3</number> + </property> + <item> + <widget class="QStackedWidget" name="stackedWidget"> + <property name="currentIndex"> + <number>1</number> + </property> + <widget class="QWidget" name="page_feed"> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <property name="spacing"> + <number>3</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QComboBox" name="combo_feed"/> + </item> + <item> + <widget class="QToolButton" name="tool_options"> + <property name="toolTip"> + <string>View Options</string> + </property> + <property name="text"> + <string/> + </property> + <property name="popupMode"> + <enum>QToolButton::InstantPopup</enum> + </property> + <property name="autoRaise"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <item> + <widget class="QLabel" name="label_lastupdate"> + <property name="text"> + <string notr="true"/> + </property> + </widget> + </item> + <item> + <widget class="QToolButton" name="tool_gotosite"> + <property name="toolTip"> + <string>Open Website</string> + </property> + <property name="text"> + <string>More</string> + </property> + <property name="autoRaise"> + <bool>true</bool> + </property> + <property name="arrowType"> + <enum>Qt::NoArrow</enum> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QTextBrowser" name="text_feed"> + <property name="undoRedoEnabled"> + <bool>false</bool> + </property> + <property name="readOnly"> + <bool>true</bool> + </property> + <property name="html"> + <string notr="true"><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Sans Serif'; font-size:9pt; font-weight:400; font-style:normal;"> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html></string> + </property> + <property name="textInteractionFlags"> + <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set> + </property> + <property name="openExternalLinks"> + <bool>true</bool> + </property> + <property name="openLinks"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </widget> + <widget class="QWidget" name="page_feed_info"> + <layout class="QVBoxLayout" name="verticalLayout_3"> + <property name="spacing"> + <number>4</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>5</number> + </property> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_3"> + <item> + <widget class="QPushButton" name="push_back1"> + <property name="text"> + <string>Back to Feeds</string> + </property> + <property name="flat"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QGroupBox" name="groupBox"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Expanding"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="title"> + <string>Feed Information</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_4"> + <property name="spacing"> + <number>0</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <widget class="QTextBrowser" name="text_feed_info"> + <property name="html"> + <string notr="true"><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Sans Serif'; font-size:9pt; font-weight:400; font-style:normal;"> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html></string> + </property> + <property name="openExternalLinks"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_8"> + <item> + <widget class="QPushButton" name="push_rm_feed"> + <property name="text"> + <string>Remove Feed</string> + </property> + <property name="flat"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + <widget class="QWidget" name="page_new_feed"> + <layout class="QVBoxLayout" name="verticalLayout_5"> + <property name="spacing"> + <number>4</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_4"> + <item> + <widget class="QPushButton" name="push_back2"> + <property name="text"> + <string>Back to Feeds</string> + </property> + <property name="flat"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QGroupBox" name="groupBox_2"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Expanding"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="title"> + <string>New Feed Subscription</string> + </property> + <property name="alignment"> + <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set> + </property> + <layout class="QVBoxLayout" name="verticalLayout_6"> + <property name="spacing"> + <number>2</number> + </property> + <property name="leftMargin"> + <number>2</number> + </property> + <property name="topMargin"> + <number>2</number> + </property> + <property name="rightMargin"> + <number>2</number> + </property> + <property name="bottomMargin"> + <number>2</number> + </property> + <item> + <widget class="QLabel" name="label"> + <property name="text"> + <string>RSS URL</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_10"> + <item> + <widget class="QLineEdit" name="line_new_url"/> + </item> + <item> + <widget class="QToolButton" name="tool_add_preset"> + <property name="toolTip"> + <string>Load a preset RSS Feed</string> + </property> + <property name="text"> + <string/> + </property> + <property name="popupMode"> + <enum>QToolButton::InstantPopup</enum> + </property> + </widget> + </item> + </layout> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_5"> + <item> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QPushButton" name="push_add_url"> + <property name="text"> + <string>Add to Feeds</string> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_2"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + </layout> + </widget> + </item> + <item> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + <widget class="QWidget" name="page_settings"> + <layout class="QVBoxLayout" name="verticalLayout_7"> + <property name="spacing"> + <number>4</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_6"> + <item> + <widget class="QPushButton" name="push_back3"> + <property name="text"> + <string>Back to Feeds</string> + </property> + <property name="flat"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QGroupBox" name="groupBox_3"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Expanding"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="title"> + <string>Feed Reader Settings</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_8"> + <property name="spacing"> + <number>2</number> + </property> + <property name="leftMargin"> + <number>2</number> + </property> + <property name="topMargin"> + <number>2</number> + </property> + <property name="rightMargin"> + <number>2</number> + </property> + <property name="bottomMargin"> + <number>2</number> + </property> + <item> + <widget class="QCheckBox" name="check_manual_sync"> + <property name="text"> + <string>Manual Sync Only</string> + </property> + </widget> + </item> + <item> + <widget class="QGroupBox" name="groupBox_4"> + <property name="toolTip"> + <string>Some RSS feeds may request custom update intervals instead of using this setting</string> + </property> + <property name="title"> + <string>Default Sync Interval</string> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_7"> + <property name="spacing"> + <number>2</number> + </property> + <property name="leftMargin"> + <number>2</number> + </property> + <property name="topMargin"> + <number>2</number> + </property> + <property name="rightMargin"> + <number>2</number> + </property> + <property name="bottomMargin"> + <number>2</number> + </property> + <item> + <widget class="QSpinBox" name="spin_synctime"> + <property name="minimum"> + <number>1</number> + </property> + <property name="maximum"> + <number>60</number> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="combo_sync_units"> + <property name="currentText"> + <string>Hour(s)</string> + </property> + <property name="currentIndex"> + <number>1</number> + </property> + <item> + <property name="text"> + <string>Minutes</string> + </property> + </item> + <item> + <property name="text"> + <string>Hour(s)</string> + </property> + </item> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <spacer name="verticalSpacer_2"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_9"> + <item> + <spacer name="horizontalSpacer_3"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QPushButton" name="push_save_settings"> + <property name="text"> + <string>Save Settings</string> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_4"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections/> +</ui> diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/rssreader/RSSObjects.cpp b/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/rssreader/RSSObjects.cpp new file mode 100644 index 00000000..0a805252 --- /dev/null +++ b/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/rssreader/RSSObjects.cpp @@ -0,0 +1,287 @@ +//=========================================== +// Lumina-DE source code +// Copyright (c) 2016, Ken Moore +// Available under the 3-clause BSD license +// See the LICENSE file for full details +//=========================================== +#include "RSSObjects.h" +#include <QNetworkRequest> +#include <QXmlStreamReader> + +#include "LSession.h" + +//============ +// PUBLIC +//============ +RSSReader::RSSReader(QObject *parent, QString settingsPrefix) : QObject(parent){ + NMAN = new QNetworkAccessManager(this); + connect(NMAN, SIGNAL(finished(QNetworkReply*)), this, SLOT(replyFinished(QNetworkReply*)) ); + connect(NMAN, SIGNAL(sslErrors(QNetworkReply*, const QList<QSslError>&)), this, SLOT(sslErrors(QNetworkReply*, const QList<QSslError>&)) ); + + setprefix = settingsPrefix; + syncTimer = new QTimer(this); + syncTimer->setInterval(300000); //5 minutes + connect(syncTimer, SIGNAL(timeout()), this, SLOT(checkTimes())); + syncTimer->start(); +} + +RSSReader::~RSSReader(){ + +} + +//Information retrieval +QStringList RSSReader::channels(){ + QStringList urls = hash.keys(); + QStringList ids; + //sort all the channels by title before output + for(int i=0; i<urls.length(); i++){ + QString title = hash[urls[i]].title; + if(title.isEmpty()){ title = "ZZZ"; } //put currently-invalid ones at the end of the list + ids << title+" : "+hash[urls[i]].originalURL; + } + ids.sort(); + //Now strip off all the titles again to just get the IDs + for(int i=0; i<ids.length(); i++){ + ids[i] = ids[i].section(" : ",-1); + } + return ids; +} + +RSSchannel RSSReader::dataForID(QString ID){ + QString key = keyForUrl(ID); + if(hash.contains(key)){ return hash[key]; } + else{ return RSSchannel(); } +} + +//Initial setup +void RSSReader::addUrls(QStringList urls){ + //qDebug() << "Add URLS:" << urls; + for(int i=0; i<urls.length(); i++){ + //Note: Make sure we get the complete URL form for accurate comparison later + QString url = QUrl(urls[i]).toString(); + QString key = keyForUrl(url); + if(hash.contains(key)){ continue; } //already handled + RSSchannel blank; + blank.originalURL = url; + hash.insert(url, blank); //put the empty struct into the hash for now + requestRSS(url); //startup the initial request for this url + } + emit newChannelsAvailable(); +} + +void RSSReader::removeUrl(QString ID){ + QString key = keyForUrl(ID); + if(hash.contains(key)){ hash.remove(key); } + emit newChannelsAvailable(); +} + +//================= +// PUBLIC SLOTS +//================= +void RSSReader::syncNow(){ + QStringList urls = hash.keys(); + for(int i=0; i<urls.length(); i++){ + requestRSS(hash[urls[i]].originalURL); + } +} + +//================= +// PRIVATE +//================= +QString RSSReader::keyForUrl(QString url){ + //get current hash key for this URL + QStringList keys = hash.keys(); + if(keys.contains(url)){ return url; } //this is already a valid key + for(int i=0; i<keys.length(); i++){ + if(hash[keys[i]].originalURL == url){ return keys[i]; } //this was an original URL + } + return ""; +} + +void RSSReader::requestRSS(QString url){ + if(!outstandingURLS.contains(url)){ + //qDebug() << "Request URL:" << url; + NMAN->get( QNetworkRequest( QUrl(url) ) ); + outstandingURLS << url; + } +} + +//RSS parsing functions +RSSchannel RSSReader::readRSS(QByteArray bytes){ + //Note: We could expand this later to support multiple "channel"s per Feed + // but it seems like there is normally only one channel anyway + //qDebug() << "Read RSS:" << bytes.left(100); + QXmlStreamReader xml(bytes); + RSSchannel rssinfo; + //qDebug() << "Can Read XML Stream:" << !xml.hasError(); + if(xml.readNextStartElement()){ + //qDebug() << " - RSS Element:" << xml.name(); + if(xml.name() == "rss" && (xml.attributes().value("version") =="2.0" || xml.attributes().value("version") =="0.91") ){ + while(xml.readNextStartElement()){ + //qDebug() << " - RSS Element:" << xml.name(); + if(xml.name()=="channel"){ rssinfo = readRSSChannel(&xml); } + else{ xml.skipCurrentElement(); } + } + } + } + if(xml.hasError()){ qDebug() << " - XML Read Error:" << xml.errorString() << "\n" << bytes; } + return rssinfo; +} +RSSchannel RSSReader::readRSSChannel(QXmlStreamReader *rss){ + RSSchannel info; + info.timetolive = -1; + while(rss->readNextStartElement()){ + //qDebug() << " - RSS Element (channel):" <<rss->name(); + if(rss->name()=="item"){ info.items << readRSSItem(rss); } + else if(rss->name()=="title"){ info.title = rss->readElementText(); } + else if(rss->name()=="link"){ + QString txt = rss->readElementText(); + if(!txt.isEmpty()){ info.link = txt; } + } + else if(rss->name()=="description"){ info.description = rss->readElementText(); } + else if(rss->name()=="lastBuildDate"){ info.lastBuildDate = RSSDateTime(rss->readElementText()); } + else if(rss->name()=="pubDate"){ info.lastPubDate = RSSDateTime(rss->readElementText()); } + else if(rss->name()=="image"){ readRSSImage(&info, rss); } + //else if(rss->name()=="skipHours"){ info.link = rss->readElementText(); } + //else if(rss->name()=="skipDays"){ info.link = rss->readElementText(); } + else if(rss->name()=="ttl"){ info.timetolive = rss->readElementText().toInt(); } + else{ rss->skipCurrentElement(); } + } + return info; +} + +RSSitem RSSReader::readRSSItem(QXmlStreamReader *rss){ + RSSitem item; + while(rss->readNextStartElement()){ + //qDebug() << " - RSS Element (Item):" << rss->name(); + if(rss->name()=="title"){ item.title = rss->readElementText(); } + else if(rss->name()=="link"){ item.link = rss->readElementText(); } + else if(rss->name()=="description"){ item.description = rss->readElementText(); } + else if(rss->name()=="comments"){ item.comments_url = rss->readElementText(); } + else if(rss->name()=="author"){ + //Special handling - this field can contain both email and name + QString raw = rss->readElementText(); + if(raw.contains("@")){ + item.author_email = raw.split(" ").filter("@").first(); + item.author = raw.remove(item.author_email).remove("(").remove(")").simplified(); //the name is often put within parentheses after the email + }else{ item.author = raw; } + } + else if(rss->name()=="guid"){ item.guid = rss->readElementText(); } + else if(rss->name()=="pubDate"){ item.pubdate = RSSDateTime(rss->readElementText()); } + else{ rss->skipCurrentElement(); } + } + return item; +} + +void RSSReader::readRSSImage(RSSchannel *item, QXmlStreamReader *rss){ + while(rss->readNextStartElement()){ + //qDebug() << " - RSS Element (Image):" << rss->name(); + if(rss->name()=="url"){ item->icon_url = rss->readElementText(); } + else if(rss->name()=="title"){ item->icon_title = rss->readElementText(); } + else if(rss->name()=="link"){ item->icon_link = rss->readElementText(); } + else if(rss->name()=="width"){ item->icon_size.setWidth(rss->readElementText().toInt()); } + else if(rss->name()=="height"){ item->icon_size.setHeight(rss->readElementText().toInt()); } + else if(rss->name()=="description"){ item->icon_description = rss->readElementText(); } + } + //Go ahead and kick off the request for the icon + if(!item->icon_url.isEmpty()){ requestRSS(item->icon_url); } +} + +QDateTime RSSReader::RSSDateTime(QString datetime){ + return QDateTime::fromString(datetime, Qt::RFC2822Date); +} + +//================= +// PRIVATE SLOTS +//================= +void RSSReader::replyFinished(QNetworkReply *reply){ + QString url = reply->request().url().toString(); + //qDebug() << "Got Reply:" << url; + QString key = keyForUrl(url); //current hash key for this URL + QByteArray data = reply->readAll(); + outstandingURLS.removeAll(url); + if(data.isEmpty()){ + //qDebug() << "No data returned:" << url; + //see if the URL can be adjusted for known issues + bool handled = false; + QUrl redirecturl = reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl(); + if(redirecturl.isValid() && (redirecturl.toString() != url )){ + //New URL redirect - make the change and send a new request + QString newurl = redirecturl.toString(); + //qDebug() << " - Redirect to:" << newurl; + if(hash.contains(key) && !hash.contains(newurl)){ + hash.insert(newurl, hash.take(key) ); //just move the data over to the new url + requestRSS(newurl); + emit newChannelsAvailable(); + handled = true; + } + } + if(!handled && hash.contains(key) ){ + emit rssChanged(hash[key].originalURL); + } + return; + } + + if(!hash.contains(key)){ + //qDebug() << " - hash does not contain URL:" << url; + //URL removed from list while a request was outstanding? + //Could also be an icon fetch response + QStringList keys = hash.keys(); + for(int i=0; i<keys.length(); i++){ + //qDebug() << " - Check for icon URL:" << hash[keys[i]].icon_url; + if(hash[keys[i]].icon_url.toLower() == url.toLower()){ //needs to be case-insensitive + //Icon fetch response + RSSchannel info = hash[keys[i]]; + QImage img = QImage::fromData(data); + info.icon = QIcon( QPixmap::fromImage(img) ); + //qDebug() << "Got Icon response:" << url << info.icon; + hash.insert(keys[i], info); //insert back into the hash + emit rssChanged( hash[keys[i]].originalURL ); + break; + } + } + reply->deleteLater(); + }else{ + //RSS reply + RSSchannel info = readRSS(data); //QNetworkReply can be used as QIODevice + reply->deleteLater(); //clean up + //Validate the info and announce any changes + if(info.title.isEmpty() || info.link.isEmpty() || info.description.isEmpty()){ + qDebug() << "Missing XML Information:" << url << info.title << info.link << info.description; + return; + } //bad info/read + //Update the bookkeeping elements of the info + if(info.timetolive<=0){ info.timetolive = LSession::handle()->DesktopPluginSettings()->value(setprefix+"default_interval_minutes", 60).toInt(); } + if(info.timetolive <=0){ info.timetolive = 60; } //error in integer conversion from settings? + info.lastsync = QDateTime::currentDateTime(); info.nextsync = info.lastsync.addSecs(info.timetolive * 60); + //Now see if anything changed and save the info into the hash + bool changed = (hash[key].lastBuildDate.isNull() || (hash[key].lastBuildDate < info.lastBuildDate) ); + bool newinfo = false; + if(changed){ newinfo = hash[key].title.isEmpty(); } //no previous info from this URL + info.originalURL = hash[key].originalURL; //make sure this info gets preserved across updates + if(!hash[key].icon.isNull()){ info.icon = hash[key].icon; } //copy over the icon from the previous reply + hash.insert(key, info); + if(newinfo){ emit newChannelsAvailable(); } //new channel + else if(changed){ emit rssChanged(info.originalURL); } //update to existing channel + } +} + +void RSSReader::sslErrors(QNetworkReply *reply, const QList<QSslError> &errors){ + int ok = 0; + qDebug() << "SSL Errors Detected (RSS Reader):" << reply->url(); + for(int i=0; i<errors.length(); i++){ + if(errors[i].error()==QSslError::SelfSignedCertificate || errors[i].error()==QSslError::SelfSignedCertificateInChain){ ok++; } + else{ qDebug() << "Unhandled SSL Error:" << errors[i].errorString(); } + } + if(ok==errors.length()){ qDebug() << " - Permitted:" << reply->url(); reply->ignoreSslErrors(); } + else{ qDebug() << " - Denied:" << reply->url(); } +} + +void RSSReader::checkTimes(){ + if(LSession::handle()->DesktopPluginSettings()->value(setprefix+"manual_sync_only", false).toBool()){ return; } + QStringList urls = hash.keys(); + QDateTime cdt = QDateTime::currentDateTime(); + for(int i=0; i<urls.length(); i++){ + if(hash[urls[i]].nextsync < cdt){ requestRSS(urls[i]); } + } +} diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/rssreader/RSSObjects.h b/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/rssreader/RSSObjects.h new file mode 100644 index 00000000..3069bf8d --- /dev/null +++ b/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/rssreader/RSSObjects.h @@ -0,0 +1,105 @@ +//=========================================== +// Lumina-DE source code +// Copyright (c) 2016, Ken Moore +// Available under the 3-clause BSD license +// See the LICENSE file for full details +//=========================================== +#ifndef _LUMINA_DESKTOP_RSS_READER_PLUGIN_OBJECT_H +#define _LUMINA_DESKTOP_RSS_READER_PLUGIN_OBJECT_H + +#include <QNetworkAccessManager> +#include <QNetworkReply> +#include <QString> +#include <QDateTime> +#include <QList> +#include <QIcon> +#include <QTimer> +#include <QXmlStreamReader> //Contained in the Qt "core" module - don't need the full "xml" module for this +#include <QSslError> + +struct RSSitem{ + //Required Fields + QString title, link, description; + + //Optional Fields + QString comments_url, author_email, author, guid; + QDateTime pubdate; //when the item was published + //IGNORED INFO from RSS2 spec: "category", "source", "enclosure" +}; + +struct RSSchannel{ + //Required fields + QString title, link, description; + + //Optional Fields + QDateTime lastBuildDate, lastPubDate; //last build/publish dates + // - channel refresh information + int timetolive; //in minutes - time until next sync should be performed + //QList<int> skiphours; + //QStringList skipdays; + // - icon info + QIcon icon; + QString icon_url, icon_title, icon_link, icon_description; + QSize icon_size; + //All items within this channel + QList<RSSitem> items; + + //Optional RSS2 elements ignored: + // "cloud", "textInput", "rating", "language", "copyright", "managingEditor", "webMaster", + // "category", "generator", "docs" + + //Internal data for bookkeeping + QDateTime lastsync, nextsync; + QString originalURL; //in case it was redirected to some "fixed" url later +}; + +class RSSReader : public QObject{ + Q_OBJECT +public: + RSSReader(QObject *parent, QString settingsPrefix); + ~RSSReader(); + + //Information retrieval + QStringList channels(); //returns all ID's + RSSchannel dataForID(QString ID); + + //Initial setup + void addUrls(QStringList urls); + void removeUrl(QString ID); + +public slots: + void syncNow(); //not generally needed + +private: + //Internal data objects + QHash<QString, RSSchannel> hash; // ID/data + QString setprefix; + QTimer *syncTimer; + QNetworkAccessManager *NMAN; + QStringList outstandingURLS; + + + //Simple hash data search functions + QString keyForUrl(QString url); + + //Network request function + void requestRSS(QString url); + + //RSS parsing functions + RSSchannel readRSS(QByteArray bytes); + RSSchannel readRSSChannel(QXmlStreamReader *rss); + RSSitem readRSSItem(QXmlStreamReader *rss); + void readRSSImage(RSSchannel *item, QXmlStreamReader *rss); + QDateTime RSSDateTime(QString datetime); + +private slots: + void replyFinished(QNetworkReply *reply); + void sslErrors(QNetworkReply *reply, const QList<QSslError> &errors); + void checkTimes(); + +signals: + void rssChanged(QString); //ID + void newChannelsAvailable(); +}; + +#endif |