From 09b5d7a8c68dfd974e3fac03121ba1725a3e9b72 Mon Sep 17 00:00:00 2001 From: thunder2 Date: Thu, 2 Aug 2012 13:17:53 +0000 Subject: [PATCH] Added the first version of the FeedReader plugin. Added a new method to RsPlugInInterfaces to stop the plugins at shutdown of RetroShare. git-svn-id: http://svn.code.sf.net/p/retroshare/code/branches/v0.5-gxs-b1@5372 b45a01b8-16f6-495d-af2f-9b41ad6348cc --- libretroshare/src/plugins/pluginmanager.cc | 15 + libretroshare/src/plugins/pluginmanager.h | 2 + libretroshare/src/retroshare/rsplugin.h | 3 + libretroshare/src/rsserver/p3face-config.cc | 3 + libretroshare/src/rsserver/p3face-server.cc | 2 + libretroshare/src/rsserver/p3face.h | 3 + libretroshare/src/rsserver/rsinit.cc | 2 +- plugins/FeedReader/FeedReader.pro | 43 + plugins/FeedReader/FeedReaderPlugin.cpp | 138 ++ plugins/FeedReader/FeedReaderPlugin.h | 60 + plugins/FeedReader/gui/AddFeedDialog.cpp | 299 +++ plugins/FeedReader/gui/AddFeedDialog.h | 64 + plugins/FeedReader/gui/AddFeedDialog.ui | 442 +++++ plugins/FeedReader/gui/FeedReaderConfig.cpp | 80 + plugins/FeedReader/gui/FeedReaderConfig.h | 57 + plugins/FeedReader/gui/FeedReaderConfig.ui | 148 ++ plugins/FeedReader/gui/FeedReaderDialog.cpp | 1088 +++++++++++ plugins/FeedReader/gui/FeedReaderDialog.h | 105 + plugins/FeedReader/gui/FeedReaderDialog.ui | 373 ++++ plugins/FeedReader/gui/FeedReaderNotify.cpp | 36 + plugins/FeedReader/gui/FeedReaderNotify.h | 45 + plugins/FeedReader/gui/FeedReader_images.qrc | 13 + plugins/FeedReader/gui/images/Feed.png | Bin 0 -> 641 bytes plugins/FeedReader/gui/images/FeedAdd.png | Bin 0 -> 1444 bytes .../gui/images/FeedErrorOverlay.png | Bin 0 -> 193 bytes .../gui/images/FeedProcessOverlay.png | Bin 0 -> 848 bytes plugins/FeedReader/gui/images/FeedReader.png | Bin 0 -> 9924 bytes plugins/FeedReader/gui/images/Folder.png | Bin 0 -> 446 bytes plugins/FeedReader/gui/images/FolderAdd.png | Bin 0 -> 446 bytes plugins/FeedReader/gui/images/Root.png | Bin 0 -> 968 bytes plugins/FeedReader/gui/images/Update.png | Bin 0 -> 1502 bytes plugins/FeedReader/interface/rsFeedReader.h | 181 ++ plugins/FeedReader/lang/FeedReader_de.ts | 339 ++++ plugins/FeedReader/lang/lang.qrc | 5 + plugins/FeedReader/services/p3FeedReader.cc | 1710 +++++++++++++++++ plugins/FeedReader/services/p3FeedReader.h | 113 ++ .../FeedReader/services/p3FeedReaderThread.cc | 1024 ++++++++++ .../FeedReader/services/p3FeedReaderThread.h | 71 + .../FeedReader/services/rsFeedReaderItems.cc | 388 ++++ .../FeedReader/services/rsFeedReaderItems.h | 149 ++ plugins/plugins.pro | 3 +- retroshare-gui/src/gui/MainWindow.cpp | 7 +- 42 files changed, 7004 insertions(+), 7 deletions(-) create mode 100644 plugins/FeedReader/FeedReader.pro create mode 100644 plugins/FeedReader/FeedReaderPlugin.cpp create mode 100644 plugins/FeedReader/FeedReaderPlugin.h create mode 100644 plugins/FeedReader/gui/AddFeedDialog.cpp create mode 100644 plugins/FeedReader/gui/AddFeedDialog.h create mode 100644 plugins/FeedReader/gui/AddFeedDialog.ui create mode 100644 plugins/FeedReader/gui/FeedReaderConfig.cpp create mode 100644 plugins/FeedReader/gui/FeedReaderConfig.h create mode 100644 plugins/FeedReader/gui/FeedReaderConfig.ui create mode 100644 plugins/FeedReader/gui/FeedReaderDialog.cpp create mode 100644 plugins/FeedReader/gui/FeedReaderDialog.h create mode 100644 plugins/FeedReader/gui/FeedReaderDialog.ui create mode 100644 plugins/FeedReader/gui/FeedReaderNotify.cpp create mode 100644 plugins/FeedReader/gui/FeedReaderNotify.h create mode 100644 plugins/FeedReader/gui/FeedReader_images.qrc create mode 100644 plugins/FeedReader/gui/images/Feed.png create mode 100644 plugins/FeedReader/gui/images/FeedAdd.png create mode 100644 plugins/FeedReader/gui/images/FeedErrorOverlay.png create mode 100644 plugins/FeedReader/gui/images/FeedProcessOverlay.png create mode 100644 plugins/FeedReader/gui/images/FeedReader.png create mode 100644 plugins/FeedReader/gui/images/Folder.png create mode 100644 plugins/FeedReader/gui/images/FolderAdd.png create mode 100644 plugins/FeedReader/gui/images/Root.png create mode 100644 plugins/FeedReader/gui/images/Update.png create mode 100644 plugins/FeedReader/interface/rsFeedReader.h create mode 100644 plugins/FeedReader/lang/FeedReader_de.ts create mode 100644 plugins/FeedReader/lang/lang.qrc create mode 100644 plugins/FeedReader/services/p3FeedReader.cc create mode 100644 plugins/FeedReader/services/p3FeedReader.h create mode 100644 plugins/FeedReader/services/p3FeedReaderThread.cc create mode 100644 plugins/FeedReader/services/p3FeedReaderThread.h create mode 100644 plugins/FeedReader/services/rsFeedReaderItems.cc create mode 100644 plugins/FeedReader/services/rsFeedReaderItems.h diff --git a/libretroshare/src/plugins/pluginmanager.cc b/libretroshare/src/plugins/pluginmanager.cc index ea7099943..aed50e791 100644 --- a/libretroshare/src/plugins/pluginmanager.cc +++ b/libretroshare/src/plugins/pluginmanager.cc @@ -134,6 +134,21 @@ void RsPluginManager::loadPlugins(const std::vector& plugin_directo std::cerr << "Loaded a total of " << _plugins.size() << " plugins." << std::endl; } +void RsPluginManager::stopPlugins() +{ + std::cerr << " Stopping plugins." << std::endl; + + for (uint32_t i = 0; i < _plugins.size(); ++i) + { + if (_plugins[i].plugin != NULL) + { + _plugins[i].plugin->stop(); +// delete _plugins[i].plugin; +// _plugins[i].plugin = NULL; + } + } +} + void RsPluginManager::getPluginStatus(int i,uint32_t& status,std::string& file_name,std::string& hash,std::string& error_string) const { if((uint32_t)i >= _plugins.size()) diff --git a/libretroshare/src/plugins/pluginmanager.h b/libretroshare/src/plugins/pluginmanager.h index 2735727ac..7c925edcb 100644 --- a/libretroshare/src/plugins/pluginmanager.h +++ b/libretroshare/src/plugins/pluginmanager.h @@ -78,6 +78,8 @@ class RsPluginManager: public RsPluginHandler, public p3Config // void loadPlugins(const std::vector& explicit_plugin_entries) ; + void stopPlugins(); + void registerCacheServices() ; void registerClientServices(p3ServiceServer *pqih) ; diff --git a/libretroshare/src/retroshare/rsplugin.h b/libretroshare/src/retroshare/rsplugin.h index 4f017f15b..e3cc21d2c 100644 --- a/libretroshare/src/retroshare/rsplugin.h +++ b/libretroshare/src/retroshare/rsplugin.h @@ -94,6 +94,9 @@ class RsPlugin virtual RsPQIService *rs_pqi_service() const { return NULL ; } virtual uint16_t rs_service_id() const { return 0 ; } + // Shutdown + virtual void stop() {} + // Filename used for saving the specific plugin configuration. Both RsCacheService and RsPQIService // derive from p3Config, which means that the service provided by the plugin can load/save its own // config by deriving loadList() and saveList() from p3Config. diff --git a/libretroshare/src/rsserver/p3face-config.cc b/libretroshare/src/rsserver/p3face-config.cc index 9624a43e4..4454a5b86 100644 --- a/libretroshare/src/rsserver/p3face-config.cc +++ b/libretroshare/src/rsserver/p3face-config.cc @@ -31,6 +31,7 @@ #include "pqi/authssl.h" #include "pqi/authgpg.h" #include "retroshare/rsinit.h" +#include "plugins/pluginmanager.h" #include "util/rsdebug.h" const int p3facemsgzone = 11453; @@ -182,6 +183,8 @@ void RsServer::rsGlobalShutDown() join(); ftserver->StopThreads(); + mPluginsManager->stopPlugins(); + // stop the p3distrib threads mForums->join(); mChannels->join(); diff --git a/libretroshare/src/rsserver/p3face-server.cc b/libretroshare/src/rsserver/p3face-server.cc index 9a4a582a1..fba404e68 100644 --- a/libretroshare/src/rsserver/p3face-server.cc +++ b/libretroshare/src/rsserver/p3face-server.cc @@ -62,6 +62,8 @@ RsServer::RsServer(RsIface &i, NotifyBase &callback) pqih = NULL; + mPluginsManager = NULL; + /* services */ ad = NULL; msgSrv = NULL; diff --git a/libretroshare/src/rsserver/p3face.h b/libretroshare/src/rsserver/p3face.h index d7e3a4e90..0290d0b0f 100644 --- a/libretroshare/src/rsserver/p3face.h +++ b/libretroshare/src/rsserver/p3face.h @@ -49,6 +49,7 @@ class p3PeerMgrIMPL; class p3LinkMgrIMPL; class p3NetMgrIMPL; class p3HistoryMgr; +class RsPluginManager; /* The Main Interface Class - for controlling the server */ @@ -161,6 +162,8 @@ class RsServer: public RsControl, public RsThread pqipersongrp *pqih; + RsPluginManager *mPluginsManager; + //sslroot *sslr; /* services */ diff --git a/libretroshare/src/rsserver/rsinit.cc b/libretroshare/src/rsserver/rsinit.cc index d6a0881da..9ee634604 100644 --- a/libretroshare/src/rsserver/rsinit.cc +++ b/libretroshare/src/rsserver/rsinit.cc @@ -2177,7 +2177,7 @@ int RsServer::StartupRetroShare() // possible entries include: /usr/lib/retroshare, ~/.retroshare/extensions/, etc. #endif - RsPluginManager *mPluginsManager = new RsPluginManager ; + mPluginsManager = new RsPluginManager ; rsPlugins = mPluginsManager ; mConfigMgr->addConfiguration("plugins.cfg", mPluginsManager); diff --git a/plugins/FeedReader/FeedReader.pro b/plugins/FeedReader/FeedReader.pro new file mode 100644 index 000000000..85b474948 --- /dev/null +++ b/plugins/FeedReader/FeedReader.pro @@ -0,0 +1,43 @@ +!include("../Common/retroshare_plugin.pri"): error("Could not include file ../Common/retroshare_plugin.pri") + +CONFIG += qt uic qrc resources + +SOURCES = FeedReaderPlugin.cpp \ + services/p3FeedReader.cc \ + services/p3FeedReaderThread.cc \ + services/rsFeedReaderItems.cc \ + gui/FeedReaderDialog.cpp \ + gui/AddFeedDialog.cpp \ + gui/FeedReaderNotify.cpp \ + gui/FeedReaderConfig.cpp + +HEADERS = FeedReaderPlugin.h \ + interface/rsFeedReader.h \ + services/p3FeedReader.h \ + services/p3FeedReaderThread.h \ + services/rsFeedReaderItems.h \ + gui/FeedReaderDialog.h \ + gui/AddFeedDialog.h \ + gui/FeedReaderNotify.h \ + gui/FeedReaderConfig.h + +FORMS = gui/FeedReaderDialog.ui \ + gui/AddFeedDialog.ui \ + gui/FeedReaderConfig.ui + +TARGET = FeedReader + +RESOURCES = gui/FeedReader_images.qrc \ + lang/lang.qrc + +win32 { + DEFINES += CURL_STATICLIB + + CURL_DIR = ../../../curl-7.26.0 + LIBXML2_DIR = ../../../libxml2-2.8.0 + LIBICONV_DIR = ../../../libiconv-1.14 + + INCLUDEPATH += $${CURL_DIR}/include $${LIBXML2_DIR}/include $${LIBICONV_DIR}/include + + LIBS += -lcurl -lxml2 -lws2_32 -lwldap32 +} diff --git a/plugins/FeedReader/FeedReaderPlugin.cpp b/plugins/FeedReader/FeedReaderPlugin.cpp new file mode 100644 index 000000000..c83be2c37 --- /dev/null +++ b/plugins/FeedReader/FeedReaderPlugin.cpp @@ -0,0 +1,138 @@ +/**************************************************************** + * RetroShare GUI is distributed under the following license: + * + * Copyright (C) 2012 by Thunder + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + ****************************************************************/ + +#include +#include + +#include +#include + +#include "FeedReaderPlugin.h" +#include "gui/FeedReaderDialog.h" +#include "gui/FeedReaderConfig.h" +#include "services/p3FeedReader.h" + +#define IMAGE_FEEDREADER ":/images/FeedReader.png" + +static void *inited = new FeedReaderPlugin(); + +extern "C" { +#ifdef WIN32 + __declspec(dllexport) +#endif + void *RETROSHARE_PLUGIN_provide() + { + static FeedReaderPlugin *p = new FeedReaderPlugin(); + + return (void*)p; + } +} + +void FeedReaderPlugin::getPluginVersion(int& major,int& minor,int& svn_rev) const +{ + major = 5; + minor = 1; + svn_rev = 4350; +} + +FeedReaderPlugin::FeedReaderPlugin() +{ + mainpage = NULL ; + mIcon = NULL ; + mPlugInHandler = NULL; + mFeedReader = NULL; +} + +void FeedReaderPlugin::setInterfaces(RsPlugInInterfaces &/*interfaces*/) +{ +} + +ConfigPage *FeedReaderPlugin::qt_config_page() const +{ + return new FeedReaderConfig(); +} + +MainPage *FeedReaderPlugin::qt_page() const +{ + if (mainpage == NULL) { + mainpage = new FeedReaderDialog(mFeedReader); + } + + return mainpage; +} + +RsPQIService *FeedReaderPlugin::rs_pqi_service() const +{ + if (mFeedReader == NULL) { + mFeedReader = new p3FeedReader(mPlugInHandler); + rsFeedReader = mFeedReader; + } + + return mFeedReader; +} + +void FeedReaderPlugin::stop() +{ + if (mFeedReader) { + mFeedReader->stop(); + } +} + +void FeedReaderPlugin::setPlugInHandler(RsPluginHandler *pgHandler) +{ + mPlugInHandler = pgHandler; +} + +QIcon *FeedReaderPlugin::qt_icon() const +{ + if (mIcon == NULL) { + Q_INIT_RESOURCE(FeedReader_images); + + mIcon = new QIcon(IMAGE_FEEDREADER); + } + + return mIcon; +} + +std::string FeedReaderPlugin::getShortPluginDescription() const +{ + return QApplication::translate("FeedReaderPlugin", "This plugin provides a Feedreader.").toUtf8().constData(); +} + +std::string FeedReaderPlugin::getPluginName() const +{ + return QApplication::translate("FeedReaderPlugin", "FeedReader").toUtf8().constData(); +} + +QTranslator* FeedReaderPlugin::qt_translator(QApplication */*app*/, const QString& languageCode) const +{ + if (languageCode == "en") { + return NULL; + } + + QTranslator* translator = new QTranslator(); + if (translator->load(":/lang/FeedReader_" + languageCode + ".qm")) { + return translator; + } + + delete(translator); + return NULL; +} diff --git a/plugins/FeedReader/FeedReaderPlugin.h b/plugins/FeedReader/FeedReaderPlugin.h new file mode 100644 index 000000000..dffcfa7b4 --- /dev/null +++ b/plugins/FeedReader/FeedReaderPlugin.h @@ -0,0 +1,60 @@ +/**************************************************************** + * RetroShare GUI is distributed under the following license: + * + * Copyright (C) 2012 by Thunder + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + ****************************************************************/ + +#pragma once + +#include +#include +#include "services/p3FeedReader.h" + +class p3FeedReader; +class RsForums; + +class FeedReaderPlugin: public RsPlugin +{ +public: + FeedReaderPlugin(); + + virtual uint16_t rs_service_id() const { return RS_PKT_TYPE_FEEDREADER_CONFIG; } + virtual RsPQIService *rs_pqi_service() const; + virtual void stop(); + + virtual MainPage *qt_page() const; + virtual QIcon *qt_icon() const; + virtual QTranslator *qt_translator(QApplication *app, const QString &languageCode) const; + + virtual void getPluginVersion(int &major, int &minor, int &svn_rev) const; + virtual void setPlugInHandler(RsPluginHandler *pgHandler); + + virtual std::string configurationFileName() const { return "feedreader.cfg" ; } + + virtual std::string getShortPluginDescription() const; + virtual std::string getPluginName() const; + virtual void setInterfaces(RsPlugInInterfaces& interfaces); + virtual ConfigPage *qt_config_page() const; + +private: + mutable p3FeedReader *mFeedReader; + mutable RsPluginHandler *mPlugInHandler; + mutable MainPage *mainpage; + mutable QIcon *mIcon; +}; + diff --git a/plugins/FeedReader/gui/AddFeedDialog.cpp b/plugins/FeedReader/gui/AddFeedDialog.cpp new file mode 100644 index 000000000..5013eaf5a --- /dev/null +++ b/plugins/FeedReader/gui/AddFeedDialog.cpp @@ -0,0 +1,299 @@ +/**************************************************************** + * RetroShare GUI is distributed under the following license: + * + * Copyright (C) 2012 by Thunder + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + ****************************************************************/ + +#include +#include +#include + +#include "AddFeedDialog.h" +#include "ui_AddFeedDialog.h" +#include "retroshare/rsforums.h" + +bool sortForumInfo(const ForumInfo& info1, const ForumInfo& info2) +{ + return QString::fromStdWString(info1.forumName).compare(QString::fromStdWString(info2.forumName), Qt::CaseInsensitive); +} + +AddFeedDialog::AddFeedDialog(RsFeedReader *feedReader, QWidget *parent) + : QDialog(parent), mFeedReader(feedReader), ui(new Ui::AddFeedDialog) +{ + ui->setupUi(this); + + connect(ui->buttonBox->button(QDialogButtonBox::Ok), SIGNAL(clicked()), this, SLOT(createFeed())); + connect(ui->buttonBox->button(QDialogButtonBox::Cancel), SIGNAL(clicked()), this, SLOT(reject())); + + connect(ui->useAuthenticationCheckBox, SIGNAL(toggled(bool)), this, SLOT(authenticationToggled())); + connect(ui->useStandardStorageTimeCheckBox, SIGNAL(toggled(bool)), this, SLOT(useStandardStorageTimeToggled())); + connect(ui->useStandardUpdateInterval, SIGNAL(toggled(bool)), this, SLOT(useStandardUpdateIntervalToggled())); + connect(ui->useStandardProxyCheckBox, SIGNAL(toggled(bool)), this, SLOT(useStandardProxyToggled())); + connect(ui->typeForumRadio, SIGNAL(toggled(bool)), this, SLOT(typeForumToggled())); + + connect(ui->urlLineEdit, SIGNAL(textChanged(QString)), this, SLOT(validate())); + connect(ui->nameLineEdit, SIGNAL(textChanged(QString)), this, SLOT(validate())); + connect(ui->useInfoFromFeedCheckBox, SIGNAL(toggled(bool)), this, SLOT(validate())); + + ui->activatedCheckBox->setChecked(true); + ui->typeLocalRadio->setChecked(true); + ui->forumComboBox->setEnabled(false); + ui->useInfoFromFeedCheckBox->setChecked(true); + ui->updateForumInfoCheckBox->setEnabled(false); + ui->updateForumInfoCheckBox->setChecked(true); + ui->forumNameLabel->hide(); + ui->useAuthenticationCheckBox->setChecked(false); + ui->useStandardStorageTimeCheckBox->setChecked(true); + ui->useStandardUpdateInterval->setChecked(true); + ui->useStandardProxyCheckBox->setChecked(true); + + /* not yet supported */ + ui->authenticationGroupBox->setEnabled(false); + + /* fill own forums */ + std::list forumList; + if (rsForums->getForumList(forumList)) { + forumList.sort(sortForumInfo); + for (std::list::iterator it = forumList.begin(); it != forumList.end(); ++it) { + ForumInfo &forumInfo = *it; + /* show only own anonymous forums */ + if ((forumInfo.subscribeFlags & RS_DISTRIB_ADMIN) && (forumInfo.forumFlags & RS_DISTRIB_AUTHEN_ANON)) { + ui->forumComboBox->addItem(QString::fromStdWString(forumInfo.forumName), QString::fromStdString(forumInfo.forumId)); + } + } + } + /* insert item to create a new forum */ + ui->forumComboBox->insertItem(0, tr("Create a new anonymous public forum"), ""); + ui->forumComboBox->setCurrentIndex(0); + + validate(); + + ui->urlLineEdit->setFocus(); +} + +AddFeedDialog::~AddFeedDialog() +{ + delete ui; +} + +void AddFeedDialog::authenticationToggled() +{ + bool checked = ui->useAuthenticationCheckBox->isChecked(); + ui->userLineEdit->setEnabled(checked); + ui->passwordLineEdit->setEnabled(checked); +} + +void AddFeedDialog::useStandardStorageTimeToggled() +{ + bool checked = ui->useStandardStorageTimeCheckBox->isChecked(); + ui->storageTimeSpinBox->setEnabled(!checked); +} + +void AddFeedDialog::useStandardUpdateIntervalToggled() +{ + bool checked = ui->useStandardUpdateInterval->isChecked(); + ui->updateIntervalSpinBox->setEnabled(!checked); +} + +void AddFeedDialog::useStandardProxyToggled() +{ + bool checked = ui->useStandardProxyCheckBox->isChecked(); + ui->proxyAddressLineEdit->setEnabled(!checked); + ui->proxyPortSpinBox->setEnabled(!checked); +} + +void AddFeedDialog::typeForumToggled() +{ + bool checked = ui->typeForumRadio->isChecked(); + ui->forumComboBox->setEnabled(checked); + ui->updateForumInfoCheckBox->setEnabled(checked); +} + +void AddFeedDialog::validate() +{ + bool ok = true; + + if (ui->urlLineEdit->text().isEmpty()) { + ok = false; + } + if (ui->nameLineEdit->text().isEmpty() && !ui->useInfoFromFeedCheckBox->isChecked()) { + ok = false; + } + + ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(ok); +} + +bool AddFeedDialog::showError(QWidget *parent, RsFeedAddResult result, const QString &title, const QString &text) +{ + QString error; + + switch (result) { + case RS_FEED_ADD_RESULT_SUCCESS: + /* no error */ + return false; + case RS_FEED_ADD_RESULT_FEED_NOT_FOUND: + error = tr("Feed not found."); + break; + case RS_FEED_ADD_RESULT_PARENT_NOT_FOUND: + error = tr("Parent not found."); + break; + case RS_FEED_ADD_RESULT_PARENT_IS_NO_FOLDER: + error = tr("Parent is no folder."); + break; + case RS_FEED_ADD_RESULT_FEED_IS_FOLDER: + error = tr("Feed is a folder."); + break; + case RS_FEED_ADD_RESULT_FEED_IS_NO_FOLDER: + error = tr("Feed is no folder."); + break; + default: + error = tr("Unknown error occured."); + } + + QMessageBox::critical(parent, title, text + "\n" + error); + + return true; +} + +void AddFeedDialog::setParent(const std::string &parentId) +{ + mParentId = parentId; +} + +bool AddFeedDialog::fillFeed(const std::string &feedId) +{ + mFeedId = feedId; + + if (!mFeedId.empty()) { + FeedInfo feedInfo; + if (!mFeedReader->getFeedInfo(mFeedId, feedInfo)) { + mFeedId.clear(); + return false; + } + + setWindowTitle(tr("Edit feed")); + ui->typeGroupBox->setEnabled(false); + + mParentId = feedInfo.parentId; + + ui->nameLineEdit->setText(QString::fromUtf8(feedInfo.name.c_str())); + ui->urlLineEdit->setText(QString::fromUtf8(feedInfo.url.c_str())); + ui->useInfoFromFeedCheckBox->setChecked(feedInfo.flag.infoFromFeed); + ui->updateForumInfoCheckBox->setChecked(feedInfo.flag.updateForumInfo); + ui->activatedCheckBox->setChecked(!feedInfo.flag.deactivated); + + ui->descriptionPlainTextEdit->setPlainText(QString::fromUtf8(feedInfo.description.c_str())); + + ui->typeGroupBox->setEnabled(false); + ui->forumComboBox->hide(); + ui->forumNameLabel->clear(); + ui->forumNameLabel->show(); + + if (feedInfo.flag.forum) { + ui->typeForumRadio->setChecked(true); + + if (feedInfo.forumId.empty()) { + ui->forumNameLabel->setText(tr("Not yet created")); + } else { + ForumInfo forumInfo; + if (rsForums->getForumInfo(feedInfo.forumId, forumInfo)) { + ui->forumNameLabel->setText(QString::fromStdWString(forumInfo.forumName)); + } else { + ui->forumNameLabel->setText(tr("Unknown forum")); + } + } + } else { + ui->typeLocalRadio->setChecked(true); + } + + ui->useAuthenticationCheckBox->setChecked(feedInfo.flag.authentication); + ui->userLineEdit->setText(QString::fromUtf8(feedInfo.user.c_str())); + ui->passwordLineEdit->setText(QString::fromUtf8(feedInfo.password.c_str())); + + ui->useStandardProxyCheckBox->setChecked(feedInfo.flag.standardProxy); + ui->proxyAddressLineEdit->setText(QString::fromUtf8(feedInfo.proxyAddress.c_str())); + ui->proxyPortSpinBox->setValue(feedInfo.proxyPort); + + ui->useStandardUpdateInterval->setChecked(feedInfo.flag.standardUpdateInterval); + ui->updateIntervalSpinBox->setValue(feedInfo.updateInterval / 60); + QDateTime dateTime; + dateTime.setTime_t(feedInfo.lastUpdate); + ui->lastUpdate->setText(dateTime.toString()); + + ui->useStandardStorageTimeCheckBox->setChecked(feedInfo.flag.standardStorageTime); + ui->storageTimeSpinBox->setValue(feedInfo.storageTime / (60 * 60 *24)); + } + + return true; +} + +void AddFeedDialog::createFeed() +{ + FeedInfo feedInfo; + if (!mFeedId.empty()) { + if (!mFeedReader->getFeedInfo(mFeedId, feedInfo)) { + QMessageBox::critical(this, tr("Edit feed"), tr("Can't edit feed. Feed does not exist.")); + return; + } + } + + feedInfo.parentId = mParentId; + + feedInfo.name = ui->nameLineEdit->text().toUtf8().constData(); + feedInfo.url = ui->urlLineEdit->text().toUtf8().constData(); + feedInfo.flag.infoFromFeed = ui->useInfoFromFeedCheckBox->isChecked(); + feedInfo.flag.updateForumInfo = ui->updateForumInfoCheckBox->isChecked() && ui->updateForumInfoCheckBox->isEnabled(); + feedInfo.flag.deactivated = !ui->activatedCheckBox->isChecked(); + + feedInfo.description = ui->descriptionPlainTextEdit->toPlainText().toUtf8().constData(); + + feedInfo.flag.forum = ui->typeForumRadio->isChecked(); + if (mFeedId.empty()) { + /* set forum (only when create a new feed) */ + feedInfo.forumId = ui->forumComboBox->itemData(ui->forumComboBox->currentIndex()).toString().toStdString(); + } + + feedInfo.flag.authentication = ui->useAuthenticationCheckBox->isChecked(); + feedInfo.user = ui->userLineEdit->text().toUtf8().constData(); + feedInfo.password = ui->passwordLineEdit->text().toUtf8().constData(); + + feedInfo.flag.standardProxy = ui->useStandardProxyCheckBox->isChecked(); + feedInfo.proxyAddress = ui->proxyAddressLineEdit->text().toUtf8().constData(); + feedInfo.proxyPort = ui->proxyPortSpinBox->value(); + + feedInfo.flag.standardUpdateInterval = ui->useStandardUpdateInterval->isChecked(); + feedInfo.updateInterval = ui->updateIntervalSpinBox->value() * 60; + + feedInfo.flag.standardStorageTime = ui->useStandardStorageTimeCheckBox->isChecked(); + feedInfo.storageTime = ui->storageTimeSpinBox->value() * 60 *60 * 24; + + if (mFeedId.empty()) { + /* add new feed */ + RsFeedAddResult result = mFeedReader->addFeed(feedInfo, mFeedId); + if (showError(this, result, tr("Create feed"), tr("Cannot create feed."))) { + return; + } + } else { + RsFeedAddResult result = mFeedReader->setFeed(mFeedId, feedInfo); + if (showError(this, result, tr("Edit feed"), tr("Cannot change feed."))) { + return; + } + } + + close(); +} diff --git a/plugins/FeedReader/gui/AddFeedDialog.h b/plugins/FeedReader/gui/AddFeedDialog.h new file mode 100644 index 000000000..c117cd789 --- /dev/null +++ b/plugins/FeedReader/gui/AddFeedDialog.h @@ -0,0 +1,64 @@ +/**************************************************************** + * RetroShare GUI is distributed under the following license: + * + * Copyright (C) 2012 by Thunder + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + ****************************************************************/ + +#ifndef ADDFEEDDIALOG_H +#define ADDFEEDDIALOG_H + +#include +#include "interface/rsFeedReader.h" + +namespace Ui { +class AddFeedDialog; +} + +class RsFeedReader; + +class AddFeedDialog : public QDialog +{ + Q_OBJECT + +public: + AddFeedDialog(RsFeedReader *feedReader, QWidget *parent); + ~AddFeedDialog(); + + static bool showError(QWidget *parent, RsFeedAddResult result, const QString &title, const QString &text); + + void setParent(const std::string &parentId); + bool fillFeed(const std::string &feedId); + +private slots: + void authenticationToggled(); + void useStandardStorageTimeToggled(); + void useStandardUpdateIntervalToggled(); + void useStandardProxyToggled(); + void typeForumToggled(); + void validate(); + void createFeed(); + +private: + RsFeedReader *mFeedReader; + std::string mFeedId; + std::string mParentId; + + Ui::AddFeedDialog *ui; +}; + +#endif // ADDFEEDDIALOG_H diff --git a/plugins/FeedReader/gui/AddFeedDialog.ui b/plugins/FeedReader/gui/AddFeedDialog.ui new file mode 100644 index 000000000..ed2587dfa --- /dev/null +++ b/plugins/FeedReader/gui/AddFeedDialog.ui @@ -0,0 +1,442 @@ + + + AddFeedDialog + + + + 0 + 0 + 715 + 559 + + + + Create new feed + + + + :/images/rstray3.png:/images/rstray3.png + + + + 0 + + + 0 + + + + + + 16777215 + 64 + + + + QFrame#headerFrame{background-image: url(:/images/connect/connectFriendBanner.png);} + + + QFrame::NoFrame + + + QFrame::Raised + + + + 6 + + + 6 + + + + + + 48 + 48 + + + + + + + + + + :/images/FeedReader.png + + + true + + + + + + + color: rgb(255, 255, 255); + + + <!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:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Arial'; font-size:24pt; font-weight:600; color:#ffffff;">Feed Details</span></p></body></html> + + + + + + + + + + QFrame::NoFrame + + + QFrame::Raised + + + + + + Authentication (not yet supported) + + + + + + Feed needs authentication + + + + + + + User + + + + + + + Password + + + + + + + + + + QLineEdit::Password + + + + + + + + + + Update interval + + + + + + Use standard update interval + + + + + + + Interval in minutes (0 = manual) + + + + + + + + 50 + 16777215 + + + + + + + + + + Last update + + + + + + + + 0 + 0 + + + + Never + + + + + + + + + + + + Storage time + + + + + + Use standard storage time + + + + + + + Days (0 = off) + + + + + + + + 50 + 16777215 + + + + 999999999 + + + + + + + + + + Proxy + + + + + + Use standard proxy + + + + + + + Server + + + + + + + + + + : + + + + + + + 65535 + + + + + + + + + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + Type + + + + 6 + + + + + Local Feed + + + + + + + + + Forum + + + + + + + + + + + 0 + 0 + + + + Forum name + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + Misc + + + + + + Activated + + + + + + + Use name and description from feed + + + + + + + Update forum information + + + + + + + + + + + + Description: + + + + + + + + + + + + + + RSS-Feed-URL: + + + + + + + + + + Name: + + + + + + + + + + + + + + + urlLineEdit + nameLineEdit + descriptionPlainTextEdit + typeLocalRadio + typeForumRadio + forumComboBox + activatedCheckBox + useInfoFromFeedCheckBox + updateForumInfoCheckBox + useAuthenticationCheckBox + userLineEdit + passwordLineEdit + useStandardStorageTimeCheckBox + storageTimeSpinBox + useStandardUpdateInterval + updateIntervalSpinBox + useStandardProxyCheckBox + proxyAddressLineEdit + proxyPortSpinBox + buttonBox + + + + + + + diff --git a/plugins/FeedReader/gui/FeedReaderConfig.cpp b/plugins/FeedReader/gui/FeedReaderConfig.cpp new file mode 100644 index 000000000..2b9102c9c --- /dev/null +++ b/plugins/FeedReader/gui/FeedReaderConfig.cpp @@ -0,0 +1,80 @@ +/**************************************************************** + * RetroShare GUI is distributed under the following license: + * + * Copyright (C) 2012 by Thunder + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + ****************************************************************/ + +#include "FeedReaderConfig.h" +#include "ui_FeedReaderConfig.h" +#include "gui/settings/rsharesettings.h" +#include "interface/rsFeedReader.h" + +/** Constructor */ +FeedReaderConfig::FeedReaderConfig(QWidget *parent, Qt::WFlags flags) + : ConfigPage(parent, flags), ui(new Ui::FeedReaderConfig) +{ + /* Invoke the Qt Designer generated object setup routine */ + ui->setupUi(this); + + connect(ui->useProxyCheckBox, SIGNAL(toggled(bool)), this, SLOT(useProxyToggled())); + + ui->proxyAddressLineEdit->setEnabled(false); + ui->proxyPortSpinBox->setEnabled(false); + + loaded = false; +} + +/** Destructor */ +FeedReaderConfig::~FeedReaderConfig() +{ + delete(ui); +} + +/** Loads the settings for this page */ +void FeedReaderConfig::load() +{ + ui->updateIntervalSpinBox->setValue(rsFeedReader->getStandardUpdateInterval() / 60); + ui->storageTimeSpinBox->setValue(rsFeedReader->getStandardStorageTime() / (60 * 60 *24)); + ui->setMsgToReadOnActivate->setChecked(Settings->valueFromGroup("FeedReaderDialog", "SetMsgToReadOnActivate", true).toBool()); + + std::string proxyAddress; + uint16_t proxyPort; + ui->useProxyCheckBox->setChecked(rsFeedReader->getStandardProxy(proxyAddress, proxyPort)); + ui->proxyAddressLineEdit->setText(QString::fromUtf8(proxyAddress.c_str())); + ui->proxyPortSpinBox->setValue(proxyPort); + + loaded = true; +} + +bool FeedReaderConfig::save(QString &/*errmsg*/) +{ + rsFeedReader->setStandardUpdateInterval(ui->updateIntervalSpinBox->value() * 60); + rsFeedReader->setStandardStorageTime(ui->storageTimeSpinBox->value() * 60 *60 * 24); + rsFeedReader->setStandardProxy(ui->useProxyCheckBox->isChecked(), ui->proxyAddressLineEdit->text().toUtf8().constData(), ui->proxyPortSpinBox->value()); + Settings->setValueToGroup("FeedReaderDialog", "SetMsgToReadOnActivate", ui->setMsgToReadOnActivate->isChecked()); + + return true; +} + +void FeedReaderConfig::useProxyToggled() +{ + bool enabled = ui->useProxyCheckBox->isChecked(); + + ui->proxyAddressLineEdit->setEnabled(enabled); + ui->proxyPortSpinBox->setEnabled(enabled); +} diff --git a/plugins/FeedReader/gui/FeedReaderConfig.h b/plugins/FeedReader/gui/FeedReaderConfig.h new file mode 100644 index 000000000..ed846269a --- /dev/null +++ b/plugins/FeedReader/gui/FeedReaderConfig.h @@ -0,0 +1,57 @@ +/**************************************************************** + * RetroShare GUI is distributed under the following license: + * + * Copyright (C) 2012 by Thunder + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + ****************************************************************/ + +#ifndef _FEEDREADERCONFIG_H +#define _FEEDREADERCONFIG_H + +#include "retroshare-gui/configpage.h" + +namespace Ui { +class FeedReaderConfig; +} + +class FeedReaderConfig : public ConfigPage +{ + Q_OBJECT + +public: + /** Default Constructor */ + FeedReaderConfig(QWidget *parent = 0, Qt::WFlags flags = 0); + /** Default Destructor */ + virtual ~FeedReaderConfig(); + + /** Saves the changes on this page */ + virtual bool save(QString &errmsg); + /** Loads the settings for this page */ + virtual void load(); + + virtual QPixmap iconPixmap() const { return QPixmap(":/images/FeedReader.png") ; } + virtual QString pageName() const { return tr("FeedReader") ; } + +private slots: + void useProxyToggled(); + +private: + Ui::FeedReaderConfig *ui; + bool loaded; +}; + +#endif diff --git a/plugins/FeedReader/gui/FeedReaderConfig.ui b/plugins/FeedReader/gui/FeedReaderConfig.ui new file mode 100644 index 000000000..36deae46b --- /dev/null +++ b/plugins/FeedReader/gui/FeedReaderConfig.ui @@ -0,0 +1,148 @@ + + + FeedReaderConfig + + + + 0 + 0 + 508 + 378 + + + + Form + + + + + + Update + + + + + + Interval in minutes (0 = manual) + + + + + + + + 50 + 16777215 + + + + 999999999 + + + + + + + + + + Storage time + + + + + + Days (0 = off) + + + + + + + + 50 + 16777215 + + + + 999999999 + + + + + + + + + + Proxy + + + + + + Use proxy + + + + + + + Server + + + + + + + + + + 65535 + + + + + + + : + + + + + + + + + + Misc + + + + + + Set message to read on activate + + + + + + + + + + Qt::Vertical + + + + 20 + 301 + + + + + + + + + diff --git a/plugins/FeedReader/gui/FeedReaderDialog.cpp b/plugins/FeedReader/gui/FeedReaderDialog.cpp new file mode 100644 index 000000000..15f6baa24 --- /dev/null +++ b/plugins/FeedReader/gui/FeedReaderDialog.cpp @@ -0,0 +1,1088 @@ +/**************************************************************** + * RetroShare GUI is distributed under the following license: + * + * Copyright (C) 2012 by Thunder + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + ****************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "FeedReaderDialog.h" +#include "ui_FeedReaderDialog.h" +#include "FeedReaderNotify.h" +#include "AddFeedDialog.h" +#include "gui/common/RSTreeWidgetItem.h" +#include "util/HandleRichText.h" +#include "gui/settings/rsharesettings.h" +#include "interface/rsFeedReader.h" +#include "retroshare/rsiface.h" + +#define COLUMN_FEED_COUNT 1 +#define COLUMN_FEED_NAME 0 +#define COLUMN_FEED_DATA 0 + +#define ROLE_FEED_ID Qt::UserRole +#define ROLE_FEED_SORT Qt::UserRole + 1 +#define ROLE_FEED_FOLDER Qt::UserRole + 2 +#define ROLE_FEED_UNREAD Qt::UserRole + 3 +#define ROLE_FEED_NAME Qt::UserRole + 4 +#define ROLE_FEED_WORKSTATE Qt::UserRole + 5 +#define ROLE_FEED_LOADING Qt::UserRole + 6 +#define ROLE_FEED_ICON Qt::UserRole + 7 +#define ROLE_FEED_ERROR Qt::UserRole + 8 +#define ROLE_FEED_DEACTIVATED Qt::UserRole + 9 + +#define COLUMN_MSG_COUNT 4 +#define COLUMN_MSG_TITLE 0 +#define COLUMN_MSG_READ 1 +#define COLUMN_MSG_PUBDATE 2 +#define COLUMN_MSG_AUTHOR 3 +#define COLUMN_MSG_DATA 0 + +#define ROLE_MSG_ID Qt::UserRole +#define ROLE_MSG_SORT Qt::UserRole + 1 +#define ROLE_MSG_NEW Qt::UserRole + 2 +#define ROLE_MSG_READ Qt::UserRole + 3 +#define ROLE_MSG_LINK Qt::UserRole + 4 + +static int filterColumnToComboBox(int nIndex) +{ + switch (nIndex) { + case COLUMN_MSG_TITLE: + return 0; + case COLUMN_MSG_PUBDATE: + return 1; + case COLUMN_MSG_AUTHOR: + return 2; + } + + return filterColumnToComboBox(COLUMN_MSG_TITLE); +} + +static int filterColumnFromComboBox(int nIndex) +{ + switch (nIndex) { + case 0: + return COLUMN_MSG_TITLE; + case 1: + return COLUMN_MSG_PUBDATE; + case 2: + return COLUMN_MSG_AUTHOR; + } + + return COLUMN_MSG_TITLE; +} + +FeedReaderDialog::FeedReaderDialog(RsFeedReader *feedReader, QWidget *parent) + : MainPage(parent), mFeedReader(feedReader), ui(new Ui::FeedReaderDialog) +{ + /* Invoke the Qt Designer generated object setup routine */ + ui->setupUi(this); + + mNotify = new FeedReaderNotify(); + mFeedReader->setNotify(mNotify); + connect(mNotify, SIGNAL(notifyFeedChanged(QString,int)), this, SLOT(feedChanged(QString,int))); + connect(mNotify, SIGNAL(notifyMsgChanged(QString,QString,int)), this, SLOT(msgChanged(QString,QString,int))); + + mProcessSettings = false; + + /* connect signals */ + connect(ui->feedTreeWidget, SIGNAL(currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)), this, SLOT(feedItemChanged(QTreeWidgetItem*))); + connect(ui->msgTreeWidget, SIGNAL(itemSelectionChanged()), this, SLOT(msgItemChanged())); + connect(ui->msgTreeWidget, SIGNAL(itemClicked(QTreeWidgetItem*,int)), this, SLOT(msgItemClicked(QTreeWidgetItem*,int))); + + connect(ui->feedTreeWidget, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(feedTreeCustomPopupMenu(QPoint))); + connect(ui->msgTreeWidget, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(msgTreeCustomPopupMenu(QPoint))); + + connect(ui->filterLineEdit, SIGNAL(textChanged(QString)), this, SLOT(filterItems(QString))); + connect(ui->filterColumnComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(filterColumnChanged())); + + connect(ui->expandButton, SIGNAL(clicked()), this, SLOT(toggleMsgText())); + + mFeedCompareRole = new RSTreeWidgetItemCompareRole; + mFeedCompareRole->setRole(COLUMN_FEED_NAME, ROLE_FEED_SORT); + + mMsgCompareRole = new RSTreeWidgetItemCompareRole; + mMsgCompareRole->setRole(COLUMN_MSG_TITLE, ROLE_MSG_SORT); + mMsgCompareRole->setRole(COLUMN_MSG_READ, ROLE_MSG_SORT); + mMsgCompareRole->setRole(COLUMN_MSG_PUBDATE, ROLE_MSG_SORT); + mMsgCompareRole->setRole(COLUMN_MSG_AUTHOR, ROLE_MSG_SORT); + + /* initialize root item */ + mRootItem = new QTreeWidgetItem(ui->feedTreeWidget); + QString name = tr("Message Folders"); + mRootItem->setText(COLUMN_FEED_NAME, name); + mRootItem->setIcon(COLUMN_FEED_NAME, QIcon(":/images/Root.png")); + mRootItem->setData(COLUMN_FEED_DATA, ROLE_FEED_NAME, name); + mRootItem->setData(COLUMN_FEED_DATA, ROLE_FEED_FOLDER, true); + mRootItem->setData(COLUMN_FEED_DATA, ROLE_FEED_ICON, QIcon(":/images/Root.png")); + mRootItem->setExpanded(true); + + /* initialize msg list */ + ui->msgTreeWidget->sortItems(COLUMN_MSG_PUBDATE, Qt::DescendingOrder); + + /* set initial size the splitter */ + QList sizes; + sizes << 300 << width(); // Qt calculates the right sizes + ui->splitter->setSizes(sizes); + + /* set header resize modes and initial section sizes */ + QHeaderView *header = ui->msgTreeWidget->header(); + header->setResizeMode(COLUMN_MSG_TITLE, QHeaderView::Interactive); + header->resizeSection(COLUMN_MSG_TITLE, 350); + header->resizeSection(COLUMN_MSG_PUBDATE, 140); + header->resizeSection(COLUMN_MSG_AUTHOR, 150); + + /* set text of column "Read" to empty - without this the column has a number as header text */ + QTreeWidgetItem *headerItem = ui->msgTreeWidget->headerItem(); + headerItem->setText(COLUMN_MSG_READ, ""); + + /* load settings */ + processSettings(true); + + /* Set header sizes for the fixed columns and resize modes, must be set after processSettings */ + header->resizeSection(COLUMN_MSG_READ, 24); + header->setResizeMode(COLUMN_MSG_READ, QHeaderView::Fixed); + + /* initialize feed list */ + ui->feedTreeWidget->sortItems(COLUMN_FEED_NAME, Qt::AscendingOrder); + + ui->msgTreeWidget->installEventFilter(this); +} + +FeedReaderDialog::~FeedReaderDialog() +{ + /* save settings */ + processSettings(false); + + delete(mFeedCompareRole); + delete(mMsgCompareRole); + delete(ui); + + mFeedReader->setNotify(NULL); + delete(mNotify); +} + +void FeedReaderDialog::processSettings(bool load) +{ + mProcessSettings = true; + + QHeaderView *header = ui->msgTreeWidget->header (); + + Settings->beginGroup(QString("FeedReaderDialog")); + + if (load) { + // load settings + + // expandButton + bool value = Settings->value("expandButton", true).toBool(); + ui->expandButton->setChecked(value); + toggleMsgText_internal(); + + // filterColumn + ui->filterColumnComboBox->setCurrentIndex(filterColumnToComboBox(Settings->value("filterColumn", COLUMN_MSG_TITLE).toInt())); + + // state of thread tree + header->restoreState(Settings->value("msgTree").toByteArray()); + + // state of splitter + ui->splitter->restoreState(Settings->value("Splitter").toByteArray()); + ui->msgSplitter->restoreState(Settings->value("msgSplitter").toByteArray()); + } else { + // save settings + + // state of thread tree + Settings->setValue("msgTree", header->saveState()); + + // state of splitter + Settings->setValue("Splitter", ui->splitter->saveState()); + Settings->setValue("msgSplitter", ui->msgSplitter->saveState()); + } + + Settings->endGroup(); + mProcessSettings = false; +} + +void FeedReaderDialog::showEvent(QShowEvent */*event*/) +{ + updateFeeds("", mRootItem); +} + +bool FeedReaderDialog::eventFilter(QObject *obj, QEvent *event) +{ + if (obj == ui->msgTreeWidget) { + if (event->type() == QEvent::KeyPress) { + QKeyEvent *keyEvent = static_cast(event); + if (keyEvent) { + if (keyEvent->key() == Qt::Key_Space) { + /* Space pressed */ + QTreeWidgetItem *item = ui->msgTreeWidget->currentItem(); + msgItemClicked(item, COLUMN_MSG_READ); + return true; // eat event + } + if (keyEvent->key() == Qt::Key_Delete) { + /* Delete pressed */ + removeMsg(); + return true; // eat event + } + } + } + } + if (obj == ui->feedTreeWidget) { + if (event->type() == QEvent::KeyPress) { + QKeyEvent *keyEvent = static_cast(event); + if (keyEvent) { + if (keyEvent->key() == Qt::Key_Delete) { + /* Delete pressed */ + removeFeed(); + return true; // eat event + } + } + } + } + /* pass the event on to the parent class */ + return MainPage::eventFilter(obj, event); +} + +std::string FeedReaderDialog::currentFeedId() +{ + QTreeWidgetItem *item = ui->feedTreeWidget->currentItem(); + if (!item) { + return ""; + } + + return item->data(COLUMN_FEED_DATA, ROLE_FEED_ID).toString().toStdString(); +} + +std::string FeedReaderDialog::currentMsgId() +{ + QTreeWidgetItem *item = ui->msgTreeWidget->currentItem(); + if (!item) { + return ""; + } + + return item->data(COLUMN_MSG_DATA, ROLE_MSG_ID).toString().toStdString(); +} + +void FeedReaderDialog::feedTreeCustomPopupMenu(QPoint /*point*/) +{ + QMenu contextMnu(this); + + bool folder = false; + QTreeWidgetItem *item = ui->feedTreeWidget->currentItem(); + if (item) { + folder = item->data(COLUMN_FEED_DATA, ROLE_FEED_FOLDER).toBool(); + } + + QMenu *menu = contextMnu.addMenu(QIcon(""), tr("New")); + QAction *action = menu->addAction(QIcon(":/images/FeedAdd.png"), tr("Feed"), this, SLOT(newFeed())); + if (!item || !folder) { + action->setEnabled(false); + } + action = menu->addAction(QIcon(":/images/FolderAdd.png"), tr("Folder"), this, SLOT(newFolder())); + if (!item || !folder) { + action->setEnabled(false); + } + + contextMnu.addSeparator(); + + action = contextMnu.addAction(QIcon(":/images/edit_16.png"), tr("Edit"), this, SLOT(editFeed())); + if (!item || item == mRootItem) { + action->setEnabled(false); + } + + action = contextMnu.addAction(QIcon(":/images/delete.png"), tr("Delete"), this, SLOT(removeFeed())); + if (!item || item == mRootItem) { + action->setEnabled(false); + } + + contextMnu.addSeparator(); + + bool deactivated = false; + if (!folder && item) { + deactivated = item->data(COLUMN_FEED_DATA, ROLE_FEED_DEACTIVATED).toBool(); + } + + action = contextMnu.addAction(QIcon(":/images/Update.png"), tr("Update"), this, SLOT(processFeed())); + action->setEnabled(!deactivated); + + action = contextMnu.addAction(QIcon(""), deactivated ? tr("Activate") : tr("Deactivate"), this, SLOT(activateFeed())); + if (!item || item == mRootItem || folder) { + action->setEnabled(false); + } + + contextMnu.exec(QCursor::pos()); +} + +void FeedReaderDialog::msgTreeCustomPopupMenu(QPoint /*point*/) +{ + QMenu contextMnu(this); + + QList selectedItems = ui->msgTreeWidget->selectedItems(); + + QAction *action = contextMnu.addAction(QIcon(""), tr("Mark as read"), this, SLOT(markAsReadMsg())); + action->setEnabled(!selectedItems.empty()); + + action = contextMnu.addAction(QIcon(""), tr("Mark as unread"), this, SLOT(markAsUnreadMsg())); + action->setEnabled(!selectedItems.empty()); + + action = contextMnu.addAction(QIcon(""), tr("Mark all as read"), this, SLOT(markAllAsReadMsg())); + + contextMnu.addSeparator(); + + action = contextMnu.addAction(QIcon(""), tr("Copy link"), this, SLOT(copyLinkMsg())); + action->setEnabled(!selectedItems.empty()); + + action = contextMnu.addAction(QIcon(""), tr("Remove"), this, SLOT(removeMsg())); + action->setEnabled(!selectedItems.empty()); + + contextMnu.exec(QCursor::pos()); +} + +void FeedReaderDialog::updateFeeds(const std::string &parentId, QTreeWidgetItem *parentItem) +{ + if (!parentItem) { + return; + } + + /* get feed infos */ + std::list feedInfos; + mFeedReader->getFeedList(parentId, feedInfos); + + int index = 0; + QTreeWidgetItem *item; + std::list::iterator feedIt; + + /* update existing and delete not existing feeds */ + while (index < parentItem->childCount()) { + item = parentItem->child(index); + std::string feedId = item->data(COLUMN_FEED_DATA, ROLE_FEED_ID).toString().toStdString(); + + /* search existing feed */ + int found = -1; + for (feedIt = feedInfos.begin (); feedIt != feedInfos.end (); ++feedIt) { + if (feedIt->feedId == feedId) { + /* found it, update it */ + updateFeedItem(item, *feedIt); + + if (feedIt->flag.folder) { + /* process child feeds */ + updateFeeds(feedIt->feedId, item); + } + + feedInfos.erase(feedIt); + found = index; + break; + } + } + if (found >= 0) { + ++index; + } else { + delete(parentItem->takeChild(index)); + } + } + + /* add new feeds */ + for (feedIt = feedInfos.begin (); feedIt != feedInfos.end (); ++feedIt) { + item = new RSTreeWidgetItem(mFeedCompareRole); + parentItem->addChild(item); + updateFeedItem(item, *feedIt); + + if (feedIt->flag.folder) { + /* process child feeds */ + updateFeeds(feedIt->feedId, item); + } + } + calculateFeedItems(); +} + +void FeedReaderDialog::calculateFeedItem(QTreeWidgetItem *item, uint32_t &unreadCount, bool &loading) +{ + uint32_t unreadCountItem = 0; + bool loadingItem = false; + + if (item->data(COLUMN_FEED_DATA, ROLE_FEED_FOLDER).toBool()) { + int childCount = item->childCount(); + for (int index = 0; index < childCount; ++index) { + calculateFeedItem(item->child(index), unreadCountItem, loadingItem); + } + } else { + unreadCountItem = item->data(COLUMN_FEED_DATA, ROLE_FEED_UNREAD).toUInt(); + loadingItem = item->data(COLUMN_FEED_DATA, ROLE_FEED_LOADING).toBool(); + } + + unreadCount += unreadCountItem; + loading = loading || loadingItem; + + QString name = item->data(COLUMN_FEED_DATA, ROLE_FEED_NAME).toString(); + QString workState = item->data(COLUMN_FEED_DATA, ROLE_FEED_WORKSTATE).toString(); + + if (unreadCountItem) { + name += QString(" (%1)").arg(unreadCountItem); + } + if (!workState.isEmpty()) { + name += QString(" (%1)").arg(workState); + } + + item->setText(COLUMN_FEED_NAME, name); + + bool deactivated = item->data(COLUMN_FEED_DATA, ROLE_FEED_DEACTIVATED).toBool(); + + QColor colorActivated; + QColor colorDeactivated = QColor(Qt::gray); + for (int i = 0; i < COLUMN_FEED_COUNT; i++) { + QFont font = item->font(i); + font.setBold(unreadCountItem != 0); + item->setFont(i, font); + + item->setTextColor(COLUMN_FEED_NAME, deactivated ? colorDeactivated : colorActivated); + } + + QIcon icon = item->data(COLUMN_FEED_DATA, ROLE_FEED_ICON).value(); + + if (deactivated) { + /* create disabled icon */ + icon = icon.pixmap(QSize(16, 16), QIcon::Disabled); + } + + QImage overlayIcon; + if (loadingItem) { + /* overlaying icon */ + overlayIcon = QImage(":/images/FeedProcessOverlay.png"); + } else if (item->data(COLUMN_FEED_DATA, ROLE_FEED_ERROR).toBool()) { + overlayIcon = QImage(":/images/FeedErrorOverlay.png"); + } + if (!overlayIcon.isNull()) { + if (icon.isNull()) { + icon = QPixmap::fromImage(overlayIcon); + } else { + QPixmap pixmap = icon.pixmap(QSize(16, 16)); + QPainter painter(&pixmap); + painter.drawImage(0, 0, overlayIcon.scaled(pixmap.size(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); + painter.end(); + icon = pixmap; + } + } + + item->setIcon(COLUMN_FEED_NAME, icon); +} + +void FeedReaderDialog::calculateFeedItems() +{ + uint32_t unreadCount = 0; + bool loading = false; + + calculateFeedItem(mRootItem, unreadCount, loading); + ui->feedTreeWidget->sortItems(COLUMN_FEED_NAME, Qt::AscendingOrder); +} + +void FeedReaderDialog::updateFeedItem(QTreeWidgetItem *item, FeedInfo &info) +{ + QString workState; + + QIcon icon; + if (info.flag.folder) { + /* use folder icon */ + icon = QIcon(":/images/Folder.png"); + } else { + long todo; // show icon from feed +// if (info.icon.empty()) { + /* use standard icon */ + icon = QIcon(":/images/Feed.png"); +// } else { +// /* use icon from feed */ +// icon = QIcon(QPixmap::fromImage(QImage((uchar*) QByteArray::fromBase64(info.icon.c_str()).constData(), 16, 16, QImage::Format_RGB32))); +// } + } + + item->setData(COLUMN_FEED_DATA, ROLE_FEED_ICON, icon); + + switch (info.workstate) { + case FeedInfo::WAITING: + break; + case FeedInfo::WAITING_TO_DOWNLOAD: + case FeedInfo::DOWNLOADING: + workState = tr("loading"); + break; + case FeedInfo::WAITING_TO_PROCESS: + case FeedInfo::PROCESSING: + workState = tr("processing"); + break; + } + + QString name = QString::fromUtf8(info.name.c_str()); + item->setData(COLUMN_FEED_DATA, ROLE_FEED_NAME, name.isEmpty() ? tr("No name") : name); + item->setData(COLUMN_FEED_DATA, ROLE_FEED_WORKSTATE, workState); + + uint32_t unreadCount; + mFeedReader->getMessageCount(info.feedId, NULL, NULL, &unreadCount); + + item->setData(COLUMN_FEED_NAME, ROLE_FEED_SORT, QString("%1_%2").arg(QString(info.flag.folder ? "0" : "1"), name)); + item->setData(COLUMN_FEED_DATA, ROLE_FEED_UNREAD, unreadCount); + item->setData(COLUMN_FEED_DATA, ROLE_FEED_LOADING, info.workstate != FeedInfo::WAITING); + item->setData(COLUMN_FEED_DATA, ROLE_FEED_ID, QString::fromStdString(info.feedId)); + item->setData(COLUMN_FEED_DATA, ROLE_FEED_FOLDER, info.flag.folder); + item->setData(COLUMN_FEED_DATA, ROLE_FEED_DEACTIVATED, info.flag.deactivated); + item->setData(COLUMN_FEED_DATA, ROLE_FEED_ERROR, info.error); + item->setToolTip(COLUMN_FEED_NAME, info.error ? QString::fromUtf8(info.errorString.c_str()) : ""); +} + +void FeedReaderDialog::updateMsgs(const std::string &feedId) +{ + std::list msgInfos; + if (!mFeedReader->getFeedMsgList(feedId, msgInfos)) { + ui->msgTreeWidget->clear(); + return; + } + + int index = 0; + QTreeWidgetItem *item; + std::list::iterator msgIt; + + /* update existing and delete not existing msgs */ + while (index < ui->msgTreeWidget->topLevelItemCount()) { + item = ui->msgTreeWidget->topLevelItem(index); + std::string msgId = item->data(COLUMN_MSG_DATA, ROLE_MSG_ID).toString().toStdString(); + + /* search existing msg */ + int found = -1; + for (msgIt = msgInfos.begin (); msgIt != msgInfos.end (); ++msgIt) { + if (msgIt->msgId == msgId) { + /* found it, update it */ + updateMsgItem(item, *msgIt); + + msgInfos.erase(msgIt); + found = index; + break; + } + } + if (found >= 0) { + ++index; + } else { + delete(ui->msgTreeWidget->takeTopLevelItem(index)); + } + } + + /* add new msgs */ + for (msgIt = msgInfos.begin (); msgIt != msgInfos.end (); ++msgIt) { + item = new RSTreeWidgetItem(mMsgCompareRole); + updateMsgItem(item, *msgIt); + ui->msgTreeWidget->addTopLevelItem(item); + } + + filterItems(ui->filterLineEdit->text()); +} + +void FeedReaderDialog::calculateMsgIconsAndFonts(QTreeWidgetItem *item) +{ + if (!item) { + return; + } + + bool isnew = item->data(COLUMN_MSG_DATA, ROLE_MSG_NEW).toBool(); + bool read = item->data(COLUMN_MSG_DATA, ROLE_MSG_READ).toBool(); + + if (read) { + item->setIcon(COLUMN_MSG_READ, QIcon(":/images/message-state-read.png")); + } else { + item->setIcon(COLUMN_MSG_READ, QIcon(":/images/message-state-unread.png")); + } + if (isnew) { + item->setIcon(COLUMN_MSG_TITLE, QIcon(":/images/message-state-new.png")); + } else { + item->setIcon(COLUMN_MSG_TITLE, QIcon()); + } + + for (int i = 0; i < COLUMN_FEED_COUNT; i++) { + QFont font = item->font(i); + font.setBold(isnew || !read); + item->setFont(i, font); + } + + item->setData(COLUMN_MSG_READ, ROLE_MSG_SORT, QString("%1_%2_%3").arg(QString(isnew ? "1" : "0"), QString(read ? "0" : "1"), item->data(COLUMN_MSG_TITLE, ROLE_MSG_SORT).toString())); +} + +void FeedReaderDialog::updateMsgItem(QTreeWidgetItem *item, FeedMsgInfo &info) +{ + QString title = QString::fromUtf8(info.title.c_str()); + QDateTime qdatetime; + qdatetime.setTime_t(info.pubDate); + + /* add string to all data */ + QString sort = QString("%1_%2_%2").arg(title, qdatetime.toString("yyyyMMdd_hhmmss"), QString::fromStdString(info.feedId)); + + item->setText(COLUMN_MSG_TITLE, title); + item->setData(COLUMN_MSG_TITLE, ROLE_MSG_SORT, sort); + + QString author = QString::fromUtf8(info.author.c_str()); + item->setText(COLUMN_MSG_AUTHOR, author); + item->setData(COLUMN_MSG_AUTHOR, ROLE_MSG_SORT, author + "_" + sort); + + /* if the message is on same date show only time */ + if (qdatetime.daysTo(QDateTime::currentDateTime()) == 0) { + item->setData(COLUMN_MSG_PUBDATE, Qt::DisplayRole, qdatetime.time()); + } else { + item->setData(COLUMN_MSG_PUBDATE, Qt::DisplayRole, qdatetime); + } + item->setData(COLUMN_MSG_PUBDATE, ROLE_MSG_SORT, QString("%1_%2_%3").arg(qdatetime.toString("yyyyMMdd_hhmmss"), title, QString::fromStdString(info.msgId))); + + item->setData(COLUMN_MSG_DATA, ROLE_MSG_ID, QString::fromStdString(info.msgId)); + item->setData(COLUMN_MSG_DATA, ROLE_MSG_LINK, QString::fromUtf8(info.link.c_str())); + item->setData(COLUMN_MSG_DATA, ROLE_MSG_READ, info.flag.read); + item->setData(COLUMN_MSG_DATA, ROLE_MSG_NEW, info.flag.isnew); + + calculateMsgIconsAndFonts(item); +} + +void FeedReaderDialog::feedChanged(const QString &feedId, int type) +{ + if (!isVisible()) { + /* complete update in showEvent */ + return; + } + + if (feedId.isEmpty()) { + return; + } + + FeedInfo feedInfo; + if (type != NOTIFY_TYPE_DEL) { + if (!mFeedReader->getFeedInfo(feedId.toStdString(), feedInfo)) { + return; + } + } + + if (type == NOTIFY_TYPE_MOD || type == NOTIFY_TYPE_DEL) { + QTreeWidgetItemIterator it(ui->feedTreeWidget); + QTreeWidgetItem *item; + while ((item = *it) != NULL) { + if (item->data(COLUMN_FEED_DATA, ROLE_FEED_ID).toString() == feedId) { + if (type == NOTIFY_TYPE_MOD) { + updateFeedItem(item, feedInfo); + } else { + delete(item); + } + break; + } + ++it; + } + } + + if (type == NOTIFY_TYPE_ADD) { + QString id = QString::fromStdString(feedInfo.parentId); + + QTreeWidgetItemIterator it(ui->feedTreeWidget); + QTreeWidgetItem *itemParent; + while ((itemParent = *it) != NULL) { + if (itemParent->data(COLUMN_FEED_DATA, ROLE_FEED_ID).toString() == id) { + QTreeWidgetItem *item = new RSTreeWidgetItem(mFeedCompareRole); + itemParent->addChild(item); + updateFeedItem(item, feedInfo); + break; + } + ++it; + } + } + calculateFeedItems(); +} + +void FeedReaderDialog::msgChanged(const QString &feedId, const QString &msgId, int type) +{ + if (!isVisible()) { + /* complete update in showEvent */ + return; + } + + if (feedId.isEmpty() || msgId.isEmpty()) { + return; + } + + if (feedId.toStdString() != currentFeedId()) { + return; + } + + FeedMsgInfo msgInfo; + if (type != NOTIFY_TYPE_DEL) { + if (!mFeedReader->getMsgInfo(feedId.toStdString(), msgId.toStdString(), msgInfo)) { + return; + } + } + + if (type == NOTIFY_TYPE_MOD || type == NOTIFY_TYPE_DEL) { + QTreeWidgetItemIterator it(ui->msgTreeWidget); + QTreeWidgetItem *item; + while ((item = *it) != NULL) { + if (item->data(COLUMN_MSG_DATA, ROLE_MSG_ID).toString() == msgId) { + if (type == NOTIFY_TYPE_MOD) { + updateMsgItem(item, msgInfo); + filterItem(item); + } else { + delete(item); + } + break; + } + ++it; + } + } + + if (type == NOTIFY_TYPE_ADD) { + QTreeWidgetItem *item = new RSTreeWidgetItem(mMsgCompareRole); + updateMsgItem(item, msgInfo); + ui->msgTreeWidget->addTopLevelItem(item); + filterItem(item); + } +} + +void FeedReaderDialog::feedItemChanged(QTreeWidgetItem *item) +{ + if (!item) { + ui->msgTreeWidget->clear(); + return; + } + + std::string feedId = item->data(COLUMN_FEED_DATA, ROLE_FEED_ID).toString().toStdString(); + updateMsgs(feedId); +} + +void FeedReaderDialog::msgItemClicked(QTreeWidgetItem *item, int column) +{ + if (item == NULL) { + return; + } + + if (column == COLUMN_MSG_READ) { + QList rows; + rows.append(item); + bool read = item->data(COLUMN_MSG_DATA, ROLE_MSG_READ).toBool(); + setMsgAsReadUnread(rows, !read); + return; + } +} + +void FeedReaderDialog::msgItemChanged() +{ + long todo; // show link somewhere + + std::string feedId = currentFeedId(); + std::string msgId = currentMsgId(); + + if (feedId.empty() || msgId.empty()) { + ui->msgTitle->clear(); +// ui->msgLink->clear(); + ui->msgText->clear(); + return; + } + + QTreeWidgetItem *item = ui->msgTreeWidget->currentItem(); + if (!item) { + /* there is something wrong */ + ui->msgTitle->clear(); +// ui->msgLink->clear(); + ui->msgText->clear(); + return; + } + + /* get msg */ + FeedMsgInfo msgInfo; + if (!mFeedReader->getMsgInfo(feedId, msgId, msgInfo)) { + ui->msgTitle->clear(); +// ui->msgLink->clear(); + ui->msgText->clear(); + return; + } + + bool setToReadOnActive = Settings->valueFromGroup("FeedReaderDialog", "SetMsgToReadOnActivate", true).toBool(); + bool isnew = item->data(COLUMN_MSG_DATA, ROLE_MSG_NEW).toBool(); + bool read = item->data(COLUMN_MSG_DATA, ROLE_MSG_READ).toBool(); + + QList row; + row.append(item); + if (read) { + if (isnew) { + /* something wrong, but set as read again to clear the new flag */ + setMsgAsReadUnread(row, true); + } + } else { + /* set to read/unread */ + setMsgAsReadUnread(row, setToReadOnActive); + } + + QString msgTxt = RsHtml().formatText(ui->msgText->document(), QString::fromUtf8(msgInfo.description.c_str()), RSHTML_FORMATTEXT_EMBED_LINKS); + + ui->msgText->setHtml(msgTxt); + ui->msgTitle->setText(QString::fromUtf8(msgInfo.title.c_str())); +// ui->msgLink->setHtml(RsHtml().formatText(NULL, QString::fromUtf8(msgInfo.link.c_str()), RSHTML_FORMATTEXT_EMBED_LINKS)); +} + +void FeedReaderDialog::setMsgAsReadUnread(QList &rows, bool read) +{ + QList::iterator rowIt; + + std::string feedId = currentFeedId(); + if (feedId.empty()) { + return; + } + + for (rowIt = rows.begin(); rowIt != rows.end(); rowIt++) { + QTreeWidgetItem *item = *rowIt; + bool rowRead = item->data(COLUMN_MSG_DATA, ROLE_MSG_READ).toBool(); + bool rowNew = item->data(COLUMN_MSG_DATA, ROLE_MSG_NEW).toBool(); + + if (rowNew || read != rowRead) { + std::string msgId = item->data(COLUMN_MSG_DATA, ROLE_MSG_ID).toString().toStdString(); + mFeedReader->setMessageRead(feedId, msgId, read); + } + } +} + +void FeedReaderDialog::filterColumnChanged() +{ + if (mProcessSettings) { + return; + } + + filterItems(ui->filterLineEdit->text()); + + // save index + int filterColumn = filterColumnFromComboBox(ui->filterColumnComboBox->currentIndex()); + Settings->setValueToGroup("FeedReaderDialog", "filterColumn", filterColumn); +} + +void FeedReaderDialog::filterItems(const QString& text) +{ + int filterColumn = filterColumnFromComboBox(ui->filterColumnComboBox->currentIndex()); + + int count = ui->msgTreeWidget->topLevelItemCount(); + for (int index = 0; index < count; ++index) { + filterItem(ui->msgTreeWidget->topLevelItem(index), text, filterColumn); + } +} + +void FeedReaderDialog::filterItem(QTreeWidgetItem *item, const QString &text, int filterColumn) +{ + if (text.isEmpty() == false) { + if (item->text(filterColumn).contains(text, Qt::CaseInsensitive)) { + item->setHidden(false); + } else { + item->setHidden(true); + } + } else { + item->setHidden(false); + } +} + +void FeedReaderDialog::filterItem(QTreeWidgetItem *item) +{ + filterItem(item, ui->filterLineEdit->text(), filterColumnFromComboBox(ui->filterColumnComboBox->currentIndex())); +} + +void FeedReaderDialog::toggleMsgText() +{ + // save state of button + Settings->setValueToGroup("FeedReaderDialog", "expandButton", ui->expandButton->isChecked()); + + toggleMsgText_internal(); +} + +void FeedReaderDialog::toggleMsgText_internal() +{ + if (ui->expandButton->isChecked()) { + ui->msgText->setVisible(true); + ui->expandButton->setIcon(QIcon(QString(":/images/edit_remove24.png"))); + ui->expandButton->setToolTip(tr("Hide")); + } else { + ui->msgText->setVisible(false); + ui->expandButton->setIcon(QIcon(QString(":/images/edit_add24.png"))); + ui->expandButton->setToolTip(tr("Expand")); + } +} + +void FeedReaderDialog::newFolder() +{ + QInputDialog dialog; + dialog.setWindowTitle(tr("Add new folder")); + dialog.setLabelText(tr("Please enter a name for the folder")); + dialog.setWindowIcon(QIcon(":/images/FeedReader.png")); + + if (dialog.exec() == QDialog::Accepted && !dialog.textValue().isEmpty()) { + std::string feedId; + RsFeedAddResult result = mFeedReader->addFolder(currentFeedId(), dialog.textValue().toUtf8().constData(), feedId); + AddFeedDialog::showError(this, result, tr("Create folder"), tr("Cannot create folder.")); + } +} + +void FeedReaderDialog::newFeed() +{ + AddFeedDialog dialog(mFeedReader, this); + dialog.setParent(currentFeedId()); + dialog.exec(); +} + +void FeedReaderDialog::removeFeed() +{ + std::string feedId = currentFeedId(); + if (feedId.empty()) { + return; + } + + QTreeWidgetItem *item = ui->feedTreeWidget->currentItem(); + if (!item) { + return; + } + + bool folder = item->data(COLUMN_FEED_DATA, ROLE_FEED_FOLDER).toBool(); + QString name = item->data(COLUMN_FEED_DATA, ROLE_FEED_NAME).toString(); + + if (QMessageBox::question(this, folder ? tr("Remove folder") : tr("Remove feed"), folder ? tr("Do you want to remove the folder %1?").arg(name) : tr("Do you want to remove the feed %1?").arg(name), QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::Yes) { + mFeedReader->removeFeed(feedId); + } +} + +void FeedReaderDialog::editFeed() +{ + std::string feedId = currentFeedId(); + if (feedId.empty()) { + return; + } + + QTreeWidgetItem *item = ui->feedTreeWidget->currentItem(); + if (!item) { + return; + } + + bool folder = item->data(COLUMN_FEED_DATA, ROLE_FEED_FOLDER).toBool(); + if (folder) { + QInputDialog dialog; + dialog.setWindowTitle(tr("Edit folder")); + dialog.setLabelText(tr("Please enter a new name for the folder")); + dialog.setWindowIcon(QIcon(":/images/FeedReader.png")); + dialog.setTextValue(item->data(COLUMN_FEED_DATA, ROLE_FEED_NAME).toString()); + + if (dialog.exec() == QDialog::Accepted && !dialog.textValue().isEmpty()) { + RsFeedAddResult result = mFeedReader->setFolder(feedId, dialog.textValue().toUtf8().constData()); + AddFeedDialog::showError(this, result, tr("Create folder"), tr("Cannot create folder.")); + } + } else { + AddFeedDialog dialog(mFeedReader, this); + if (!dialog.fillFeed(feedId)) { + return; + } + dialog.exec(); + + } +} + +void FeedReaderDialog::activateFeed() +{ + std::string feedId = currentFeedId(); + if (feedId.empty()) { + return; + } + + FeedInfo feedInfo; + if (!mFeedReader->getFeedInfo(feedId, feedInfo)) { + return; + } + + if (feedInfo.flag.folder) { + return; + } + + feedInfo.flag.deactivated = !feedInfo.flag.deactivated; + + mFeedReader->setFeed(feedId, feedInfo); +} + +void FeedReaderDialog::processFeed() +{ + std::string feedId = currentFeedId(); + /* empty feed id process all feeds */ + + mFeedReader->processFeed(feedId); +} + +void FeedReaderDialog::markAsReadMsg() +{ + QList selectedItems = ui->msgTreeWidget->selectedItems(); + setMsgAsReadUnread(selectedItems, true); +} + +void FeedReaderDialog::markAsUnreadMsg() +{ + QList selectedItems = ui->msgTreeWidget->selectedItems(); + setMsgAsReadUnread(selectedItems, false); +} + +void FeedReaderDialog::markAllAsReadMsg() +{ + QList items; + + QTreeWidgetItemIterator it(ui->msgTreeWidget); + QTreeWidgetItem *item; + while ((item = *it) != NULL) { + if (!item->isHidden()) { + items.push_back(item); + } + ++it; + } + setMsgAsReadUnread(items, true); +} + +void FeedReaderDialog::copyLinkMsg() +{ + QString links; + + QTreeWidgetItemIterator it(ui->msgTreeWidget, QTreeWidgetItemIterator::Selected); + QTreeWidgetItem *item; + while ((item = *it) != NULL) { + QString link = item->data(COLUMN_MSG_DATA, ROLE_MSG_LINK).toString(); + if (!link.isEmpty()) { + links += link + "\n"; + } + ++it; + } + + if (links.isEmpty()) { + return; + } + + QApplication::clipboard()->setText(links); +} + +void FeedReaderDialog::removeMsg() +{ + std::string feedId = currentFeedId(); + if (feedId.empty()) { + return; + } + + QList selectedItems = ui->msgTreeWidget->selectedItems(); + QList::iterator it; + std::list msgIds; + + for (it = selectedItems.begin(); it != selectedItems.end(); ++it) { + msgIds.push_back((*it)->data(COLUMN_MSG_DATA, ROLE_MSG_ID).toString().toStdString()); + } + mFeedReader->removeMsgs(feedId, msgIds); +} diff --git a/plugins/FeedReader/gui/FeedReaderDialog.h b/plugins/FeedReader/gui/FeedReaderDialog.h new file mode 100644 index 000000000..1bb6c14b8 --- /dev/null +++ b/plugins/FeedReader/gui/FeedReaderDialog.h @@ -0,0 +1,105 @@ +/**************************************************************** + * RetroShare GUI is distributed under the following license: + * + * Copyright (C) 2012 by Thunder + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + ****************************************************************/ + +#ifndef _FEEDREADERDIALOG_H +#define _FEEDREADERDIALOG_H + +#include +#include "interface/rsFeedReader.h" + +namespace Ui { +class FeedReaderDialog; +} + +class QTreeWidgetItem; +class RsFeedReader; +class RSTreeWidgetItemCompareRole; +class FeedReaderNotify; + +class FeedReaderDialog : public MainPage +{ + Q_OBJECT + +public: + FeedReaderDialog(RsFeedReader *feedReader, QWidget *parent = 0); + ~FeedReaderDialog(); + +protected: + virtual void showEvent(QShowEvent *e); + bool eventFilter(QObject *obj, QEvent *ev); + +private slots: + void feedTreeCustomPopupMenu(QPoint point); + void msgTreeCustomPopupMenu(QPoint point); + void feedItemChanged(QTreeWidgetItem *item); + void msgItemChanged(); + void msgItemClicked(QTreeWidgetItem *item, int column); + void filterColumnChanged(); + void filterItems(const QString &text); + void toggleMsgText(); + void newFolder(); + void newFeed(); + void removeFeed(); + void editFeed(); + void activateFeed(); + void processFeed(); + void markAsReadMsg(); + void markAsUnreadMsg(); + void markAllAsReadMsg(); + void copyLinkMsg(); + void removeMsg(); + + /* FeedReaderNotify */ + void feedChanged(const QString &feedId, int type); + void msgChanged(const QString &feedId, const QString &msgId, int type); + +private: + std::string currentFeedId(); + std::string currentMsgId(); + void processSettings(bool load); + void updateFeeds(const std::string &parentId, QTreeWidgetItem *parentItem); + void updateFeedItem(QTreeWidgetItem *item, FeedInfo &info); + void updateMsgs(const std::string &feedId); + void calculateMsgIconsAndFonts(QTreeWidgetItem *item); + void updateMsgItem(QTreeWidgetItem *item, FeedMsgInfo &info); + void setMsgAsReadUnread(QList &rows, bool read); + void filterItem(QTreeWidgetItem *item, const QString &text, int filterColumn); + void filterItem(QTreeWidgetItem *item); + void toggleMsgText_internal(); + + void calculateFeedItems(); + void calculateFeedItem(QTreeWidgetItem *item, uint32_t &unreadCount, bool &loading); + + bool mProcessSettings; + QTreeWidgetItem *mRootItem; + RSTreeWidgetItemCompareRole *mFeedCompareRole; + RSTreeWidgetItemCompareRole *mMsgCompareRole; + + // gui interface + RsFeedReader *mFeedReader; + FeedReaderNotify *mNotify; + + /** Qt Designer generated object */ + Ui::FeedReaderDialog *ui; +}; + +#endif + diff --git a/plugins/FeedReader/gui/FeedReaderDialog.ui b/plugins/FeedReader/gui/FeedReaderDialog.ui new file mode 100644 index 000000000..95c076e29 --- /dev/null +++ b/plugins/FeedReader/gui/FeedReaderDialog.ui @@ -0,0 +1,373 @@ + + + FeedReaderDialog + + + + 0 + 0 + 738 + 583 + + + + + + + + + + Qt::Horizontal + + + + + 300 + 300 + + + + QFrame#frame{border: none;} + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 0 + + + 0 + + + + + QFrame#feedsHeaderFrame{ +background-color: qlineargradient(x1:0, y1:0, x2:0, y2:1, + stop:0 #FEFEFE, stop:1 #E8E8E8); + +border: 1px solid #CCCCCC;} + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 2 + + + + + + + + :/images/Feed.png + + + + + + + <!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:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Arial'; font-size:10pt; font-weight:600;">Feeds</span></p></body></html> + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + + 9 + + + + Qt::CustomContextMenu + + + QAbstractItemView::NoEditTriggers + + + false + + + true + + + + + + + + + + + + + Qt::Vertical + + + + + + + + 0 + 32 + + + + QFrame#frame_2{ +background-color: qlineargradient(x1:0, y1:0, x2:0, y2:1, + stop:0 #FEFEFE, stop:1 #E8E8E8); + +border: 1px solid #CCCCCC;} + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 2 + + + + + 2 + + + + + + + + :/images/find-16.png + + + + + + + Search forums + + + + + + + + + + 0 + 0 + + + + + MS Shell Dlg 2 + + + + 0 + + + + Title + + + + + Date + + + + + Author + + + + + + + + + + + + 9 + + + + Qt::CustomContextMenu + + + QAbstractItemView::NoEditTriggers + + + QAbstractItemView::ExtendedSelection + + + true + + + true + + + + Title + + + + + + + + + :/images/message-state-header.png:/images/message-state-header.png + + + + + Date + + + + + Author + + + + + + + + + + + 0 + 0 + + + + + 10 + 75 + true + + + + Message: + + + + + + + QLabel#msgTitle{ +border: 2px solid #CCCCCC; +border-radius: 6px; +background: white;} + + + true + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse + + + + + + + + 24 + 24 + + + + Qt::NoFocus + + + + + + + :/images/edit_remove24.png:/images/edit_remove24.png + + + true + + + true + + + + + + + + + + + 0 + 10 + + + + + 9 + + + + + + + + + + + LineEditClear + QLineEdit +
gui/common/LineEditClear.h
+
+ + LinkTextBrowser + QTextBrowser +
gui/common/LinkTextBrowser.h
+
+
+ + + + + +
diff --git a/plugins/FeedReader/gui/FeedReaderNotify.cpp b/plugins/FeedReader/gui/FeedReaderNotify.cpp new file mode 100644 index 000000000..59853c114 --- /dev/null +++ b/plugins/FeedReader/gui/FeedReaderNotify.cpp @@ -0,0 +1,36 @@ +/**************************************************************** + * RetroShare GUI is distributed under the following license: + * + * Copyright (C) 2012 by Thunder + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + ****************************************************************/ + +#include "FeedReaderNotify.h" + +FeedReaderNotify::FeedReaderNotify() : QObject() +{ +} + +void FeedReaderNotify::feedChanged(const std::string &feedId, int type) +{ + emit notifyFeedChanged(QString::fromStdString(feedId), type); +} + +void FeedReaderNotify::msgChanged(const std::string &feedId, const std::string &msgId, int type) +{ + emit notifyMsgChanged(QString::fromStdString(feedId), QString::fromStdString(msgId), type); +} diff --git a/plugins/FeedReader/gui/FeedReaderNotify.h b/plugins/FeedReader/gui/FeedReaderNotify.h new file mode 100644 index 000000000..572b540ac --- /dev/null +++ b/plugins/FeedReader/gui/FeedReaderNotify.h @@ -0,0 +1,45 @@ +/**************************************************************** + * RetroShare GUI is distributed under the following license: + * + * Copyright (C) 2012 by Thunder + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + ****************************************************************/ + +#ifndef _FEEDREADERNOTIFY_H +#define _FEEDREADERNOTIFY_H + +#include +#include "interface/rsFeedReader.h" + +class FeedReaderNotify : public QObject, public RsFeedReaderNotify +{ + Q_OBJECT + +public: + FeedReaderNotify(); + + /* RsFeedReaderNotify */ + virtual void feedChanged(const std::string &feedId, int type); + virtual void msgChanged(const std::string &feedId, const std::string &msgId, int type); + +signals: + void notifyFeedChanged(const QString &feedId, int type); + void notifyMsgChanged(const QString &feedId, const QString &msgId, int type); +}; + +#endif + diff --git a/plugins/FeedReader/gui/FeedReader_images.qrc b/plugins/FeedReader/gui/FeedReader_images.qrc new file mode 100644 index 000000000..6acbedfab --- /dev/null +++ b/plugins/FeedReader/gui/FeedReader_images.qrc @@ -0,0 +1,13 @@ + + + images/FeedReader.png + images/Root.png + images/Folder.png + images/Feed.png + images/FeedProcessOverlay.png + images/FeedErrorOverlay.png + images/FolderAdd.png + images/FeedAdd.png + images/Update.png + + diff --git a/plugins/FeedReader/gui/images/Feed.png b/plugins/FeedReader/gui/images/Feed.png new file mode 100644 index 0000000000000000000000000000000000000000..176ec7d0790a42058230a38c2ac247a4571e7e95 GIT binary patch literal 641 zcmV-{0)G98P)_Sg-|jUqJ|+D|<Hprmhci>ug~NUK-1~m_oO}K!hs?23mdn3K zLiXKW$){QE0t%Xg1x+e;h^fHMLV?ow>M%3JQ(G)&!xiL1IblnMAKK&JqGHlR^} z(*h(MP;v_LOEot@(K>-X! z;aeaDm=fTD0IzJoJ^?%eI1a;(hp9s`g+0^);ULs2t@sT1H>#WwkjvLCz-t@e7NA9d zu$K8%P`q6?UxsBx(CdbREl_&}-nPQXCHNvhj{pNUV6Olt1eh&`x|_;(6viJz)38c5 z0X?;FOlifwLRXpg1$ZRDpbcmc;J5%K=ajm523`tCd6+$R5Oy+IJ) z69I-SfM<_{)GQ%|M0Z`2(#K)o0+dUwn+5f2E>%^^55n`K5dDGonS?ZK38}hFQf`%$ z4VA_Ilx~J;FD|R;kd&t_DVxnyp=PR5Ga2oZuK%p>L{iQ4Ud=R_)=X(b&(5O%w6lSm bu@}DquQ%lpkeEAk00000NkvXXu0mjf2i+rl literal 0 HcmV?d00001 diff --git a/plugins/FeedReader/gui/images/FeedAdd.png b/plugins/FeedReader/gui/images/FeedAdd.png new file mode 100644 index 0000000000000000000000000000000000000000..19b38d6d548f30c889eb6039d447a71ba85fb612 GIT binary patch literal 1444 zcmV;V1zY-wP)vzwsvk*o&$(NpU?$`7CUgtX; zXtXsu4Upw(j^idMYgv{t-XI-3#<)Y`dvDg>KR_4$UqhtO2AnrF#yLY0Ig!T8a8Q^j>yqc zPr^UkOF4_tz83hd^op~#hPN`n#YVeWHCZE=jB)6mYJ#?QQ^6lHkkerxa}T*hFLIF% z}nm~}o@za-$GC;QfIC7)@ zeCV_V5E;?7{qS|0#QDwlR6xrHD*;9sK=w93u4rJY8s0!gnxMCoL1%KYu>4m=axROE z?;x^)3v3S3`zpCJbfs;iy>( zbIFu~=*5ukJ%;SSuTTn`)99Hu;nuI<>nRc!?un{^GwUn_NQxHo{Uw-_SzRC_@kn!4 z#Pv0JVnHKZlb4dUYJzi=pX9ZDkb-~bE6#xMm2YvY+bF*9Sxg1|@~&B!RFx7fCJDT) z5*lkIOQM{PP@IREuw+sQLRlw_)7HX0_kC!MHU^QBBZ#-GBmF1?g#I~-o81O+(U)-* zaPl3KP*a&^0P|b{ra4XO;juol>>83cen6)0H?lTD9`T5CB`BKR2z~u)ICA$029g8F z47Q*&?8kD+19Y52*KLEiWPcv;<68u%NQqWc4wm@>%=4P=4^`q0QeB4;yRe<+cj+c@ zh@uSsm3tuuH^OTxrad0rBX`i$=RnQ%cHAJqvab^=;8;VEIHe*jC;)9;1Nhno%9R0z zVmFL-4^@+nfs(gQAbfl^a;XSgpK?sb=hdC4u{e;09v!h5n*A2 zpDLx61|5LirLMyxfG4VlnbX1_PxK7v@;UhSW$lNxHLmUMiB7E)4}&)sk5$d1#`#zr zYoa~WpVW0cKxf&i;2lY1uXl}n^yW|5PZ(h9bDH{U2XMcWKTREFQ^11qP~yyqoASmJKKWwdDN0i>CUKIOFy5mK7?pw{agb&mw-oC% zF1Cl<2$J&85D>&gTK1Fp3jxYpxw6y}m4_GOxnLeM!hmYHF_*OMs%v!gApSrKowTC& y7l8*4;U4&X*x>TsDIVMN#uN^EeGtn3=Kck*o+42OKKWn(0000^v?_mz7ify>{C~Ug&C~T)9l{zU7XM>C01DbBJ1$wzsBZes9s^>bP0l+XkKfg(su literal 0 HcmV?d00001 diff --git a/plugins/FeedReader/gui/images/FeedProcessOverlay.png b/plugins/FeedReader/gui/images/FeedProcessOverlay.png new file mode 100644 index 0000000000000000000000000000000000000000..95526940c20fa6cbd6b2f4e7c96ecc7e7c921530 GIT binary patch literal 848 zcmV-W1F!svP)oW{ zDW$#GOOdpP2El;Z;=xiiR#b$DCPazRnAvPL*$;PSc6RgjF)4+Rqf-RgcP=x}!2AC_ zAMgM5@r;+R08juZ02BZU00n>o!1D=ETcV0ziBMd#kgV7*Cc&}i>3Itf&{Wmt zMn2flO8u%DMzyR6oNv(^&C5xs^8V zdJ|P9;@{Rmdla&k&l7+bKVPS|>}_E1@c@CKK~hUHYK?Kf+ra+*0mM@9!a~?w2_54Q zc42)Gu8nyEP`bTAHMQ3;9(afuj4~CTBBxKo%?c4LhBKr8YAjH!t%XAw_|<@;V-lP8 zJ^c)IJj#+kx-Z^calFxZm_@YYwR-v-T>$?g;nFnTeiZYhdKR6dgixComwK^AV$>x!~>K! zMl^T%6fF_hC$B9DsB*xSau16q^oHOkIVA&+=IH)E*KshYs1FE$I9^`CXcq1!#hk2p adj9|;6)pBTIVCLs0000StO&>uS)ve< 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

k3jO2g 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 literal 0 HcmV?d00001 diff --git a/plugins/FeedReader/gui/images/FolderAdd.png b/plugins/FeedReader/gui/images/FolderAdd.png new file mode 100644 index 0000000000000000000000000000000000000000..0075a86c922fc723e96d44d44b4da839451b1f1f GIT binary patch literal 446 zcmV;v0YUzWP)Tav*l?S&1Hc>$jxjK^k4{I^Vf z4rIH05K?GNDyfTD4>oofD#f^ ofE%irhe!jyFu;;BDNe@*0DXs)2gXBa2mk;807*qoM6N<$f=+L<6#xJL literal 0 HcmV?d00001 diff --git a/plugins/FeedReader/gui/images/Root.png b/plugins/FeedReader/gui/images/Root.png new file mode 100644 index 0000000000000000000000000000000000000000..35097f23b328c75478dfd2790e6cd8004019b95f GIT binary patch literal 968 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 Ef?03EkN^Mx literal 0 HcmV?d00001 diff --git a/plugins/FeedReader/interface/rsFeedReader.h b/plugins/FeedReader/interface/rsFeedReader.h new file mode 100644 index 000000000..82d650db9 --- /dev/null +++ b/plugins/FeedReader/interface/rsFeedReader.h @@ -0,0 +1,181 @@ +/**************************************************************** + * RetroShare GUI is distributed under the following license: + * + * Copyright (C) 2012 by Thunder + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + ****************************************************************/ + + #ifndef RETROSHARE_FEEDREADER_GUI_INTERFACE_H +#define RETROSHARE_FEEDREADER_GUI_INTERFACE_H + +#include +#include +#include + +class RsFeedReader; +extern RsFeedReader *rsFeedReader; + +enum RsFeedAddResult +{ + RS_FEED_ADD_RESULT_SUCCESS, + RS_FEED_ADD_RESULT_FEED_NOT_FOUND, + RS_FEED_ADD_RESULT_PARENT_NOT_FOUND, + RS_FEED_ADD_RESULT_PARENT_IS_NO_FOLDER, + RS_FEED_ADD_RESULT_FEED_IS_FOLDER, + RS_FEED_ADD_RESULT_FEED_IS_NO_FOLDER +}; + +class FeedInfo +{ +public: + enum WorkState + { + WAITING, + WAITING_TO_DOWNLOAD, + DOWNLOADING, + WAITING_TO_PROCESS, + PROCESSING + }; + +public: + FeedInfo() + { + proxyPort = 0; + updateInterval = 0; + lastUpdate = 0; + storageTime = 0; + error = false; + flag.folder = false; + flag.infoFromFeed = false; + flag.standardStorageTime = false; + flag.standardUpdateInterval = false; + flag.standardProxy = false; + flag.authentication = false; + flag.deactivated = false; + flag.forum = false; + flag.updateForumInfo = false; + } + + std::string feedId; + std::string parentId; + std::string url; + std::string name; + std::string description; + std::string icon; + std::string user; + std::string password; + std::string proxyAddress; + uint16_t proxyPort; + uint32_t updateInterval; + time_t lastUpdate; + uint32_t storageTime; + std::string forumId; + WorkState workstate; + bool error; + std::string errorString; + + struct { + bool folder : 1; + bool infoFromFeed : 1; + bool standardStorageTime : 1; + bool standardUpdateInterval : 1; + bool standardProxy : 1; + bool authentication : 1; + bool deactivated : 1; + bool forum : 1; + bool updateForumInfo : 1; + } flag; +}; + +class FeedMsgInfo +{ +public: + FeedMsgInfo() + { + pubDate = 0; + flag.isnew = false; + flag.read = false; + } + + std::string msgId; + std::string feedId; + std::string title; + std::string link; + std::string author; + std::string description; + time_t pubDate; + + struct { + bool isnew : 1; + bool read : 1; + } flag; +}; + +class RsFeedReaderNotify +{ +public: + RsFeedReaderNotify() {} + + virtual void feedChanged(const std::string &/*feedId*/, int /*type*/) {} + virtual void msgChanged(const std::string &/*feedId*/, const std::string &/*msgId*/, int /*type*/) {} +}; + +class RsFeedReader +{ +public: + RsFeedReader() {} + virtual ~RsFeedReader() {} + + virtual void stop() = 0; + virtual void setNotify(RsFeedReaderNotify *notify) = 0; + + virtual uint32_t getStandardStorageTime() = 0; + virtual void setStandardStorageTime(uint32_t storageTime) = 0; + virtual uint32_t getStandardUpdateInterval() = 0; + virtual void setStandardUpdateInterval(uint32_t updateInterval) = 0; + virtual bool getStandardProxy(std::string &proxyAddress, uint16_t &proxyPort) = 0; + virtual void setStandardProxy(bool useProxy, const std::string &proxyAddress, uint16_t proxyPort) = 0; + + virtual RsFeedAddResult addFolder(const std::string parentId, const std::string &name, std::string &feedId) = 0; + virtual RsFeedAddResult setFolder(const std::string &feedId, const std::string &name) = 0; + virtual RsFeedAddResult addFeed(const FeedInfo &feedInfo, std::string &feedId) = 0; + virtual RsFeedAddResult setFeed(const std::string &feedId, const FeedInfo &feedInfo) = 0; + virtual bool removeFeed(const std::string &feedId) = 0; + virtual void getFeedList(const std::string &parentId, std::list &feedInfos) = 0; + virtual bool getFeedInfo(const std::string &feedId, FeedInfo &feedInfo) = 0; + virtual bool getMsgInfo(const std::string &feedId, const std::string &msgId, FeedMsgInfo &msgInfo) = 0; + virtual bool removeMsg(const std::string &feedId, const std::string &msgId) = 0; + virtual bool removeMsgs(const std::string &feedId, const std::list &msgIds) = 0; + virtual bool getMessageCount(const std::string &feedId, uint32_t *msgCount, uint32_t *newCount, uint32_t *unreadCount) = 0; + virtual bool getFeedMsgList(const std::string &feedId, std::list &msgInfos) = 0; + virtual bool processFeed(const std::string &feedId) = 0; + virtual bool setMessageRead(const std::string &feedId, const std::string &msgId, bool read) = 0; + + /* get Ids */ +// virtual uint32_t getRankingsCount() = 0; +// virtual float getMaxRank() = 0; +// virtual bool getRankings(uint32_t first, uint32_t count, std::list &rids) = 0; +// virtual bool getRankDetails(std::string rid, RsRankDetails &details) = 0; + + /* Add New Comment / Msg */ +// virtual std::string newRankMsg(std::wstring link, std::wstring title, std::wstring comment, int32_t score) = 0; +// virtual bool updateComment(std::string rid, std::wstring comment, int32_t score) = 0; + +// virtual std::string anonRankMsg(std::string rid, std::wstring link, std::wstring title) = 0; +}; + +#endif diff --git a/plugins/FeedReader/lang/FeedReader_de.ts b/plugins/FeedReader/lang/FeedReader_de.ts new file mode 100644 index 000000000..07d48332a --- /dev/null +++ b/plugins/FeedReader/lang/FeedReader_de.ts @@ -0,0 +1,339 @@ + + + + + AddLinksDialog + + + + Add Link + Link hinzufügen + + + + <!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:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'DejaVu Sans'; font-size:18pt; font-weight:600; color:#ffffff;">Add Link to Cloud</span></p></body></html> + <!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:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'DejaVu Sans'; font-size:18pt; font-weight:600; color:#ffffff;">Link zur Wolke hinzufügen</span></p></body></html> + + + + Cancel + Abbrechen + + + + Add a new Link + Neuen Link hinzufügen + + + + Title: + Titel: + + + + Url: + Url: + + + + Add Anonymous Link + Anonym hinzufügen + + + + +2 Great! + +2 Großartig! + + + + +1 Good + +1 Gut + + + + 0 Okay + 0 In Ordnung + + + + -1 Sux + -1 Nervt + + + + -2 Bad Link + -2 Schlechter Link + + + + New Link + Neuer Link + + + + Add Link Failure + Link hinzufügen fehlgeschlagen + + + + Missing Link and/or Title + Titel und/oder Url fehlt + + + + LinksCloudPlugin + + + This plugin provides a set of cached links, and a voting system to promote them. + Das Plugin stellt Links und ein Wahlsystem zur Verfügung, um sie zu verbreiten. + + + + LinksCloud + Verknüpfungswolke + + + + LinksDialog + + + Title / Comment + Titel / Kommentar + + + + Score + Punkte + + + + Peer / Link + Nachbar / Link + + + + Sort by + Sortiere nach + + + + Combo + Kombiniert + + + + Time + Zeit + + + + Ranking + Platzierung + + + + In last + Im letzten + + + + Month + Monat + + + + Week + Woche + + + + Day + Tag + + + + + From + Von + + + + All Peers + Alle Nachbarn + + + + Own Links + Eigene Links + + + + Show + Zeige + + + + Top 100 + Top 100 + + + + 101-200 + + + + + 201-300 + + + + + 301-400 + + + + + 401-500 + + + + + Bottom 100 + Letzten 100 + + + + Link: + Link: + + + + Add Anonymous Link + Anonym hinzufügen + + + + Add Link/Comment + Link/Kommentar hinzufügen + + + + Title: + Titel: + + + + Score: + Punkte: + + + + +2 Great! + +2 Großartig! + + + + +1 Good + +1 Gut + + + + 0 Okay + 0 In Ordnung + + + + -1 Sux + -1 Nervt + + + + -2 Bad Link + -2 Schlechter Link + + + + Url: + Url: + + + + <html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"><span style=" font-weight:600;">Links Cloud</span></p></body></html> + <html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"><span style=" font-weight:600;">Verknüpfungswolke</span></p></body></html> + + + + Add new link + Neuen Link hinzufügen + + + + Share Link Anonymously + Link anonym verteilen + + + + Vote on Link + Wähle für Link + + + + Download + Herunterladen + + + + Expand + Erweitern + + + + Hide + Verbergen + + + + File Request Confirmation + Bestätigung der Dateianforderung + + + + The file has been added to your download list. + Die Datei wurde zur Downloadliste hinzugefügt. + + + + File Request canceled + Dateianforderung abgebrochen + + + + The file has not been added to your download list, because you already have it. + Die folgende Datei wurde nicht zur Downloadliste hinzugefügt, da Du diese schon hast. + + + + File Request Error + Fehler bei der Dateianforderung + + + + The file link is malformed. + Link ist fehlerhaft. + + + diff --git a/plugins/FeedReader/lang/lang.qrc b/plugins/FeedReader/lang/lang.qrc new file mode 100644 index 000000000..67e313476 --- /dev/null +++ b/plugins/FeedReader/lang/lang.qrc @@ -0,0 +1,5 @@ + + + + + diff --git a/plugins/FeedReader/services/p3FeedReader.cc b/plugins/FeedReader/services/p3FeedReader.cc new file mode 100644 index 000000000..712835f4b --- /dev/null +++ b/plugins/FeedReader/services/p3FeedReader.cc @@ -0,0 +1,1710 @@ +/**************************************************************** + * RetroShare GUI is distributed under the following license: + * + * Copyright (C) 2012 by Thunder + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + ****************************************************************/ + +#include "RsFeedReaderItems.h" +#include "p3FeedReader.h" +#include "p3FeedReaderThread.h" +#include "serialiser/rsconfigitems.h" +#include "retroshare/rsiface.h" +#include "retroshare/rsforums.h" +#include "util/rsstring.h" + +RsFeedReader *rsFeedReader = NULL; + +#define FEEDREADER_CLEAN_INTERVAL 1 * 60 * 60 // check every hour + +/********* + * #define FEEDREADER_DEBUG + *********/ + +p3FeedReader::p3FeedReader(RsPluginHandler* pgHandler) + : RsPQIService(RS_PKT_TYPE_FEEDREADER_CONFIG, CONFIG_TYPE_FEEDREADER, 5, pgHandler), + mFeedReaderMtx("p3FeedReader"), mDownloadMutex("p3FeedReaderDownload"), mProcessMutex("p3FeedReaderProcess") +{ + mNextFeedId = 1; + mNextMsgId = 1; + mStandardUpdateInterval = 60 * 60; // 60 minutes + mStandardStorageTime = 30 * 60 * 60 * 24; // 30 days + mStandardUseProxy = false; + mStandardProxyPort = 0; + mLastClean = 0; + mNotify = NULL; + + /* start download thread */ + p3FeedReaderThread *frt = new p3FeedReaderThread(this, p3FeedReaderThread::DOWNLOAD); + mThreads.push_back(frt); + frt->start(); + + /* start process thread */ + frt = new p3FeedReaderThread(this, p3FeedReaderThread::PROCESS); + mThreads.push_back(frt); + frt->start(); +} + +/***************************************************************************/ +/****************************** RsFeedReader *******************************/ +/***************************************************************************/ + +static void feedToInfo(const RsFeedReaderFeed *feed, FeedInfo &info) +{ + info.feedId = feed->feedId; + info.parentId = feed->parentId; + info.url = feed->url; + info.name = feed->name; + info.description = feed->description; + info.icon = feed->icon; + info.user = feed->user; + info.password = feed->password; + info.proxyAddress = feed->proxyAddress; + info.proxyPort = feed->proxyPort; + info.updateInterval = feed->updateInterval; + info.lastUpdate = feed->lastUpdate; + info.forumId = feed->forumId; + info.storageTime = feed->storageTime; + info.error = (feed->errorState != RS_FEED_ERRORSTATE_OK); // currently only as bool + info.errorString = feed->errorString; + + info.flag.folder = (feed->flag & RS_FEED_FLAG_FOLDER); + info.flag.infoFromFeed = (feed->flag & RS_FEED_FLAG_INFO_FROM_FEED); + info.flag.standardStorageTime = (feed->flag & RS_FEED_FLAG_STANDARD_STORAGE_TIME); + info.flag.standardUpdateInterval = (feed->flag & RS_FEED_FLAG_STANDARD_UPDATE_INTERVAL); + info.flag.standardProxy = (feed->flag & RS_FEED_FLAG_STANDARD_PROXY); + info.flag.authentication = (feed->flag & RS_FEED_FLAG_AUTHENTICATION); + info.flag.deactivated = (feed->flag & RS_FEED_FLAG_DEACTIVATED); + info.flag.forum = (feed->flag & RS_FEED_FLAG_FORUM); + info.flag.updateForumInfo = (feed->flag & RS_FEED_FLAG_UPDATE_FORUM_INFO); + + switch (feed->workstate) { + case RsFeedReaderFeed::WAITING: + info.workstate = FeedInfo::WAITING; + break; + case RsFeedReaderFeed::WAITING_TO_DOWNLOAD: + info.workstate = FeedInfo::WAITING_TO_DOWNLOAD; + break; + case RsFeedReaderFeed::DOWNLOADING: + info.workstate = FeedInfo::DOWNLOADING; + break; + case RsFeedReaderFeed::WAITING_TO_PROCESS: + info.workstate = FeedInfo::WAITING_TO_PROCESS; + break; + case RsFeedReaderFeed::PROCESSING: + info.workstate = FeedInfo::PROCESSING; + break; + } +} + +static void infoToFeed(const FeedInfo &info, RsFeedReaderFeed *feed, bool add) +{ +// feed->feedId = info.feedId; + feed->parentId = info.parentId; + feed->url = info.url; + feed->name = info.name; + feed->description = info.description; +// feed->icon = info.icon; + feed->user = info.user; + feed->password = info.password; + feed->proxyAddress = info.proxyAddress; + feed->proxyPort = info.proxyPort; + feed->updateInterval = info.updateInterval; +// feed->lastUpdate = info.lastUpdate; + feed->storageTime = info.storageTime; + if (add) { + /* set forum id only when adding a feed */ + feed->forumId = info.forumId; + } + + uint32_t oldFlag = feed->flag; + feed->flag = 0; + if (info.flag.infoFromFeed) { + feed->flag |= RS_FEED_FLAG_INFO_FROM_FEED; + } + if (info.flag.standardStorageTime) { + feed->flag |= RS_FEED_FLAG_STANDARD_STORAGE_TIME; + } + if (info.flag.standardUpdateInterval) { + feed->flag |= RS_FEED_FLAG_STANDARD_UPDATE_INTERVAL; + } + if (info.flag.standardProxy) { + feed->flag |= RS_FEED_FLAG_STANDARD_PROXY; + } + if (info.flag.authentication) { + feed->flag |= RS_FEED_FLAG_AUTHENTICATION; + } + if (info.flag.deactivated) { + feed->flag |= RS_FEED_FLAG_DEACTIVATED; + } + if (add) { + /* only set when adding a new feed */ + if (info.flag.folder) { + feed->flag |= RS_FEED_FLAG_FOLDER; + } + if (info.flag.forum) { + feed->flag |= RS_FEED_FLAG_FORUM; + } + } else { + /* use old bits */ + feed->flag |= (oldFlag & (RS_FEED_FLAG_FOLDER | RS_FEED_FLAG_FORUM)); + } + if (info.flag.updateForumInfo) { + feed->flag |= RS_FEED_FLAG_UPDATE_FORUM_INFO; + } +} + +static void feedMsgToInfo(const RsFeedReaderMsg *msg, FeedMsgInfo &info) +{ + info.msgId = msg->msgId; + info.feedId = msg->feedId; + info.title = msg->title; + info.link = msg->link; + info.author = msg->author; + info.description = msg->description; + info.pubDate = msg->pubDate; + + info.flag.isnew = (msg->flag & RS_FEEDMSG_FLAG_NEW); + info.flag.read = (msg->flag & RS_FEEDMSG_FLAG_READ); +} + +void p3FeedReader::setNotify(RsFeedReaderNotify *notify) +{ + mNotify = notify; +} + +uint32_t p3FeedReader::getStandardStorageTime() +{ + RsStackMutex stack(mFeedReaderMtx); /******* LOCK STACK MUTEX *********/ + return mStandardStorageTime; +} + +void p3FeedReader::setStandardStorageTime(uint32_t storageTime) +{ + RsStackMutex stack(mFeedReaderMtx); /******* LOCK STACK MUTEX *********/ + if (mStandardStorageTime != storageTime) { + mStandardStorageTime = storageTime; + IndicateConfigChanged(); + } +} + +uint32_t p3FeedReader::getStandardUpdateInterval() +{ + RsStackMutex stack(mFeedReaderMtx); /******* LOCK STACK MUTEX *********/ + return mStandardUpdateInterval; +} + +void p3FeedReader::setStandardUpdateInterval(uint32_t updateInterval) +{ + RsStackMutex stack(mFeedReaderMtx); /******* LOCK STACK MUTEX *********/ + if (mStandardUpdateInterval != updateInterval) { + mStandardUpdateInterval = updateInterval; + IndicateConfigChanged(); + } +} + +bool p3FeedReader::getStandardProxy(std::string &proxyAddress, uint16_t &proxyPort) +{ + RsStackMutex stack(mFeedReaderMtx); /******* LOCK STACK MUTEX *********/ + proxyAddress = mStandardProxyAddress; + proxyPort = mStandardProxyPort; + return mStandardUseProxy; +} + +void p3FeedReader::setStandardProxy(bool useProxy, const std::string &proxyAddress, uint16_t proxyPort) +{ + RsStackMutex stack(mFeedReaderMtx); /******* LOCK STACK MUTEX *********/ + if (useProxy != mStandardUseProxy || proxyAddress != mStandardProxyAddress || proxyPort != mStandardProxyPort) { + mStandardProxyAddress = proxyAddress; + mStandardProxyPort = proxyPort; + mStandardUseProxy = useProxy; + IndicateConfigChanged(); + } +} + +void p3FeedReader::stop() +{ + /* stop threads */ + std::list::iterator it; + for (it = mThreads.begin(); it != mThreads.end(); ++it) { + (*it)->join(); + } + mThreads.clear(); +} + +RsFeedAddResult p3FeedReader::addFolder(const std::string parentId, const std::string &name, std::string &feedId) +{ + feedId.clear(); + + { + RsStackMutex stack(mFeedReaderMtx); /******* LOCK STACK MUTEX *********/ + + if (!parentId.empty()) { + /* check parent id */ + std::map::iterator parentIt = mFeeds.find(parentId); + if (parentIt == mFeeds.end()) { +#ifdef FEEDREADER_DEBUG + std::cerr << "p3FeedReader::addFolder - parent id " << parentId << " not found" << std::endl; +#endif + return RS_FEED_ADD_RESULT_PARENT_NOT_FOUND; + } + + if ((parentIt->second->flag & RS_FEED_FLAG_FOLDER) == 0) { +#ifdef FEEDREADER_DEBUG + std::cerr << "p3FeedReader::addFolder - parent " << parentIt->second->name << " is no folder" << std::endl; +#endif + return RS_FEED_ADD_RESULT_PARENT_IS_NO_FOLDER; + } + } + + RsFeedReaderFeed *fi = new RsFeedReaderFeed; + rs_sprintf(fi->feedId, "%lu", mNextFeedId++); + fi->parentId = parentId; + fi->name = name; + fi->flag = RS_FEED_FLAG_FOLDER; + mFeeds[fi->feedId] = fi; + + feedId = fi->feedId; + } + + IndicateConfigChanged(); + + if (mNotify) { + mNotify->feedChanged(feedId, NOTIFY_TYPE_ADD); + } + + return RS_FEED_ADD_RESULT_SUCCESS; +} + +RsFeedAddResult p3FeedReader::setFolder(const std::string &feedId, const std::string &name) +{ + { + RsStackMutex stack(mFeedReaderMtx); /******* LOCK STACK MUTEX *********/ + +#ifdef FEEDREADER_DEBUG + std::cerr << "p3FeedReader::setFolder - feed id " << feedId << ", name " << name << std::endl; +#endif + + std::map::iterator feedIt = mFeeds.find(feedId); + if (feedIt == mFeeds.end()) { +#ifdef FEEDREADER_DEBUG + std::cerr << "p3FeedReader::setFolder - feed id " << feedId << " not found" << std::endl; +#endif + return RS_FEED_ADD_RESULT_FEED_NOT_FOUND; + } + + if ((feedIt->second->flag & RS_FEED_FLAG_FOLDER) == 0) { +#ifdef FEEDREADER_DEBUG + std::cerr << "p3FeedReader::setFolder - feed " << feedIt->second->name << " is no folder" << std::endl; +#endif + return RS_FEED_ADD_RESULT_FEED_IS_NO_FOLDER; + } + + RsFeedReaderFeed *fi = feedIt->second; + if (fi->name == name) { + return RS_FEED_ADD_RESULT_SUCCESS; + } + fi->name = name; + } + + IndicateConfigChanged(); + + if (mNotify) { + mNotify->feedChanged(feedId, NOTIFY_TYPE_MOD); + } + + return RS_FEED_ADD_RESULT_SUCCESS; +} + +RsFeedAddResult p3FeedReader::addFeed(const FeedInfo &feedInfo, std::string &feedId) +{ + feedId.clear(); + + { + RsStackMutex stack(mFeedReaderMtx); /******* LOCK STACK MUTEX *********/ + +#ifdef FEEDREADER_DEBUG + std::cerr << "p3FeedReader::addFeed - add feed " << feedInfo.name << ", url " << feedInfo.url << std::endl; +#endif + + if (!feedInfo.parentId.empty()) { + /* check parent id */ + std::map::iterator parentIt = mFeeds.find(feedInfo.parentId); + if (parentIt == mFeeds.end()) { +#ifdef FEEDREADER_DEBUG + std::cerr << "p3FeedReader::addFeed - parent id " << feedInfo.parentId << " not found" << std::endl; +#endif + return RS_FEED_ADD_RESULT_PARENT_NOT_FOUND; + } + + if ((parentIt->second->flag & RS_FEED_FLAG_FOLDER) == 0) { +#ifdef FEEDREADER_DEBUG + std::cerr << "p3FeedReader::addFeed - parent " << parentIt->second->name << " is no folder" << std::endl; +#endif + return RS_FEED_ADD_RESULT_PARENT_IS_NO_FOLDER; + } + } + + RsFeedReaderFeed *fi = new RsFeedReaderFeed; + infoToFeed(feedInfo, fi, true); + rs_sprintf(fi->feedId, "%lu", mNextFeedId++); + + mFeeds[fi->feedId] = fi; + + feedId = fi->feedId; + } + + IndicateConfigChanged(); + + if (mNotify) { + mNotify->feedChanged(feedId, NOTIFY_TYPE_ADD); + } + + return RS_FEED_ADD_RESULT_SUCCESS; +} + +RsFeedAddResult p3FeedReader::setFeed(const std::string &feedId, const FeedInfo &feedInfo) +{ + std::string forumId; + ForumInfo forumInfo; + + { + RsStackMutex stack(mFeedReaderMtx); /******* LOCK STACK MUTEX *********/ + +#ifdef FEEDREADER_DEBUG + std::cerr << "p3FeedReader::setFeed - set feed " << feedInfo.name << ", url " << feedInfo.url << std::endl; +#endif + + std::map::iterator feedIt = mFeeds.find(feedId); + if (feedIt == mFeeds.end()) { +#ifdef FEEDREADER_DEBUG + std::cerr << "p3FeedReader::setFeed - feed id " << feedId << " not found" << std::endl; +#endif + return RS_FEED_ADD_RESULT_FEED_NOT_FOUND; + } + + if (feedIt->second->flag & RS_FEED_FLAG_FOLDER) { +#ifdef FEEDREADER_DEBUG + std::cerr << "p3FeedReader::setFeed - feed " << feedIt->second->name << " is a folder" << std::endl; +#endif + return RS_FEED_ADD_RESULT_FEED_IS_FOLDER; + } + + if (!feedInfo.parentId.empty()) { + /* check parent id */ + std::map::iterator parentIt = mFeeds.find(feedInfo.parentId); + if (parentIt == mFeeds.end()) { +#ifdef FEEDREADER_DEBUG + std::cerr << "p3FeedReader::setFeed - parent id " << feedInfo.parentId << " not found" << std::endl; +#endif + return RS_FEED_ADD_RESULT_PARENT_NOT_FOUND; + } + + if ((parentIt->second->flag & RS_FEED_FLAG_FOLDER) == 0) { +#ifdef FEEDREADER_DEBUG + std::cerr << "p3FeedReader::setFeed - parent " << parentIt->second->name << " is no folder" << std::endl; +#endif + return RS_FEED_ADD_RESULT_PARENT_IS_NO_FOLDER; + } + } + + RsFeedReaderFeed *fi = feedIt->second; + std::string oldName = fi->name; + std::string oldDescription = fi->description; + + infoToFeed(feedInfo, fi, false); + + if ((fi->flag & RS_FEED_FLAG_FORUM) && (fi->flag & RS_FEED_FLAG_UPDATE_FORUM_INFO) && !fi->forumId.empty() && + (fi->name != oldName || fi->description != oldDescription)) { + /* name or description changed, update forum */ + forumId = fi->forumId; + librs::util::ConvertUtf8ToUtf16(fi->name, forumInfo.forumName); + librs::util::ConvertUtf8ToUtf16(fi->description, forumInfo.forumDesc); + } + } + + IndicateConfigChanged(); + + if (mNotify) { + mNotify->feedChanged(feedId, NOTIFY_TYPE_MOD); + } + + if (!forumId.empty()) { + /* name or description changed, update forum */ + if (!rsForums->setForumInfo(forumId, forumInfo)) { +#ifdef FEEDREADER_DEBUG + std::cerr << "p3FeedReader::setFeed - can't change forum " << forumId << std::endl; +#endif + } + } + + return RS_FEED_ADD_RESULT_SUCCESS; +} + +void p3FeedReader::deleteAllMsgs_locked(RsFeedReaderFeed *fi) +{ + if (!fi) { + return; + } + + std::map::iterator msgIt; + for (msgIt = fi->mMsgs.begin(); msgIt != fi->mMsgs.end(); ++msgIt) { + delete(msgIt->second); + } + + fi->mMsgs.clear(); +} + +bool p3FeedReader::removeFeed(const std::string &feedId) +{ + std::list removedFeedIds; + + { + RsStackMutex stack(mFeedReaderMtx); /******* LOCK STACK MUTEX *********/ + + std::map::iterator feedIt = mFeeds.find(feedId); + if (feedIt == mFeeds.end()) { +#ifdef FEEDREADER_DEBUG + std::cerr << "p3FeedReader::removeFeed - feed " << feedId << " not found" << std::endl; +#endif + return false; + } + + removedFeedIds.push_back(feedId); + + RsFeedReaderFeed *fi = feedIt->second; + mFeeds.erase(feedIt); + + if (fi->flag & RS_FEED_FLAG_FOLDER) { + std::list feedIds; + feedIds.push_back(fi->feedId); + while (!feedIds.empty()) { + std::string parentId = feedIds.front(); + feedIds.pop_front(); + + std::map::iterator feedIt1; + for (feedIt1 = mFeeds.begin(); feedIt1 != mFeeds.end(); ) { + RsFeedReaderFeed *fi1 = feedIt1->second; + + if (fi1->parentId == parentId) { + removedFeedIds.push_back(fi1->feedId); + + std::map::iterator tempIt = feedIt1; + ++feedIt1; + mFeeds.erase(tempIt); + + if (fi1->flag & RS_FEED_FLAG_FOLDER) { + feedIds.push_back(fi->feedId); + } + + deleteAllMsgs_locked(fi1); + delete(fi1); + + continue; + } + ++feedIt1; + } + } + } + + deleteAllMsgs_locked(fi); + delete(fi); + } + + IndicateConfigChanged(); + + if (mNotify) { + /* only notify remove of feed */ + std::list::iterator it; + for (it = removedFeedIds.begin(); it != removedFeedIds.end(); ++it) { + mNotify->feedChanged(*it, NOTIFY_TYPE_DEL); + } + } + + return true; +} + +void p3FeedReader::getFeedList(const std::string &parentId, std::list &feedInfos) +{ + RsStackMutex stack(mFeedReaderMtx); /******* LOCK STACK MUTEX *********/ + + std::map::iterator feedIt; + for (feedIt = mFeeds.begin(); feedIt != mFeeds.end(); ++feedIt) { + RsFeedReaderFeed *fi = feedIt->second; + + if (fi->parentId == parentId) { + FeedInfo feedInfo; + feedToInfo(fi, feedInfo); + feedInfos.push_back(feedInfo); + } + } +} + +bool p3FeedReader::getFeedInfo(const std::string &feedId, FeedInfo &feedInfo) +{ + RsStackMutex stack(mFeedReaderMtx); /******* LOCK STACK MUTEX *********/ + + std::map::iterator feedIt = mFeeds.find(feedId); + if (feedIt == mFeeds.end()) { +#ifdef FEEDREADER_DEBUG + std::cerr << "p3FeedReader::getFeedInfo - feed " << feedId << " not found" << std::endl; +#endif + return false; + } + + feedToInfo(feedIt->second, feedInfo); + + return true; +} + +bool p3FeedReader::getMsgInfo(const std::string &feedId, const std::string &msgId, FeedMsgInfo &msgInfo) +{ + RsStackMutex stack(mFeedReaderMtx); /******* LOCK STACK MUTEX *********/ + + std::map::iterator feedIt = mFeeds.find(feedId); + if (feedIt == mFeeds.end()) { +#ifdef FEEDREADER_DEBUG + std::cerr << "p3FeedReader::getMsgInfo - feed " << feedId << " not found" << std::endl; +#endif + return false; + } + + RsFeedReaderFeed *fi = feedIt->second; + + std::map::iterator msgIt; + msgIt = fi->mMsgs.find(msgId); + if (msgIt == fi->mMsgs.end()) { +#ifdef FEEDREADER_DEBUG + std::cerr << "p3FeedReader::getMsgInfo - msg " << msgId << " not found" << std::endl; +#endif + return false; + } + + feedMsgToInfo(msgIt->second, msgInfo); + + return true; +} + +bool p3FeedReader::removeMsg(const std::string &feedId, const std::string &msgId) +{ + { + RsStackMutex stack(mFeedReaderMtx); /******* LOCK STACK MUTEX *********/ + + std::map::iterator feedIt = mFeeds.find(feedId); + if (feedIt == mFeeds.end()) { +#ifdef FEEDREADER_DEBUG + std::cerr << "p3FeedReader::removeMsg - feed " << feedId << " not found" << std::endl; +#endif + return false; + } + + RsFeedReaderFeed *fi = feedIt->second; + + std::map::iterator msgIt; + msgIt = fi->mMsgs.find(msgId); + if (msgIt == fi->mMsgs.end()) { +#ifdef FEEDREADER_DEBUG + std::cerr << "p3FeedReader::removeMsg - msg " << msgId << " not found" << std::endl; +#endif + return false; + } + + msgIt->second->flag |= RS_FEEDMSG_FLAG_DELETED; + } + + IndicateConfigChanged(); + if (mNotify) { + mNotify->feedChanged(feedId, NOTIFY_TYPE_MOD); + mNotify->msgChanged(feedId, msgId, NOTIFY_TYPE_DEL); + } + + return true; +} + +bool p3FeedReader::removeMsgs(const std::string &feedId, const std::list &msgIds) +{ + std::list removedMsgs; + + { + RsStackMutex stack(mFeedReaderMtx); /******* LOCK STACK MUTEX *********/ + + std::map::iterator feedIt = mFeeds.find(feedId); + if (feedIt == mFeeds.end()) { +#ifdef FEEDREADER_DEBUG + std::cerr << "p3FeedReader::removeMsgs - feed " << feedId << " not found" << std::endl; +#endif + return false; + } + + RsFeedReaderFeed *fi = feedIt->second; + + std::list::const_iterator idIt; + for (idIt = msgIds.begin(); idIt != msgIds.end(); ++idIt) { + std::map::iterator msgIt; + msgIt = fi->mMsgs.find(*idIt); + if (msgIt == fi->mMsgs.end()) { +#ifdef FEEDREADER_DEBUG + std::cerr << "p3FeedReader::removeMsgs - msg " << *idIt << " not found" << std::endl; +#endif + continue; + } + + msgIt->second->flag |= RS_FEEDMSG_FLAG_DELETED; + + removedMsgs.push_back(*idIt); + } + } + + if (mNotify && !removedMsgs.empty()) { + mNotify->feedChanged(feedId, NOTIFY_TYPE_MOD); + + std::list::iterator it; + for (it = removedMsgs.begin(); it != removedMsgs.end(); ++it) { + mNotify->msgChanged(feedId, *it, NOTIFY_TYPE_DEL); + } + } + + return true; +} + +bool p3FeedReader::getMessageCount(const std::string &feedId, uint32_t *msgCount, uint32_t *newCount, uint32_t *unreadCount) +{ + if (msgCount) *msgCount = 0; + if (unreadCount) *unreadCount = 0; + if (newCount) *newCount = 0; + + if (!msgCount && !unreadCount && !newCount) { + return true; + } + + RsStackMutex stack(mFeedReaderMtx); /******* LOCK STACK MUTEX *********/ + + std::map::iterator feedIt = mFeeds.find(feedId); + if (feedIt == mFeeds.end()) { +#ifdef FEEDREADER_DEBUG + std::cerr << "p3FeedReader::getMessageCount - feed " << feedId << " not found" << std::endl; +#endif + return false; + } + + RsFeedReaderFeed *fi = feedIt->second; + + std::map::iterator msgIt; + for (msgIt = fi->mMsgs.begin(); msgIt != fi->mMsgs.end(); ++msgIt) { + RsFeedReaderMsg *mi = msgIt->second; + + if (mi->flag & RS_FEEDMSG_FLAG_DELETED) { + continue; + } + + if (msgCount) ++(*msgCount); + if (newCount && (mi->flag & RS_FEEDMSG_FLAG_NEW)) ++(*newCount); + if (unreadCount && (mi->flag & RS_FEEDMSG_FLAG_READ) == 0) ++(*unreadCount); + } + + return true; +} + +bool p3FeedReader::getFeedMsgList(const std::string &feedId, std::list &msgInfos) +{ + RsStackMutex stack(mFeedReaderMtx); /******* LOCK STACK MUTEX *********/ + + std::map::iterator feedIt = mFeeds.find(feedId); + if (feedIt == mFeeds.end()) { +#ifdef FEEDREADER_DEBUG + std::cerr << "p3FeedReader::getFeedMsgList - feed " << feedId << " not found" << std::endl; +#endif + return false; + } + + RsFeedReaderFeed *fi = feedIt->second; + + std::map::iterator msgIt; + for (msgIt = fi->mMsgs.begin(); msgIt != fi->mMsgs.end(); ++msgIt) { + RsFeedReaderMsg *mi = msgIt->second; + + if (mi->flag & RS_FEEDMSG_FLAG_DELETED) { + continue; + } + + FeedMsgInfo msgInfo; + feedMsgToInfo(mi, msgInfo); + msgInfos.push_back(msgInfo); + } + + return true; +} + +static bool canProcessFeed(RsFeedReaderFeed *fi) +{ + if (fi->flag & RS_FEED_FLAG_DEACTIVATED) { + /* deactivated */ + return false; + } + + if (fi->workstate != RsFeedReaderFeed::WAITING) { + /* should be working */ + return false; + } + + if (fi->flag & RS_FEED_FLAG_FOLDER) { + /* folder */ + return false; + } + + return true; +} + +bool p3FeedReader::processFeed(const std::string &feedId) +{ + std::list feedToDownload; + + { + RsStackMutex stack(mFeedReaderMtx); /******* LOCK STACK MUTEX *********/ + + std::map::iterator feedIt; + + if (feedId.empty()) { + /* process all feeds */ + for (feedIt = mFeeds.begin(); feedIt != mFeeds.end(); ++feedIt) { + RsFeedReaderFeed *fi = feedIt->second; + + if (!canProcessFeed(fi)) { + continue; + } + + /* add to download list */ + feedToDownload.push_back(fi->feedId); + fi->workstate = RsFeedReaderFeed::WAITING_TO_DOWNLOAD; + fi->content.clear(); + +#ifdef FEEDREADER_DEBUG + std::cerr << "p3FeedReader::processFeed - starting feed " << fi->feedId << " (" << fi->name << ")" << std::endl; +#endif + } + } else { + feedIt = mFeeds.find(feedId); + if (feedIt == mFeeds.end()) { +#ifdef FEEDREADER_DEBUG + std::cerr << "p3FeedReader::processFeed - feed " << feedId << " not found" << std::endl; +#endif + return false; + } + + RsFeedReaderFeed *fi = feedIt->second; + if (fi->flag & RS_FEED_FLAG_FOLDER) { + std::list feedIds; + feedIds.push_back(fi->feedId); + while (!feedIds.empty()) { + std::string parentId = feedIds.front(); + feedIds.pop_front(); + + std::map::iterator feedIt1; + for (feedIt1 = mFeeds.begin(); feedIt1 != mFeeds.end(); ++feedIt1) { + RsFeedReaderFeed *fi1 = feedIt1->second; + + if (fi1->parentId == parentId) { + if (fi1->flag & RS_FEED_FLAG_FOLDER) { + feedIds.push_back(fi1->feedId); + } else { + if (canProcessFeed(fi1)) { + fi1->workstate = RsFeedReaderFeed::WAITING_TO_DOWNLOAD; + fi1->content.clear(); + + feedToDownload.push_back(fi1->feedId); +#ifdef FEEDREADER_DEBUG + std::cerr << "p3FeedReader::processFeed - starting feed " << fi1->feedId << " (" << fi1->name << ")" << std::endl; +#endif + } + } + } + } + } + } else { + if (canProcessFeed(fi)) { + fi->workstate = RsFeedReaderFeed::WAITING_TO_DOWNLOAD; + fi->content.clear(); + + feedToDownload.push_back(fi->feedId); +#ifdef FEEDREADER_DEBUG + std::cerr << "p3FeedReader::processFeed - starting feed " << fi->feedId << " (" << fi->name << ")" << std::endl; +#endif + } + } + } + } + + std::list notifyIds; + std::list::iterator it; + + if (!feedToDownload.empty()) { + RsStackMutex stack(mDownloadMutex); /******* LOCK STACK MUTEX *********/ + + for (it = feedToDownload.begin(); it != feedToDownload.end(); ++it) { + if (std::find(mDownloadFeeds.begin(), mDownloadFeeds.end(), *it) == mDownloadFeeds.end()) { + mDownloadFeeds.push_back(*it); + notifyIds.push_back(*it); + } + } + } + + if (mNotify) { + for (it = notifyIds.begin(); it != notifyIds.end(); ++it) { + mNotify->feedChanged(*it, NOTIFY_TYPE_MOD); + } + } + + return true; +} + +bool p3FeedReader::setMessageRead(const std::string &feedId, const std::string &msgId, bool read) +{ + bool changed = false; + + { + RsStackMutex stack(mFeedReaderMtx); /******* LOCK STACK MUTEX *********/ + + std::map::iterator feedIt = mFeeds.find(feedId); + if (feedIt == mFeeds.end()) { +#ifdef FEEDREADER_DEBUG + std::cerr << "p3FeedReader::setMessageRead - feed " << feedId << " not found" << std::endl; +#endif + return false; + } + + RsFeedReaderFeed *fi = feedIt->second; + + std::map::iterator msgIt; + msgIt = fi->mMsgs.find(msgId); + if (msgIt == fi->mMsgs.end()) { +#ifdef FEEDREADER_DEBUG + std::cerr << "p3FeedReader::setMessageRead - msg " << msgId << " not found" << std::endl; +#endif + return false; + } + + RsFeedReaderMsg *mi = msgIt->second; + uint32_t oldFlag = mi->flag; + mi->flag &= ~RS_FEEDMSG_FLAG_NEW; + if (read) { + /* remove flag new */ + mi->flag |= RS_FEEDMSG_FLAG_READ; + } else { + mi->flag &= ~RS_FEEDMSG_FLAG_READ; + } + + changed = (mi->flag != oldFlag); + } + + if (changed) { + IndicateConfigChanged(); + if (mNotify) { + mNotify->feedChanged(feedId, NOTIFY_TYPE_MOD); + mNotify->msgChanged(feedId, msgId, NOTIFY_TYPE_MOD); + } + } + + return true; +} + +/***************************************************************************/ +/****************************** p3Service **********************************/ +/***************************************************************************/ + +int p3FeedReader::tick() +{ + /* clean feeds */ + cleanFeeds(); + + time_t currentTime = time(NULL); + std::list feedToDownload; + std::map::iterator feedIt; + + { + RsStackMutex stack(mFeedReaderMtx); /******* LOCK STACK MUTEX *********/ + + for (feedIt = mFeeds.begin(); feedIt != mFeeds.end(); ++feedIt) { + RsFeedReaderFeed *fi = feedIt->second; + + if (!canProcessFeed(fi)) { + continue; + } + + uint32_t updateInterval; + if (fi->flag & RS_FEED_FLAG_STANDARD_UPDATE_INTERVAL) { + updateInterval = mStandardUpdateInterval; + } else { + updateInterval = fi->updateInterval; + } + if (fi->lastUpdate == 0 || fi->lastUpdate + (long) updateInterval <= currentTime) { + /* add to download list */ + feedToDownload.push_back(fi->feedId); + fi->workstate = RsFeedReaderFeed::WAITING_TO_DOWNLOAD; + fi->content.clear(); + +#ifdef FEEDREADER_DEBUG + std::cerr << "p3FeedReader::tick - starting feed " << fi->feedId << " (" << fi->name << ")" << std::endl; +#endif + } + } + } + + std::list notifyIds; + std::list::iterator it; + + if (!feedToDownload.empty()) { + RsStackMutex stack(mDownloadMutex); /******* LOCK STACK MUTEX *********/ + + for (it = feedToDownload.begin(); it != feedToDownload.end(); ++it) { + if (std::find(mDownloadFeeds.begin(), mDownloadFeeds.end(), *it) == mDownloadFeeds.end()) { + mDownloadFeeds.push_back(*it); + notifyIds.push_back(*it); + } + } + } + + if (mNotify) { + for (it = notifyIds.begin(); it != notifyIds.end(); ++it) { + mNotify->feedChanged(*it, NOTIFY_TYPE_MOD); + } + } + + return 0; +} + +void p3FeedReader::cleanFeeds() +{ + time_t currentTime = time(NULL); + + if (mLastClean == 0 || mLastClean + FEEDREADER_CLEAN_INTERVAL <= currentTime) { + RsStackMutex stack(mFeedReaderMtx); /******* LOCK STACK MUTEX *********/ + + std::list > removedMsgIds; + std::map::iterator feedIt; + for (feedIt = mFeeds.begin(); feedIt != mFeeds.end(); ++feedIt) { + RsFeedReaderFeed *fi = feedIt->second; + + uint32_t storageTime = 0; + if (fi->flag & RS_FEED_FLAG_STANDARD_STORAGE_TIME) { + storageTime = mStandardStorageTime; + } else { + storageTime = fi->storageTime; + } + if (storageTime > 0) { + uint32_t removedMsgs = 0; + + std::map::iterator msgIt; + for (msgIt = fi->mMsgs.begin(); msgIt != fi->mMsgs.end(); ) { + RsFeedReaderMsg *mi = msgIt->second; + + if (mi->flag & RS_FEEDMSG_FLAG_DELETED) { + if (mi->pubDate < currentTime - (long) storageTime) { + removedMsgIds.push_back(std::pair (fi->feedId, mi->msgId)); + delete(mi); + std::map::iterator deleteIt = msgIt++; + fi->mMsgs.erase(deleteIt); + ++removedMsgs; + continue; + } + } + ++msgIt; + } +#ifdef FEEDREADER_DEBUG + std::cerr << "p3FeedReader::tick - feed " << fi->feedId << " (" << fi->name << ") cleaned, " << removedMsgs << " messages removed" << std::endl; +#endif + } + } + mLastClean = currentTime; + + if (removedMsgIds.size()) { + IndicateConfigChanged(); + + std::list >::iterator it; + for (it = removedMsgIds.begin(); it != removedMsgIds.end(); ++it) { + mNotify->msgChanged(it->first, it->second, NOTIFY_TYPE_DEL); + } + } + } +} + +/***************************************************************************/ +/****************************** p3Config ***********************************/ +/***************************************************************************/ + +RsSerialiser *p3FeedReader::setupSerialiser() +{ + RsSerialiser *rss = new RsSerialiser(); + + /* add in the types we need! */ + rss->addSerialType(new RsFeedReaderSerialiser()); + rss->addSerialType(new RsGeneralConfigSerialiser()); + + return rss; +} + +bool p3FeedReader::saveList(bool &cleanup, std::list & saveData) +{ + mFeedReaderMtx.lock(); /*********************** LOCK *******/ + + cleanup = false; + + RsConfigKeyValueSet *rskv = new RsConfigKeyValueSet(); + + RsTlvKeyValue kv; + kv.key = "StandardStorageTime"; + rs_sprintf(kv.value, "%u", mStandardStorageTime); + rskv->tlvkvs.pairs.push_back(kv); + + kv.key = "StandardUpdateInterval"; + rs_sprintf(kv.value, "%u", mStandardUpdateInterval); + rskv->tlvkvs.pairs.push_back(kv); + + kv.key = "StandardUseProxy"; + rs_sprintf(kv.value, "%hu", mStandardUseProxy ? 1 : 0); + rskv->tlvkvs.pairs.push_back(kv); + + kv.key = "StandardProxyAddress"; + rs_sprintf(kv.value, "%s", mStandardProxyAddress.c_str()); + rskv->tlvkvs.pairs.push_back(kv); + + kv.key = "StandardProxyPort"; + rs_sprintf(kv.value, "%hu", mStandardProxyPort); + rskv->tlvkvs.pairs.push_back(kv); + + /* Add KeyValue to saveList */ + saveData.push_back(rskv); + + std::map::iterator it1; + for (it1 = mFeeds.begin(); it1 != mFeeds.end(); ++it1) { + RsFeedReaderFeed *fi = it1->second; + saveData.push_back(fi); + + std::map::iterator it2; + for (it2 = fi->mMsgs.begin(); it2 != fi->mMsgs.end(); ++it2) { + saveData.push_back(it2->second); + } + } + + /* list completed! */ + return true; +} + +void p3FeedReader::saveDone() +{ + mFeedReaderMtx.unlock(); /*********************** UNLOCK *******/ + return; +} + +bool p3FeedReader::loadList(std::list& load) +{ + std::list::iterator it; + RsFeedReaderFeed *fi; + RsFeedReaderMsg *mi; + RsConfigKeyValueSet *rskv; + +#ifdef FEEDREADER_DEBUG + std::cerr << "p3FeedReader::loadList() Item Count: " << load.size(); + std::cerr << std::endl; +#endif + + mNextFeedId = 1; + mNextMsgId = 1; + + std::map msgs; + + for (it = load.begin(); it != load.end(); ++it) { + /* switch on type */ + if (NULL != (fi = dynamic_cast(*it))) { + uint32_t feedId = 0; + if (sscanf(fi->feedId.c_str(), "%u", &feedId) == 1) { + RsStackMutex stack(mFeedReaderMtx); /********** STACK LOCKED MTX ******/ + if (mFeeds.find(fi->feedId) != mFeeds.end()) { + /* feed with the same id exists */ + delete mFeeds[fi->feedId]; + } + mFeeds[fi->feedId] = fi; + + if (feedId + 1 > mNextFeedId) { + mNextFeedId = feedId + 1; + } + } else { + /* invalid feed id */ + delete(*it); + } + } else if (NULL != (mi = dynamic_cast(*it))) { + if (msgs.find(mi->msgId) != msgs.end()) { + delete msgs[mi->msgId]; + } + msgs[mi->msgId] = mi; + } else if (NULL != (rskv = dynamic_cast(*it))) { + std::list::iterator kit; + for(kit = rskv->tlvkvs.pairs.begin(); kit != rskv->tlvkvs.pairs.end(); kit++) { + if (kit->key == "StandardStorageTime") { + uint32_t value; + if (sscanf(kit->value.c_str(), "%u", &value) == 1) { + mStandardStorageTime = value; + } + } else if (kit->key == "StandardUpdateInterval") { + uint32_t value; + if (sscanf(kit->value.c_str(), "%u", &value) == 1) { + mStandardUpdateInterval = value; + } + } else if (kit->key == "StandardUseProxy") { + uint16_t value; + if (sscanf(kit->value.c_str(), "%hu", &value) == 1) { + mStandardUseProxy = value == 1 ? true : false; + } + } else if (kit->key == "StandardProxyAddress") { + mStandardProxyAddress = kit->value; + } else if (kit->key == "StandardProxyPort") { + uint16_t value; + if (sscanf(kit->value.c_str(), "%hu", &value) == 1) { + mStandardProxyPort = value; + } + } + } + } else { + /* cleanup */ + delete(*it); + } + } + + /* now sort msgs into feeds */ + RsStackMutex stack(mFeedReaderMtx); /********** STACK LOCKED MTX ******/ + + std::map::iterator it1; + for (it1 = msgs.begin(); it1 != msgs.end(); ++it1) { + uint32_t msgId = 0; + if (sscanf(it1->first.c_str(), "%u", &msgId) == 1) { + std::map::iterator it2 = mFeeds.find(it1->second->feedId); + if (it2 == mFeeds.end()) { + /* feed does not exist exists */ + delete it1->second; + continue; + } + it2->second->mMsgs[it1->first] = it1->second; + if (msgId + 1 > mNextMsgId) { + mNextMsgId = msgId + 1; + } + } else { + /* invalid msg id */ + delete(it1->second); + } + } + + return true; +} + +/***************************************************************************/ +/****************************** internal ***********************************/ +/***************************************************************************/ + +bool p3FeedReader::getFeedToDownload(RsFeedReaderFeed &feed) +{ + std::string feedId; + + { + RsStackMutex stack(mDownloadMutex); /******* LOCK STACK MUTEX *********/ + + if (mDownloadFeeds.empty()) { + /* nothing to download */ + return false; + } + + /* get next feed id to download */ + feedId = mDownloadFeeds.front(); + mDownloadFeeds.pop_front(); + } + + { + RsStackMutex stack(mFeedReaderMtx); /******* LOCK STACK MUTEX *********/ + + /* find feed */ + std::map::iterator it = mFeeds.find(feedId); + if (it == mFeeds.end()) { + /* feed not found */ +#ifdef FEEDREADER_DEBUG + std::cerr << "p3FeedReader::getFeedToDownload - feed " << feedId << " not found" << std::endl; +#endif + return false; + } + + if (it->second->workstate != RsFeedReaderFeed::WAITING_TO_DOWNLOAD) { + std::cerr << "p3FeedReader::getFeedToDownload - feed in wrong work state for download " << it->second->workstate << std::endl; + return false; + } + + /* set state to downloading */ + it->second->workstate = RsFeedReaderFeed::DOWNLOADING; + + /* return a copy of the feed */ + feed = *(it->second); + +#ifdef FEEDREADER_DEBUG + std::cerr << "p3FeedReader::getFeedToDownload - feed " << it->second->feedId << " (" << it->second->name << ") is starting to download" << std::endl; +#endif + } + + if (mNotify) { + mNotify->feedChanged(feedId, NOTIFY_TYPE_MOD); + } + + return true; +} + +void p3FeedReader::onDownloadSuccess(const std::string &feedId, const std::string &content, std::string &icon) +{ + { + RsStackMutex stack(mFeedReaderMtx); /******* LOCK STACK MUTEX *********/ + + /* find feed */ + std::map::iterator it = mFeeds.find(feedId); + if (it == mFeeds.end()) { + /* feed not found */ +#ifdef FEEDREADER_DEBUG + std::cerr << "p3FeedReader::onDownloadSuccess - feed " << feedId << " not found" << std::endl; +#endif + return; + } + + RsFeedReaderFeed *fi = it->second; + fi->workstate = RsFeedReaderFeed::WAITING_TO_PROCESS; + fi->content = content; + + if (fi->icon != icon) { + fi->icon = icon; + + IndicateConfigChanged(); + } + +#ifdef FEEDREADER_DEBUG + std::cerr << "p3FeedReader::onDownloadSuccess - feed " << fi->feedId << " (" << fi->name << ") add to process" << std::endl; +#endif + } + + { + RsStackMutex stack(mProcessMutex); /******* LOCK STACK MUTEX *********/ + + if (std::find(mProcessFeeds.begin(), mProcessFeeds.end(), feedId) == mProcessFeeds.end()) { + mProcessFeeds.push_back(feedId); + } + + } + + if (mNotify) { + mNotify->feedChanged(feedId, NOTIFY_TYPE_MOD); + } +} + +void p3FeedReader::onDownloadError(const std::string &feedId, p3FeedReaderThread::DownloadResult result, const std::string &error) +{ + { + RsStackMutex stack(mFeedReaderMtx); /******* LOCK STACK MUTEX *********/ + + /* find feed */ + std::map::iterator it = mFeeds.find(feedId); + if (it == mFeeds.end()) { + /* feed not found */ +#ifdef FEEDREADER_DEBUG + std::cerr << "p3FeedReader::onDownloadError - feed " << feedId << " not found" << std::endl; +#endif + return; + } + + RsFeedReaderFeed *fi = it->second; + fi->workstate = RsFeedReaderFeed::WAITING; + fi->lastUpdate = time(NULL); + fi->content.clear(); + + long todo; // sort error codes + switch (result) { + case p3FeedReaderThread::DOWNLOAD_SUCCESS: + /* this should not happen */ + std::cerr << "p3FeedReader::onDownloadError - success given as error" << std::endl; + fi->errorState = RS_FEED_ERRORSTATE_DOWNLOAD_INTERNAL_ERROR; + break; + case p3FeedReaderThread::DOWNLOAD_ERROR: + fi->errorState = RS_FEED_ERRORSTATE_DOWNLOAD_ERROR; + break; + case p3FeedReaderThread::DOWNLOAD_ERROR_INIT: + case p3FeedReaderThread::DOWNLOAD_INTERNAL_ERROR: + fi->errorState = RS_FEED_ERRORSTATE_DOWNLOAD_INTERNAL_ERROR; + break; + case p3FeedReaderThread::DOWNLOAD_UNKNOWN_CONTENT_TYPE: + fi->errorState = RS_FEED_ERRORSTATE_DOWNLOAD_UNKNOWN_CONTENT_TYPE; + break; + case p3FeedReaderThread::DOWNLOAD_NOT_FOUND: + fi->errorState = RS_FEED_ERRORSTATE_DOWNLOAD_NOT_FOUND; + break; + case p3FeedReaderThread::DOWNLOAD_UNKOWN_RESPONSE_CODE: + fi->errorState = RS_FEED_ERRORSTATE_DOWNLOAD_UNKOWN_RESPONSE_CODE; + break; + default: + fi->errorState = RS_FEED_ERRORSTATE_DOWNLOAD_INTERNAL_ERROR; + } + + fi->errorString = error; + +#ifdef FEEDREADER_DEBUG + std::cerr << "p3FeedReader::onDownloadError - feed " << fi->feedId << " (" << fi->name << ") error download, result = " << result << ", errorState = " << fi->errorState << ", error = " << error << std::endl; +#endif + + IndicateConfigChanged(); + } + + if (mNotify) { + mNotify->feedChanged(feedId, NOTIFY_TYPE_MOD); + } +} + +bool p3FeedReader::getFeedToProcess(RsFeedReaderFeed &feed) +{ + std::string feedId; + + { + RsStackMutex stack(mProcessMutex); /******* LOCK STACK MUTEX *********/ + + if (mProcessFeeds.empty()) { + /* nothing to process */ + return false; + } + + /* get next feed id to process */ + feedId = mProcessFeeds.front(); + mProcessFeeds.pop_front(); + } + + { + RsStackMutex stack(mFeedReaderMtx); /******* LOCK STACK MUTEX *********/ + + /* find feed */ + std::map::iterator it = mFeeds.find(feedId); + if (it == mFeeds.end()) { + /* feed not found */ +#ifdef FEEDREADER_DEBUG + std::cerr << "p3FeedReader::getFeedToProcess - feed " << feedId << " not found" << std::endl; +#endif + return false; + } + + if (it->second->workstate != RsFeedReaderFeed::WAITING_TO_PROCESS) { + std::cerr << "p3FeedReader::getFeedToProcess - feed in wrong state for process " << it->second->workstate << std::endl; + return false; + } + + RsFeedReaderFeed *fi = it->second; + + /* set state to processing */ + fi->workstate = RsFeedReaderFeed::PROCESSING; + fi->errorState = RS_FEED_ERRORSTATE_OK; + fi->errorString.clear(); + + /* return a copy of the feed */ + feed = *fi; + +#ifdef FEEDREADER_DEBUG + std::cerr << "p3FeedReader::getFeedToProcess - feed " << it->second->feedId << " (" << it->second->name << ") is starting to process" << std::endl; +#endif + } + + if (mNotify) { + mNotify->feedChanged(feedId, NOTIFY_TYPE_MOD); + } + + return true; +} + +void p3FeedReader::onProcessSuccess(const std::string &feedId, std::list &msgs) +{ +#ifdef FEEDREADER_DEBUG + std::cerr << "p3FeedReader::onProcessSuccess - feed " << feedId << " got " << msgs.size() << " messages" << std::endl; +#endif + + std::list addedMsgs; + std::string forumId; + std::list forumMsgs; + + { + RsStackMutex stack(mFeedReaderMtx); /******* LOCK STACK MUTEX *********/ + + /* find feed */ + std::map::iterator it = mFeeds.find(feedId); + if (it == mFeeds.end()) { + /* feed not found */ +#ifdef FEEDREADER_DEBUG + std::cerr << "p3FeedReader::onProcessSuccess - feed " << feedId << " not found" << std::endl; +#endif + return; + } + + RsFeedReaderFeed *fi = it->second; + bool forum = (fi->flag & RS_FEED_FLAG_FORUM); + uint32_t errorState = RS_FEED_ERRORSTATE_OK; + + if (forum) { + if (fi->forumId.empty()) { + /* create new forum */ + long todo; // search for existing forum? + + /* search for existing own forum */ +// std::list forumList; +// if (rsForums->getForumList(forumList)) { +// std::wstring wName = StringToWString(name); +// for (std::list::iterator it = forumList.begin(); it != forumList.end(); ++it) { +// if (it->forumName == wName) { +// std::cout << "DEBUG_RSS2FORUM: Found existing forum " << it->forumId << " for " << name << std::endl; +// return it->forumId; +// } +// } +// } + + std::wstring forumName; + librs::util::ConvertUtf8ToUtf16(fi->name, forumName); + std::wstring forumDescription; + librs::util::ConvertUtf8ToUtf16(fi->description, forumDescription); + /* create anonymous public forum */ + fi->forumId = rsForums->createForum(forumName, forumDescription, RS_DISTRIB_PUBLIC | RS_DISTRIB_AUTHEN_ANON); + + forumId = fi->forumId; + + if (fi->forumId.empty()) { + errorState = RS_FEED_ERRORSTATE_FORUM_CREATE; + +#ifdef FEEDREADER_DEBUG + std::cerr << "p3FeedReader::onProcessSuccess - can't create forum for feed " << feedId << " (" << it->second->name << ") - ignore all messages" << std::endl; + } else { + std::cerr << "p3FeedReader::onProcessSuccess - forum " << forumId << " (" << fi->name << ") created" << std::endl; +#endif + } + } else { + /* check forum */ + ForumInfo forumInfo; + if (rsForums->getForumInfo(fi->forumId, forumInfo)) { + if ((forumInfo.subscribeFlags & RS_DISTRIB_ADMIN) == 0) { + errorState = RS_FEED_ERRORSTATE_FORUM_NO_ADMIN; + } else if ((forumInfo.forumFlags & RS_DISTRIB_AUTHEN_REQ) || (forumInfo.forumFlags & RS_DISTRIB_AUTHEN_ANON) == 0) { + errorState = RS_FEED_ERRORSTATE_FORUM_NO_ANONYMOUS_FORUM; + } else { + forumId = fi->forumId; + } + } else { + errorState = RS_FEED_ERRORSTATE_FORUM_NOT_FOUND; + } + } + } + + /* process msgs */ +#ifdef FEEDREADER_DEBUG + uint32_t newMsgs = 0; +#endif + + if (errorState == RS_FEED_ERRORSTATE_OK) { + std::list::iterator newMsgIt; + for (newMsgIt = msgs.begin(); newMsgIt != msgs.end(); ) { + RsFeedReaderMsg *miNew = *newMsgIt; + /* search for exisiting msg */ + std::map::iterator msgIt; + for (msgIt = fi->mMsgs.begin(); msgIt != fi->mMsgs.end(); ++msgIt) { + RsFeedReaderMsg *mi = msgIt->second; + if (mi->title == miNew->title && mi->link == mi->link && mi->author == mi->author) { + /* msg exist */ + break; + } + } + if (msgIt == fi->mMsgs.end()) { + /* add new msg */ + rs_sprintf(miNew->msgId, "%lu", mNextMsgId++); + if (forum) { + miNew->flag = RS_FEEDMSG_FLAG_DELETED; + forumMsgs.push_back(*miNew); +// miNew->description.clear(); + } else { + miNew->flag = RS_FEEDMSG_FLAG_NEW; + addedMsgs.push_back(miNew->msgId); + } + fi->mMsgs[miNew->msgId] = miNew; + newMsgIt = msgs.erase(newMsgIt); + +#ifdef FEEDREADER_DEBUG + ++newMsgs; +#endif + } else { + /* msg was updated */ + ++newMsgIt; + } +#ifdef FEEDREADER_DEBUG + std::cerr << "p3FeedReader::onProcessSuccess - feed " << fi->feedId << " (" << fi->name << ") added " << newMsgs << "/" << msgs.size() << " messages" << std::endl; +#endif + } + } + + fi->workstate = RsFeedReaderFeed::WAITING; + fi->content.clear(); + fi->lastUpdate = time(NULL); + fi->errorState = errorState; + fi->errorString.clear(); + + IndicateConfigChanged(); + } + + if (!forumId.empty() && !forumMsgs.empty()) { + /* add messages as forum messages */ + std::list::iterator msgIt; + for (msgIt = forumMsgs.begin(); msgIt != forumMsgs.end(); ++msgIt) { + RsFeedReaderMsg &mi = *msgIt; + + /* convert to forum messages */ + ForumMsgInfo forumMsgInfo; + forumMsgInfo.forumId = forumId; + librs::util::ConvertUtf8ToUtf16(mi.title, forumMsgInfo.title); + + std::string description = mi.description; + /* add link */ + if (!mi.link.empty()) { + description += "
" + mi.link + ""; + } + librs::util::ConvertUtf8ToUtf16(description, forumMsgInfo.msg); + + if (rsForums->ForumMessageSend(forumMsgInfo)) { + /* set to new */ + rsForums->setMessageStatus(forumMsgInfo.forumId, forumMsgInfo.msgId, 0, FORUM_MSG_STATUS_MASK); + } else { +#ifdef FEEDREADER_DEBUG + std::cerr << "p3FeedReader::onProcessSuccess - can't add forum message " << mi.title << " for feed " << forumId << std::endl; +#endif + } + } + } + + if (mNotify) { + mNotify->feedChanged(feedId, NOTIFY_TYPE_MOD); + + std::list::iterator it; + for (it = addedMsgs.begin(); it != addedMsgs.end(); ++it) { + mNotify->msgChanged(feedId, *it, NOTIFY_TYPE_ADD); + } + } +} + +void p3FeedReader::onProcessError(const std::string &feedId, p3FeedReaderThread::ProcessResult result) +{ + { + RsStackMutex stack(mFeedReaderMtx); /******* LOCK STACK MUTEX *********/ + + /* find feed */ + std::map::iterator it = mFeeds.find(feedId); + if (it == mFeeds.end()) { + /* feed not found */ +#ifdef FEEDREADER_DEBUG + std::cerr << "p3FeedReader::onProcessError - feed " << feedId << " not found" << std::endl; +#endif + return; + } + + RsFeedReaderFeed *fi = it->second; + fi->workstate = RsFeedReaderFeed::WAITING; + fi->lastUpdate = time(NULL); + fi->content.clear(); + + long todo; // sort error codes + switch (result) { + case p3FeedReaderThread::PROCESS_SUCCESS: + /* this should not happen */ + std::cerr << "p3FeedReader::onProcessError - success given as error" << std::endl; + fi->errorState = RS_FEED_ERRORSTATE_PROCESS_INTERNAL_ERROR; + break; + case p3FeedReaderThread::PROCESS_ERROR_INIT: + fi->errorState = RS_FEED_ERRORSTATE_PROCESS_INTERNAL_ERROR; + break; + default: + fi->errorState = RS_FEED_ERRORSTATE_PROCESS_INTERNAL_ERROR; + } + +// fi->errorString = error; + +#ifdef FEEDREADER_DEBUG + std::cerr << "p3FeedReader::onProcessError - feed " << fi->feedId << " (" << fi->name << ") error process, result = " << result << ", errorState = " << fi->errorState << std::endl; +#endif + + IndicateConfigChanged(); + } + + if (mNotify) { + mNotify->feedChanged(feedId, NOTIFY_TYPE_MOD); + } +} + +void p3FeedReader::setFeedInfo(const std::string &feedId, const std::string &name, const std::string &description) +{ + bool changed = false; + std::string forumId; + ForumInfo forumInfo; + + { + RsStackMutex stack(mFeedReaderMtx); /******* LOCK STACK MUTEX *********/ + + /* find feed */ + std::map::iterator it = mFeeds.find(feedId); + if (it == mFeeds.end()) { + /* feed not found */ +#ifdef FEEDREADER_DEBUG + std::cerr << "p3FeedReader::setFeedInfo - feed " << feedId << " not found" << std::endl; +#endif + return; + } + + RsFeedReaderFeed *fi = it->second; + if (fi->name != name) { +#ifdef FEEDREADER_DEBUG + std::cerr << "p3FeedReader::setFeedInfo - feed " << fi->feedId << " changed name from " << fi->name << " to " << name << std::endl; +#endif + fi->name = name; + changed = true; + } + if (fi->description != description) { +#ifdef FEEDREADER_DEBUG + std::cerr << "p3FeedReader::setFeedInfo - feed " << fi->feedId << " changed description from " << fi->description << " to " << description << std::endl; +#endif + fi->description = description; + changed = true; + } + + if (changed && (fi->flag & RS_FEED_FLAG_FORUM) && (fi->flag & RS_FEED_FLAG_UPDATE_FORUM_INFO) && !fi->forumId.empty()) { + /* change forum too */ + forumId = fi->forumId; + librs::util::ConvertUtf8ToUtf16(fi->name, forumInfo.forumName); + librs::util::ConvertUtf8ToUtf16(fi->description, forumInfo.forumDesc); + } + } + + if (changed) { + IndicateConfigChanged(); + + if (mNotify) { + mNotify->feedChanged(feedId, NOTIFY_TYPE_MOD); + } + } + + if (!forumId.empty()) { + /* name or description changed, update forum */ + if (!rsForums->setForumInfo(forumId, forumInfo)) { +#ifdef FEEDREADER_DEBUG + std::cerr << "p3FeedReader::setFeed - can't change forum " << forumId << std::endl; +#endif + } + } +} diff --git a/plugins/FeedReader/services/p3FeedReader.h b/plugins/FeedReader/services/p3FeedReader.h new file mode 100644 index 000000000..2e62e16da --- /dev/null +++ b/plugins/FeedReader/services/p3FeedReader.h @@ -0,0 +1,113 @@ +/**************************************************************** + * RetroShare GUI is distributed under the following license: + * + * Copyright (C) 2012 by Thunder + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + ****************************************************************/ + + #ifndef P3_FEEDREADER +#define P3_FEEDREADER + +#include "retroshare/rsplugin.h" +#include "plugins/rspqiservice.h" +#include "interface/rsFeedReader.h" +#include "p3FeedReaderThread.h" + +class RsFeedReaderFeed; + +//TODO: get new id's +const uint8_t RS_PKT_TYPE_FEEDREADER_CONFIG = 0xf0; +const uint32_t CONFIG_TYPE_FEEDREADER = 0x0001; + +class p3FeedReader : public RsPQIService, public RsFeedReader +{ +public: + p3FeedReader(RsPluginHandler *pgHandler); + + /****************** FeedReader Interface *************/ + virtual void stop(); + virtual void setNotify(RsFeedReaderNotify *notify); + + virtual uint32_t getStandardStorageTime(); + virtual void setStandardStorageTime(uint32_t storageTime); + virtual uint32_t getStandardUpdateInterval(); + virtual void setStandardUpdateInterval(uint32_t updateInterval); + virtual bool getStandardProxy(std::string &proxyAddress, uint16_t &proxyPort); + virtual void setStandardProxy(bool useProxy, const std::string &proxyAddress, uint16_t proxyPort); + + virtual RsFeedAddResult addFolder(const std::string parentId, const std::string &name, std::string &feedId); + virtual RsFeedAddResult setFolder(const std::string &feedId, const std::string &name); + virtual RsFeedAddResult addFeed(const FeedInfo &feedInfo, std::string &feedId); + virtual RsFeedAddResult setFeed(const std::string &feedId, const FeedInfo &feedInfo); + virtual bool removeFeed(const std::string &feedId); + virtual void getFeedList(const std::string &parentId, std::list &feedInfos); + virtual bool getFeedInfo(const std::string &feedId, FeedInfo &feedInfo); + virtual bool getMsgInfo(const std::string &feedId, const std::string &msgId, FeedMsgInfo &msgInfo); + virtual bool removeMsg(const std::string &feedId, const std::string &msgId); + virtual bool removeMsgs(const std::string &feedId, const std::list &msgIds); + virtual bool getMessageCount(const std::string &feedId, uint32_t *msgCount, uint32_t *newCount, uint32_t *unreadCount); + virtual bool getFeedMsgList(const std::string &feedId, std::list &msgInfos); + virtual bool processFeed(const std::string &feedId); + virtual bool setMessageRead(const std::string &feedId, const std::string &msgId, bool read); + + /****************** p3Service STUFF ******************/ + virtual int tick(); + + /****************** internal STUFF *******************/ + bool getFeedToDownload(RsFeedReaderFeed &feed); + void onDownloadSuccess(const std::string &feedId, const std::string &content, std::string &icon); + void onDownloadError(const std::string &feedId, p3FeedReaderThread::DownloadResult result, const std::string &error); + void onProcessSuccess(const std::string &feedId, std::list &msgs); + void onProcessError(const std::string &feedId, p3FeedReaderThread::ProcessResult result); + + bool getFeedToProcess(RsFeedReaderFeed &feed); + + void setFeedInfo(const std::string &feedId, const std::string &name, const std::string &description); + +protected: + /****************** p3Config STUFF *******************/ + virtual RsSerialiser *setupSerialiser(); + virtual bool saveList(bool &cleanup, std::list&); + virtual bool loadList(std::list& load); + virtual void saveDone(); + +private: + void cleanFeeds(); + void deleteAllMsgs_locked(RsFeedReaderFeed *fi); + + std::list mThreads; + uint32_t mNextFeedId; + uint32_t mNextMsgId; + time_t mLastClean; + RsFeedReaderNotify *mNotify; + + RsMutex mFeedReaderMtx; + uint32_t mStandardUpdateInterval; + uint32_t mStandardStorageTime; + bool mStandardUseProxy; + std::string mStandardProxyAddress; + uint16_t mStandardProxyPort; + std::map mFeeds; + + RsMutex mDownloadMutex; + std::list mDownloadFeeds; + + RsMutex mProcessMutex; + std::list mProcessFeeds; +}; + +#endif diff --git a/plugins/FeedReader/services/p3FeedReaderThread.cc b/plugins/FeedReader/services/p3FeedReaderThread.cc new file mode 100644 index 000000000..02215368e --- /dev/null +++ b/plugins/FeedReader/services/p3FeedReaderThread.cc @@ -0,0 +1,1024 @@ +/**************************************************************** + * RetroShare GUI is distributed under the following license: + * + * Copyright (C) 2012 by Thunder + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + ****************************************************************/ + +#include "p3FeedReaderThread.h" +#include "rsFeedReaderItems.h" +#include "util/rsstring.h" + +#include +#include +#include + +enum FeedFormat { FORMAT_RSS, FORMAT_RDF }; + +/********* + * #define FEEDREADER_DEBUG + *********/ + +p3FeedReaderThread::p3FeedReaderThread(p3FeedReader *feedReader, Type type) : RsThread(), mFeedReader(feedReader), mType(type) +{ + if (type == PROCESS) { + mCharEncodingHandler = xmlFindCharEncodingHandler ("UTF8"); + + if (!mCharEncodingHandler) { + /* no encoding handler found */ + std::cerr << "p3FeedReaderThread::p3FeedReaderThread - no encoding handler found" << std::endl; + } + } else { + mCharEncodingHandler = NULL; + } +} + +/***************************************************************************/ +/****************************** Thread *************************************/ +/***************************************************************************/ + +void p3FeedReaderThread::run() +{ + while (isRunning()) { +#ifdef WIN32 + Sleep(1000); +#else + usleep(1000000); +#endif + /* every second */ + + switch (mType) { + case DOWNLOAD: + { + RsFeedReaderFeed feed; + if (mFeedReader->getFeedToDownload(feed)) { + std::string content; + std::string icon; + std::string error; + + DownloadResult result = download(feed, content, icon, error); + if (result == DOWNLOAD_SUCCESS) { + mFeedReader->onDownloadSuccess(feed.feedId, content, icon); + } else { + mFeedReader->onDownloadError(feed.feedId, result, error); + } + } + } + break; + case PROCESS: + { + RsFeedReaderFeed feed; + if (mFeedReader->getFeedToProcess(feed)) { + std::list entries; + std::string error; + + ProcessResult result = process(feed, entries, error); + if (result == PROCESS_SUCCESS) { + mFeedReader->onProcessSuccess(feed.feedId, entries); + } else { + mFeedReader->onProcessError(feed.feedId, result); + } + + std::list::iterator it; + for (it = entries.begin(); it != entries.end(); ++it) { + delete (*it); + } + entries.clear(); + } + } + break; + } + } +} + +/***************************************************************************/ +/****************************** Download ***********************************/ +/***************************************************************************/ + +static size_t writeFunctionString (void *ptr, size_t size, size_t nmemb, void *stream) +{ + std::string *s = (std::string*) stream; + s->append ((char*) ptr, size * nmemb); + + return nmemb * size; +} + +static size_t writeFunctionBinary (void *ptr, size_t size, size_t nmemb, void *stream) +{ + std::vector *bytes = (std::vector*) stream; + + std::vector newBytes; + newBytes.resize(size * nmemb); + memcpy(newBytes.data(), ptr, newBytes.size()); + + bytes->insert(bytes->end(), newBytes.begin(), newBytes.end()); + + return nmemb * size; +} + +static int progressCallback (void *clientp, double /*dltotal*/, double /*dlnow*/, double /*ultotal*/, double /*ulnow*/) +{ + p3FeedReaderThread *thread = (p3FeedReaderThread*) clientp; + + if (!thread->isRunning()) { + /* thread was stopped */ + return 1; + } + + long todo; // show progress in gui + + return 0; +} + +static bool getFavicon (std::string url, const std::string &proxy, std::string &icon) +{ + icon.clear(); + + if (url.substr(0, 7) == "http://") { + int found = url.find("/", 7); + if (found >= 0) { + url.erase(found, url.length() - found); + } + } else { + return false; + } + + bool result = false; + + CURL *curl = curl_easy_init(); + if (curl) { + url += "/favicon.ico"; + + std::vector vicon; + curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1); + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeFunctionBinary); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &vicon); + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1); + curl_easy_setopt(curl, CURLOPT_COOKIESESSION, 1); + + if (!proxy.empty()) { + curl_easy_setopt(curl, CURLOPT_PROXY, proxy.c_str()); + } + + CURLcode code = curl_easy_perform(curl); + if (code == CURLE_OK) { + long response; + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response); + if (response == 200) { + char *contentType = NULL; + curl_easy_getinfo(curl, CURLINFO_CONTENT_TYPE, &contentType); + if (contentType && + (strnicmp(contentType, "image/x-icon", 12) == 0 || + strnicmp(contentType, "application/octet-stream", 24) == 0 || + strnicmp(contentType, "text/plain", 10) == 0)) { + if (!vicon.empty()) { + long todo; // check it + // Set up a base64 encoding BIO that writes to a memory BIO + BIO *b64 = BIO_new(BIO_f_base64()); + if (b64) { + BIO *out = BIO_new(BIO_s_mem()); + if (out) { + BIO_set_flags(out, BIO_CLOSE); // probably redundant + b64 = BIO_push(b64, out); + // Send the data + BIO_write(b64, vicon.data(), vicon.size()); + // Collect the encoded data + BIO_flush(b64); + char* temp; + int count = BIO_get_mem_data(out, &temp); + if (count && temp) { + icon = temp; + result = true; + } + } + BIO_free_all(b64); + } + } +// char *encode = NULL; +// size_t encodeSize = 0; +// code = Curl_base64_encode(curl, (const char*) vicon.data(), vicon.size(), &encode, &encodeSize); +// if (code == CURLE_OK && encodeSize) { +// icon = encode; +// free(encode); +// encode = NULL; +// result = true; +// } + } + } + } + + curl_easy_cleanup(curl); + curl = NULL; + } + + return result; +} + +p3FeedReaderThread::DownloadResult p3FeedReaderThread::download(const RsFeedReaderFeed &feed, std::string &content, std::string &icon, std::string &error) +{ +#ifdef FEEDREADER_DEBUG + std::cerr << "p3FeedReaderThread::download - feed " << feed.feedId << " (" << feed.name << ")" << std::endl; +#endif + + content.clear(); + error.clear(); + + DownloadResult result; + + CURL *curl = curl_easy_init(); + if (curl) { + std::string proxy; + if (feed.flag & RS_FEED_FLAG_STANDARD_PROXY) { + std::string standardProxyAddress; + uint16_t standardProxyPort; + if (mFeedReader->getStandardProxy(standardProxyAddress, standardProxyPort)) { + rs_sprintf(proxy, "%s:%u", standardProxyAddress.c_str(), standardProxyPort); + } + } else { + if (!feed.proxyAddress.empty() && feed.proxyPort) { + rs_sprintf(proxy, "%s:%u", feed.proxyAddress.c_str(), feed.proxyPort); + } + } + + curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0); + curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, progressCallback); + curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, this); + curl_easy_setopt(curl, CURLOPT_URL, feed.url.c_str()); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeFunctionString); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &content); + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1); + + if (!proxy.empty()) { + curl_easy_setopt(curl, CURLOPT_PROXY, proxy.c_str()); + } + + CURLcode code = curl_easy_perform(curl); + if (code == CURLE_OK) { + long response; + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response); + if (response == 200) { + char *contentType = NULL; + curl_easy_getinfo(curl, CURLINFO_CONTENT_TYPE, &contentType); + if (contentType && + (strnicmp(contentType, "text/xml", 8) == 0 || + strnicmp(contentType, "application/rss+xml", 19) == 0 || + strnicmp(contentType, "application/xml", 15) == 0 || + strnicmp(contentType, "application/xhtml+xml", 21) == 0)) { + /* ok */ + result = DOWNLOAD_SUCCESS; + } else { + result = DOWNLOAD_UNKNOWN_CONTENT_TYPE; + error = contentType ? contentType : ""; + } + } else if (response == 404) { + result = DOWNLOAD_NOT_FOUND; + } else { + result = DOWNLOAD_UNKOWN_RESPONSE_CODE; + rs_sprintf(error, "%ld", response); + } + } else { + result = DOWNLOAD_ERROR; + error = curl_easy_strerror(code); + } + + curl_easy_cleanup(curl); + curl = NULL; + + getFavicon(feed.url, proxy, icon); + } else { + result = DOWNLOAD_ERROR_INIT; + } + +#ifdef FEEDREADER_DEBUG + std::cerr << "p3FeedReaderThread::download - feed " << feed.feedId << " (" << feed.name << "), result " << result << ", error = " << error << std::endl; +#endif + + return result; +} + +/***************************************************************************/ +/****************************** Process ************************************/ +/***************************************************************************/ + +static bool convertOutput(xmlCharEncodingHandlerPtr charEncodingHandler, const xmlChar *output, std::string &text) +{ + if (!output) { + return false; + } + + if (charEncodingHandler == NULL || charEncodingHandler->output == NULL) { + return false; + } + + bool result = false; + int sizeOut = xmlStrlen(output) + 1; + int sizeIn = sizeOut * 2 - 1; + char *input = (char*) malloc(sizeIn * sizeof(char)); + + if (input) { + int temp = sizeOut - 1; + int ret = charEncodingHandler->output((xmlChar*) input, &sizeIn, (const xmlChar *) output, &temp); + if ((ret < 0) || (temp - sizeOut + 1)) { + if (ret < 0) { + std::cerr << "convertOutput: conversion wasn't successful." << std::endl; + } else { + std::cerr << "convertOutput: conversion wasn't successful. converted: " << temp << " octets." << std::endl; + } + } else { + text.assign(input, sizeIn); + result = true; + } + + free(input); + input = NULL; + } else { + std::cerr << "convertOutput: no mem" << std::endl; + } + + return result; +} + +static xmlNodePtr findNode(xmlNodePtr node, const char *name, bool children = false) +{ + if (node->name) { + if (xmlStrcasecmp (node->name, (xmlChar*) name) == 0) { + return node; + } + } + + xmlNodePtr nodeFound = NULL; + if (children) { + if (node->children) { + nodeFound = findNode(node->children, name, children); + if (nodeFound) { + return nodeFound; + } + } + } + + if (node->next) { + nodeFound = findNode(node->next, name, children); + if (nodeFound) { + return nodeFound; + } + } + + return NULL; +} + +static xmlNodePtr getNextItem(FeedFormat feedFormat, xmlNodePtr channel, xmlNodePtr item) +{ + if (!channel) { + return NULL; + } + + if (!item) { + switch (feedFormat) { + case FORMAT_RSS: + item = channel->children; + break; + case FORMAT_RDF: + item = channel->next; + break; + default: + return NULL; + } + } else { + item = item->next; + } + for (; item; item = item->next) { + if (item->type == XML_ELEMENT_NODE && xmlStrcasecmp (item->name, (xmlChar*) "item") == 0) { + break; + } + } + + return item; +} + +static bool getChildText(/*xmlCharEncodingHandlerPtr*/ void *charEncodingHandler, xmlNodePtr node, const char *childName, std::string &text) +{ + if (node == NULL || node->children == NULL) { + return FALSE; + } + + xmlNodePtr child = findNode(node->children, childName, true); + if (!child) { + return false; + } + + if (child->type != XML_ELEMENT_NODE) { + return false; + } + + if (!child->children) { + return false; + } + + if (child->children->type != XML_TEXT_NODE) { + return false; + } + + if (child->children->content) { + return convertOutput((xmlCharEncodingHandlerPtr) charEncodingHandler, child->children->content, text); + } + + return true; +} + +static void splitString(std::string s, std::vector &v, const char d) +{ + v.clear(); + + std::string::size_type p; + while ((p = s.find_first_of(d)) != std::string::npos) { + v.push_back(s.substr(0, p)); + s.erase(0, p + 1); + } + if (!s.empty()) { + v.push_back(s); + } +} + +static unsigned int ymdhms_to_seconds(int year, int mon, int day, int hour, int minute, int second) +{ + if (sizeof(time_t) == 4) + { + if ((time_t)-1 < 0) + { + if (year >= 2038) + { + year = 2038; + mon = 0; + day = 1; + hour = 0; + minute = 0; + second = 0; + } + } + else + { + if (year >= 2115) + { + year = 2115; + mon = 0; + day = 1; + hour = 0; + minute = 0; + second = 0; + } + } + } + + unsigned int ret = (day - 32075) /* days */ + + 1461L * (year + 4800L + (mon - 14) / 12) / 4 + + 367 * (mon - 2 - (mon - 14) / 12 * 12) / 12 + - 3 * ((year + 4900L + (mon - 14) / 12) / 100) / 4 + - 2440588; + ret = 24*ret + hour; /* hours */ + ret = 60*ret + minute; /* minutes */ + ret = 60*ret + second; /* seconds */ + + return ret; +} + +static const char haystack[37]="janfebmaraprmayjunjulaugsepoctnovdec"; + +// we follow the recommendation of rfc2822 to consider all +// obsolete time zones not listed here equivalent to "-0000" +static const struct { + const char tzName[4]; + int tzOffset; +} known_zones[] = { + { "UT", 0 }, + { "GMT", 0 }, + { "EST", -300 }, + { "EDT", -240 }, + { "CST", -360 }, + { "CDT", -300 }, + { "MST", -420 }, + { "MDT", -360 }, + { "PST", -480 }, + { "PDT", -420 }, + { { 0,0,0,0 }, 0 } +}; + +// copied from KRFCDate::parseDate +static time_t parseRFC822Date(const std::string &pubDate) +{ + if (pubDate.empty()) + return 0; + + // This parse a date in the form: + // Wednesday, 09-Nov-99 23:12:40 GMT + // or + // Sat, 01-Jan-2000 08:00:00 GMT + // or + // Sat, 01 Jan 2000 08:00:00 GMT + // or + // 01 Jan 99 22:00 +0100 (exceptions in rfc822/rfc2822) + // + // We ignore the weekday + // + time_t result = 0; + int offset = 0; + char *newPosStr; + const char *dateString = pubDate.c_str(); + int day = 0; + char monthStr[4]; + int month = -1; + int year = 0; + int hour = 0; + int minute = 0; + int second = 0; + + // Strip leading space + while(*dateString && isspace(*dateString)) + dateString++; + + // Strip weekday + while(*dateString && !isdigit(*dateString) && !isspace(*dateString)) + dateString++; + + // Strip trailing space + while(*dateString && isspace(*dateString)) + dateString++; + + if (!*dateString) + return result; // Invalid date + + if (isalpha(*dateString)) + { + // ' Nov 5 1994 18:15:30 GMT' + // Strip leading space + while(*dateString && isspace(*dateString)) + dateString++; + + for(int i=0; i < 3;i++) + { + if (!*dateString || (*dateString == '-') || isspace(*dateString)) + return result; // Invalid date + monthStr[i] = tolower(*dateString++); + } + monthStr[3] = '\0'; + + newPosStr = (char*)strstr(haystack, monthStr); + + if (!newPosStr) + return result; // Invalid date + + month = (newPosStr-haystack)/3; // Jan=00, Feb=01, Mar=02, .. + + if ((month < 0) || (month > 11)) + return result; // Invalid date + + while (*dateString && isalpha(*dateString)) + dateString++; // Skip rest of month-name + } + + // ' 09-Nov-99 23:12:40 GMT' + // ' 5 1994 18:15:30 GMT' + day = strtol(dateString, &newPosStr, 10); + dateString = newPosStr; + + if ((day < 1) || (day > 31)) + return result; // Invalid date; + + if (!*dateString) + return result; // Invalid date + + while(*dateString && (isspace(*dateString) || (*dateString == '-'))) + dateString++; + + if (month == -1) + { + for(int i=0; i < 3;i++) + { + if (!*dateString || (*dateString == '-') || isspace(*dateString)) + return result; // Invalid date + monthStr[i] = tolower(*dateString++); + } + monthStr[3] = '\0'; + + newPosStr = (char*)strstr(haystack, monthStr); + + if (!newPosStr) + return result; // Invalid date + + month = (newPosStr-haystack)/3; // Jan=00, Feb=01, Mar=02, .. + + if ((month < 0) || (month > 11)) + return result; // Invalid date + + while (*dateString && isalpha(*dateString)) + dateString++; // Skip rest of month-name + + } + + // '-99 23:12:40 GMT' + while(*dateString && (isspace(*dateString) || (*dateString == '-'))) + dateString++; + + if (!*dateString || !isdigit(*dateString)) + return result; // Invalid date + + // '99 23:12:40 GMT' + year = strtol(dateString, &newPosStr, 10); + dateString = newPosStr; + + // Y2K: Solve 2 digit years + if ((year >= 0) && (year < 50)) + year += 2000; + + if ((year >= 50) && (year < 100)) + year += 1900; // Y2K + + if ((year < 1900) || (year > 2500)) + return result; // Invalid date + + // Don't fail if the time is missing. + if (*dateString) + { + // ' 23:12:40 GMT' + if (!isspace(*dateString++)) + return result; // Invalid date + + hour = strtol(dateString, &newPosStr, 10); + dateString = newPosStr; + + if ((hour < 0) || (hour > 23)) + return result; // Invalid date + + if (!*dateString) + return result; // Invalid date + + // ':12:40 GMT' + if (*dateString++ != ':') + return result; // Invalid date + + minute = strtol(dateString, &newPosStr, 10); + dateString = newPosStr; + + if ((minute < 0) || (minute > 59)) + return result; // Invalid date + + if (!*dateString) + return result; // Invalid date + + // ':40 GMT' + if (*dateString != ':' && !isspace(*dateString)) + return result; // Invalid date + + // seconds are optional in rfc822 + rfc2822 + if (*dateString ==':') { + dateString++; + + second = strtol(dateString, &newPosStr, 10); + dateString = newPosStr; + + if ((second < 0) || (second > 59)) + return result; // Invalid date + } else { + dateString++; + } + + while(*dateString && isspace(*dateString)) + dateString++; + } + + // don't fail if the time zone is missing, some + // broken mail-/news-clients omit the time zone + if (*dateString) { + if ((strncasecmp(dateString, "gmt", 3) == 0) || + (strncasecmp(dateString, "utc", 3) == 0)) + { + dateString += 3; + while(*dateString && isspace(*dateString)) + dateString++; + } + + if ((*dateString == '+') || (*dateString == '-')) { + offset = strtol(dateString, &newPosStr, 10); + if (abs(offset) < 30) + { + dateString = newPosStr; + + offset = offset * 100; + + if (*dateString && *(dateString+1)) + { + dateString++; + int minutes = strtol(dateString, &newPosStr, 10); + if (offset > 0) + offset += minutes; + else + offset -= minutes; + } + } + + if ((offset < -9959) || (offset > 9959)) + return result; // Invalid date + + int sgn = (offset < 0)? -1:1; + offset = abs(offset); + offset = ((offset / 100)*60 + (offset % 100))*sgn; + } else { + for (int i=0; known_zones[i].tzName != 0; i++) { + if (0 == strncasecmp(dateString, known_zones[i].tzName, strlen(known_zones[i].tzName))) { + offset = known_zones[i].tzOffset; + break; + } + } + } + } + + result = ymdhms_to_seconds(year, month+1, day, hour, minute, second); + + // avoid negative time values + if ((offset > 0) && (offset > result)) + offset = 0; + + result -= offset*60; + + // If epoch 0 return epoch +1 which is Thu, 01-Jan-70 00:00:01 GMT + // This is so that parse error and valid epoch 0 return values won't + // be the same for sensitive applications... + if (result < 1) result = 1; + + return result; +} + +// copied and converted to std::string from KRFCDate::parseDateISO8601 +static time_t parseISO8601Date(const std::string &pubDate) +{ + if (pubDate.empty()) { + return 0; + } + + // These dates look like this: + // YYYY-MM-DDTHH:MM:SS + // But they may also have 0, 1 or 2 suffixes. + // Suffix 1: .secfrac (fraction of second) + // Suffix 2: Either 'Z' or +zone or -zone, where zone is HHMM + + unsigned int year = 0; + unsigned int month = 0; + unsigned int mday = 0; + unsigned int hour = 0; + unsigned int min = 0; + unsigned int sec = 0; + + int offset = 0; + + std::string input = pubDate; + + // First find the 'T' separator, if any. + int tPos = input.find('T'); + + // If there is no time, no month or no day specified, fill those missing + // fields so that 'input' matches YYYY-MM-DDTHH:MM:SS + if (-1 == tPos) { + int dashes = 0; + std::string::iterator it; + for (it = input.begin(); it != input.end(); ++it) { + if (*it == '-') { + ++dashes; + } + } + if (0 == dashes) { + input += "-01-01"; + } else if (1 == dashes) { + input += "-01"; + } + tPos = input.length(); + input += "T12:00:00"; + } + + // Now parse the date part. + + std::string dateString = input.substr(0, tPos);//.stripWhiteSpace(); + + std::string timeString = input.substr(tPos + 1);//.stripWhiteSpace(); + + std::vector l; + splitString(dateString, l, '-'); + + if (l.size() < 3) + return 0; + + sscanf(l[0].c_str(), "%u", &year); + sscanf(l[1].c_str(), "%u", &month); + sscanf(l[2].c_str(), "%u", &mday); + + // Z suffix means UTC. + if ('Z' == timeString[timeString.length() - 1]) { + timeString.erase(timeString.length() - 1, 1); + } + + // +zone or -zone suffix (offset from UTC). + + int plusPos = timeString.find_last_of('+'); + + if (-1 != plusPos) { + std::string offsetString = timeString.substr(plusPos + 1); + + unsigned int offsetHour; + unsigned int offsetMinute; + + sscanf(offsetString.substr(0, 1).c_str(), "%u", &offsetHour); + sscanf(offsetString.substr(offsetString.length() - 2).c_str(), "%u", &offsetMinute); + + offset = offsetHour * 60 + offsetMinute; + + timeString = timeString.substr(0, plusPos); + } else { + int minusPos = timeString.find_last_of('-'); + + if (-1 != minusPos) { + std::string offsetString = timeString.substr(minusPos + 1); + + unsigned int offsetHour; + unsigned int offsetMinute; + + sscanf(offsetString.substr(0, 1).c_str(), "%u", &offsetHour); + sscanf(offsetString.substr(offsetString.length() - 2).c_str(), "%u", &offsetMinute); + + timeString = timeString.substr(0, minusPos); + } + } + + // secfrac suffix. + int dotPos = timeString.find_last_of('.'); + + if (-1 != dotPos) { + timeString = timeString.substr(0, dotPos); + } + + // Now parse the time part. + + splitString(timeString, l, ':'); + + if (l.size() < 3) + return 0; + + sscanf(l[0].c_str(), "%u", &hour); + sscanf(l[1].c_str(), "%u", &min); + sscanf(l[2].c_str(), "%u", &sec); + + time_t result = ymdhms_to_seconds(year, month, mday, hour, min, sec); + + // avoid negative time values + if ((offset > 0) && (offset > result)) + offset = 0; + + result -= offset*60; + + // If epoch 0 return epoch +1 which is Thu, 01-Jan-70 00:00:01 GMT + // This is so that parse error and valid epoch 0 return values won't + // be the same for sensitive applications... + if (result < 1) result = 1; + + return result; +} + +p3FeedReaderThread::ProcessResult p3FeedReaderThread::process(const RsFeedReaderFeed &feed, std::list &entries, std::string &error) +{ +#ifdef FEEDREADER_DEBUG + std::cerr << "p3FeedReaderThread::process - feed " << feed.feedId << " (" << feed.name << ")" << std::endl; +#endif + + ProcessResult result = PROCESS_SUCCESS; + + xmlDocPtr document = xmlReadDoc((const xmlChar*) feed.content.c_str(), "", NULL, XML_PARSE_NOERROR | XML_PARSE_NOWARNING | XML_PARSE_COMPACT | XML_PARSE_NOCDATA); + if (document) { + xmlNodePtr root = xmlDocGetRootElement(document); + if (root) { + FeedFormat feedFormat; + if (xmlStrcmp (root->name, (xmlChar*) "rss") == 0) { + feedFormat = FORMAT_RSS; + } else if (xmlStrcmp (root->name, (xmlChar*) "rdf") != 0) { + feedFormat = FORMAT_RDF; + } else { + result = PROCESS_UNKNOWN_FORMAT; + error = "Only RSS or RDF supported"; + } + + if (result == PROCESS_SUCCESS) { + xmlNodePtr channel = findNode(root->children, "channel"); + if (channel) { + /* import header info */ + if (feed.flag & RS_FEED_FLAG_INFO_FROM_FEED) { + std::string title; + if (getChildText(mCharEncodingHandler, channel, "title", title) && !title.empty()) { + std::string::size_type p; + while ((p = title.find_first_of("\r\n")) != std::string::npos) { + title.erase(p, 1); + } + std::string description; + getChildText(mCharEncodingHandler, channel, "description", description); + mFeedReader->setFeedInfo(feed.feedId, title, description); + } + } + + /* get item count */ + xmlNodePtr node; + for (node = NULL; (node = getNextItem(feedFormat, channel, node)) != NULL; ) { + if (!isRunning()) { + break; + } + + std::string title; + if (!getChildText(mCharEncodingHandler, node, "title", title) || title.empty()) { + continue; + } + + /* remove newlines */ + std::string::size_type p; + while ((p = title.find_first_of("\r\n")) != std::string::npos) { + title.erase(p, 1); + } + + RsFeedReaderMsg *item = new RsFeedReaderMsg(); + item->msgId.clear(); // is calculated later + item->feedId = feed.feedId; + item->title = title; + + getChildText(mCharEncodingHandler, node, "link", item->link); + + long todo; // remove sid +// // remove sid= +// CString sLinkUpper = sLink; +// sLinkUpper.MakeUpper (); +// int nSIDStart = sLinkUpper.Find (TEXT("SID=")); +// if (nSIDStart != -1) { +// int nSIDEnd1 = sLinkUpper.Find (TEXT(";"), nSIDStart); +// int nSIDEnd2 = sLinkUpper.Find (TEXT("#"), nSIDStart); + +// if (nSIDEnd1 == -1) { +// nSIDEnd1 = sLinkUpper.GetLength (); +// } +// if (nSIDEnd2 == -1) { +// nSIDEnd2 = sLinkUpper.GetLength (); +// } + +// if (nSIDStart > 0 && sLinkUpper [nSIDStart - 1] == '&') { +// nSIDStart--; +// } + +// int nSIDEnd = min (nSIDEnd1, nSIDEnd2); +// sLink.Delete (nSIDStart, nSIDEnd - nSIDStart); +// } + + getChildText(mCharEncodingHandler, node, "author", item->author); + getChildText(mCharEncodingHandler, node, "description", item->description); + + std::string pubDate; + if (getChildText(mCharEncodingHandler, node, "pubdate", pubDate)) { + item->pubDate = parseRFC822Date(pubDate); + } + if (getChildText(mCharEncodingHandler, node, "date", pubDate)) { + item->pubDate = parseISO8601Date (pubDate); + } + + if (item->pubDate == 0) { + /* use current time */ + item->pubDate = time(NULL); + } + + entries.push_back(item); + } + } else { + result = PROCESS_UNKNOWN_FORMAT; + error = "Channel not found"; + } + } + } else { + result = PROCESS_UNKNOWN_FORMAT; + error = "Can't read document"; + } + + xmlFreeDoc(document); + } else { + result = PROCESS_ERROR_INIT; + } + +#ifdef FEEDREADER_DEBUG + std::cerr << "p3FeedReaderThread::process - feed " << feed.feedId << " (" << feed.name << "), result " << result << ", error = " << error << std::endl; +#endif + + return result; +} diff --git a/plugins/FeedReader/services/p3FeedReaderThread.h b/plugins/FeedReader/services/p3FeedReaderThread.h new file mode 100644 index 000000000..f27213bad --- /dev/null +++ b/plugins/FeedReader/services/p3FeedReaderThread.h @@ -0,0 +1,71 @@ +/**************************************************************** + * RetroShare GUI is distributed under the following license: + * + * Copyright (C) 2012 by Thunder + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + ****************************************************************/ + + #ifndef P3_FEEDREADERTHREAD +#define P3_FEEDREADERTHREAD + +#include "util/rsthreads.h" +#include + +class p3FeedReader; +class RsFeedReaderFeed; +class RsFeedReaderMsg; + +class p3FeedReaderThread : public RsThread +{ +public: + enum Type + { + DOWNLOAD, + PROCESS + }; + enum DownloadResult + { + DOWNLOAD_SUCCESS, + DOWNLOAD_ERROR_INIT, + DOWNLOAD_ERROR, + DOWNLOAD_UNKNOWN_CONTENT_TYPE, + DOWNLOAD_NOT_FOUND, + DOWNLOAD_UNKOWN_RESPONSE_CODE, + DOWNLOAD_INTERNAL_ERROR + }; + enum ProcessResult + { + PROCESS_SUCCESS, + PROCESS_ERROR_INIT, + PROCESS_UNKNOWN_FORMAT + }; + +public: + p3FeedReaderThread(p3FeedReader *feedReader, Type type); + +private: + virtual void run(); + + DownloadResult download(const RsFeedReaderFeed &feed, std::string &content, std::string &icon, std::string &error); + ProcessResult process(const RsFeedReaderFeed &feed, std::list &entries, std::string &error); + + p3FeedReader *mFeedReader; + Type mType; + /*xmlCharEncodingHandlerPtr*/ void *mCharEncodingHandler; +}; + +#endif diff --git a/plugins/FeedReader/services/rsFeedReaderItems.cc b/plugins/FeedReader/services/rsFeedReaderItems.cc new file mode 100644 index 000000000..8b6222994 --- /dev/null +++ b/plugins/FeedReader/services/rsFeedReaderItems.cc @@ -0,0 +1,388 @@ +/**************************************************************** + * RetroShare GUI is distributed under the following license: + * + * Copyright (C) 2012 by Thunder + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + ****************************************************************/ + +#include "serialiser/rsbaseserial.h" +#include "serialiser/rstlvbase.h" +#include "rsFeedReaderItems.h" + +/*************************************************************************/ + +RsFeedReaderFeed::RsFeedReaderFeed() : RsItem(RS_PKT_VERSION1, RS_PKT_CLASS_CONFIG, RS_PKT_TYPE_FEEDREADER_CONFIG, RS_PKT_SUBTYPE_FEEDREADER_FEED) +{ + clear(); +} + +void RsFeedReaderFeed::clear() +{ + feedId.clear(); + parentId.clear(); + name.clear(); + url.clear(); + user.clear(); + password.clear(); + proxyAddress.clear(); + proxyPort = 0; + updateInterval = 0; + lastUpdate = 0; + storageTime = 0; + flag = 0; + forumId.clear(); + description.clear(); + icon.clear(); + errorState = RS_FEED_ERRORSTATE_OK; + errorString.clear(); + + workstate = WAITING; + content.clear(); +} + +std::ostream &RsFeedReaderFeed::print(std::ostream &out, uint16_t /*indent*/) +{ + return out; +} + +uint32_t RsFeedReaderSerialiser::sizeFeed(RsFeedReaderFeed *item) +{ + uint32_t s = 8; /* header */ + s += GetTlvStringSize(item->feedId); + s += GetTlvStringSize(item->parentId); + s += GetTlvStringSize(item->url); + s += GetTlvStringSize(item->name); + s += GetTlvStringSize(item->description); + s += GetTlvStringSize(item->icon); + s += GetTlvStringSize(item->user); + s += GetTlvStringSize(item->password); + s += GetTlvStringSize(item->proxyAddress); + s += sizeof(uint16_t); /* proxyPort */ + s += sizeof(uint32_t); /* updateInterval */ + s += sizeof(time_t); /* lastscan */ + s += sizeof(uint32_t); /* storageTime */ + s += sizeof(uint32_t); /* flag */ + s += GetTlvStringSize(item->forumId); + s += sizeof(uint32_t); /* errorstate */ + s += GetTlvStringSize(item->errorString); + + return s; +} + +/* serialise the data to the buffer */ +bool RsFeedReaderSerialiser::serialiseFeed(RsFeedReaderFeed *item, void *data, uint32_t *pktsize) +{ + uint32_t tlvsize = sizeFeed(item); + uint32_t offset = 0; + + if (*pktsize < tlvsize) + return false; /* not enough space */ + + *pktsize = tlvsize; + + bool ok = true; + + ok &= setRsItemHeader(data, tlvsize, item->PacketId(), tlvsize); + + /* skip the header */ + offset += 8; + + /* add values */ + ok &= SetTlvString(data, tlvsize, &offset, TLV_TYPE_STR_GENID, item->feedId); + ok &= SetTlvString(data, tlvsize, &offset, TLV_TYPE_STR_VALUE, item->parentId); + ok &= SetTlvString(data, tlvsize, &offset, TLV_TYPE_STR_LINK, item->url); + ok &= SetTlvString(data, tlvsize, &offset, TLV_TYPE_STR_NAME, item->name); + ok &= SetTlvString(data, tlvsize, &offset, TLV_TYPE_STR_COMMENT, item->description); + ok &= SetTlvString(data, tlvsize, &offset, TLV_TYPE_STR_VALUE, item->icon); + ok &= SetTlvString(data, tlvsize, &offset, TLV_TYPE_STR_VALUE, item->user); + ok &= SetTlvString(data, tlvsize, &offset, TLV_TYPE_STR_VALUE, item->password); + ok &= SetTlvString(data, tlvsize, &offset, TLV_TYPE_STR_VALUE, item->proxyAddress); + ok &= setRawUInt16(data, tlvsize, &offset, item->proxyPort); + ok &= setRawUInt32(data, tlvsize, &offset, item->updateInterval); + ok &= setRawUInt32(data, tlvsize, &offset, item->lastUpdate); + ok &= setRawUInt32(data, tlvsize, &offset, item->storageTime); + ok &= setRawUInt32(data, tlvsize, &offset, item->flag); + ok &= SetTlvString(data, tlvsize, &offset, TLV_TYPE_STR_VALUE, item->forumId); + ok &= setRawUInt32(data, tlvsize, &offset, item->errorState); + ok &= SetTlvString(data, tlvsize, &offset, TLV_TYPE_STR_VALUE, item->errorString); + + if (offset != tlvsize) + { + ok = false; + std::cerr << "RsFeedReaderSerialiser::serialiseFeed() Size Error! " << std::endl; + } + + return ok; +} + +RsFeedReaderFeed *RsFeedReaderSerialiser::deserialiseFeed(void *data, uint32_t *pktsize) +{ + /* get the type and size */ + uint32_t rstype = getRsItemId(data); + uint32_t rssize = getRsItemSize(data); + + uint32_t offset = 0; + + if ((RS_PKT_VERSION1 != getRsItemVersion(rstype)) || + (RS_PKT_CLASS_CONFIG != getRsItemClass(rstype)) || + (RS_PKT_TYPE_FEEDREADER_CONFIG != getRsItemType(rstype)) || + (RS_PKT_SUBTYPE_FEEDREADER_FEED != getRsItemSubType(rstype))) + { + return NULL; /* wrong type */ + } + + if (*pktsize < rssize) /* check size */ + return NULL; /* not enough data */ + + /* set the packet length */ + *pktsize = rssize; + + bool ok = true; + + /* ready to load */ + RsFeedReaderFeed *item = new RsFeedReaderFeed(); + item->clear(); + + /* skip the header */ + offset += 8; + + /* get values */ + ok &= GetTlvString(data, rssize, &offset, TLV_TYPE_STR_GENID, item->feedId); + ok &= GetTlvString(data, rssize, &offset, TLV_TYPE_STR_VALUE, item->parentId); + ok &= GetTlvString(data, rssize, &offset, TLV_TYPE_STR_LINK, item->url); + ok &= GetTlvString(data, rssize, &offset, TLV_TYPE_STR_NAME, item->name); + ok &= GetTlvString(data, rssize, &offset, TLV_TYPE_STR_COMMENT, item->description); + ok &= GetTlvString(data, rssize, &offset, TLV_TYPE_STR_VALUE, item->icon); + ok &= GetTlvString(data, rssize, &offset, TLV_TYPE_STR_VALUE, item->user); + ok &= GetTlvString(data, rssize, &offset, TLV_TYPE_STR_VALUE, item->password); + ok &= GetTlvString(data, rssize, &offset, TLV_TYPE_STR_VALUE, item->proxyAddress); + ok &= getRawUInt16(data, rssize, &offset, &(item->proxyPort)); + ok &= getRawUInt32(data, rssize, &offset, &(item->updateInterval)); + ok &= getRawUInt32(data, rssize, &offset, (uint32_t*) &(item->lastUpdate)); + ok &= getRawUInt32(data, rssize, &offset, &(item->storageTime)); + ok &= getRawUInt32(data, rssize, &offset, &(item->flag)); + ok &= GetTlvString(data, rssize, &offset, TLV_TYPE_STR_VALUE, item->forumId); + ok &= getRawUInt32(data, rssize, &offset, &(item->errorState)); + ok &= GetTlvString(data, rssize, &offset, TLV_TYPE_STR_VALUE, item->errorString); + + if (offset != rssize) + { + /* error */ + delete item; + return NULL; + } + + if (!ok) + { + delete item; + return NULL; + } + + return item; +} + +/*************************************************************************/ + +RsFeedReaderMsg::RsFeedReaderMsg() : RsItem(RS_PKT_VERSION1, RS_PKT_CLASS_CONFIG, RS_PKT_TYPE_FEEDREADER_CONFIG, RS_PKT_SUBTYPE_FEEDREADER_MSG) +{ + clear(); +} + +void RsFeedReaderMsg::clear() +{ + msgId.clear(); + feedId.clear(); + title.clear(); + link.clear(); + author.clear(); + description.clear(); + pubDate = 0; + flag = 0; +} + +std::ostream &RsFeedReaderMsg::print(std::ostream &out, uint16_t /*indent*/) +{ + return out; +} + +uint32_t RsFeedReaderSerialiser::sizeMsg(RsFeedReaderMsg *item) +{ + uint32_t s = 8; /* header */ + s += GetTlvStringSize(item->msgId); + s += GetTlvStringSize(item->feedId); + s += GetTlvStringSize(item->title); + s += GetTlvStringSize(item->link); + s += GetTlvStringSize(item->author); + s += GetTlvStringSize(item->description); + s += sizeof(time_t); /* pubDate */ + s += sizeof(uint32_t); /* flag */ + + return s; +} + +/* serialise the data to the buffer */ +bool RsFeedReaderSerialiser::serialiseMsg(RsFeedReaderMsg *item, void *data, uint32_t *pktsize) +{ + uint32_t tlvsize = sizeMsg(item); + uint32_t offset = 0; + + if (*pktsize < tlvsize) + return false; /* not enough space */ + + *pktsize = tlvsize; + + bool ok = true; + + ok &= setRsItemHeader(data, tlvsize, item->PacketId(), tlvsize); + + /* skip the header */ + offset += 8; + + /* add values */ + ok &= SetTlvString(data, tlvsize, &offset, TLV_TYPE_STR_GENID, item->msgId); + ok &= SetTlvString(data, tlvsize, &offset, TLV_TYPE_STR_VALUE, item->feedId); + ok &= SetTlvString(data, tlvsize, &offset, TLV_TYPE_STR_NAME, item->title); + ok &= SetTlvString(data, tlvsize, &offset, TLV_TYPE_STR_LINK, item->link); + ok &= SetTlvString(data, tlvsize, &offset, TLV_TYPE_STR_VALUE, item->author); + ok &= SetTlvString(data, tlvsize, &offset, TLV_TYPE_STR_COMMENT, item->description); + ok &= setRawUInt32(data, tlvsize, &offset, item->pubDate); + ok &= setRawUInt32(data, tlvsize, &offset, item->flag); + + if (offset != tlvsize) + { + ok = false; + std::cerr << "RsFeedReaderSerialiser::serialiseMsg() Size Error! " << std::endl; + } + + return ok; +} + +RsFeedReaderMsg *RsFeedReaderSerialiser::deserialiseMsg(void *data, uint32_t *pktsize) +{ + /* get the type and size */ + uint32_t rstype = getRsItemId(data); + uint32_t rssize = getRsItemSize(data); + + uint32_t offset = 0; + + if ((RS_PKT_VERSION1 != getRsItemVersion(rstype)) || + (RS_PKT_CLASS_CONFIG != getRsItemClass(rstype)) || + (RS_PKT_TYPE_FEEDREADER_CONFIG != getRsItemType(rstype)) || + (RS_PKT_SUBTYPE_FEEDREADER_MSG != getRsItemSubType(rstype))) + { + return NULL; /* wrong type */ + } + + if (*pktsize < rssize) /* check size */ + return NULL; /* not enough data */ + + /* set the packet length */ + *pktsize = rssize; + + bool ok = true; + + /* ready to load */ + RsFeedReaderMsg *item = new RsFeedReaderMsg(); + item->clear(); + + /* skip the header */ + offset += 8; + + /* get values */ + ok &= GetTlvString(data, rssize, &offset, TLV_TYPE_STR_GENID, item->msgId); + ok &= GetTlvString(data, rssize, &offset, TLV_TYPE_STR_VALUE, item->feedId); + ok &= GetTlvString(data, rssize, &offset, TLV_TYPE_STR_NAME, item->title); + ok &= GetTlvString(data, rssize, &offset, TLV_TYPE_STR_LINK, item->link); + ok &= GetTlvString(data, rssize, &offset, TLV_TYPE_STR_VALUE, item->author); + ok &= GetTlvString(data, rssize, &offset, TLV_TYPE_STR_COMMENT, item->description); + ok &= getRawUInt32(data, rssize, &offset, (uint32_t*) &(item->pubDate)); + ok &= getRawUInt32(data, rssize, &offset, &(item->flag)); + + if (offset != rssize) + { + /* error */ + delete item; + return NULL; + } + + if (!ok) + { + delete item; + return NULL; + } + + return item; +} + +/*************************************************************************/ + +uint32_t RsFeedReaderSerialiser::size(RsItem *item) +{ + RsFeedReaderFeed *fi; + RsFeedReaderMsg *ei; + + if (NULL != (fi = dynamic_cast(item))) + { + return sizeFeed((RsFeedReaderFeed*) item); + } + if (NULL != (ei = dynamic_cast(item))) + { + return sizeMsg((RsFeedReaderMsg*) item); + } + + return 0; +} + +bool RsFeedReaderSerialiser::serialise(RsItem *item, void *data, uint32_t *pktsize) +{ + RsFeedReaderFeed *fi; + RsFeedReaderMsg *ei; + + if (NULL != (fi = dynamic_cast(item))) + { + return serialiseFeed((RsFeedReaderFeed*) item, data, pktsize); + } + if (NULL != (ei = dynamic_cast(item))) + { + return serialiseMsg((RsFeedReaderMsg*) item, data, pktsize); + } + + return false; +} + +RsItem *RsFeedReaderSerialiser::deserialise(void *data, uint32_t *pktsize) +{ + /* get the type and size */ + uint32_t rstype = getRsItemId(data); + + if ((RS_PKT_VERSION1 != getRsItemVersion(rstype)) || + (RS_PKT_CLASS_CONFIG != getRsItemClass(rstype)) || + (RS_PKT_TYPE_FEEDREADER_CONFIG != getRsItemType(rstype))) + { + return NULL; /* wrong type */ + } + + switch (getRsItemSubType(rstype)) + { + case RS_PKT_SUBTYPE_FEEDREADER_FEED: + return deserialiseFeed(data, pktsize); + case RS_PKT_SUBTYPE_FEEDREADER_MSG: + return deserialiseMsg(data, pktsize); + } + + return NULL; +} diff --git a/plugins/FeedReader/services/rsFeedReaderItems.h b/plugins/FeedReader/services/rsFeedReaderItems.h new file mode 100644 index 000000000..92c911864 --- /dev/null +++ b/plugins/FeedReader/services/rsFeedReaderItems.h @@ -0,0 +1,149 @@ +/**************************************************************** + * RetroShare GUI is distributed under the following license: + * + * Copyright (C) 2012 by Thunder + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + ****************************************************************/ + + #ifndef RS_FEEDREADER_ITEMS_H +#define RS_FEEDREADER_ITEMS_H + +#include "serialiser/rsserial.h" +#include "serialiser/rstlvtypes.h" + +#include "p3FeedReader.h" + +const uint8_t RS_PKT_SUBTYPE_FEEDREADER_FEED = 0x02; +const uint8_t RS_PKT_SUBTYPE_FEEDREADER_MSG = 0x03; + +/**************************************************************************/ + +#define RS_FEED_ERRORSTATE_OK 0 +#define RS_FEED_ERRORSTATE_DOWNLOAD_INTERNAL_ERROR 1 +#define RS_FEED_ERRORSTATE_DOWNLOAD_ERROR 2 +#define RS_FEED_ERRORSTATE_DOWNLOAD_UNKNOWN_CONTENT_TYPE 3 +#define RS_FEED_ERRORSTATE_DOWNLOAD_NOT_FOUND 4 +#define RS_FEED_ERRORSTATE_DOWNLOAD_UNKOWN_RESPONSE_CODE 5 + +#define RS_FEED_ERRORSTATE_PROCESS_INTERNAL_ERROR 50 + +#define RS_FEED_ERRORSTATE_FORUM_CREATE 100 +#define RS_FEED_ERRORSTATE_FORUM_NOT_FOUND 101 +#define RS_FEED_ERRORSTATE_FORUM_NO_ADMIN 102 +#define RS_FEED_ERRORSTATE_FORUM_NO_ANONYMOUS_FORUM 103 + +#define RS_FEED_FLAG_FOLDER 0x001 +#define RS_FEED_FLAG_INFO_FROM_FEED 0x002 +#define RS_FEED_FLAG_STANDARD_STORAGE_TIME 0x004 +#define RS_FEED_FLAG_STANDARD_UPDATE_INTERVAL 0x008 +#define RS_FEED_FLAG_STANDARD_PROXY 0x010 +#define RS_FEED_FLAG_AUTHENTICATION 0x020 +#define RS_FEED_FLAG_DEACTIVATED 0x040 +#define RS_FEED_FLAG_FORUM 0x080 +#define RS_FEED_FLAG_UPDATE_FORUM_INFO 0x100 + +class RsFeedReaderFeed : public RsItem +{ +public: + enum WorkState { + WAITING, + WAITING_TO_DOWNLOAD, + DOWNLOADING, + WAITING_TO_PROCESS, + PROCESSING + }; + +public: + RsFeedReaderFeed(); + virtual ~RsFeedReaderFeed() {} + + virtual void clear(); + virtual std::ostream& print(std::ostream &out, uint16_t indent = 0); + + std::string feedId; + std::string parentId; + std::string name; + std::string url; + std::string user; + std::string password; + std::string proxyAddress; + uint16_t proxyPort; + uint32_t updateInterval; + time_t lastUpdate; + uint32_t flag; // RS_FEED_FLAG_... + std::string forumId; + uint32_t storageTime; + std::string description; + std::string icon; + uint32_t errorState; + std::string errorString; + + /* Not Serialised */ + WorkState workstate; + std::string content; + + std::map mMsgs; +}; + +#define RS_FEEDMSG_FLAG_DELETED 1 +#define RS_FEEDMSG_FLAG_NEW 2 +#define RS_FEEDMSG_FLAG_READ 4 + +class RsFeedReaderMsg : public RsItem +{ +public: + RsFeedReaderMsg(); + virtual ~RsFeedReaderMsg() {} + + virtual void clear(); + virtual std::ostream& print(std::ostream &out, uint16_t indent = 0); + + std::string msgId; + std::string feedId; + std::string title; + std::string link; + std::string author; + std::string description; + time_t pubDate; + uint32_t flag; // RS_FEEDMSG_FLAG_... +}; + +class RsFeedReaderSerialiser: public RsSerialType +{ +public: + RsFeedReaderSerialiser() : RsSerialType(RS_PKT_VERSION1, RS_PKT_CLASS_CONFIG, RS_PKT_TYPE_FEEDREADER_CONFIG) {} + virtual ~RsFeedReaderSerialiser() {} + + virtual uint32_t size(RsItem *item); + virtual bool serialise(RsItem *item, void *data, uint32_t *size); + virtual RsItem *deserialise(void *data, uint32_t *size); + +private: + /* For RS_PKT_SUBTYPE_FEEDREADER_FEED */ + virtual uint32_t sizeFeed(RsFeedReaderFeed *item); + virtual bool serialiseFeed(RsFeedReaderFeed *item, void *data, uint32_t *size); + virtual RsFeedReaderFeed *deserialiseFeed(void *data, uint32_t *size); + + /* For RS_PKT_SUBTYPE_FEEDREADER_MSG */ + virtual uint32_t sizeMsg(RsFeedReaderMsg *item); + virtual bool serialiseMsg(RsFeedReaderMsg *item, void *data, uint32_t *size); + virtual RsFeedReaderMsg *deserialiseMsg(void *data, uint32_t *size); +}; + +/**************************************************************************/ + +#endif diff --git a/plugins/plugins.pro b/plugins/plugins.pro index 37b804d8c..a8840da6e 100644 --- a/plugins/plugins.pro +++ b/plugins/plugins.pro @@ -2,4 +2,5 @@ TEMPLATE = subdirs SUBDIRS += \ LinksCloud \ - VOIP + VOIP \ + FeedReader diff --git a/retroshare-gui/src/gui/MainWindow.cpp b/retroshare-gui/src/gui/MainWindow.cpp index 98b95a182..0d97b5fd5 100644 --- a/retroshare-gui/src/gui/MainWindow.cpp +++ b/retroshare-gui/src/gui/MainWindow.cpp @@ -323,10 +323,7 @@ MainWindow::MainWindow(QWidget* parent, Qt::WFlags flags) addAction(new QAction(QIcon(IMAGE_UNFINISHED), tr("Unfinished"), ui.toolBar), SLOT(showApplWindow())); #endif - if (activatePage((Page) Settings->getLastPageInMainWindow()) == false) { - /* Select the first action */ - grp->actions()[0]->setChecked(true); - } + ui.stackPages->setCurrentIndex(Settings->getLastPageInMainWindow()); /** StatusBar section ********/ /* initialize combobox in status bar */ @@ -388,7 +385,7 @@ MainWindow::MainWindow(QWidget* parent, Qt::WFlags flags) /** Destructor. */ MainWindow::~MainWindow() { - Settings->setLastPageInMainWindow(getActivatePage()); + Settings->setLastPageInMainWindow(ui.stackPages->currentIndex()); delete peerstatus; delete natstatus;