Change project directory structure.

Change project directory structure according to application structure.
Change 'nox' configuration option to something more meaningful 'nogui'.
Rename 'Icons' folder to 'icons' (similar to other folders).
Partially add 'nowebui' option support.
Remove QConf project file.
This commit is contained in:
Vladimir Golovnev (Glassez)
2015-01-18 15:13:06 +03:00
parent e4c7f52bb3
commit ff9a281b72
797 changed files with 841 additions and 829 deletions

View File

@@ -0,0 +1,645 @@
/*
* Bittorrent Client using Qt4 and libtorrent.
* Copyright (C) 2010 Christophe Dumez
*
* 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.
*
* In addition, as a special exception, the copyright holders give permission to
* link this program with the OpenSSL project's "OpenSSL" library (or with
* modified versions of it that use the same license as the "OpenSSL" library),
* and distribute the linked executables. You must obey the GNU General Public
* License in all respects for all of the code used other than "OpenSSL". If you
* modify file(s), you may extend this exception to your version of the file(s),
* but you are not obligated to do so. If you do not wish to do so, delete this
* exception statement from your version.
*
* Contact : chris@qbittorrent.org
*/
#include <QMessageBox>
#include <QFileDialog>
#include <QDebug>
#include <QMenu>
#include <QCursor>
#include "automatedrssdownloader.h"
#include "ui_automatedrssdownloader.h"
#include "rssdownloadrulelist.h"
#include "preferences.h"
#include "rssmanager.h"
#include "rssfeed.h"
#include "iconprovider.h"
#include "autoexpandabledialog.h"
#include "fs_utils.h"
AutomatedRssDownloader::AutomatedRssDownloader(const QWeakPointer<RssManager>& manager, QWidget *parent) :
QDialog(parent),
ui(new Ui::AutomatedRssDownloader),
m_manager(manager), m_editedRule(0)
{
ui->setupUi(this);
// Icons
ui->removeRuleBtn->setIcon(IconProvider::instance()->getIcon("list-remove"));
ui->addRuleBtn->setIcon(IconProvider::instance()->getIcon("list-add"));
// Ui Settings
ui->listRules->setSortingEnabled(true);
ui->listRules->setSelectionMode(QAbstractItemView::ExtendedSelection);
ui->treeMatchingArticles->setSortingEnabled(true);
ui->hsplitter->setCollapsible(0, false);
ui->hsplitter->setCollapsible(1, false);
ui->hsplitter->setCollapsible(2, true); // Only the preview list is collapsible
bool ok; Q_UNUSED(ok);
ok = connect(ui->checkRegex, SIGNAL(toggled(bool)), SLOT(updateFieldsToolTips(bool)));
Q_ASSERT(ok);
ok = connect(ui->listRules, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(displayRulesListMenu(const QPoint&)));
Q_ASSERT(ok);
m_ruleList = manager.toStrongRef()->downloadRules();
m_editableRuleList = new RssDownloadRuleList; // Read rule list from disk
m_episodeValidator = new QRegExpValidator(
QRegExp("^(^[1-9]{1,1}\\d{0,3}x([1-9]{1,1}\\d{0,3}(-([1-9]{1,1}\\d{0,3})?)?;){1,}){1,1}",
Qt::CaseInsensitive),
ui->lineEFilter);
ui->lineEFilter->setValidator(m_episodeValidator);
QString tip = "<p>" + tr("Matches articles based on episode filter.") + "</p><p><b>" + tr("Example: ") +
"1x2;8-15;5;30-;</b>" + tr(" will match 2, 5, 8 through 15, 30 and onward episodes of season one", "example X will match") + "</p>";
tip += "<p>" + tr("Episode filter rules: ") + "</p><ul><li>" + tr("Season number is a mandatory non-zero value") + "</li>" +
"<li>" + tr("Episode number is a mandatory non-zero value") + "</li>" +
"<li>" + tr("Filter must end with semicolon") + "</li>" +
"<li>" + tr("Three range types for episodes are supported: ") + "</li>" + "<li><ul>"
"<li>" + tr("Single number: <b>1x25;</b> matches episode 25 of season one") + "</li>" +
"<li>" + tr("Normal range: <b>1x25-40;</b> matches episodes 25 through 40 of season one") + "</li>" +
"<li>" + tr("Infinite range: <b>1x25-;</b> matches episodes 25 and upward of season one") + "</li>" + "</ul></li></ul>";
ui->lineEFilter->setToolTip(tip);
initLabelCombobox();
loadFeedList();
loadSettings();
ok = connect(ui->listRules, SIGNAL(itemSelectionChanged()), SLOT(updateRuleDefinitionBox()));
Q_ASSERT(ok);
ok = connect(ui->listRules, SIGNAL(itemSelectionChanged()), SLOT(updateFeedList()));
Q_ASSERT(ok);
ok = connect(ui->listFeeds, SIGNAL(itemChanged(QListWidgetItem*)), SLOT(handleFeedCheckStateChange(QListWidgetItem*)));
Q_ASSERT(ok);
// Update matching articles when necessary
ok = connect(ui->lineContains, SIGNAL(textEdited(QString)), SLOT(updateMatchingArticles()));
Q_ASSERT(ok);
ok = connect(ui->lineContains, SIGNAL(textEdited(QString)), SLOT(updateMustLineValidity()));
Q_ASSERT(ok);
ok = connect(ui->lineNotContains, SIGNAL(textEdited(QString)), SLOT(updateMatchingArticles()));
Q_ASSERT(ok);
ok = connect(ui->lineNotContains, SIGNAL(textEdited(QString)), SLOT(updateMustNotLineValidity()));
Q_ASSERT(ok);
ok = connect(ui->checkRegex, SIGNAL(stateChanged(int)), SLOT(updateMatchingArticles()));
Q_ASSERT(ok);
ok = connect(ui->checkRegex, SIGNAL(stateChanged(int)), SLOT(updateMustLineValidity()));
Q_ASSERT(ok);
ok = connect(ui->checkRegex, SIGNAL(stateChanged(int)), SLOT(updateMustNotLineValidity()));
Q_ASSERT(ok);
ok = connect(this, SIGNAL(finished(int)), SLOT(on_finished(int)));
Q_ASSERT(ok);
ok = connect(ui->lineEFilter, SIGNAL(textEdited(QString)), SLOT(updateMatchingArticles()));
Q_ASSERT(ok);
editHotkey = new QShortcut(QKeySequence("F2"), ui->listRules, 0, 0, Qt::WidgetShortcut);
ok = connect(editHotkey, SIGNAL(activated()), SLOT(renameSelectedRule()));
Q_ASSERT(ok);
ok = connect(ui->listRules, SIGNAL(doubleClicked(QModelIndex)), SLOT(renameSelectedRule()));
Q_ASSERT(ok);
deleteHotkey = new QShortcut(QKeySequence(QKeySequence::Delete), ui->listRules, 0, 0, Qt::WidgetShortcut);
ok = connect(deleteHotkey, SIGNAL(activated()), SLOT(on_removeRuleBtn_clicked()));
Q_ASSERT(ok);
updateRuleDefinitionBox();
updateFeedList();
}
AutomatedRssDownloader::~AutomatedRssDownloader()
{
qDebug() << Q_FUNC_INFO;
delete editHotkey;
delete deleteHotkey;
delete ui;
delete m_editableRuleList;
delete m_episodeValidator;
}
void AutomatedRssDownloader::loadSettings()
{
// load dialog geometry
const Preferences* const pref = Preferences::instance();
restoreGeometry(pref->getRssGeometry());
ui->checkEnableDownloader->setChecked(pref->isRssDownloadingEnabled());
ui->hsplitter->restoreState(pref->getRssHSplitterSizes());
// Display download rules
loadRulesList();
}
void AutomatedRssDownloader::saveSettings()
{
Preferences::instance()->setRssDownloadingEnabled(ui->checkEnableDownloader->isChecked());
// Save dialog geometry
Preferences* const pref = Preferences::instance();
pref->setRssGeometry(saveGeometry());
pref->setRssHSplitterSizes(ui->hsplitter->saveState());
}
void AutomatedRssDownloader::loadRulesList()
{
// Make sure we save the current item before clearing
if (m_editedRule) {
saveEditedRule();
}
ui->listRules->clear();
foreach (const QString &rule_name, m_editableRuleList->ruleNames()) {
QListWidgetItem *item = new QListWidgetItem(rule_name, ui->listRules);
item->setFlags(item->flags()|Qt::ItemIsUserCheckable);
if (m_editableRuleList->getRule(rule_name)->isEnabled())
item->setCheckState(Qt::Checked);
else
item->setCheckState(Qt::Unchecked);
}
if (ui->listRules->count() > 0 && !ui->listRules->currentItem())
ui->listRules->setCurrentRow(0);
}
void AutomatedRssDownloader::loadFeedList()
{
const Preferences* const pref = Preferences::instance();
const QStringList feed_aliases = pref->getRssFeedsAliases();
const QStringList feed_urls = pref->getRssFeedsUrls();
QStringList existing_urls;
for (int i=0; i<feed_aliases.size(); ++i) {
QString feed_url = feed_urls.at(i);
feed_url = feed_url.split("\\").last();
qDebug() << Q_FUNC_INFO << feed_url;
if (existing_urls.contains(feed_url)) continue;
QListWidgetItem *item = new QListWidgetItem(feed_aliases.at(i), ui->listFeeds);
item->setData(Qt::UserRole, feed_url);
item->setFlags(item->flags()|Qt::ItemIsUserCheckable);
existing_urls << feed_url;
}
}
void AutomatedRssDownloader::updateFeedList()
{
disconnect(ui->listFeeds, SIGNAL(itemChanged(QListWidgetItem*)), this, SLOT(handleFeedCheckStateChange(QListWidgetItem*)));
for (int i=0; i<ui->listFeeds->count(); ++i) {
QListWidgetItem *item = ui->listFeeds->item(i);
const QString feed_url = item->data(Qt::UserRole).toString();
bool all_enabled = false;
foreach (const QListWidgetItem *ruleItem, ui->listRules->selectedItems()) {
RssDownloadRulePtr rule = m_editableRuleList->getRule(ruleItem->text());
if (!rule) continue;
qDebug() << "Rule" << rule->name() << "affects" << rule->rssFeeds().size() << "feeds.";
foreach (QString test, rule->rssFeeds()) {
qDebug() << "Feed is " << test;
}
if (rule->rssFeeds().contains(feed_url)) {
qDebug() << "Rule " << rule->name() << " affects feed " << feed_url;
all_enabled = true;
} else {
qDebug() << "Rule " << rule->name() << " does NOT affect feed " << feed_url;
all_enabled = false;
break;
}
}
if (all_enabled)
item->setCheckState(Qt::Checked);
else
item->setCheckState(Qt::Unchecked);
}
ui->listFeeds->setEnabled(!ui->listRules->selectedItems().isEmpty());
connect(ui->listFeeds, SIGNAL(itemChanged(QListWidgetItem*)), SLOT(handleFeedCheckStateChange(QListWidgetItem*)));
updateMatchingArticles();
}
bool AutomatedRssDownloader::isRssDownloaderEnabled() const
{
return ui->checkEnableDownloader->isChecked();
}
void AutomatedRssDownloader::updateRuleDefinitionBox()
{
qDebug() << Q_FUNC_INFO;
// Save previous rule first
saveEditedRule();
// Update rule definition box
const QList<QListWidgetItem*> selection = ui->listRules->selectedItems();
if (selection.count() == 1) {
m_editedRule = selection.first();
RssDownloadRulePtr rule = getCurrentRule();
if (rule) {
ui->lineContains->setText(rule->mustContain());
ui->lineNotContains->setText(rule->mustNotContain());
QString ep = rule->episodeFilter();
if (!ep.isEmpty())
ui->lineEFilter->setText(ep);
else
ui->lineEFilter->clear();
ui->saveDiffDir_check->setChecked(!rule->savePath().isEmpty());
ui->lineSavePath->setText(fsutils::toNativePath(rule->savePath()));
ui->checkRegex->setChecked(rule->useRegex());
if (rule->label().isEmpty()) {
ui->comboLabel->setCurrentIndex(-1);
ui->comboLabel->clearEditText();
} else {
ui->comboLabel->setCurrentIndex(ui->comboLabel->findText(rule->label()));
}
ui->comboAddPaused->setCurrentIndex(rule->addPaused());
ui->spinIgnorePeriod->setValue(rule->ignoreDays());
QDateTime dateTime = rule->lastMatch();
QString lMatch = tr("Last match: ");
if (dateTime.isValid())
lMatch += QString::number(dateTime.daysTo(QDateTime::currentDateTime())) + tr(" days ago.");
else
lMatch += tr("Unknown");
ui->lblLastMatch->setText(lMatch);
updateMustLineValidity();
updateMustNotLineValidity();
} else {
// New rule
clearRuleDefinitionBox();
ui->lineContains->setText(selection.first()->text());
ui->comboAddPaused->setCurrentIndex(0);
}
updateFieldsToolTips(ui->checkRegex->isChecked());
// Enable
ui->ruleDefBox->setEnabled(true);
} else {
m_editedRule = 0;
// Clear
clearRuleDefinitionBox();
ui->ruleDefBox->setEnabled(false);
}
}
void AutomatedRssDownloader::clearRuleDefinitionBox()
{
ui->lineContains->clear();
ui->lineNotContains->clear();
ui->saveDiffDir_check->setChecked(false);
ui->lineSavePath->clear();
ui->comboLabel->clearEditText();
ui->checkRegex->setChecked(false);
ui->spinIgnorePeriod->setValue(0);
updateFieldsToolTips(ui->checkRegex->isChecked());
updateMustLineValidity();
updateMustNotLineValidity();
}
RssDownloadRulePtr AutomatedRssDownloader::getCurrentRule() const
{
QListWidgetItem * current_item = ui->listRules->currentItem();
if (current_item)
return m_editableRuleList->getRule(current_item->text());
return RssDownloadRulePtr();
}
void AutomatedRssDownloader::initLabelCombobox()
{
// Load custom labels
const QStringList customLabels = Preferences::instance()->getTorrentLabels();
foreach (const QString& label, customLabels) {
ui->comboLabel->addItem(label);
}
}
void AutomatedRssDownloader::saveEditedRule()
{
if (!m_editedRule) return;
qDebug() << Q_FUNC_INFO << m_editedRule;
if (ui->listRules->findItems(m_editedRule->text(), Qt::MatchExactly).isEmpty()) {
qDebug() << "Could not find rule" << m_editedRule->text() << "in the UI list";
qDebug() << "Probably removed the item, no need to save it";
return;
}
RssDownloadRulePtr rule = m_editableRuleList->getRule(m_editedRule->text());
if (!rule) {
rule = RssDownloadRulePtr(new RssDownloadRule);
rule->setName(m_editedRule->text());
}
if (m_editedRule->checkState() == Qt::Unchecked)
rule->setEnabled(false);
else
rule->setEnabled(true);
rule->setUseRegex(ui->checkRegex->isChecked());
rule->setMustContain(ui->lineContains->text());
rule->setMustNotContain(ui->lineNotContains->text());
rule->setEpisodeFilter(ui->lineEFilter->text());
if (ui->saveDiffDir_check->isChecked())
rule->setSavePath(ui->lineSavePath->text());
else
rule->setSavePath("");
rule->setLabel(ui->comboLabel->currentText());
rule->setAddPaused(RssDownloadRule::AddPausedState(ui->comboAddPaused->currentIndex()));
// Save new label
if (!rule->label().isEmpty())
Preferences::instance()->addTorrentLabel(rule->label());
rule->setIgnoreDays(ui->spinIgnorePeriod->value());
//rule->setRssFeeds(getSelectedFeeds());
// Save it
m_editableRuleList->saveRule(rule);
}
void AutomatedRssDownloader::on_addRuleBtn_clicked()
{
// Ask for a rule name
const QString rule_name = AutoExpandableDialog::getText(this, tr("New rule name"), tr("Please type the name of the new download rule."));
if (rule_name.isEmpty()) return;
// Check if this rule name already exists
if (m_editableRuleList->getRule(rule_name)) {
QMessageBox::warning(this, tr("Rule name conflict"), tr("A rule with this name already exists, please choose another name."));
return;
}
// Add the new rule to the list
QListWidgetItem * item = new QListWidgetItem(rule_name, ui->listRules);
item->setFlags(item->flags()|Qt::ItemIsUserCheckable);
item->setCheckState(Qt::Checked); // Enable as a default
ui->listRules->clearSelection();
ui->listRules->setCurrentItem(item);
}
void AutomatedRssDownloader::on_removeRuleBtn_clicked()
{
const QList<QListWidgetItem*> selection = ui->listRules->selectedItems();
if (selection.isEmpty()) return;
// Ask for confirmation
QString confirm_text;
if (selection.count() == 1)
confirm_text = tr("Are you sure you want to remove the download rule named %1?").arg(selection.first()->text());
else
confirm_text = tr("Are you sure you want to remove the selected download rules?");
if (QMessageBox::question(this, tr("Rule deletion confirmation"), confirm_text, QMessageBox::Yes, QMessageBox::No) != QMessageBox::Yes)
return;
foreach (QListWidgetItem *item, selection) {
// Actually remove the item
ui->listRules->removeItemWidget(item);
const QString rule_name = item->text();
// Clean up memory
delete item;
qDebug("Removed item for the UI list");
// Remove it from the m_editableRuleList
m_editableRuleList->removeRule(rule_name);
}
}
void AutomatedRssDownloader::on_browseSP_clicked()
{
QString save_path = QFileDialog::getExistingDirectory(this, tr("Destination directory"), QDir::homePath());
if (!save_path.isEmpty())
ui->lineSavePath->setText(fsutils::toNativePath(save_path));
}
void AutomatedRssDownloader::on_exportBtn_clicked()
{
if (m_editableRuleList->isEmpty()) {
QMessageBox::warning(this, tr("Invalid action"), tr("The list is empty, there is nothing to export."));
return;
}
// Ask for a save path
QString save_path = QFileDialog::getSaveFileName(this, tr("Where would you like to save the list?"), QDir::homePath(), tr("Rules list (*.rssrules)"));
if (save_path.isEmpty()) return;
if (!save_path.endsWith(".rssrules", Qt::CaseInsensitive))
save_path += ".rssrules";
if (!m_editableRuleList->serialize(save_path)) {
QMessageBox::warning(this, tr("I/O Error"), tr("Failed to create the destination file"));
return;
}
}
void AutomatedRssDownloader::on_importBtn_clicked()
{
// Ask for filter path
QString load_path = QFileDialog::getOpenFileName(this, tr("Please point to the RSS download rules file"), QDir::homePath(), tr("Rules list")+QString(" (*.rssrules *.filters)"));
if (load_path.isEmpty() || !QFile::exists(load_path)) return;
// Load it
if (!m_editableRuleList->unserialize(load_path)) {
QMessageBox::warning(this, tr("Import Error"), tr("Failed to import the selected rules file"));
return;
}
// Reload the rule list
loadRulesList();
}
void AutomatedRssDownloader::displayRulesListMenu(const QPoint &pos)
{
Q_UNUSED(pos);
QMenu menu;
QAction *addAct = menu.addAction(IconProvider::instance()->getIcon("list-add"), tr("Add new rule..."));
QAction *delAct = 0;
QAction *renameAct = 0;
const QList<QListWidgetItem*> selection = ui->listRules->selectedItems();
if (!selection.isEmpty()) {
if (selection.count() == 1) {
delAct = menu.addAction(IconProvider::instance()->getIcon("list-remove"), tr("Delete rule"));
menu.addSeparator();
renameAct = menu.addAction(IconProvider::instance()->getIcon("edit-rename"), tr("Rename rule..."));
} else {
delAct = menu.addAction(IconProvider::instance()->getIcon("list-remove"), tr("Delete selected rules"));
}
}
QAction *act = menu.exec(QCursor::pos());
if (!act) return;
if (act == addAct) {
on_addRuleBtn_clicked();
return;
}
if (act == delAct) {
on_removeRuleBtn_clicked();
return;
}
if (act == renameAct) {
renameSelectedRule();
return;
}
}
void AutomatedRssDownloader::renameSelectedRule()
{
const QList<QListWidgetItem*> selection = ui->listRules->selectedItems();
if (selection.isEmpty())
return;
QListWidgetItem *item = selection.first();
forever {
QString new_name = AutoExpandableDialog::getText(this, tr("Rule renaming"), tr("Please type the new rule name"), QLineEdit::Normal, item->text());
new_name = new_name.trimmed();
if (new_name.isEmpty()) return;
if (m_editableRuleList->ruleNames().contains(new_name, Qt::CaseInsensitive)) {
QMessageBox::warning(this, tr("Rule name conflict"), tr("A rule with this name already exists, please choose another name."));
} else {
// Rename the rule
m_editableRuleList->renameRule(item->text(), new_name);
item->setText(new_name);
return;
}
}
}
void AutomatedRssDownloader::handleFeedCheckStateChange(QListWidgetItem *feed_item)
{
if (ui->ruleDefBox->isEnabled()) {
// Make sure the current rule is saved
saveEditedRule();
}
const QString feed_url = feed_item->data(Qt::UserRole).toString();
foreach (QListWidgetItem* rule_item, ui->listRules->selectedItems()) {
RssDownloadRulePtr rule = m_editableRuleList->getRule(rule_item->text());
Q_ASSERT(rule);
QStringList affected_feeds = rule->rssFeeds();
if (feed_item->checkState() == Qt::Checked) {
if (!affected_feeds.contains(feed_url))
affected_feeds << feed_url;
} else {
if (affected_feeds.contains(feed_url))
affected_feeds.removeOne(feed_url);
}
// Save the updated rule
if (affected_feeds.size() != rule->rssFeeds().size()) {
rule->setRssFeeds(affected_feeds);
m_editableRuleList->saveRule(rule);
}
}
// Update Matching articles
updateMatchingArticles();
}
void AutomatedRssDownloader::updateMatchingArticles()
{
ui->treeMatchingArticles->clear();
RssManagerPtr manager = m_manager.toStrongRef();
if (!manager)
return;
const QHash<QString, RssFeedPtr> all_feeds = manager->getAllFeedsAsHash();
foreach (const QListWidgetItem *rule_item, ui->listRules->selectedItems()) {
RssDownloadRulePtr rule = m_editableRuleList->getRule(rule_item->text());
if (!rule) continue;
foreach (const QString &feed_url, rule->rssFeeds()) {
qDebug() << Q_FUNC_INFO << feed_url;
if (!all_feeds.contains(feed_url)) continue; // Feed was removed
RssFeedPtr feed = all_feeds.value(feed_url);
Q_ASSERT(feed);
if (!feed) continue;
const QStringList matching_articles = rule->findMatchingArticles(feed);
if (!matching_articles.isEmpty())
addFeedArticlesToTree(feed, matching_articles);
}
}
}
void AutomatedRssDownloader::addFeedArticlesToTree(const RssFeedPtr& feed, const QStringList &articles)
{
// Check if this feed is already in the tree
QTreeWidgetItem *treeFeedItem = 0;
for (int i=0; i<ui->treeMatchingArticles->topLevelItemCount(); ++i) {
QTreeWidgetItem *item = ui->treeMatchingArticles->topLevelItem(i);
if (item->data(0, Qt::UserRole).toString() == feed->url()) {
treeFeedItem = item;
break;
}
}
// If there is none, create it
if (!treeFeedItem) {
treeFeedItem = new QTreeWidgetItem(QStringList() << feed->displayName());
treeFeedItem->setToolTip(0, feed->displayName());
QFont f = treeFeedItem->font(0);
f.setBold(true);
treeFeedItem->setFont(0, f);
treeFeedItem->setData(0, Qt::DecorationRole, IconProvider::instance()->getIcon("inode-directory"));
treeFeedItem->setData(0, Qt::UserRole, feed->url());
ui->treeMatchingArticles->addTopLevelItem(treeFeedItem);
}
// Insert the articles
foreach (const QString &art, articles) {
QTreeWidgetItem *item = new QTreeWidgetItem(QStringList() << art);
item->setToolTip(0, art);
treeFeedItem->addChild(item);
}
ui->treeMatchingArticles->expandItem(treeFeedItem);
}
void AutomatedRssDownloader::updateFieldsToolTips(bool regex)
{
QString tip;
if (regex) {
tip = tr("Regex mode: use Perl-like regular expressions");
ui->lineContains->setToolTip(tip);
ui->lineNotContains->setToolTip(tip);
} else {
tip = tr("Wildcard mode: you can use<ul><li>? to match any single character</li><li>* to match zero or more of any characters</li><li>Whitespaces count as AND operators</li></ul>");
ui->lineContains->setToolTip(tip);
tip = tr("Wildcard mode: you can use<ul><li>? to match any single character</li><li>* to match zero or more of any characters</li><li>| is used as OR operator</li></ul>");
ui->lineNotContains->setToolTip(tip);
}
}
void AutomatedRssDownloader::updateMustLineValidity()
{
const QString text = ui->lineContains->text();
bool valid = true;
QStringList tokens;
if (ui->checkRegex->isChecked())
tokens << text;
else
tokens << text.split(" ");
foreach (const QString &token, tokens) {
QRegExp reg(token, Qt::CaseInsensitive, ui->checkRegex->isChecked() ? QRegExp::RegExp : QRegExp::Wildcard);
if (!reg.isValid()) {
valid = false;
break;
}
}
if (valid) {
ui->lineContains->setStyleSheet("");
ui->lbl_must_stat->setPixmap(QPixmap());
} else {
ui->lineContains->setStyleSheet("QLineEdit { color: #ff0000; }");
ui->lbl_must_stat->setPixmap(IconProvider::instance()->getIcon("task-attention").pixmap(16, 16));
}
}
void AutomatedRssDownloader::updateMustNotLineValidity()
{
const QString text = ui->lineNotContains->text();
bool valid = true;
QStringList tokens;
if (ui->checkRegex->isChecked())
tokens << text;
else
tokens << text.split("|");
foreach (const QString &token, tokens) {
QRegExp reg(token, Qt::CaseInsensitive, ui->checkRegex->isChecked() ? QRegExp::RegExp : QRegExp::Wildcard);
if (!reg.isValid()) {
valid = false;
break;
}
}
if (valid) {
ui->lineNotContains->setStyleSheet("");
ui->lbl_mustnot_stat->setPixmap(QPixmap());
} else {
ui->lineNotContains->setStyleSheet("QLineEdit { color: #ff0000; }");
ui->lbl_mustnot_stat->setPixmap(IconProvider::instance()->getIcon("task-attention").pixmap(16, 16));
}
}
void AutomatedRssDownloader::on_finished(int result) {
Q_UNUSED(result);
// Save current item on exit
saveEditedRule();
m_ruleList->replace(m_editableRuleList);
m_ruleList->saveRulesToStorage();
saveSettings();
}

View File

@@ -0,0 +1,103 @@
/*
* Bittorrent Client using Qt4 and libtorrent.
* Copyright (C) 2010 Christophe Dumez
*
* 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.
*
* In addition, as a special exception, the copyright holders give permission to
* link this program with the OpenSSL project's "OpenSSL" library (or with
* modified versions of it that use the same license as the "OpenSSL" library),
* and distribute the linked executables. You must obey the GNU General Public
* License in all respects for all of the code used other than "OpenSSL". If you
* modify file(s), you may extend this exception to your version of the file(s),
* but you are not obligated to do so. If you do not wish to do so, delete this
* exception statement from your version.
*
* Contact : chris@qbittorrent.org
*/
#ifndef AUTOMATEDRSSDOWNLOADER_H
#define AUTOMATEDRSSDOWNLOADER_H
#include <QDialog>
#include <QWeakPointer>
#include <QShortcut>
#include <QRegExpValidator>
#include "rssdownloadrule.h"
QT_BEGIN_NAMESPACE
namespace Ui {
class AutomatedRssDownloader;
}
QT_END_NAMESPACE
class RssDownloadRuleList;
class RssManager;
QT_BEGIN_NAMESPACE
class QListWidgetItem;
QT_END_NAMESPACE
class AutomatedRssDownloader : public QDialog
{
Q_OBJECT
public:
explicit AutomatedRssDownloader(const QWeakPointer<RssManager>& manager, QWidget *parent = 0);
~AutomatedRssDownloader();
bool isRssDownloaderEnabled() const;
protected slots:
void loadSettings();
void saveSettings();
void loadRulesList();
void handleFeedCheckStateChange(QListWidgetItem* feed_item);
void updateRuleDefinitionBox();
void clearRuleDefinitionBox();
void saveEditedRule();
void loadFeedList();
void updateFeedList();
private slots:
void displayRulesListMenu(const QPoint& pos);
void on_addRuleBtn_clicked();
void on_removeRuleBtn_clicked();
void on_browseSP_clicked();
void on_exportBtn_clicked();
void on_importBtn_clicked();
void renameSelectedRule();
void updateMatchingArticles();
void updateFieldsToolTips(bool regex);
void updateMustLineValidity();
void updateMustNotLineValidity();
void on_finished(int result);
private:
RssDownloadRulePtr getCurrentRule() const;
void initLabelCombobox();
void addFeedArticlesToTree(const RssFeedPtr& feed, const QStringList& articles);
private:
Ui::AutomatedRssDownloader *ui;
QWeakPointer<RssManager> m_manager;
QListWidgetItem* m_editedRule;
RssDownloadRuleList *m_ruleList;
RssDownloadRuleList *m_editableRuleList;
QRegExpValidator *m_episodeValidator;
QShortcut *editHotkey;
QShortcut *deleteHotkey;
};
#endif // AUTOMATEDRSSDOWNLOADER_H

View File

@@ -0,0 +1,615 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>AutomatedRssDownloader</class>
<widget class="QDialog" name="AutomatedRssDownloader">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>816</width>
<height>494</height>
</rect>
</property>
<property name="windowTitle">
<string>Automated RSS Downloader</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_5">
<item>
<widget class="QCheckBox" name="checkEnableDownloader">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Enable the automated RSS downloader</string>
</property>
</widget>
</item>
<item>
<widget class="QSplitter" name="hsplitter">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<widget class="QWidget" name="layoutWidget">
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLabel" name="label">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Download rules</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Expanding</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QToolButton" name="removeRuleBtn">
<property name="text">
<string/>
</property>
<property name="iconSize">
<size>
<width>24</width>
<height>20</height>
</size>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="addRuleBtn">
<property name="text">
<string/>
</property>
<property name="iconSize">
<size>
<width>24</width>
<height>20</height>
</size>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<widget class="QListWidget" name="listRules">
<property name="contextMenuPolicy">
<enum>Qt::CustomContextMenu</enum>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="layoutWidget1">
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<widget class="QGroupBox" name="ruleDefBox">
<property name="title">
<string>Rule definition</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_6">
<item>
<widget class="QCheckBox" name="checkRegex">
<property name="text">
<string>Use regular expressions</string>
</property>
</widget>
</item>
<item>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Must contain:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="0" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QLineEdit" name="lineContains"/>
</item>
<item>
<widget class="QLabel" name="lbl_must_stat">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>18</width>
<height>18</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>18</width>
<height>18</height>
</size>
</property>
<property name="text">
<string notr="true"/>
</property>
</widget>
</item>
</layout>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Must not contain:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="1" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_5">
<item>
<widget class="QLineEdit" name="lineNotContains"/>
</item>
<item>
<widget class="QLabel" name="lbl_mustnot_stat">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>18</width>
<height>18</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>18</width>
<height>18</height>
</size>
</property>
<property name="text">
<string notr="true"/>
</property>
</widget>
</item>
</layout>
</item>
<item row="2" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_6">
<item>
<widget class="QLineEdit" name="lineEFilter"/>
</item>
<item>
<widget class="QLabel" name="lblEFilterStat">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>18</width>
<height>18</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>18</width>
<height>18</height>
</size>
</property>
<property name="text">
<string notr="true"/>
</property>
</widget>
</item>
</layout>
</item>
<item row="2" column="0">
<widget class="QLabel" name="lblEFilter">
<property name="text">
<string>Episode filter:</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<layout class="QFormLayout" name="formLayout_2">
<item row="0" column="0">
<widget class="QLabel" name="label_7">
<property name="text">
<string>Assign label:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="comboLabel">
<property name="editable">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QCheckBox" name="saveDiffDir_check">
<property name="text">
<string>Save to a different directory</string>
</property>
</widget>
</item>
<item>
<layout class="QFormLayout" name="formLayout_3">
<item row="0" column="0">
<widget class="QLabel" name="label_6">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Save to:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="0" column="1">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLineEdit" name="lineSavePath">
<property name="enabled">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="browseSP">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string notr="true">...</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_8">
<item>
<widget class="QLabel" name="lblIgnoreDays">
<property name="text">
<string comment="... X days">Ignore subsequent matches for (0 to disable)</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_4">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QSpinBox" name="spinIgnorePeriod">
<property name="enabled">
<bool>true</bool>
</property>
<property name="suffix">
<string> days</string>
</property>
<property name="maximum">
<number>360</number>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_7">
<item>
<spacer name="horizontalSpacer_5">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="lblLastMatch">
<property name="enabled">
<bool>true</bool>
</property>
<property name="text">
<string notr="true"/>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_9">
<item>
<widget class="QLabel" name="lblAddPaused">
<property name="text">
<string>Add Paused:</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="comboAddPaused">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<item>
<property name="text">
<string>Use global setting</string>
</property>
</item>
<item>
<property name="text">
<string>Always add paused</string>
</property>
</item>
<item>
<property name="text">
<string>Never add paused</string>
</property>
</item>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label_2">
<property name="font">
<font>
<weight>50</weight>
<bold>false</bold>
</font>
</property>
<property name="text">
<string>Apply rule to feeds:</string>
</property>
</widget>
</item>
<item>
<widget class="QListWidget" name="listFeeds"/>
</item>
</layout>
</item>
</layout>
</widget>
<widget class="QWidget" name="layoutWidget2">
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QLabel" name="label_3">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Matching RSS articles</string>
</property>
</widget>
</item>
<item>
<widget class="QTreeWidget" name="treeMatchingArticles">
<attribute name="headerVisible">
<bool>false</bool>
</attribute>
<column>
<property name="text">
<string notr="true">1</string>
</property>
</column>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QPushButton" name="importBtn">
<property name="text">
<string>Import...</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="exportBtn">
<property name="text">
<string>Export...</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Close</set>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>AutomatedRssDownloader</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>750</x>
<y>483</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>AutomatedRssDownloader</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>805</x>
<y>483</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>saveDiffDir_check</sender>
<signal>toggled(bool)</signal>
<receiver>label_6</receiver>
<slot>setEnabled(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>304</x>
<y>171</y>
</hint>
<hint type="destinationlabel">
<x>377</x>
<y>205</y>
</hint>
</hints>
</connection>
<connection>
<sender>saveDiffDir_check</sender>
<signal>toggled(bool)</signal>
<receiver>lineSavePath</receiver>
<slot>setEnabled(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>474</x>
<y>174</y>
</hint>
<hint type="destinationlabel">
<x>476</x>
<y>204</y>
</hint>
</hints>
</connection>
<connection>
<sender>saveDiffDir_check</sender>
<signal>toggled(bool)</signal>
<receiver>browseSP</receiver>
<slot>setEnabled(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>544</x>
<y>166</y>
</hint>
<hint type="destinationlabel">
<x>549</x>
<y>209</y>
</hint>
</hints>
</connection>
</connections>
</ui>

104
src/gui/rss/cookiesdlg.cpp Normal file
View File

@@ -0,0 +1,104 @@
/*
* Bittorrent Client using Qt4 and libtorrent.
* Copyright (C) 2010 Christophe Dumez
*
* 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.
*
* In addition, as a special exception, the copyright holders give permission to
* link this program with the OpenSSL project's "OpenSSL" library (or with
* modified versions of it that use the same license as the "OpenSSL" library),
* and distribute the linked executables. You must obey the GNU General Public
* License in all respects for all of the code used other than "OpenSSL". If you
* modify file(s), you may extend this exception to your version of the file(s),
* but you are not obligated to do so. If you do not wish to do so, delete this
* exception statement from your version.
*
* Contact : chris@qbittorrent.org arnaud@qbittorrent.org
*/
#include "cookiesdlg.h"
#include "ui_cookiesdlg.h"
#include "iconprovider.h"
#include <QNetworkCookie>
enum CookiesCols { COOKIE_KEY, COOKIE_VALUE};
CookiesDlg::CookiesDlg(QWidget *parent, const QList<QByteArray> &raw_cookies) :
QDialog(parent),
ui(new Ui::CookiesDlg)
{
ui->setupUi(this);
// Icons
ui->add_btn->setIcon(IconProvider::instance()->getIcon("list-add"));
ui->del_btn->setIcon(IconProvider::instance()->getIcon("list-remove"));
ui->infos_lbl->setText(tr("Common keys for cookies are : '%1', '%2'.\nYou should get this information from your Web browser preferences.").arg("uid").arg("pass"));
foreach (const QByteArray &raw_cookie, raw_cookies) {
QList<QByteArray> cookie_parts = raw_cookie.split('=');
if (cookie_parts.size() != 2) continue;
const int i = ui->cookiesTable->rowCount();
ui->cookiesTable->setRowCount(i+1);
ui->cookiesTable->setItem(i, COOKIE_KEY, new QTableWidgetItem(cookie_parts.first().data()));
ui->cookiesTable->setItem(i, COOKIE_VALUE, new QTableWidgetItem(cookie_parts.last().data()));
}
}
CookiesDlg::~CookiesDlg()
{
delete ui;
}
void CookiesDlg::on_add_btn_clicked() {
ui->cookiesTable->setRowCount(ui->cookiesTable->rowCount()+1);
// Edit first column
ui->cookiesTable->editItem(ui->cookiesTable->item(ui->cookiesTable->rowCount()-1, COOKIE_KEY));
}
void CookiesDlg::on_del_btn_clicked() {
// Get selected cookie
QList<QTableWidgetItem*> selection = ui->cookiesTable->selectedItems();
if (!selection.isEmpty()) {
ui->cookiesTable->removeRow(selection.first()->row());
}
}
QList<QByteArray> CookiesDlg::getCookies() const {
QList<QByteArray> ret;
for (int i=0; i<ui->cookiesTable->rowCount(); ++i) {
QString key;
if (ui->cookiesTable->item(i, COOKIE_KEY))
key = ui->cookiesTable->item(i, COOKIE_KEY)->text().trimmed();
QString value;
if (ui->cookiesTable->item(i, COOKIE_VALUE))
value = ui->cookiesTable->item(i, COOKIE_VALUE)->text().trimmed();
if (!key.isEmpty() && !value.isEmpty()) {
const QString raw_cookie = key+"="+value;
qDebug("Cookie: %s", qPrintable(raw_cookie));
ret << raw_cookie.toLocal8Bit();
}
}
return ret;
}
QList<QByteArray> CookiesDlg::askForCookies(QWidget *parent, const QList<QByteArray> &raw_cookies, bool *ok) {
CookiesDlg dlg(parent, raw_cookies);
if (dlg.exec()) {
*ok = true;
return dlg.getCookies();
}
*ok = false;
return QList<QByteArray>();
}

60
src/gui/rss/cookiesdlg.h Normal file
View File

@@ -0,0 +1,60 @@
/*
* Bittorrent Client using Qt4 and libtorrent.
* Copyright (C) 2010 Christophe Dumez
*
* 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.
*
* In addition, as a special exception, the copyright holders give permission to
* link this program with the OpenSSL project's "OpenSSL" library (or with
* modified versions of it that use the same license as the "OpenSSL" library),
* and distribute the linked executables. You must obey the GNU General Public
* License in all respects for all of the code used other than "OpenSSL". If you
* modify file(s), you may extend this exception to your version of the file(s),
* but you are not obligated to do so. If you do not wish to do so, delete this
* exception statement from your version.
*
* Contact : chris@qbittorrent.org arnaud@qbittorrent.org
*/
#ifndef COOKIESDLG_H
#define COOKIESDLG_H
#include <QDialog>
QT_BEGIN_NAMESPACE
namespace Ui {
class CookiesDlg;
}
QT_END_NAMESPACE
class CookiesDlg : public QDialog
{
Q_OBJECT
public:
explicit CookiesDlg(QWidget *parent = 0, const QList<QByteArray> &raw_cookies = QList<QByteArray>());
~CookiesDlg();
QList<QByteArray> getCookies() const;
static QList<QByteArray> askForCookies(QWidget *parent, const QList<QByteArray> &raw_cookies, bool *ok);
protected slots:
void on_add_btn_clicked();
void on_del_btn_clicked();
private:
Ui::CookiesDlg *ui;
};
#endif // COOKIESDLG_H

172
src/gui/rss/cookiesdlg.ui Normal file
View File

@@ -0,0 +1,172 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>CookiesDlg</class>
<widget class="QDialog" name="CookiesDlg">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>300</height>
</rect>
</property>
<property name="windowTitle">
<string>Cookies management</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QTableWidget" name="cookiesTable">
<property name="alternatingRowColors">
<bool>true</bool>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::SingleSelection</enum>
</property>
<attribute name="horizontalHeaderStretchLastSection">
<bool>true</bool>
</attribute>
<attribute name="verticalHeaderVisible">
<bool>false</bool>
</attribute>
<attribute name="verticalHeaderStretchLastSection">
<bool>false</bool>
</attribute>
<column>
<property name="text">
<string extracomment="As in Key/Value pair">Key</string>
</property>
</column>
<column>
<property name="text">
<string extracomment="As in Key/Value pair">Value</string>
</property>
</column>
</widget>
</item>
<item row="0" column="1">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QToolButton" name="add_btn">
<property name="text">
<string notr="true"/>
</property>
<property name="iconSize">
<size>
<width>20</width>
<height>20</height>
</size>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>5</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QToolButton" name="del_btn">
<property name="text">
<string notr="true"/>
</property>
<property name="iconSize">
<size>
<width>20</width>
<height>20</height>
</size>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer_3">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item row="2" column="0" colspan="2">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="infos_lbl">
<property name="text">
<string notr="true">TextLabel</string>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>CookiesDlg</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>CookiesDlg</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@@ -0,0 +1,231 @@
/*
* Bittorrent Client using Qt4 and libtorrent.
* Copyright (C) 2010 Christophe Dumez
*
* 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.
*
* In addition, as a special exception, the copyright holders give permission to
* link this program with the OpenSSL project's "OpenSSL" library (or with
* modified versions of it that use the same license as the "OpenSSL" library),
* and distribute the linked executables. You must obey the GNU General Public
* License in all respects for all of the code used other than "OpenSSL". If you
* modify file(s), you may extend this exception to your version of the file(s),
* but you are not obligated to do so. If you do not wish to do so, delete this
* exception statement from your version.
*
* Contact: chris@qbittorrent.org, arnaud@qbittorrent.org
*/
#include "feedlistwidget.h"
#include "rssmanager.h"
#include "rssfeed.h"
#include "iconprovider.h"
FeedListWidget::FeedListWidget(QWidget *parent, const RssManagerPtr& rssmanager): QTreeWidget(parent), m_rssManager(rssmanager) {
setContextMenuPolicy(Qt::CustomContextMenu);
setDragDropMode(QAbstractItemView::InternalMove);
setSelectionMode(QAbstractItemView::ExtendedSelection);
setColumnCount(1);
headerItem()->setText(0, tr("RSS feeds"));
m_unreadStickyItem = new QTreeWidgetItem(this);
m_unreadStickyItem->setText(0, tr("Unread") + QString::fromUtf8(" (") + QString::number(rssmanager->unreadCount())+ QString(")"));
m_unreadStickyItem->setData(0,Qt::DecorationRole, IconProvider::instance()->getIcon("mail-folder-inbox"));
itemAdded(m_unreadStickyItem, rssmanager);
connect(this, SIGNAL(currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)), SLOT(updateCurrentFeed(QTreeWidgetItem*)));
setCurrentItem(m_unreadStickyItem);
}
FeedListWidget::~FeedListWidget() {
delete m_unreadStickyItem;
}
void FeedListWidget::itemAdded(QTreeWidgetItem *item, const RssFilePtr& file) {
m_rssMapping[item] = file;
if (RssFeedPtr feed = qSharedPointerDynamicCast<RssFeed>(file)) {
m_feedsItems[feed->id()] = item;
}
}
void FeedListWidget::itemAboutToBeRemoved(QTreeWidgetItem *item) {
RssFilePtr file = m_rssMapping.take(item);
if (RssFeedPtr feed = qSharedPointerDynamicCast<RssFeed>(file)) {
m_feedsItems.remove(feed->id());
} if (RssFolderPtr folder = qSharedPointerDynamicCast<RssFolder>(file)) {
RssFeedList feeds = folder->getAllFeeds();
foreach (const RssFeedPtr& feed, feeds) {
m_feedsItems.remove(feed->id());
}
}
}
bool FeedListWidget::hasFeed(const QString &url) const {
return m_feedsItems.contains(QUrl(url).toString());
}
QList<QTreeWidgetItem*> FeedListWidget::getAllFeedItems() const {
return m_feedsItems.values();
}
QTreeWidgetItem* FeedListWidget::stickyUnreadItem() const {
return m_unreadStickyItem;
}
QStringList FeedListWidget::getItemPath(QTreeWidgetItem* item) const {
QStringList path;
if (item) {
if (item->parent())
path << getItemPath(item->parent());
path.append(getRSSItem(item)->id());
}
return path;
}
QList<QTreeWidgetItem*> FeedListWidget::getAllOpenFolders(QTreeWidgetItem *parent) const {
QList<QTreeWidgetItem*> open_folders;
int nbChildren;
if (parent)
nbChildren = parent->childCount();
else
nbChildren = topLevelItemCount();
for (int i=0; i<nbChildren; ++i) {
QTreeWidgetItem *item;
if (parent)
item = parent->child(i);
else
item = topLevelItem(i);
if (isFolder(item) && item->isExpanded()) {
QList<QTreeWidgetItem*> open_subfolders = getAllOpenFolders(item);
if (!open_subfolders.empty()) {
open_folders << open_subfolders;
} else {
open_folders << item;
}
}
}
return open_folders;
}
QList<QTreeWidgetItem*> FeedListWidget::getAllFeedItems(QTreeWidgetItem* folder) {
QList<QTreeWidgetItem*> feeds;
const int nbChildren = folder->childCount();
for (int i=0; i<nbChildren; ++i) {
QTreeWidgetItem *item = folder->child(i);
if (isFeed(item)) {
feeds << item;
} else {
feeds << getAllFeedItems(item);
}
}
return feeds;
}
RssFilePtr FeedListWidget::getRSSItem(QTreeWidgetItem *item) const {
return m_rssMapping.value(item, RssFilePtr());
}
bool FeedListWidget::isFeed(QTreeWidgetItem *item) const
{
return (qSharedPointerDynamicCast<RssFeed>(m_rssMapping.value(item)) != NULL);
}
bool FeedListWidget::isFolder(QTreeWidgetItem *item) const
{
return (qSharedPointerDynamicCast<RssFolder>(m_rssMapping.value(item)) != NULL);
}
QString FeedListWidget::getItemID(QTreeWidgetItem *item) const {
return m_rssMapping.value(item)->id();
}
QTreeWidgetItem* FeedListWidget::getTreeItemFromUrl(const QString &url) const {
return m_feedsItems.value(url, 0);
}
RssFeedPtr FeedListWidget::getRSSItemFromUrl(const QString &url) const {
return qSharedPointerDynamicCast<RssFeed>(getRSSItem(getTreeItemFromUrl(url)));
}
QTreeWidgetItem* FeedListWidget::currentItem() const {
return m_currentFeed;
}
QTreeWidgetItem* FeedListWidget::currentFeed() const {
return m_currentFeed;
}
void FeedListWidget::updateCurrentFeed(QTreeWidgetItem* new_item) {
if (!new_item) return;
if (!m_rssMapping.contains(new_item)) return;
if (isFeed(new_item) || new_item == m_unreadStickyItem)
m_currentFeed = new_item;
}
void FeedListWidget::dragMoveEvent(QDragMoveEvent * event) {
QTreeWidget::dragMoveEvent(event);
QTreeWidgetItem *item = itemAt(event->pos());
// Prohibit dropping onto global unread counter
if (item == m_unreadStickyItem) {
event->ignore();
return;
}
// Prohibit dragging of global unread counter
if (selectedItems().contains(m_unreadStickyItem)) {
event->ignore();
return;
}
// Prohibit dropping onto feeds
if (item && isFeed(item)) {
event->ignore();
return;
}
}
void FeedListWidget::dropEvent(QDropEvent *event) {
qDebug("dropEvent");
QList<QTreeWidgetItem*> folders_altered;
QTreeWidgetItem *dest_folder_item = itemAt(event->pos());
RssFolderPtr dest_folder;
if (dest_folder_item) {
dest_folder = qSharedPointerCast<RssFolder>(getRSSItem(dest_folder_item));
folders_altered << dest_folder_item;
} else {
dest_folder = m_rssManager;
}
QList<QTreeWidgetItem *> src_items = selectedItems();
// Check if there is not going to overwrite another file
foreach (QTreeWidgetItem *src_item, src_items) {
RssFilePtr file = getRSSItem(src_item);
if (dest_folder->hasChild(file->id())) {
QTreeWidget::dropEvent(event);
return;
}
}
// Proceed with the move
foreach (QTreeWidgetItem *src_item, src_items) {
QTreeWidgetItem *parent_folder = src_item->parent();
if (parent_folder && !folders_altered.contains(parent_folder))
folders_altered << parent_folder;
// Actually move the file
RssFilePtr file = getRSSItem(src_item);
m_rssManager->moveFile(file, dest_folder);
}
QTreeWidget::dropEvent(event);
if (dest_folder_item)
dest_folder_item->setExpanded(true);
// Emit signal for update
if (!folders_altered.empty())
emit foldersAltered(folders_altered);
}

View File

@@ -0,0 +1,90 @@
/*
* Bittorrent Client using Qt4 and libtorrent.
* Copyright (C) 2010 Christophe Dumez
*
* 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.
*
* In addition, as a special exception, the copyright holders give permission to
* link this program with the OpenSSL project's "OpenSSL" library (or with
* modified versions of it that use the same license as the "OpenSSL" library),
* and distribute the linked executables. You must obey the GNU General Public
* License in all respects for all of the code used other than "OpenSSL". If you
* modify file(s), you may extend this exception to your version of the file(s),
* but you are not obligated to do so. If you do not wish to do so, delete this
* exception statement from your version.
*
* Contact: chris@qbittorrent.org, arnaud@qbittorrent.org
*/
#ifndef FEEDLIST_H
#define FEEDLIST_H
#include <QTreeWidget>
#include <QTreeWidgetItem>
#include <QDropEvent>
#include <QDragMoveEvent>
#include <QStringList>
#include <QHash>
#include <QUrl>
#include "rssfile.h"
#include "rssfeed.h"
#include "rssmanager.h"
class FeedListWidget: public QTreeWidget {
Q_OBJECT
public:
FeedListWidget(QWidget *parent, const RssManagerPtr& rssManager);
~FeedListWidget();
bool hasFeed(const QString &url) const;
QList<QTreeWidgetItem*> getAllFeedItems() const;
QTreeWidgetItem* stickyUnreadItem() const;
QStringList getItemPath(QTreeWidgetItem* item) const;
QList<QTreeWidgetItem*> getAllOpenFolders(QTreeWidgetItem *parent=0) const;
QList<QTreeWidgetItem*> getAllFeedItems(QTreeWidgetItem* folder);
RssFilePtr getRSSItem(QTreeWidgetItem *item) const;
bool isFeed(QTreeWidgetItem *item) const;
bool isFolder(QTreeWidgetItem *item) const;
QString getItemID(QTreeWidgetItem *item) const;
QTreeWidgetItem* getTreeItemFromUrl(const QString &url) const;
RssFeedPtr getRSSItemFromUrl(const QString &url) const;
QTreeWidgetItem* currentItem() const;
QTreeWidgetItem* currentFeed() const;
public slots:
void itemAdded(QTreeWidgetItem *item, const RssFilePtr& file);
void itemAboutToBeRemoved(QTreeWidgetItem *item);
signals:
void foldersAltered(const QList<QTreeWidgetItem*> &folders);
private slots:
void updateCurrentFeed(QTreeWidgetItem* new_item);
protected:
void dragMoveEvent(QDragMoveEvent * event);
void dropEvent(QDropEvent *event);
private:
RssManagerPtr m_rssManager;
QHash<QTreeWidgetItem*, RssFilePtr> m_rssMapping;
QHash<QString, QTreeWidgetItem*> m_feedsItems;
QTreeWidgetItem* m_currentFeed;
QTreeWidgetItem *m_unreadStickyItem;
};
#endif // FEEDLIST_H

View File

@@ -0,0 +1,93 @@
#include "htmlbrowser.h"
#include <QDebug>
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QNetworkDiskCache>
#include <QStyle>
#include <QApplication>
#include <QDir>
#include <QDateTime>
#include <QScrollBar>
#include "fs_utils.h"
HtmlBrowser::HtmlBrowser(QWidget* parent)
: QTextBrowser(parent)
{
m_netManager = new QNetworkAccessManager(this);
m_diskCache = new QNetworkDiskCache(this);
m_diskCache->setCacheDirectory(QDir::cleanPath(fsutils::cacheLocation() + "/rss"));
m_diskCache->setMaximumCacheSize(50 * 1024 * 1024);
qDebug() << "HtmlBrowser cache path:" << m_diskCache->cacheDirectory() << " max size:" << m_diskCache->maximumCacheSize() / 1024 / 1024 << "MB";
m_netManager->setCache(m_diskCache);
connect(m_netManager, SIGNAL(finished(QNetworkReply *)), this, SLOT(resourceLoaded(QNetworkReply*)));
}
HtmlBrowser::~HtmlBrowser()
{
}
QVariant HtmlBrowser::loadResource(int type, const QUrl &name)
{
if(type == QTextDocument::ImageResource) {
QUrl url(name);
if(url.scheme().isEmpty())
url.setScheme("http");
QIODevice *dev = m_diskCache->data(url);
if(dev != 0) {
qDebug() << "HtmlBrowser::loadResource() cache " << url.toString();
QByteArray res = dev->readAll();
delete dev;
return res;
}
if(!m_activeRequests.contains(url)) {
m_activeRequests.insert(url, true);
qDebug() << "HtmlBrowser::loadResource() get " << url.toString();
QNetworkRequest req(url);
req.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache);
m_netManager->get(req);
}
return QVariant();
}
return QTextBrowser::loadResource(type, name);
}
void HtmlBrowser::resourceLoaded(QNetworkReply *reply)
{
m_activeRequests.remove(reply->request().url());
if(reply->error() == QNetworkReply::NoError && reply->size() > 0) {
qDebug() << "HtmlBrowser::resourceLoaded() save " << reply->request().url().toString();
}
else {
// If resource failed to load, replace it with warning icon and store it in cache for 1 day.
// Otherwise HTMLBrowser will keep trying to download it every time article is displayed,
// since it's not possible to cache error responses.
QNetworkCacheMetaData metaData;
QNetworkCacheMetaData::AttributesMap atts;
metaData.setUrl(reply->request().url());
metaData.setSaveToDisk(true);
atts[QNetworkRequest::HttpStatusCodeAttribute] = 200;
atts[QNetworkRequest::HttpReasonPhraseAttribute] = "Ok";
metaData.setAttributes(atts);
metaData.setLastModified(QDateTime::currentDateTime());
metaData.setExpirationDate(QDateTime::currentDateTime().addDays(1));
QIODevice *dev = m_diskCache->prepare(metaData);
if(!dev)
return;
QApplication::style()->standardIcon(QStyle::SP_MessageBoxWarning).pixmap(32, 32).save(dev, "PNG");
m_diskCache->insert(dev);
}
// Refresh the document display and keep scrollbars where they are
int sx = horizontalScrollBar()->value();
int sy = verticalScrollBar()->value();
document()->setHtml(document()->toHtml());
horizontalScrollBar()->setValue(sx);
verticalScrollBar()->setValue(sy);
}

30
src/gui/rss/htmlbrowser.h Normal file
View File

@@ -0,0 +1,30 @@
#ifndef HTMLBROWSER_H
#define HTMLBROWSER_H
#include <QTextBrowser>
#include <QHash>
class QNetworkAccessManager;
class QNetworkDiskCache;
class QNetworkReply;
class HtmlBrowser: public QTextBrowser
{
Q_OBJECT
public:
explicit HtmlBrowser(QWidget* parent = 0);
~HtmlBrowser();
virtual QVariant loadResource(int type, const QUrl &name);
protected:
QNetworkAccessManager *m_netManager;
QNetworkDiskCache *m_diskCache;
QHash<QUrl, bool> m_activeRequests;
protected slots:
void resourceLoaded(QNetworkReply *reply);
};
#endif // HTMLBROWSER_H

36
src/gui/rss/rss.pri Normal file
View File

@@ -0,0 +1,36 @@
INCLUDEPATH += $$PWD
HEADERS += $$PWD/rss_imp.h \
$$PWD/rsssettingsdlg.h \
$$PWD/feedlistwidget.h \
$$PWD/rssmanager.h \
$$PWD/rssfeed.h \
$$PWD/rssfolder.h \
$$PWD/rssfile.h \
$$PWD/rssarticle.h \
$$PWD/automatedrssdownloader.h \
$$PWD/rssdownloadrule.h \
$$PWD/rssdownloadrulelist.h \
$$PWD/cookiesdlg.h \
$$PWD/rssparser.h \
$$PWD/htmlbrowser.h
SOURCES += $$PWD/rss_imp.cpp \
$$PWD/rsssettingsdlg.cpp \
$$PWD/feedlistwidget.cpp \
$$PWD/rssmanager.cpp \
$$PWD/rssfeed.cpp \
$$PWD/rssfolder.cpp \
$$PWD/rssarticle.cpp \
$$PWD/automatedrssdownloader.cpp \
$$PWD/rssdownloadrule.cpp \
$$PWD/rssdownloadrulelist.cpp \
$$PWD/cookiesdlg.cpp \
$$PWD/rssfile.cpp \
$$PWD/rssparser.cpp \
$$PWD/htmlbrowser.cpp
FORMS += $$PWD/rss.ui \
$$PWD/rsssettingsdlg.ui \
$$PWD/automatedrssdownloader.ui \
$$PWD/cookiesdlg.ui

240
src/gui/rss/rss.ui Normal file
View File

@@ -0,0 +1,240 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>RSS</class>
<widget class="QWidget" name="RSS">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>811</width>
<height>447</height>
</rect>
</property>
<property name="acceptDrops">
<bool>false</bool>
</property>
<property name="windowTitle">
<string>Search</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QToolButton" name="newFeedButton">
<property name="minimumSize">
<size>
<width>32</width>
<height>32</height>
</size>
</property>
<property name="text">
<string>New subscription</string>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonTextBesideIcon</enum>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="markReadButton">
<property name="minimumSize">
<size>
<width>32</width>
<height>32</height>
</size>
</property>
<property name="text">
<string>Mark items read</string>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonTextBesideIcon</enum>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="updateAllButton">
<property name="minimumSize">
<size>
<width>32</width>
<height>32</height>
</size>
</property>
<property name="toolTip">
<string>Refresh RSS streams</string>
</property>
<property name="text">
<string>Update all</string>
</property>
<property name="iconSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonTextBesideIcon</enum>
</property>
</widget>
</item>
<item>
<spacer>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="rssDownloaderBtn">
<property name="text">
<string>RSS Downloader...</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="settingsButton">
<property name="text">
<string>Settings...</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QSplitter" name="splitter_h">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<widget class="QWidget" name="layoutWidget">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="news_lbl">
<property name="font">
<font>
<weight>50</weight>
<bold>false</bold>
</font>
</property>
<property name="text">
<string>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Sans'; font-size:10pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Torrents:&lt;/span&gt; &lt;span style=&quot; font-style:italic;&quot;&gt;(double-click to download)&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item>
<widget class="QSplitter" name="splitter_v">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<widget class="QListWidget" name="listArticles">
<property name="contextMenuPolicy">
<enum>Qt::CustomContextMenu</enum>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::SingleSelection</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectItems</enum>
</property>
</widget>
<widget class="HtmlBrowser" name="textBrowser">
<property name="openExternalLinks">
<bool>true</bool>
</property>
</widget>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
<action name="actionDelete">
<property name="text">
<string>Delete</string>
</property>
<property name="toolTip">
<string>Delete</string>
</property>
</action>
<action name="actionRename">
<property name="text">
<string>Rename...</string>
</property>
<property name="toolTip">
<string>Rename</string>
</property>
</action>
<action name="actionUpdate">
<property name="text">
<string>Update</string>
</property>
<property name="toolTip">
<string>Update</string>
</property>
</action>
<action name="actionNew_subscription">
<property name="text">
<string>New subscription...</string>
</property>
</action>
<action name="actionUpdate_all_feeds">
<property name="text">
<string>Update all feeds</string>
</property>
<property name="toolTip">
<string>Update all feeds</string>
</property>
</action>
<action name="actionMark_items_read">
<property name="text">
<string>Mark items read</string>
</property>
<property name="toolTip">
<string>Mark items read</string>
</property>
</action>
<action name="actionDownload_torrent">
<property name="text">
<string>Download torrent</string>
</property>
</action>
<action name="actionOpen_news_URL">
<property name="text">
<string>Open news URL</string>
</property>
</action>
<action name="actionCopy_feed_URL">
<property name="text">
<string>Copy feed URL</string>
</property>
</action>
<action name="actionNew_folder">
<property name="text">
<string>New folder...</string>
</property>
</action>
<action name="actionManage_cookies">
<property name="text">
<string>Manage cookies...</string>
</property>
</action>
</widget>
<customwidgets>
<customwidget>
<class>HtmlBrowser</class>
<extends>QTextBrowser</extends>
<header>htmlbrowser.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>

798
src/gui/rss/rss_imp.cpp Normal file
View File

@@ -0,0 +1,798 @@
/*
* Bittorrent Client using Qt4 and libtorrent.
* Copyright (C) 2006 Christophe Dumez, Arnaud Demaiziere
*
* 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.
*
* In addition, as a special exception, the copyright holders give permission to
* link this program with the OpenSSL project's "OpenSSL" library (or with
* modified versions of it that use the same license as the "OpenSSL" library),
* and distribute the linked executables. You must obey the GNU General Public
* License in all respects for all of the code used other than "OpenSSL". If you
* modify file(s), you may extend this exception to your version of the file(s),
* but you are not obligated to do so. If you do not wish to do so, delete this
* exception statement from your version.
*
* Contact : chris@qbittorrent.org arnaud@qbittorrent.org
*/
#include <QDesktopServices>
#include <QMenu>
#include <QStandardItemModel>
#include <QMessageBox>
#include <QString>
#include <QClipboard>
#include <QDragMoveEvent>
#include <QDebug>
#include "rss_imp.h"
#include "feedlistwidget.h"
#include "qbtsession.h"
#include "cookiesdlg.h"
#include "preferences.h"
#include "rsssettingsdlg.h"
#include "rssmanager.h"
#include "rssfolder.h"
#include "rssarticle.h"
#include "rssparser.h"
#include "rssfeed.h"
#include "automatedrssdownloader.h"
#include "iconprovider.h"
#include "autoexpandabledialog.h"
namespace Article
{
enum ArticleRoles
{
TitleRole = Qt::DisplayRole,
IconRole = Qt::DecorationRole,
ColorRole = Qt::ForegroundRole,
IdRole = Qt::UserRole + 1,
FeedUrlRole = Qt::UserRole + 2
};
}
// display a right-click menu
void RSSImp::displayRSSListMenu(const QPoint& pos)
{
if (!m_feedList->indexAt(pos).isValid())
// No item under the mouse, clear selection
m_feedList->clearSelection();
QMenu myRSSListMenu(this);
QList<QTreeWidgetItem*> selectedItems = m_feedList->selectedItems();
if (selectedItems.size() > 0) {
myRSSListMenu.addAction(actionUpdate);
myRSSListMenu.addAction(actionMark_items_read);
myRSSListMenu.addSeparator();
if (selectedItems.size() == 1) {
if (m_feedList->getRSSItem(selectedItems.first()) != m_rssManager) {
myRSSListMenu.addAction(actionRename);
myRSSListMenu.addAction(actionDelete);
myRSSListMenu.addSeparator();
if (m_feedList->isFolder(selectedItems.first()))
myRSSListMenu.addAction(actionNew_folder);
else
myRSSListMenu.addAction(actionManage_cookies);
}
}
else {
myRSSListMenu.addAction(actionDelete);
myRSSListMenu.addSeparator();
}
myRSSListMenu.addAction(actionNew_subscription);
if (m_feedList->isFeed(selectedItems.first())) {
myRSSListMenu.addSeparator();
myRSSListMenu.addAction(actionCopy_feed_URL);
}
}
else {
myRSSListMenu.addAction(actionNew_subscription);
myRSSListMenu.addAction(actionNew_folder);
myRSSListMenu.addSeparator();
myRSSListMenu.addAction(actionUpdate_all_feeds);
}
myRSSListMenu.exec(QCursor::pos());
}
void RSSImp::displayItemsListMenu(const QPoint&)
{
QMenu myItemListMenu(this);
QList<QListWidgetItem*> selectedItems = listArticles->selectedItems();
if (selectedItems.size() > 0) {
bool has_attachment = false;
foreach (const QListWidgetItem* item, selectedItems) {
if (m_feedList->getRSSItemFromUrl(item->data(Article::FeedUrlRole).toString())
->getItem(item->data(Article::IdRole).toString())->hasAttachment()) {
has_attachment = true;
break;
}
}
if (has_attachment)
myItemListMenu.addAction(actionDownload_torrent);
myItemListMenu.addAction(actionOpen_news_URL);
}
myItemListMenu.exec(QCursor::pos());
}
void RSSImp::on_actionManage_cookies_triggered()
{
Q_ASSERT(!m_feedList->selectedItems().empty());
// Get feed hostname
QString feed_url = m_feedList->getItemID(m_feedList->selectedItems().first());
QString feed_hostname = QUrl::fromEncoded(feed_url.toUtf8()).host();
qDebug("RSS Feed hostname is: %s", qPrintable(feed_hostname));
Q_ASSERT(!feed_hostname.isEmpty());
bool ok = false;
Preferences* const pref = Preferences::instance();
QList<QByteArray> raw_cookies = CookiesDlg::askForCookies(this, pref->getHostNameCookies(feed_hostname), &ok);
if (ok) {
qDebug() << "Settings cookies for host name: " << feed_hostname;
pref->setHostNameCookies(feed_hostname, raw_cookies);
}
}
void RSSImp::askNewFolder()
{
QTreeWidgetItem* parent_item = 0;
RssFolderPtr rss_parent;
if (m_feedList->selectedItems().size() > 0) {
parent_item = m_feedList->selectedItems().at(0);
rss_parent = qSharedPointerDynamicCast<RssFolder>(m_feedList->getRSSItem(parent_item));
Q_ASSERT(rss_parent);
}
else {
rss_parent = m_rssManager;
}
bool ok;
QString new_name = AutoExpandableDialog::getText(this, tr("Please choose a folder name"), tr("Folder name:"), QLineEdit::Normal, tr("New folder"), &ok);
if (!ok)
return;
RssFolderPtr newFolder = rss_parent->addFolder(new_name);
QTreeWidgetItem* folderItem = createFolderListItem(newFolder);
if (parent_item)
parent_item->addChild(folderItem);
else
m_feedList->addTopLevelItem(folderItem);
// Notify TreeWidget
m_feedList->itemAdded(folderItem, newFolder);
// Expand parent folder to display new folder
if (parent_item)
parent_item->setExpanded(true);
m_rssManager->saveStreamList();
}
// add a stream by a button
void RSSImp::on_newFeedButton_clicked()
{
// Determine parent folder for new feed
QTreeWidgetItem *parent_item = 0;
QList<QTreeWidgetItem *> selected_items = m_feedList->selectedItems();
if (!selected_items.empty()) {
parent_item = selected_items.first();
// Consider the case where the user clicked on Unread item
if (parent_item == m_feedList->stickyUnreadItem())
parent_item = 0;
else
if (!m_feedList->isFolder(parent_item))
parent_item = parent_item->parent();
}
RssFolderPtr rss_parent;
if (parent_item)
rss_parent = qSharedPointerCast<RssFolder>(m_feedList->getRSSItem(parent_item));
else
rss_parent = m_rssManager;
// Ask for feed URL
bool ok;
QString clip_txt = qApp->clipboard()->text();
QString default_url = "http://";
if (clip_txt.startsWith("http://", Qt::CaseInsensitive) || clip_txt.startsWith("https://", Qt::CaseInsensitive) || clip_txt.startsWith("ftp://", Qt::CaseInsensitive))
default_url = clip_txt;
QString newUrl = AutoExpandableDialog::getText(this, tr("Please type a rss stream url"), tr("Stream URL:"), QLineEdit::Normal, default_url, &ok);
if (!ok)
return;
newUrl = newUrl.trimmed();
if (newUrl.isEmpty())
return;
if (m_feedList->hasFeed(newUrl)) {
QMessageBox::warning(this, "qBittorrent",
tr("This rss feed is already in the list."),
QMessageBox::Ok);
return;
}
RssFeedPtr stream = rss_parent->addStream(m_rssManager.data(), newUrl);
// Create TreeWidget item
QTreeWidgetItem* item = createFolderListItem(stream);
if (parent_item)
parent_item->addChild(item);
else
m_feedList->addTopLevelItem(item);
// Notify TreeWidget
m_feedList->itemAdded(item, stream);
stream->refresh();
m_rssManager->saveStreamList();
}
// delete a stream by a button
void RSSImp::deleteSelectedItems()
{
QList<QTreeWidgetItem*> selectedItems = m_feedList->selectedItems();
if (selectedItems.isEmpty())
return;
int ret;
if (selectedItems.size() > 1) {
ret = QMessageBox::question(this, tr("Are you sure? -- qBittorrent"), tr("Are you sure you want to delete these elements from the list?"),
tr("&Yes"), tr("&No"),
QString(), 0, 1);
}
else {
if (selectedItems.first() == m_feedList->stickyUnreadItem())
return;
ret = QMessageBox::question(this, tr("Are you sure? -- qBittorrent"), tr("Are you sure you want to delete this element from the list?"),
tr("&Yes"), tr("&No"),
QString(), 0, 1);
}
if (ret)
return;
foreach (QTreeWidgetItem* item, selectedItems) {
if (m_feedList->currentFeed() == item) {
textBrowser->clear();
m_currentArticle = 0;
listArticles->clear();
}
if (item == m_feedList->stickyUnreadItem())
continue;
RssFilePtr rss_item = m_feedList->getRSSItem(item);
QTreeWidgetItem* parent = item->parent();
// Notify TreeWidget
m_feedList->itemAboutToBeRemoved(item);
// Actually delete the item
rss_item->parent()->removeChild(rss_item->id());
delete item;
// Update parents count
while (parent && parent != m_feedList->invisibleRootItem()) {
updateItemInfos (parent);
parent = parent->parent();
}
// Clear feed data from RSS parser (possible caching).
RssFeed* rssFeed = dynamic_cast<RssFeed*>(rss_item.data());
if (rssFeed)
m_rssManager->rssParser()->clearFeedData(rssFeed->url());
}
m_rssManager->saveStreamList();
// Update Unread items
updateItemInfos(m_feedList->stickyUnreadItem());
}
void RSSImp::loadFoldersOpenState()
{
QStringList open_folders = Preferences::instance()->getRssOpenFolders();
foreach (const QString& var_path, open_folders) {
QStringList path = var_path.split("\\");
QTreeWidgetItem* parent = 0;
foreach (const QString& name, path) {
int nbChildren = 0;
if (parent)
nbChildren = parent->childCount();
else
nbChildren = m_feedList->topLevelItemCount();
for (int i = 0; i < nbChildren; ++i) {
QTreeWidgetItem* child;
if (parent)
child = parent->child(i);
else
child = m_feedList->topLevelItem(i);
if (m_feedList->getRSSItem(child)->id() == name) {
parent = child;
parent->setExpanded(true);
qDebug("expanding folder %s", qPrintable(name));
break;
}
}
}
}
}
void RSSImp::saveFoldersOpenState()
{
QStringList open_folders;
QList<QTreeWidgetItem*> items = m_feedList->getAllOpenFolders();
foreach (QTreeWidgetItem* item, items) {
QString path = m_feedList->getItemPath(item).join("\\");
qDebug("saving open folder: %s", qPrintable(path));
open_folders << path;
}
Preferences::instance()->setRssOpenFolders(open_folders);
}
// refresh all streams by a button
void RSSImp::refreshAllFeeds()
{
foreach (QTreeWidgetItem* item, m_feedList->getAllFeedItems())
item->setData(0, Qt::DecorationRole, QVariant(QIcon(":/icons/loading.png")));
m_rssManager->refresh();
}
void RSSImp::downloadSelectedTorrents()
{
QList<QListWidgetItem*> selected_items = listArticles->selectedItems();
foreach (const QListWidgetItem* item, selected_items) {
if (!item) continue;
RssFeedPtr feed = m_feedList->getRSSItemFromUrl(item->data(Article::FeedUrlRole).toString());
if (!feed) continue;
RssArticlePtr article = feed->getItem(item->data(Article::IdRole).toString());
if (!article) continue;
QString torrentLink = article->torrentUrl();
// Check if it is a magnet link
if (torrentLink.startsWith("magnet:", Qt::CaseInsensitive)) {
QBtSession::instance()->addMagnetInteractive(torrentLink);
}
else {
// Load possible cookies
QString feed_url = m_feedList->getItemID(m_feedList->selectedItems().first());
QString feed_hostname = QUrl::fromEncoded(feed_url.toUtf8()).host();
QList<QNetworkCookie> cookies = Preferences::instance()->getHostNameQNetworkCookies(feed_hostname);
qDebug("Loaded %d cookies for RSS item\n", cookies.size());
QBtSession::instance()->downloadFromUrl(torrentLink, cookies);
}
}
}
// open the url of the selected RSS articles in the Web browser
void RSSImp::openSelectedArticlesUrls()
{
QList<QListWidgetItem *> selected_items = listArticles->selectedItems();
foreach (const QListWidgetItem* item, selected_items) {
RssArticlePtr news = m_feedList->getRSSItemFromUrl(item->data(Article::FeedUrlRole).toString())
->getItem(item->data(Article::IdRole).toString());
const QString link = news->link();
if (!link.isEmpty())
QDesktopServices::openUrl(QUrl(link));
}
}
//right-click on stream : give it an alias
void RSSImp::renameSelectedRssFile()
{
QList<QTreeWidgetItem*> selectedItems = m_feedList->selectedItems();
if (selectedItems.size() != 1)
return;
QTreeWidgetItem* item = selectedItems.first();
if (item == m_feedList->stickyUnreadItem())
return;
RssFilePtr rss_item = m_feedList->getRSSItem(item);
bool ok;
QString newName;
do {
newName = AutoExpandableDialog::getText(this, tr("Please choose a new name for this RSS feed"), tr("New feed name:"), QLineEdit::Normal, m_feedList->getRSSItem(item)->displayName(), &ok);
// Check if name is already taken
if (ok) {
if (rss_item->parent()->hasChild(newName)) {
QMessageBox::warning(0, tr("Name already in use"), tr("This name is already used by another item, please choose another one."));
ok = false;
}
}
else {
return;
}
} while (!ok);
// Rename item
rss_item->rename(newName);
// Update TreeWidget
updateItemInfos(item);
}
// right-click on stream : refresh it
void RSSImp::refreshSelectedItems()
{
QList<QTreeWidgetItem*> selectedItems = m_feedList->selectedItems();
foreach (QTreeWidgetItem* item, selectedItems) {
RssFilePtr file = m_feedList->getRSSItem(item);
// Update icons
if (item == m_feedList->stickyUnreadItem()) {
refreshAllFeeds();
return;
}
else {
if (!file->refresh())
continue;
// Update UI
if (qSharedPointerDynamicCast<RssFeed>(file)) {
item->setData(0, Qt::DecorationRole, QVariant(QIcon(":/icons/loading.png")));
}
else if (qSharedPointerDynamicCast<RssFolder>(file)) {
// Update feeds in the folder
foreach (QTreeWidgetItem *feed, m_feedList->getAllFeedItems(item))
feed->setData(0, Qt::DecorationRole, QVariant(QIcon(":/icons/loading.png")));
}
}
}
}
void RSSImp::copySelectedFeedsURL()
{
QStringList URLs;
QList<QTreeWidgetItem*> selectedItems = m_feedList->selectedItems();
QTreeWidgetItem* item;
foreach (item, selectedItems)
if (m_feedList->isFeed(item))
URLs << m_feedList->getItemID(item);
qApp->clipboard()->setText(URLs.join("\n"));
}
void RSSImp::on_markReadButton_clicked()
{
QList<QTreeWidgetItem*> selectedItems = m_feedList->selectedItems();
foreach (QTreeWidgetItem* item, selectedItems) {
RssFilePtr rss_item = m_feedList->getRSSItem(item);
Q_ASSERT(rss_item);
rss_item->markAsRead();
updateItemInfos(item);
}
// Update article list
if (!selectedItems.isEmpty())
populateArticleList(m_feedList->currentItem());
}
QTreeWidgetItem* RSSImp::createFolderListItem(const RssFilePtr& rssFile)
{
Q_ASSERT(rssFile);
QTreeWidgetItem* item = new QTreeWidgetItem;
item->setData(0, Qt::DisplayRole, QVariant(rssFile->displayName() + QString::fromUtf8(" (") + QString::number(rssFile->unreadCount()) + QString(")")));
item->setData(0, Qt::DecorationRole, rssFile->icon());
return item;
}
void RSSImp::fillFeedsList(QTreeWidgetItem* parent, const RssFolderPtr& rss_parent)
{
QList<RssFilePtr> children;
if (parent)
children = rss_parent->getContent();
else
children = m_rssManager->getContent();
foreach (const RssFilePtr& rssFile, children) {
QTreeWidgetItem* item = createFolderListItem(rssFile);
Q_ASSERT(item);
if (parent)
parent->addChild(item);
else
m_feedList->addTopLevelItem(item);
// Notify TreeWidget of item addition
m_feedList->itemAdded(item, rssFile);
// Recursive call if this is a folder.
if (RssFolderPtr folder = qSharedPointerDynamicCast<RssFolder>(rssFile))
fillFeedsList(item, folder);
}
}
QListWidgetItem* RSSImp::createArticleListItem(const RssArticlePtr& article)
{
Q_ASSERT(article);
QListWidgetItem* item = new QListWidgetItem;
item->setData(Article::TitleRole, article->title());
item->setData(Article::FeedUrlRole, article->parent()->url());
item->setData(Article::IdRole, article->guid());
if (article->isRead()) {
item->setData(Article::ColorRole, QVariant(QColor("grey")));
item->setData(Article::IconRole, QVariant(QIcon(":/icons/sphere.png")));
}
else {
item->setData(Article::ColorRole, QVariant(QColor("blue")));
item->setData(Article::IconRole, QVariant(QIcon(":/icons/sphere2.png")));
}
return item;
}
// fills the newsList
void RSSImp::populateArticleList(QTreeWidgetItem* item)
{
if (!item) {
listArticles->clear();
return;
}
RssFilePtr rss_item = m_feedList->getRSSItem(item);
if (!rss_item)
return;
// Clear the list first
textBrowser->clear();
m_currentArticle = 0;
listArticles->clear();
qDebug("Getting the list of news");
RssArticleList articles;
if (rss_item == m_rssManager)
articles = rss_item->unreadArticleListByDateDesc();
else
articles = rss_item->articleListByDateDesc();
qDebug("Got the list of news");
foreach (const RssArticlePtr& article, articles) {
QListWidgetItem* articleItem = createArticleListItem(article);
listArticles->addItem(articleItem);
}
qDebug("Added all news to the GUI");
}
// display a news
void RSSImp::refreshTextBrowser()
{
QList<QListWidgetItem*> selection = listArticles->selectedItems();
if (selection.empty()) return;
Q_ASSERT(selection.size() == 1);
QListWidgetItem *item = selection.first();
Q_ASSERT(item);
if (item == m_currentArticle) return;
// Stop displaying previous news if necessary
if (m_feedList->currentFeed() == m_feedList->stickyUnreadItem()) {
if (m_currentArticle) {
disconnect(listArticles, SIGNAL(itemSelectionChanged()), this, SLOT(refreshTextBrowser()));
listArticles->removeItemWidget(m_currentArticle);
Q_ASSERT(m_currentArticle);
delete m_currentArticle;
connect(listArticles, SIGNAL(itemSelectionChanged()), this, SLOT(refreshTextBrowser()));
}
m_currentArticle = item;
}
RssFeedPtr stream = m_feedList->getRSSItemFromUrl(item->data(Article::FeedUrlRole).toString());
RssArticlePtr article = stream->getItem(item->data(Article::IdRole).toString());
QString html;
html += "<div style='border: 2px solid red; margin-left: 5px; margin-right: 5px; margin-bottom: 5px;'>";
html += "<div style='background-color: #678db2; font-weight: bold; color: #fff;'>" + article->title() + "</div>";
if (article->date().isValid())
html += "<div style='background-color: #efefef;'><b>" + tr("Date: ") + "</b>" + article->date().toLocalTime().toString(Qt::SystemLocaleLongDate) + "</div>";
if (!article->author().isEmpty())
html += "<div style='background-color: #efefef;'><b>" + tr("Author: ") + "</b>" + article->author() + "</div>";
html += "</div>";
html += "<div style='margin-left: 5px; margin-right: 5px;'>";
if(Qt::mightBeRichText(article->description())) {
html += article->description();
}
else {
QString description = article->description();
QRegExp rx;
// If description is plain text, replace BBCode tags with HTML and wrap everything in <pre></pre> so it looks nice
rx.setMinimal(true);
rx.setCaseSensitivity(Qt::CaseInsensitive);
rx.setPattern("\\[img\\](.+)\\[/img\\]");
description = description.replace(rx, "<img src=\"\\1\">");
rx.setPattern("\\[url=(\")?(.+)\\1\\]");
description = description.replace(rx, "<a href=\"\\2\">");
description = description.replace("[/url]", "</a>", Qt::CaseInsensitive);
rx.setPattern("\\[(/)?([bius])\\]");
description = description.replace(rx, "<\\1\\2>");
rx.setPattern("\\[color=(\")?(.+)\\1\\]");
description = description.replace(rx, "<span style=\"color:\\2\">");
description = description.replace("[/color]", "</span>", Qt::CaseInsensitive);
rx.setPattern("\\[size=(\")?(.+)\\d\\1\\]");
description = description.replace(rx, "<span style=\"font-size:\\2px\">");
description = description.replace("[/size]", "</span>", Qt::CaseInsensitive);
html += "<pre>" + description + "</pre>";
}
html += "</div>";
textBrowser->setHtml(html);
article->markAsRead();
item->setData(Article::ColorRole, QVariant(QColor("grey")));
item->setData(Article::IconRole, QVariant(QIcon(":/icons/sphere.png")));
// Decrement feed nb unread news
updateItemInfos(m_feedList->stickyUnreadItem());
updateItemInfos(m_feedList->getTreeItemFromUrl(item->data(Article::FeedUrlRole).toString()));
}
void RSSImp::saveSlidersPosition()
{
// Remember sliders positions
Preferences* const pref = Preferences::instance();
pref->setRssHSplitterState(splitter_h->saveState());
pref->setRssVSplitterState(splitter_v->saveState());
qDebug("Splitters position saved");
}
void RSSImp::restoreSlidersPosition()
{
const Preferences* const pref = Preferences::instance();
QByteArray pos_h = pref->getRssHSplitterState();
if (!pos_h.isNull())
splitter_h->restoreState(pos_h);
QByteArray pos_v = pref->getRssVSplitterState();
if (!pos_v.isNull())
splitter_v->restoreState(pos_v);
}
void RSSImp::updateItemsInfos(const QList<QTreeWidgetItem*>& items)
{
foreach (QTreeWidgetItem* item, items)
updateItemInfos(item);
}
void RSSImp::updateItemInfos(QTreeWidgetItem *item)
{
RssFilePtr rss_item = m_feedList->getRSSItem(item);
if (!rss_item)
return;
QString name;
if (rss_item == m_rssManager)
name = tr("Unread");
else
name = rss_item->displayName();
item->setText(0, name + QString::fromUtf8(" (") + QString::number(rss_item->unreadCount()) + QString(")"));
// If item has a parent, update it too
if (item->parent())
updateItemInfos(item->parent());
}
void RSSImp::updateFeedIcon(const QString& url, const QString& iconPath)
{
QTreeWidgetItem* item = m_feedList->getTreeItemFromUrl(url);
item->setData(0, Qt::DecorationRole, QVariant(QIcon(iconPath)));
}
void RSSImp::updateFeedInfos(const QString& url, const QString& display_name, uint nbUnread)
{
qDebug() << Q_FUNC_INFO << display_name;
QTreeWidgetItem *item = m_feedList->getTreeItemFromUrl(url);
RssFeedPtr stream = qSharedPointerCast<RssFeed>(m_feedList->getRSSItem(item));
item->setText(0, display_name + QString::fromUtf8(" (") + QString::number(nbUnread) + QString(")"));
if (!stream->isLoading())
item->setData(0, Qt::DecorationRole, QVariant(stream->icon()));
// Update parent
if (item->parent())
updateItemInfos(item->parent());
// Update Unread item
updateItemInfos(m_feedList->stickyUnreadItem());
}
void RSSImp::onFeedContentChanged(const QString& url)
{
qDebug() << Q_FUNC_INFO << url;
QTreeWidgetItem *item = m_feedList->getTreeItemFromUrl(url);
// If the feed is selected, update the displayed news
if (m_feedList->currentItem() == item ) {
populateArticleList(item);
}
else {
// Update unread items
if (m_feedList->currentItem() == m_feedList->stickyUnreadItem())
populateArticleList(m_feedList->stickyUnreadItem());
}
}
void RSSImp::updateRefreshInterval(uint val)
{
m_rssManager->updateRefreshInterval(val);
}
RSSImp::RSSImp(QWidget *parent):
QWidget(parent),
m_rssManager(new RssManager)
{
setupUi(this);
// Icons
actionCopy_feed_URL->setIcon(IconProvider::instance()->getIcon("edit-copy"));
actionDelete->setIcon(IconProvider::instance()->getIcon("edit-delete"));
actionDownload_torrent->setIcon(IconProvider::instance()->getIcon("download"));
actionManage_cookies->setIcon(IconProvider::instance()->getIcon("preferences-web-browser-cookies"));
actionMark_items_read->setIcon(IconProvider::instance()->getIcon("mail-mark-read"));
actionNew_folder->setIcon(IconProvider::instance()->getIcon("folder-new"));
actionNew_subscription->setIcon(IconProvider::instance()->getIcon("list-add"));
actionOpen_news_URL->setIcon(IconProvider::instance()->getIcon("application-x-mswinurl"));
actionRename->setIcon(IconProvider::instance()->getIcon("edit-rename"));
actionUpdate->setIcon(IconProvider::instance()->getIcon("view-refresh"));
actionUpdate_all_feeds->setIcon(IconProvider::instance()->getIcon("view-refresh"));
newFeedButton->setIcon(IconProvider::instance()->getIcon("list-add"));
markReadButton->setIcon(IconProvider::instance()->getIcon("mail-mark-read"));
updateAllButton->setIcon(IconProvider::instance()->getIcon("view-refresh"));
rssDownloaderBtn->setIcon(IconProvider::instance()->getIcon("download"));
settingsButton->setIcon(IconProvider::instance()->getIcon("preferences-system"));
m_feedList = new FeedListWidget(splitter_h, m_rssManager);
splitter_h->insertWidget(0, m_feedList);
listArticles->setSelectionBehavior(QAbstractItemView::SelectItems);
listArticles->setSelectionMode(QAbstractItemView::SingleSelection);
editHotkey = new QShortcut(QKeySequence("F2"), m_feedList, 0, 0, Qt::WidgetShortcut);
connect(editHotkey, SIGNAL(activated()), SLOT(renameSelectedRssFile()));
connect(m_feedList, SIGNAL(doubleClicked(QModelIndex)), SLOT(renameSelectedRssFile()));
deleteHotkey = new QShortcut(QKeySequence(QKeySequence::Delete), m_feedList, 0, 0, Qt::WidgetShortcut);
connect(deleteHotkey, SIGNAL(activated()), SLOT(deleteSelectedItems()));
m_rssManager->loadStreamList();
fillFeedsList();
populateArticleList(m_feedList->currentItem());
loadFoldersOpenState();
connect(m_rssManager.data(), SIGNAL(feedInfosChanged(QString, QString, unsigned int)), SLOT(updateFeedInfos(QString, QString, unsigned int)));
connect(m_rssManager.data(), SIGNAL(feedContentChanged(QString)), SLOT(onFeedContentChanged(QString)));
connect(m_rssManager.data(), SIGNAL(feedIconChanged(QString, QString)), SLOT(updateFeedIcon(QString, QString)));
connect(m_feedList, SIGNAL(customContextMenuRequested(const QPoint &)), SLOT(displayRSSListMenu(const QPoint &)));
connect(listArticles, SIGNAL(customContextMenuRequested(const QPoint &)), SLOT(displayItemsListMenu(const QPoint &)));
// Feeds list actions
connect(actionDelete, SIGNAL(triggered()), this, SLOT(deleteSelectedItems()));
connect(actionRename, SIGNAL(triggered()), this, SLOT(renameSelectedRssFile()));
connect(actionUpdate, SIGNAL(triggered()), this, SLOT(refreshSelectedItems()));
connect(actionNew_folder, SIGNAL(triggered()), this, SLOT(askNewFolder()));
connect(actionNew_subscription, SIGNAL(triggered()), this, SLOT(on_newFeedButton_clicked()));
connect(actionUpdate_all_feeds, SIGNAL(triggered()), this, SLOT(refreshAllFeeds()));
connect(updateAllButton, SIGNAL(clicked()), SLOT(refreshAllFeeds()));
connect(actionCopy_feed_URL, SIGNAL(triggered()), this, SLOT(copySelectedFeedsURL()));
connect(actionMark_items_read, SIGNAL(triggered()), this, SLOT(on_markReadButton_clicked()));
// News list actions
connect(actionOpen_news_URL, SIGNAL(triggered()), this, SLOT(openSelectedArticlesUrls()));
connect(actionDownload_torrent, SIGNAL(triggered()), this, SLOT(downloadSelectedTorrents()));
connect(m_feedList, SIGNAL(currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)), this, SLOT(populateArticleList(QTreeWidgetItem*)));
connect(m_feedList, SIGNAL(foldersAltered(QList<QTreeWidgetItem*>)), this, SLOT(updateItemsInfos(QList<QTreeWidgetItem*>)));
connect(listArticles, SIGNAL(itemSelectionChanged()), this, SLOT(refreshTextBrowser()));
connect(listArticles, SIGNAL(itemDoubleClicked(QListWidgetItem *)), this, SLOT(downloadSelectedTorrents()));
// Refresh all feeds
m_rssManager->refresh();
// Restore sliders position
restoreSlidersPosition();
// Bind saveSliders slots
connect(splitter_v, SIGNAL(splitterMoved(int, int)), this, SLOT(saveSlidersPosition()));
connect(splitter_h, SIGNAL(splitterMoved(int, int)), this, SLOT(saveSlidersPosition()));
qDebug("RSSImp constructed");
}
RSSImp::~RSSImp()
{
qDebug("Deleting RSSImp...");
saveFoldersOpenState();
delete editHotkey;
delete deleteHotkey;
delete m_feedList;
qDebug("RSSImp deleted");
}
void RSSImp::on_settingsButton_clicked()
{
RssSettingsDlg dlg(this);
if (dlg.exec())
updateRefreshInterval(Preferences::instance()->getRSSRefreshInterval());
}
void RSSImp::on_rssDownloaderBtn_clicked()
{
AutomatedRssDownloader dlg(m_rssManager, this);
dlg.exec();
if (dlg.isRssDownloaderEnabled()) {
m_rssManager->recheckRssItemsForDownload();
refreshAllFeeds();
}
}

101
src/gui/rss/rss_imp.h Normal file
View File

@@ -0,0 +1,101 @@
/*
* Bittorrent Client using Qt4 and libtorrent.
* Copyright (C) 2006 Christophe Dumez, Arnaud Demaiziere
*
* 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.
*
* In addition, as a special exception, the copyright holders give permission to
* link this program with the OpenSSL project's "OpenSSL" library (or with
* modified versions of it that use the same license as the "OpenSSL" library),
* and distribute the linked executables. You must obey the GNU General Public
* License in all respects for all of the code used other than "OpenSSL". If you
* modify file(s), you may extend this exception to your version of the file(s),
* but you are not obligated to do so. If you do not wish to do so, delete this
* exception statement from your version.
*
* Contact : chris@qbittorrent.org arnaud@qbittorrent.org
*/
#ifndef __RSS_IMP_H__
#define __RSS_IMP_H__
#define REFRESH_MAX_LATENCY 600000
#include <QPointer>
#include <QShortcut>
#include "ui_rss.h"
#include "rssfolder.h"
#include "rssmanager.h"
class FeedListWidget;
QT_BEGIN_NAMESPACE
class QTreeWidgetItem;
QT_END_NAMESPACE
class RSSImp: public QWidget, public Ui::RSS
{
Q_OBJECT
public:
RSSImp(QWidget *parent);
~RSSImp();
public slots:
void deleteSelectedItems();
void updateRefreshInterval(uint val);
private slots:
void on_newFeedButton_clicked();
void refreshAllFeeds();
void on_markReadButton_clicked();
void displayRSSListMenu(const QPoint&);
void displayItemsListMenu(const QPoint&);
void renameSelectedRssFile();
void refreshSelectedItems();
void copySelectedFeedsURL();
void populateArticleList(QTreeWidgetItem* item);
void refreshTextBrowser();
void updateFeedIcon(const QString &url, const QString &icon_path);
void updateFeedInfos(const QString &url, const QString &display_name, uint nbUnread);
void onFeedContentChanged(const QString& url);
void updateItemsInfos(const QList<QTreeWidgetItem*> &items);
void updateItemInfos(QTreeWidgetItem *item);
void openSelectedArticlesUrls();
void downloadSelectedTorrents();
void fillFeedsList(QTreeWidgetItem *parent = 0, const RssFolderPtr& rss_parent = RssFolderPtr());
void saveSlidersPosition();
void restoreSlidersPosition();
void askNewFolder();
void saveFoldersOpenState();
void loadFoldersOpenState();
void on_actionManage_cookies_triggered();
void on_settingsButton_clicked();
void on_rssDownloaderBtn_clicked();
private:
static QListWidgetItem* createArticleListItem(const RssArticlePtr& article);
static QTreeWidgetItem* createFolderListItem(const RssFilePtr& rssFile);
private:
RssManagerPtr m_rssManager;
FeedListWidget *m_feedList;
QListWidgetItem* m_currentArticle;
QShortcut *editHotkey;
QShortcut *deleteHotkey;
};
#endif

128
src/gui/rss/rssarticle.cpp Normal file
View File

@@ -0,0 +1,128 @@
/*
* Bittorrent Client using Qt4 and libtorrent.
* Copyright (C) 2010 Christophe Dumez, Arnaud Demaiziere
*
* 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.
*
* In addition, as a special exception, the copyright holders give permission to
* link this program with the OpenSSL project's "OpenSSL" library (or with
* modified versions of it that use the same license as the "OpenSSL" library),
* and distribute the linked executables. You must obey the GNU General Public
* License in all respects for all of the code used other than "OpenSSL". If you
* modify file(s), you may extend this exception to your version of the file(s),
* but you are not obligated to do so. If you do not wish to do so, delete this
* exception statement from your version.
*
* Contact: chris@qbittorrent.org, arnaud@qbittorrent.org
*/
#include <QVariant>
#include <QDebug>
#include <iostream>
#include "rssarticle.h"
#include "rssfeed.h"
// public constructor
RssArticle::RssArticle(RssFeed* parent, const QString& guid):
m_parent(parent), m_guid(guid), m_read(false) {}
bool RssArticle::hasAttachment() const {
return !m_torrentUrl.isEmpty();
}
QVariantHash RssArticle::toHash() const {
QVariantHash item;
item["title"] = m_title;
item["id"] = m_guid;
item["torrent_url"] = m_torrentUrl;
item["news_link"] = m_link;
item["description"] = m_description;
item["date"] = m_date;
item["author"] = m_author;
item["read"] = m_read;
return item;
}
RssArticlePtr hashToRssArticle(RssFeed* parent, const QVariantHash& h) {
const QString guid = h.value("id").toString();
if (guid.isEmpty())
return RssArticlePtr();
RssArticlePtr art(new RssArticle(parent, guid));
art->m_title = h.value("title", "").toString();
art->m_torrentUrl = h.value("torrent_url", "").toString();
art->m_link = h.value("news_link", "").toString();
art->m_description = h.value("description").toString();
art->m_date = h.value("date").toDateTime();
art->m_author = h.value("author").toString();
art->m_read = h.value("read", false).toBool();
return art;
}
RssFeed* RssArticle::parent() const {
return m_parent;
}
const QString& RssArticle::author() const {
return m_author;
}
const QString& RssArticle::torrentUrl() const {
return m_torrentUrl.isEmpty() ? m_link : m_torrentUrl;
}
const QString& RssArticle::link() const {
return m_link;
}
QString RssArticle::description() const
{
return m_description.isNull() ? "" : m_description;
}
const QDateTime& RssArticle::date() const {
return m_date;
}
bool RssArticle::isRead() const {
return m_read;
}
void RssArticle::markAsRead() {
if (m_read)
return;
m_read = true;
m_parent->decrementUnreadCount();
m_parent->markAsDirty();
emit articleWasRead();
}
const QString& RssArticle::guid() const
{
return m_guid;
}
const QString& RssArticle::title() const
{
return m_title;
}
void RssArticle::handleTorrentDownloadSuccess(const QString &url) {
if (url == m_torrentUrl || url == m_link)
markAsRead();
}

88
src/gui/rss/rssarticle.h Normal file
View File

@@ -0,0 +1,88 @@
/*
* Bittorrent Client using Qt4 and libtorrent.
* Copyright (C) 2010 Christophe Dumez, Arnaud Demaiziere
*
* 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.
*
* In addition, as a special exception, the copyright holders give permission to
* link this program with the OpenSSL project's "OpenSSL" library (or with
* modified versions of it that use the same license as the "OpenSSL" library),
* and distribute the linked executables. You must obey the GNU General Public
* License in all respects for all of the code used other than "OpenSSL". If you
* modify file(s), you may extend this exception to your version of the file(s),
* but you are not obligated to do so. If you do not wish to do so, delete this
* exception statement from your version.
*
* Contact: chris@qbittorrent.org, arnaud@qbittorrent.org
*/
#ifndef RSSARTICLE_H
#define RSSARTICLE_H
#include <QXmlStreamReader>
#include <QDateTime>
#include <QVariantHash>
#include <QSharedPointer>
class RssFeed;
class RssArticle;
typedef QSharedPointer<RssArticle> RssArticlePtr;
// Item of a rss stream, single information
class RssArticle : public QObject {
Q_OBJECT
public:
RssArticle(RssFeed* parent, const QString& guid);
// Accessors
bool hasAttachment() const;
const QString& guid() const;
RssFeed* parent() const;
const QString& title() const;
const QString& author() const;
const QString& torrentUrl() const;
const QString& link() const;
QString description() const;
const QDateTime& date() const;
bool isRead() const;
// Setters
void markAsRead();
// Serialization
QVariantHash toHash() const;
signals:
void articleWasRead();
public slots:
void handleTorrentDownloadSuccess(const QString& url);
friend RssArticlePtr hashToRssArticle(RssFeed* parent, const QVariantHash& hash);
private:
RssFeed* m_parent;
QString m_guid;
QString m_title;
QString m_torrentUrl;
QString m_link;
QString m_description;
QDateTime m_date;
QString m_author;
bool m_read;
};
RssArticlePtr hashToRssArticle(RssFeed* parent, const QVariantHash& hash);
#endif // RSSARTICLE_H

View File

@@ -0,0 +1,200 @@
/*
* Bittorrent Client using Qt4 and libtorrent.
* Copyright (C) 2010 Christophe Dumez
*
* 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.
*
* In addition, as a special exception, the copyright holders give permission to
* link this program with the OpenSSL project's "OpenSSL" library (or with
* modified versions of it that use the same license as the "OpenSSL" library),
* and distribute the linked executables. You must obey the GNU General Public
* License in all respects for all of the code used other than "OpenSSL". If you
* modify file(s), you may extend this exception to your version of the file(s),
* but you are not obligated to do so. If you do not wish to do so, delete this
* exception statement from your version.
*
* Contact : chris@qbittorrent.org
*/
#include <QRegExp>
#include <QDebug>
#include <QDir>
#include "rssdownloadrule.h"
#include "preferences.h"
#include "rssfeed.h"
#include "rssarticle.h"
#include "fs_utils.h"
RssDownloadRule::RssDownloadRule(): m_enabled(false), m_useRegex(false)
{
}
bool RssDownloadRule::matches(const QString &article_title) const
{
foreach (const QString& token, m_mustContain) {
if (!token.isEmpty()) {
QRegExp reg(token, Qt::CaseInsensitive, m_useRegex ? QRegExp::RegExp : QRegExp::Wildcard);
if (reg.indexIn(article_title) < 0)
return false;
}
}
qDebug("Checking not matching tokens");
// Checking not matching
foreach (const QString& token, m_mustNotContain) {
if (!token.isEmpty()) {
QRegExp reg(token, Qt::CaseInsensitive, m_useRegex ? QRegExp::RegExp : QRegExp::Wildcard);
if (reg.indexIn(article_title) > -1)
return false;
}
}
if (!m_episodeFilter.isEmpty()) {
qDebug("Checking episode filter");
QRegExp f("(^\\d{1,4})x(.*;$)");
int pos = f.indexIn(m_episodeFilter);
if (pos < 0)
return false;
QString s = f.cap(1);
QStringList eps = f.cap(2).split(";");
QString expStr;
expStr += "s0?" + s + "[ -_\\.]?" + "e0?";
foreach (const QString& ep, eps) {
if (ep.isEmpty())
continue;
if (ep.indexOf('-') != -1) { // Range detected
QString partialPattern = "s0?" + s + "[ -_\\.]?" + "e(0?\\d{1,4})";
QRegExp reg(partialPattern, Qt::CaseInsensitive);
if (ep.endsWith('-')) { // Infinite range
int epOurs = ep.left(ep.size() - 1).toInt();
// Extract partial match from article and ocmpare as digits
pos = reg.indexIn(article_title);
if (pos != -1) {
int epTheirs = reg.cap(1).toInt();
if (epTheirs >= epOurs)
return true;
}
}
else { // Normal range
QStringList range = ep.split('-');
Q_ASSERT(range.size() == 2);
if (range.first().toInt() > range.last().toInt())
continue; // Ignore this subrule completely
int epOursFirst = range.first().toInt();
int epOursLast = range.last().toInt();
// Extract partial match from article and ocmpare as digits
pos = reg.indexIn(article_title);
if (pos != -1) {
int epTheirs = reg.cap(1).toInt();
if (epOursFirst <= epTheirs && epOursLast >= epTheirs)
return true;
}
}
}
else { // Single number
QRegExp reg(expStr + ep + "\\D", Qt::CaseInsensitive);
if (reg.indexIn(article_title) != -1)
return true;
}
}
return false;
}
return true;
}
void RssDownloadRule::setMustContain(const QString &tokens)
{
if (m_useRegex)
m_mustContain = QStringList() << tokens;
else
m_mustContain = tokens.split(" ");
}
void RssDownloadRule::setMustNotContain(const QString &tokens)
{
if (m_useRegex)
m_mustNotContain = QStringList() << tokens;
else
m_mustNotContain = tokens.split("|");
}
RssDownloadRulePtr RssDownloadRule::fromVariantHash(const QVariantHash &rule_hash)
{
RssDownloadRulePtr rule(new RssDownloadRule);
rule->setName(rule_hash.value("name").toString());
rule->setUseRegex(rule_hash.value("use_regex", false).toBool());
rule->setMustContain(rule_hash.value("must_contain").toString());
rule->setMustNotContain(rule_hash.value("must_not_contain").toString());
rule->setEpisodeFilter(rule_hash.value("episode_filter").toString());
rule->setRssFeeds(rule_hash.value("affected_feeds").toStringList());
rule->setEnabled(rule_hash.value("enabled", false).toBool());
rule->setSavePath(rule_hash.value("save_path").toString());
rule->setLabel(rule_hash.value("label_assigned").toString());
rule->setAddPaused(AddPausedState(rule_hash.value("add_paused").toUInt()));
rule->setLastMatch(rule_hash.value("last_match").toDateTime());
rule->setIgnoreDays(rule_hash.value("ignore_days").toInt());
return rule;
}
QVariantHash RssDownloadRule::toVariantHash() const
{
QVariantHash hash;
hash["name"] = m_name;
hash["must_contain"] = m_mustContain.join(" ");
hash["must_not_contain"] = m_mustNotContain.join("|");
hash["save_path"] = m_savePath;
hash["affected_feeds"] = m_rssFeeds;
hash["enabled"] = m_enabled;
hash["label_assigned"] = m_label;
hash["use_regex"] = m_useRegex;
hash["add_paused"] = m_apstate;
hash["episode_filter"] = m_episodeFilter;
hash["last_match"] = m_lastMatch;
hash["ignore_days"] = m_ignoreDays;
return hash;
}
bool RssDownloadRule::operator==(const RssDownloadRule &other) const {
return m_name == other.name();
}
void RssDownloadRule::setSavePath(const QString &save_path)
{
if (!save_path.isEmpty() && QDir(save_path) != QDir(Preferences::instance()->getSavePath()))
m_savePath = fsutils::fromNativePath(save_path);
else
m_savePath = QString();
}
QStringList RssDownloadRule::findMatchingArticles(const RssFeedPtr& feed) const
{
QStringList ret;
const RssArticleHash& feed_articles = feed->articleHash();
RssArticleHash::ConstIterator artIt = feed_articles.begin();
RssArticleHash::ConstIterator artItend = feed_articles.end();
for ( ; artIt != artItend ; ++artIt) {
const QString title = artIt.value()->title();
if (matches(title))
ret << title;
}
return ret;
}

View File

@@ -0,0 +1,102 @@
/*
* Bittorrent Client using Qt4 and libtorrent.
* Copyright (C) 2010 Christophe Dumez
*
* 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.
*
* In addition, as a special exception, the copyright holders give permission to
* link this program with the OpenSSL project's "OpenSSL" library (or with
* modified versions of it that use the same license as the "OpenSSL" library),
* and distribute the linked executables. You must obey the GNU General Public
* License in all respects for all of the code used other than "OpenSSL". If you
* modify file(s), you may extend this exception to your version of the file(s),
* but you are not obligated to do so. If you do not wish to do so, delete this
* exception statement from your version.
*
* Contact : chris@qbittorrent.org
*/
#ifndef RSSDOWNLOADRULE_H
#define RSSDOWNLOADRULE_H
#include <QStringList>
#include <QVariantHash>
#include <QSharedPointer>
#include <QDateTime>
class RssFeed;
typedef QSharedPointer<RssFeed> RssFeedPtr;
class RssDownloadRule;
typedef QSharedPointer<RssDownloadRule> RssDownloadRulePtr;
class RssDownloadRule
{
public:
enum AddPausedState {
USE_GLOBAL = 0,
ALWAYS_PAUSED,
NEVER_PAUSED
};
explicit RssDownloadRule();
static RssDownloadRulePtr fromVariantHash(const QVariantHash &rule_hash);
QVariantHash toVariantHash() const;
bool matches(const QString &article_title) const;
void setMustContain(const QString &tokens);
void setMustNotContain(const QString &tokens);
inline QStringList rssFeeds() const { return m_rssFeeds; }
inline void setRssFeeds(const QStringList& rss_feeds) { m_rssFeeds = rss_feeds; }
inline QString name() const { return m_name; }
inline void setName(const QString &name) { m_name = name; }
inline QString savePath() const { return m_savePath; }
void setSavePath(const QString &save_path);
inline AddPausedState addPaused() const { return m_apstate; }
inline void setAddPaused(const AddPausedState &aps) { m_apstate = aps; }
inline QString label() const { return m_label; }
inline void setLabel(const QString &_label) { m_label = _label; }
inline bool isEnabled() const { return m_enabled; }
inline void setEnabled(bool enable) { m_enabled = enable; }
inline void setLastMatch(const QDateTime& d) { m_lastMatch = d; }
inline QDateTime lastMatch() const { return m_lastMatch; }
inline void setIgnoreDays(int d) { m_ignoreDays = d; }
inline int ignoreDays() const { return m_ignoreDays; }
inline QString mustContain() const { return m_mustContain.join(" "); }
inline QString mustNotContain() const { return m_mustNotContain.join("|"); }
inline bool useRegex() const { return m_useRegex; }
inline void setUseRegex(bool enabled) { m_useRegex = enabled; }
inline QString episodeFilter() const { return m_episodeFilter; }
inline void setEpisodeFilter(const QString& e) { m_episodeFilter = e; }
QStringList findMatchingArticles(const RssFeedPtr& feed) const;
// Operators
bool operator==(const RssDownloadRule &other) const;
private:
QString m_name;
QStringList m_mustContain;
QStringList m_mustNotContain;
QString m_episodeFilter;
QString m_savePath;
QString m_label;
bool m_enabled;
QStringList m_rssFeeds;
bool m_useRegex;
AddPausedState m_apstate;
QDateTime m_lastMatch;
int m_ignoreDays;
};
#endif // RSSDOWNLOADRULE_H

View File

@@ -0,0 +1,172 @@
/*
* Bittorrent Client using Qt4 and libtorrent.
* Copyright (C) 2010 Christophe Dumez
*
* 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.
*
* In addition, as a special exception, the copyright holders give permission to
* link this program with the OpenSSL project's "OpenSSL" library (or with
* modified versions of it that use the same license as the "OpenSSL" library),
* and distribute the linked executables. You must obey the GNU General Public
* License in all respects for all of the code used other than "OpenSSL". If you
* modify file(s), you may extend this exception to your version of the file(s),
* but you are not obligated to do so. If you do not wish to do so, delete this
* exception statement from your version.
*
* Contact : chris@qbittorrent.org
*/
#include <QFile>
#include <QDataStream>
#include <QDebug>
#include "rssdownloadrulelist.h"
#include "preferences.h"
#include "qinisettings.h"
RssDownloadRuleList::RssDownloadRuleList()
{
loadRulesFromStorage();
}
RssDownloadRulePtr RssDownloadRuleList::findMatchingRule(const QString &feed_url, const QString &article_title) const
{
Q_ASSERT(Preferences::instance()->isRssDownloadingEnabled());
QStringList rule_names = m_feedRules.value(feed_url);
foreach (const QString &rule_name, rule_names) {
RssDownloadRulePtr rule = m_rules[rule_name];
if (rule->isEnabled() && rule->matches(article_title)) return rule;
}
return RssDownloadRulePtr();
}
void RssDownloadRuleList::replace(RssDownloadRuleList *other) {
m_rules.clear();
m_feedRules.clear();
foreach (const QString& name, other->ruleNames()) {
saveRule(other->getRule(name));
}
}
void RssDownloadRuleList::saveRulesToStorage()
{
QIniSettings qBTRSS("qBittorrent", "qBittorrent-rss");
qBTRSS.setValue("download_rules", toVariantHash());
}
void RssDownloadRuleList::loadRulesFromStorage()
{
QIniSettings qBTRSS("qBittorrent", "qBittorrent-rss");
loadRulesFromVariantHash(qBTRSS.value("download_rules").toHash());
}
QVariantHash RssDownloadRuleList::toVariantHash() const
{
QVariantHash ret;
foreach (const RssDownloadRulePtr &rule, m_rules.values()) {
ret.insert(rule->name(), rule->toVariantHash());
}
return ret;
}
void RssDownloadRuleList::loadRulesFromVariantHash(const QVariantHash &h)
{
QVariantHash::ConstIterator it = h.begin();
QVariantHash::ConstIterator itend = h.end();
for ( ; it != itend; ++it) {
RssDownloadRulePtr rule = RssDownloadRule::fromVariantHash(it.value().toHash());
if (rule && !rule->name().isEmpty())
saveRule(rule);
}
}
void RssDownloadRuleList::saveRule(const RssDownloadRulePtr &rule)
{
qDebug() << Q_FUNC_INFO << rule->name();
Q_ASSERT(rule);
if (m_rules.contains(rule->name())) {
qDebug("This is an update, removing old rule first");
removeRule(rule->name());
}
m_rules.insert(rule->name(), rule);
// Update feedRules hashtable
foreach (const QString &feed_url, rule->rssFeeds()) {
m_feedRules[feed_url].append(rule->name());
}
qDebug() << Q_FUNC_INFO << "EXIT";
}
void RssDownloadRuleList::removeRule(const QString &name)
{
qDebug() << Q_FUNC_INFO << name;
if (!m_rules.contains(name)) return;
RssDownloadRulePtr rule = m_rules.take(name);
// Update feedRules hashtable
foreach (const QString &feed_url, rule->rssFeeds()) {
m_feedRules[feed_url].removeOne(rule->name());
}
}
void RssDownloadRuleList::renameRule(const QString &old_name, const QString &new_name)
{
if (!m_rules.contains(old_name)) return;
RssDownloadRulePtr rule = m_rules.take(old_name);
rule->setName(new_name);
m_rules.insert(new_name, rule);
// Update feedRules hashtable
foreach (const QString &feed_url, rule->rssFeeds()) {
m_feedRules[feed_url].replace(m_feedRules[feed_url].indexOf(old_name), new_name);
}
}
RssDownloadRulePtr RssDownloadRuleList::getRule(const QString &name) const
{
return m_rules.value(name);
}
bool RssDownloadRuleList::serialize(const QString& path)
{
QFile f(path);
if (f.open(QIODevice::WriteOnly)) {
QDataStream out(&f);
out.setVersion(QDataStream::Qt_4_5);
out << toVariantHash();
f.close();
return true;
} else {
return false;
}
}
bool RssDownloadRuleList::unserialize(const QString &path)
{
QFile f(path);
if (f.open(QIODevice::ReadOnly)) {
QDataStream in(&f);
in.setVersion(QDataStream::Qt_4_5);
QVariantHash tmp;
in >> tmp;
f.close();
if (tmp.isEmpty())
return false;
qDebug("Processing was successful!");
loadRulesFromVariantHash(tmp);
return true;
} else {
qDebug("Error: could not open file at %s", qPrintable(path));
return false;
}
}

View File

@@ -0,0 +1,69 @@
/*
* Bittorrent Client using Qt4 and libtorrent.
* Copyright (C) 2010 Christophe Dumez
*
* 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.
*
* In addition, as a special exception, the copyright holders give permission to
* link this program with the OpenSSL project's "OpenSSL" library (or with
* modified versions of it that use the same license as the "OpenSSL" library),
* and distribute the linked executables. You must obey the GNU General Public
* License in all respects for all of the code used other than "OpenSSL". If you
* modify file(s), you may extend this exception to your version of the file(s),
* but you are not obligated to do so. If you do not wish to do so, delete this
* exception statement from your version.
*
* Contact : chris@qbittorrent.org
*/
#ifndef RSSDOWNLOADRULELIST_H
#define RSSDOWNLOADRULELIST_H
#include <QList>
#include <QHash>
#include <QVariantHash>
#include "rssdownloadrule.h"
class RssDownloadRuleList
{
Q_DISABLE_COPY(RssDownloadRuleList)
public:
RssDownloadRuleList();
RssDownloadRulePtr findMatchingRule(const QString &feed_url, const QString &article_title) const;
// Operators
void saveRule(const RssDownloadRulePtr &rule);
void removeRule(const QString &name);
void renameRule(const QString &old_name, const QString &new_name);
RssDownloadRulePtr getRule(const QString &name) const;
inline QStringList ruleNames() const { return m_rules.keys(); }
inline bool isEmpty() const { return m_rules.isEmpty(); }
void saveRulesToStorage();
bool serialize(const QString& path);
bool unserialize(const QString& path);
void replace(RssDownloadRuleList* other);
private:
void loadRulesFromStorage();
void loadRulesFromVariantHash(const QVariantHash& l);
QVariantHash toVariantHash() const;
private:
QHash<QString, RssDownloadRulePtr> m_rules;
QHash<QString, QStringList> m_feedRules;
};
#endif // RSSDOWNLOADFILTERLIST_H

436
src/gui/rss/rssfeed.cpp Normal file
View File

@@ -0,0 +1,436 @@
/*
* Bittorrent Client using Qt4 and libtorrent.
* Copyright (C) 2010 Christophe Dumez, Arnaud Demaiziere
*
* 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.
*
* In addition, as a special exception, the copyright holders give permission to
* link this program with the OpenSSL project's "OpenSSL" library (or with
* modified versions of it that use the same license as the "OpenSSL" library),
* and distribute the linked executables. You must obey the GNU General Public
* License in all respects for all of the code used other than "OpenSSL". If you
* modify file(s), you may extend this exception to your version of the file(s),
* but you are not obligated to do so. If you do not wish to do so, delete this
* exception statement from your version.
*
* Contact: chris@qbittorrent.org, arnaud@qbittorrent.org
*/
#include <QDebug>
#include "rssfeed.h"
#include "rssmanager.h"
#include "qbtsession.h"
#include "rssfolder.h"
#include "preferences.h"
#include "qinisettings.h"
#include "rssarticle.h"
#include "rssparser.h"
#include "misc.h"
#include "rssdownloadrulelist.h"
#include "downloadthread.h"
#include "fs_utils.h"
#include "logger.h"
bool rssArticleDateRecentThan(const RssArticlePtr& left, const RssArticlePtr& right)
{
return left->date() > right->date();
}
RssFeed::RssFeed(RssManager* manager, RssFolder* parent, const QString& url):
m_manager(manager),
m_parent(parent),
m_url (QUrl::fromEncoded(url.toUtf8()).toString()),
m_icon(":/icons/oxygen/application-rss+xml.png"),
m_unreadCount(0),
m_dirty(false),
m_inErrorState(false),
m_loading(false)
{
qDebug() << Q_FUNC_INFO << m_url;
// Listen for new RSS downloads
connect(manager->rssDownloader(), SIGNAL(downloadFinished(QString,QString)), SLOT(handleFinishedDownload(QString,QString)));
connect(manager->rssDownloader(), SIGNAL(downloadFailure(QString,QString)), SLOT(handleDownloadFailure(QString,QString)));
connect(manager->rssParser(), SIGNAL(feedTitle(QString,QString)), SLOT(handleFeedTitle(QString,QString)));
connect(manager->rssParser(), SIGNAL(newArticle(QString,QVariantHash)), SLOT(handleNewArticle(QString,QVariantHash)));
connect(manager->rssParser(), SIGNAL(feedParsingFinished(QString,QString)), SLOT(handleFeedParsingFinished(QString,QString)));
// Download the RSS Feed icon
m_iconUrl = iconUrl();
manager->rssDownloader()->downloadUrl(m_iconUrl);
// Load old RSS articles
loadItemsFromDisk();
}
RssFeed::~RssFeed()
{
if (!m_icon.startsWith(":/") && QFile::exists(m_icon))
fsutils::forceRemove(m_icon);
}
void RssFeed::saveItemsToDisk()
{
qDebug() << Q_FUNC_INFO << m_url;
if (!m_dirty)
return;
markAsDirty(false);
QIniSettings qBTRSS("qBittorrent", "qBittorrent-rss");
QVariantList old_items;
RssArticleHash::ConstIterator it = m_articles.begin();
RssArticleHash::ConstIterator itend = m_articles.end();
for ( ; it != itend; ++it) {
old_items << it.value()->toHash();
}
qDebug("Saving %d old items for feed %s", old_items.size(), qPrintable(displayName()));
QHash<QString, QVariant> all_old_items = qBTRSS.value("old_items", QHash<QString, QVariant>()).toHash();
all_old_items[m_url] = old_items;
qBTRSS.setValue("old_items", all_old_items);
}
void RssFeed::loadItemsFromDisk()
{
QIniSettings qBTRSS("qBittorrent", "qBittorrent-rss");
QHash<QString, QVariant> all_old_items = qBTRSS.value("old_items", QHash<QString, QVariant>()).toHash();
const QVariantList old_items = all_old_items.value(m_url, QVariantList()).toList();
qDebug("Loading %d old items for feed %s", old_items.size(), qPrintable(displayName()));
foreach (const QVariant& var_it, old_items) {
QVariantHash item = var_it.toHash();
RssArticlePtr rss_item = hashToRssArticle(this, item);
if (rss_item)
addArticle(rss_item);
}
}
void RssFeed::addArticle(const RssArticlePtr& article) {
int lbIndex = -1;
int max_articles = Preferences::instance()->getRSSMaxArticlesPerFeed();
if (!m_articles.contains(article->guid())) {
markAsDirty();
// Update unreadCount
if (!article->isRead())
++m_unreadCount;
// Insert in hash table
m_articles[article->guid()] = article;
// Insertion sort
RssArticleList::Iterator lowerBound = qLowerBound(m_articlesByDate.begin(), m_articlesByDate.end(), article, rssArticleDateRecentThan);
m_articlesByDate.insert(lowerBound, article);
lbIndex = m_articlesByDate.indexOf(article);
if (m_articlesByDate.size() > max_articles) {
RssArticlePtr oldestArticle = m_articlesByDate.takeLast();
m_articles.remove(oldestArticle->guid());
// Update unreadCount
if (!oldestArticle->isRead())
--m_unreadCount;
}
// Check if article was inserted at the end of the list and will break max_articles limit
if (Preferences::instance()->isRssDownloadingEnabled()) {
if (lbIndex < max_articles && !article->isRead())
downloadArticleTorrentIfMatching(m_manager->downloadRules(), article);
}
}
else {
// m_articles.contains(article->guid())
// Try to download skipped articles
if (Preferences::instance()->isRssDownloadingEnabled()) {
RssArticlePtr skipped = m_articles.value(article->guid(), RssArticlePtr());
if (skipped) {
if (!skipped->isRead())
downloadArticleTorrentIfMatching(m_manager->downloadRules(), skipped);
}
}
}
}
QList<QNetworkCookie> RssFeed::feedCookies() const
{
QString feed_hostname = QUrl::fromEncoded(m_url.toUtf8()).host();
return Preferences::instance()->getHostNameQNetworkCookies(feed_hostname);
}
bool RssFeed::refresh()
{
if (m_loading) {
qWarning() << Q_FUNC_INFO << "Feed" << displayName() << "is already being refreshed, ignoring request";
return false;
}
m_loading = true;
// Download the RSS again
m_manager->rssDownloader()->downloadUrl(m_url, feedCookies());
return true;
}
void RssFeed::removeAllSettings()
{
qDebug() << "Removing all settings / history for feed: " << m_url;
QIniSettings qBTRSS("qBittorrent", "qBittorrent-rss");
QVariantHash feeds_w_downloader = qBTRSS.value("downloader_on", QVariantHash()).toHash();
if (feeds_w_downloader.contains(m_url)) {
feeds_w_downloader.remove(m_url);
qBTRSS.setValue("downloader_on", feeds_w_downloader);
}
QVariantHash all_feeds_filters = qBTRSS.value("feed_filters", QVariantHash()).toHash();
if (all_feeds_filters.contains(m_url)) {
all_feeds_filters.remove(m_url);
qBTRSS.setValue("feed_filters", all_feeds_filters);
}
QVariantHash all_old_items = qBTRSS.value("old_items", QVariantHash()).toHash();
if (all_old_items.contains(m_url)) {
all_old_items.remove(m_url);
qBTRSS.setValue("old_items", all_old_items);
}
}
bool RssFeed::isLoading() const
{
return m_loading;
}
QString RssFeed::title() const
{
return m_title;
}
void RssFeed::rename(const QString &new_name)
{
qDebug() << "Renaming stream to" << new_name;
m_alias = new_name;
}
// Return the alias if the stream has one, the url if it has no alias
QString RssFeed::displayName() const
{
if (!m_alias.isEmpty())
return m_alias;
if (!m_title.isEmpty())
return m_title;
return m_url;
}
QString RssFeed::url() const
{
return m_url;
}
QIcon RssFeed::icon() const
{
if (m_inErrorState)
return QIcon(":/icons/oxygen/unavailable.png");
return QIcon(m_icon);
}
bool RssFeed::hasCustomIcon() const
{
return !m_icon.startsWith(":/");
}
void RssFeed::setIconPath(const QString& path)
{
if (path.isEmpty() || !QFile::exists(path))
return;
m_icon = path;
}
RssArticlePtr RssFeed::getItem(const QString& guid) const
{
return m_articles.value(guid);
}
uint RssFeed::count() const
{
return m_articles.size();
}
void RssFeed::markAsRead()
{
RssArticleHash::ConstIterator it = m_articles.begin();
RssArticleHash::ConstIterator itend = m_articles.end();
for ( ; it != itend; ++it) {
it.value()->markAsRead();
}
m_unreadCount = 0;
m_manager->forwardFeedInfosChanged(m_url, displayName(), 0);
}
void RssFeed::markAsDirty(bool dirty)
{
m_dirty = dirty;
}
uint RssFeed::unreadCount() const
{
return m_unreadCount;
}
RssArticleList RssFeed::articleListByDateDesc() const
{
return m_articlesByDate;
}
RssArticleList RssFeed::unreadArticleListByDateDesc() const
{
RssArticleList unread_news;
RssArticleList::ConstIterator it = m_articlesByDate.begin();
RssArticleList::ConstIterator itend = m_articlesByDate.end();
for ( ; it != itend; ++it) {
if (!(*it)->isRead())
unread_news << *it;
}
return unread_news;
}
// download the icon from the adress
QString RssFeed::iconUrl() const
{
// XXX: This works for most sites but it is not perfect
return QString("http://") + QUrl(m_url).host() + QString("/favicon.ico");
}
// read and store the downloaded rss' informations
void RssFeed::handleFinishedDownload(const QString& url, const QString& filePath)
{
if (url == m_url) {
qDebug() << Q_FUNC_INFO << "Successfully downloaded RSS feed at" << url;
// Parse the download RSS
m_manager->rssParser()->parseRssFile(m_url, filePath);
} else if (url == m_iconUrl) {
m_icon = filePath;
qDebug() << Q_FUNC_INFO << "icon path:" << m_icon;
m_manager->forwardFeedIconChanged(m_url, m_icon);
}
}
void RssFeed::handleDownloadFailure(const QString& url, const QString& error)
{
if (url != m_url)
return;
m_inErrorState = true;
m_loading = false;
m_manager->forwardFeedInfosChanged(m_url, displayName(), m_unreadCount);
qWarning() << "Failed to download RSS feed at" << url;
qWarning() << "Reason:" << error;
}
void RssFeed::handleFeedTitle(const QString& feedUrl, const QString& title)
{
if (feedUrl != m_url)
return;
if (m_title == title)
return;
m_title = title;
// Notify that we now have something better than a URL to display
if (m_alias.isEmpty())
m_manager->forwardFeedInfosChanged(feedUrl, title, m_unreadCount);
}
void RssFeed::downloadArticleTorrentIfMatching(RssDownloadRuleList* rules, const RssArticlePtr& article)
{
Q_ASSERT(Preferences::instance()->isRssDownloadingEnabled());
RssDownloadRulePtr matching_rule = rules->findMatchingRule(m_url, article->title());
if (!matching_rule)
return;
if (matching_rule->ignoreDays() > 0) {
QDateTime lastMatch = matching_rule->lastMatch();
if (lastMatch.isValid()) {
if (QDateTime::currentDateTime() < lastMatch.addDays(matching_rule->ignoreDays())) {
connect(article.data(), SIGNAL(articleWasRead()), SLOT(handleArticleStateChanged()), Qt::UniqueConnection);
article->markAsRead();
return;
}
}
}
matching_rule->setLastMatch(QDateTime::currentDateTime());
rules->saveRulesToStorage();
// Download the torrent
const QString& torrent_url = article->torrentUrl();
Logger::instance()->addMessage(tr("Automatically downloading %1 torrent from %2 RSS feed...").arg(article->title()).arg(displayName()));
connect(QBtSession::instance(), SIGNAL(newDownloadedTorrentFromRss(QString)), article.data(), SLOT(handleTorrentDownloadSuccess(const QString&)), Qt::UniqueConnection);
connect(article.data(), SIGNAL(articleWasRead()), SLOT(handleArticleStateChanged()), Qt::UniqueConnection);
if (torrent_url.startsWith("magnet:", Qt::CaseInsensitive))
QBtSession::instance()->addMagnetSkipAddDlg(torrent_url, matching_rule->savePath(), matching_rule->label(), matching_rule->addPaused());
else
QBtSession::instance()->downloadUrlAndSkipDialog(torrent_url, matching_rule->savePath(), matching_rule->label(), feedCookies(), matching_rule->addPaused());
}
void RssFeed::recheckRssItemsForDownload()
{
Q_ASSERT(Preferences::instance()->isRssDownloadingEnabled());
RssDownloadRuleList* rules = m_manager->downloadRules();
foreach (const RssArticlePtr& article, m_articlesByDate) {
if (!article->isRead())
downloadArticleTorrentIfMatching(rules, article);
}
}
void RssFeed::handleNewArticle(const QString& feedUrl, const QVariantHash& articleData)
{
if (feedUrl != m_url)
return;
RssArticlePtr article = hashToRssArticle(this, articleData);
if (article.isNull()) {
qDebug() << "Article hash corrupted or guid is uncomputable; feed url: " << feedUrl;
return;
}
Q_ASSERT(article);
addArticle(article);
m_manager->forwardFeedInfosChanged(m_url, displayName(), m_unreadCount);
// FIXME: We should forward the information here but this would seriously decrease
// performance with current design.
//m_manager->forwardFeedContentChanged(m_url);
}
void RssFeed::handleFeedParsingFinished(const QString& feedUrl, const QString& error)
{
if (feedUrl != m_url)
return;
if (!error.isEmpty()) {
qWarning() << "Failed to parse RSS feed at" << feedUrl;
qWarning() << "Reason:" << error;
}
m_loading = false;
m_inErrorState = !error.isEmpty();
m_manager->forwardFeedInfosChanged(m_url, displayName(), m_unreadCount);
// XXX: Would not be needed if we did this in handleNewArticle() instead
m_manager->forwardFeedContentChanged(m_url);
saveItemsToDisk();
}
void RssFeed::handleArticleStateChanged() {
m_manager->forwardFeedInfosChanged(m_url, displayName(), m_unreadCount);
}
void RssFeed::decrementUnreadCount()
{
--m_unreadCount;
}

116
src/gui/rss/rssfeed.h Normal file
View File

@@ -0,0 +1,116 @@
/*
* Bittorrent Client using Qt4 and libtorrent.
* Copyright (C) 2010 Christophe Dumez, Arnaud Demaiziere
*
* 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.
*
* In addition, as a special exception, the copyright holders give permission to
* link this program with the OpenSSL project's "OpenSSL" library (or with
* modified versions of it that use the same license as the "OpenSSL" library),
* and distribute the linked executables. You must obey the GNU General Public
* License in all respects for all of the code used other than "OpenSSL". If you
* modify file(s), you may extend this exception to your version of the file(s),
* but you are not obligated to do so. If you do not wish to do so, delete this
* exception statement from your version.
*
* Contact: chris@qbittorrent.org, arnaud@qbittorrent.org
*/
#ifndef RSSFEED_H
#define RSSFEED_H
#include <QHash>
#include <QSharedPointer>
#include <QVariantHash>
#include <QXmlStreamReader>
#include <QNetworkCookie>
#include "rssfile.h"
class RssFeed;
class RssManager;
class RssDownloadRuleList;
typedef QHash<QString, RssArticlePtr> RssArticleHash;
typedef QSharedPointer<RssFeed> RssFeedPtr;
typedef QList<RssFeedPtr> RssFeedList;
bool rssArticleDateRecentThan(const RssArticlePtr& left, const RssArticlePtr& right);
class RssFeed: public QObject, public RssFile {
Q_OBJECT
public:
RssFeed(RssManager* manager, RssFolder* m_parent, const QString& url);
virtual ~RssFeed();
virtual RssFolder* parent() const { return m_parent; }
virtual void setParent(RssFolder* parent) { m_parent = parent; }
bool refresh();
virtual QString id() const { return m_url; }
virtual void removeAllSettings();
virtual void saveItemsToDisk();
bool isLoading() const;
QString title() const;
virtual void rename(const QString &alias);
virtual QString displayName() const;
QString url() const;
virtual QIcon icon() const;
bool hasCustomIcon() const;
void setIconPath(const QString &pathHierarchy);
RssArticlePtr getItem(const QString &guid) const;
uint count() const;
virtual void markAsRead();
void markAsDirty(bool dirty = true);
virtual uint unreadCount() const;
virtual RssArticleList articleListByDateDesc() const;
const RssArticleHash& articleHash() const { return m_articles; }
virtual RssArticleList unreadArticleListByDateDesc() const;
void decrementUnreadCount();
void recheckRssItemsForDownload();
private slots:
void handleFinishedDownload(const QString& url, const QString &file_path);
void handleDownloadFailure(const QString &url, const QString& error);
void handleFeedTitle(const QString& feedUrl, const QString& title);
void handleNewArticle(const QString& feedUrl, const QVariantHash& article);
void handleFeedParsingFinished(const QString& feedUrl, const QString& error);
void handleArticleStateChanged();
private:
QString iconUrl() const;
void loadItemsFromDisk();
void addArticle(const RssArticlePtr& article);
void downloadArticleTorrentIfMatching(RssDownloadRuleList* rules, const RssArticlePtr& article);
QList<QNetworkCookie> feedCookies() const;
private:
RssManager* m_manager;
RssArticleHash m_articles;
RssArticleList m_articlesByDate; // Articles sorted by date (more recent first)
RssFolder* m_parent;
QString m_title;
QString m_url;
QString m_alias;
QString m_icon;
QString m_iconUrl;
uint m_unreadCount;
bool m_dirty;
bool m_inErrorState;
bool m_loading;
};
#endif // RSSFEED_H

40
src/gui/rss/rssfile.cpp Normal file
View File

@@ -0,0 +1,40 @@
/*
* Bittorrent Client using Qt4 and libtorrent.
* Copyright (C) 2010 Christophe Dumez, Arnaud Demaiziere
*
* 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.
*
* In addition, as a special exception, the copyright holders give permission to
* link this program with the OpenSSL project's "OpenSSL" library (or with
* modified versions of it that use the same license as the "OpenSSL" library),
* and distribute the linked executables. You must obey the GNU General Public
* License in all respects for all of the code used other than "OpenSSL". If you
* modify file(s), you may extend this exception to your version of the file(s),
* but you are not obligated to do so. If you do not wish to do so, delete this
* exception statement from your version.
*
* Contact: chris@qbittorrent.org, arnaud@qbittorrent.org
*/
#include "rssfile.h"
#include "rssfolder.h"
QStringList RssFile::pathHierarchy() const {
QStringList path;
if (parent())
path << parent()->pathHierarchy();
path << id();
return path;
}

75
src/gui/rss/rssfile.h Normal file
View File

@@ -0,0 +1,75 @@
/*
* Bittorrent Client using Qt4 and libtorrent.
* Copyright (C) 2010 Christophe Dumez, Arnaud Demaiziere
*
* 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.
*
* In addition, as a special exception, the copyright holders give permission to
* link this program with the OpenSSL project's "OpenSSL" library (or with
* modified versions of it that use the same license as the "OpenSSL" library),
* and distribute the linked executables. You must obey the GNU General Public
* License in all respects for all of the code used other than "OpenSSL". If you
* modify file(s), you may extend this exception to your version of the file(s),
* but you are not obligated to do so. If you do not wish to do so, delete this
* exception statement from your version.
*
* Contact: chris@qbittorrent.org, arnaud@qbittorrent.org
*/
#ifndef RSSFILE_H
#define RSSFILE_H
#include <QIcon>
#include <QList>
#include <QStringList>
#include <QSharedPointer>
class RssFolder;
class RssFile;
class RssArticle;
typedef QSharedPointer<RssFile> RssFilePtr;
typedef QSharedPointer<RssArticle> RssArticlePtr;
typedef QList<RssArticlePtr> RssArticleList;
typedef QList<RssFilePtr> RssFileList;
/**
* Parent interface for RssFolder and RssFeed.
*/
class RssFile {
public:
virtual ~RssFile() {}
virtual uint unreadCount() const = 0;
virtual QString displayName() const = 0;
virtual QString id() const = 0;
virtual QIcon icon() const = 0;
virtual void rename(const QString &new_name) = 0;
virtual void markAsRead() = 0;
virtual RssFolder* parent() const = 0;
virtual void setParent(RssFolder* parent) = 0;
virtual bool refresh() = 0;
virtual RssArticleList articleListByDateDesc() const = 0;
virtual RssArticleList unreadArticleListByDateDesc() const = 0;
virtual void removeAllSettings() = 0;
virtual void saveItemsToDisk() = 0;
virtual void recheckRssItemsForDownload() = 0;
QStringList pathHierarchy() const;
protected:
uint m_unreadCount;
};
#endif // RSSFILE_H

262
src/gui/rss/rssfolder.cpp Normal file
View File

@@ -0,0 +1,262 @@
/*
* Bittorrent Client using Qt4 and libtorrent.
* Copyright (C) 2010 Christophe Dumez, Arnaud Demaiziere
*
* 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.
*
* In addition, as a special exception, the copyright holders give permission to
* link this program with the OpenSSL project's "OpenSSL" library (or with
* modified versions of it that use the same license as the "OpenSSL" library),
* and distribute the linked executables. You must obey the GNU General Public
* License in all respects for all of the code used other than "OpenSSL". If you
* modify file(s), you may extend this exception to your version of the file(s),
* but you are not obligated to do so. If you do not wish to do so, delete this
* exception statement from your version.
*
* Contact: chris@qbittorrent.org, arnaud@qbittorrent.org
*/
#include <QDebug>
#include "iconprovider.h"
#include "rssfolder.h"
#include "rssarticle.h"
#include "qbtsession.h"
#include "rssmanager.h"
#include "rssfeed.h"
RssFolder::RssFolder(RssFolder *parent, const QString &name): m_parent(parent), m_name(name) {
}
RssFolder::~RssFolder() {
}
unsigned int RssFolder::unreadCount() const {
uint nb_unread = 0;
RssFileHash::ConstIterator it = m_children.begin();
RssFileHash::ConstIterator itend = m_children.end();
for ( ; it != itend; ++it) {
nb_unread += it.value()->unreadCount();
}
return nb_unread;
}
void RssFolder::removeChild(const QString &childId) {
if (m_children.contains(childId)) {
RssFilePtr child = m_children.take(childId);
child->removeAllSettings();
}
}
RssFolderPtr RssFolder::addFolder(const QString &name) {
RssFolderPtr subfolder;
if (!m_children.contains(name)) {
subfolder = RssFolderPtr(new RssFolder(this, name));
m_children[name] = subfolder;
} else {
subfolder = qSharedPointerDynamicCast<RssFolder>(m_children.value(name));
}
return subfolder;
}
RssFeedPtr RssFolder::addStream(RssManager* manager, const QString &url) {
qDebug() << Q_FUNC_INFO << manager << url;
RssFeedPtr stream(new RssFeed(manager, this, url));
Q_ASSERT(stream);
qDebug() << "Stream URL is " << stream->url();
Q_ASSERT(!m_children.contains(stream->url()));
m_children[stream->url()] = stream;
stream->refresh();
return stream;
}
// Refresh All Children
bool RssFolder::refresh() {
RssFileHash::ConstIterator it = m_children.begin();
RssFileHash::ConstIterator itend = m_children.end();
bool refreshed = false;
for ( ; it != itend; ++it) {
if (it.value()->refresh())
refreshed = true;
}
return refreshed;
}
RssArticleList RssFolder::articleListByDateDesc() const {
RssArticleList news;
RssFileHash::ConstIterator it = m_children.begin();
RssFileHash::ConstIterator itend = m_children.end();
for ( ; it != itend; ++it) {
int n = news.size();
news << it.value()->articleListByDateDesc();
std::inplace_merge(news.begin(), news.begin() + n, news.end(), rssArticleDateRecentThan);
}
return news;
}
RssArticleList RssFolder::unreadArticleListByDateDesc() const {
RssArticleList unread_news;
RssFileHash::ConstIterator it = m_children.begin();
RssFileHash::ConstIterator itend = m_children.end();
for ( ; it != itend; ++it) {
int n = unread_news.size();
unread_news << it.value()->unreadArticleListByDateDesc();
std::inplace_merge(unread_news.begin(), unread_news.begin() + n, unread_news.end(), rssArticleDateRecentThan);
}
return unread_news;
}
RssFileList RssFolder::getContent() const {
return m_children.values();
}
unsigned int RssFolder::getNbFeeds() const {
uint nbFeeds = 0;
RssFileHash::ConstIterator it = m_children.begin();
RssFileHash::ConstIterator itend = m_children.end();
for ( ; it != itend; ++it) {
if (RssFolderPtr folder = qSharedPointerDynamicCast<RssFolder>(it.value()))
nbFeeds += folder->getNbFeeds();
else
++nbFeeds; // Feed
}
return nbFeeds;
}
QString RssFolder::displayName() const {
return m_name;
}
void RssFolder::rename(const QString &new_name) {
if (m_name == new_name) return;
Q_ASSERT(!m_parent->hasChild(new_name));
if (!m_parent->hasChild(new_name)) {
// Update parent
m_parent->renameChildFolder(m_name, new_name);
// Actually rename
m_name = new_name;
}
}
void RssFolder::markAsRead() {
RssFileHash::ConstIterator it = m_children.begin();
RssFileHash::ConstIterator itend = m_children.end();
for ( ; it != itend; ++it) {
it.value()->markAsRead();
}
}
RssFeedList RssFolder::getAllFeeds() const {
RssFeedList streams;
RssFileHash::ConstIterator it = m_children.begin();
RssFileHash::ConstIterator itend = m_children.end();
for ( ; it != itend; ++it) {
if (RssFeedPtr feed = qSharedPointerDynamicCast<RssFeed>(it.value())) {
streams << feed;
} else if (RssFolderPtr folder = qSharedPointerDynamicCast<RssFolder>(it.value())) {
streams << folder->getAllFeeds();
}
}
return streams;
}
QHash<QString, RssFeedPtr> RssFolder::getAllFeedsAsHash() const {
QHash<QString, RssFeedPtr> ret;
RssFileHash::ConstIterator it = m_children.begin();
RssFileHash::ConstIterator itend = m_children.end();
for ( ; it != itend; ++it) {
if (RssFeedPtr feed = qSharedPointerDynamicCast<RssFeed>(it.value())) {
qDebug() << Q_FUNC_INFO << feed->url();
ret[feed->url()] = feed;
} else if (RssFolderPtr folder = qSharedPointerDynamicCast<RssFolder>(it.value())) {
ret.unite(folder->getAllFeedsAsHash());
}
}
return ret;
}
void RssFolder::addFile(const RssFilePtr& item) {
if (RssFeedPtr feed = qSharedPointerDynamicCast<RssFeed>(item)) {
Q_ASSERT(!m_children.contains(feed->url()));
m_children[feed->url()] = item;
qDebug("Added feed %s to folder ./%s", qPrintable(feed->url()), qPrintable(m_name));
} else if (RssFolderPtr folder = qSharedPointerDynamicCast<RssFolder>(item)) {
Q_ASSERT(!m_children.contains(folder->displayName()));
m_children[folder->displayName()] = item;
qDebug("Added folder %s to folder ./%s", qPrintable(folder->displayName()), qPrintable(m_name));
}
// Update parent
item->setParent(this);
}
void RssFolder::removeAllItems() {
m_children.clear();
}
void RssFolder::removeAllSettings() {
RssFileHash::ConstIterator it = m_children.begin();
RssFileHash::ConstIterator itend = m_children.end();
for ( ; it != itend; ++it) {
it.value()->removeAllSettings();
}
}
void RssFolder::saveItemsToDisk()
{
foreach (const RssFilePtr& child, m_children.values()) {
child->saveItemsToDisk();
}
}
QString RssFolder::id() const
{
return m_name;
}
QIcon RssFolder::icon() const
{
return IconProvider::instance()->getIcon("inode-directory");
}
bool RssFolder::hasChild(const QString &childId) {
return m_children.contains(childId);
}
void RssFolder::renameChildFolder(const QString &old_name, const QString &new_name)
{
Q_ASSERT(m_children.contains(old_name));
RssFilePtr folder = m_children.take(old_name);
m_children[new_name] = folder;
}
RssFilePtr RssFolder::takeChild(const QString &childId)
{
return m_children.take(childId);
}
void RssFolder::recheckRssItemsForDownload()
{
RssFileHash::ConstIterator it = m_children.begin();
RssFileHash::ConstIterator itend = m_children.end();
for ( ; it != itend; ++it) {
it.value()->recheckRssItemsForDownload();
}
}

88
src/gui/rss/rssfolder.h Normal file
View File

@@ -0,0 +1,88 @@
/*
* Bittorrent Client using Qt4 and libtorrent.
* Copyright (C) 2010 Christophe Dumez, Arnaud Demaiziere
*
* 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.
*
* In addition, as a special exception, the copyright holders give permission to
* link this program with the OpenSSL project's "OpenSSL" library (or with
* modified versions of it that use the same license as the "OpenSSL" library),
* and distribute the linked executables. You must obey the GNU General Public
* License in all respects for all of the code used other than "OpenSSL". If you
* modify file(s), you may extend this exception to your version of the file(s),
* but you are not obligated to do so. If you do not wish to do so, delete this
* exception statement from your version.
*
* Contact: chris@qbittorrent.org, arnaud@qbittorrent.org
*/
#ifndef RSSFOLDER_H
#define RSSFOLDER_H
#include <QHash>
#include <QSharedPointer>
#include "rssfile.h"
class RssFolder;
class RssFeed;
class RssManager;
typedef QHash<QString, RssFilePtr> RssFileHash;
typedef QSharedPointer<RssFeed> RssFeedPtr;
typedef QSharedPointer<RssFolder> RssFolderPtr;
typedef QList<RssFeedPtr> RssFeedList;
class RssFolder: public QObject, public RssFile {
Q_OBJECT
public:
RssFolder(RssFolder *parent = 0, const QString &name = QString());
virtual ~RssFolder();
virtual RssFolder* parent() const { return m_parent; }
virtual void setParent(RssFolder* parent) { m_parent = parent; }
virtual unsigned int unreadCount() const;
RssFeedPtr addStream(RssManager* manager, const QString &url);
RssFolderPtr addFolder(const QString &name);
unsigned int getNbFeeds() const;
RssFileList getContent() const;
RssFeedList getAllFeeds() const;
QHash<QString, RssFeedPtr> getAllFeedsAsHash() const;
virtual QString displayName() const;
virtual QString id() const;
virtual QIcon icon() const;
bool hasChild(const QString &childId);
virtual RssArticleList articleListByDateDesc() const;
virtual RssArticleList unreadArticleListByDateDesc() const;
virtual void removeAllSettings();
virtual void saveItemsToDisk();
void removeAllItems();
void renameChildFolder(const QString &old_name, const QString &new_name);
RssFilePtr takeChild(const QString &childId);
void recheckRssItemsForDownload();
public slots:
virtual bool refresh();
void addFile(const RssFilePtr& item);
void removeChild(const QString &childId);
virtual void rename(const QString &new_name);
virtual void markAsRead();
private:
RssFolder *m_parent;
QString m_name;
RssFileHash m_children;
};
#endif // RSSFOLDER_H

167
src/gui/rss/rssmanager.cpp Normal file
View File

@@ -0,0 +1,167 @@
/*
* Bittorrent Client using Qt4 and libtorrent.
* Copyright (C) 2010 Christophe Dumez, Arnaud Demaiziere
*
* 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.
*
* In addition, as a special exception, the copyright holders give permission to
* link this program with the OpenSSL project's "OpenSSL" library (or with
* modified versions of it that use the same license as the "OpenSSL" library),
* and distribute the linked executables. You must obey the GNU General Public
* License in all respects for all of the code used other than "OpenSSL". If you
* modify file(s), you may extend this exception to your version of the file(s),
* but you are not obligated to do so. If you do not wish to do so, delete this
* exception statement from your version.
*
* Contact: chris@qbittorrent.org, arnaud@qbittorrent.org
*/
#include <QDebug>
#include "rssmanager.h"
#include "preferences.h"
#include "qbtsession.h"
#include "rssfeed.h"
#include "rssarticle.h"
#include "rssdownloadrulelist.h"
#include "rssparser.h"
#include "downloadthread.h"
static const int MSECS_PER_MIN = 60000;
RssManager::RssManager():
m_rssDownloader(new DownloadThread(this)),
m_downloadRules(new RssDownloadRuleList),
m_rssParser(new RssParser(this))
{
connect(&m_refreshTimer, SIGNAL(timeout()), SLOT(refresh()));
m_refreshInterval = Preferences::instance()->getRSSRefreshInterval();
m_refreshTimer.start(m_refreshInterval * MSECS_PER_MIN);
}
RssManager::~RssManager()
{
qDebug("Deleting RSSManager...");
delete m_downloadRules;
delete m_rssParser;
saveItemsToDisk();
saveStreamList();
qDebug("RSSManager deleted");
}
DownloadThread* RssManager::rssDownloader() const
{
return m_rssDownloader;
}
RssParser* RssManager::rssParser() const
{
return m_rssParser;
}
void RssManager::updateRefreshInterval(uint val)
{
if (m_refreshInterval != val) {
m_refreshInterval = val;
m_refreshTimer.start(m_refreshInterval*60000);
qDebug("New RSS refresh interval is now every %dmin", m_refreshInterval);
}
}
void RssManager::loadStreamList()
{
const Preferences* const pref = Preferences::instance();
const QStringList streamsUrl = pref->getRssFeedsUrls();
const QStringList aliases = pref->getRssFeedsAliases();
if (streamsUrl.size() != aliases.size()) {
std::cerr << "Corrupted Rss list, not loading it\n";
return;
}
uint i = 0;
qDebug() << Q_FUNC_INFO << streamsUrl;
foreach (QString s, streamsUrl) {
QStringList path = s.split("\\", QString::SkipEmptyParts);
if (path.empty()) continue;
const QString feed_url = path.takeLast();
qDebug() << "Feed URL:" << feed_url;
// Create feed path (if it does not exists)
RssFolder* feed_parent = this;
foreach (const QString &folder_name, path) {
qDebug() << "Adding parent folder:" << folder_name;
feed_parent = feed_parent->addFolder(folder_name).data();
}
// Create feed
qDebug() << "Adding feed to parent folder";
RssFeedPtr stream = feed_parent->addStream(this, feed_url);
const QString& alias = aliases[i];
if (!alias.isEmpty()) {
stream->rename(alias);
}
++i;
}
qDebug("NB RSS streams loaded: %d", streamsUrl.size());
}
void RssManager::forwardFeedContentChanged(const QString& url)
{
emit feedContentChanged(url);
}
void RssManager::forwardFeedInfosChanged(const QString& url, const QString& displayName, uint unreadCount)
{
emit feedInfosChanged(url, displayName, unreadCount);
}
void RssManager::forwardFeedIconChanged(const QString& url, const QString& iconPath)
{
emit feedIconChanged(url, iconPath);
}
void RssManager::moveFile(const RssFilePtr& file, const RssFolderPtr& destinationFolder)
{
RssFolder* src_folder = file->parent();
if (destinationFolder != src_folder) {
// Remove reference in old folder
src_folder->takeChild(file->id());
// add to new Folder
destinationFolder->addFile(file);
} else {
qDebug("Nothing to move, same destination folder");
}
}
void RssManager::saveStreamList() const
{
QStringList streamsUrl;
QStringList aliases;
RssFeedList streams = getAllFeeds();
foreach (const RssFeedPtr& stream, streams) {
// This backslash has nothing to do with path handling
QString stream_path = stream->pathHierarchy().join("\\");
if (stream_path.isNull())
stream_path = "";
qDebug("Saving stream path: %s", qPrintable(stream_path));
streamsUrl << stream_path;
aliases << stream->displayName();
}
Preferences* const pref = Preferences::instance();
pref->setRssFeedsUrls(streamsUrl);
pref->setRssFeedsAliases(aliases);
}
RssDownloadRuleList* RssManager::downloadRules() const
{
Q_ASSERT(m_downloadRules);
return m_downloadRules;
}

79
src/gui/rss/rssmanager.h Normal file
View File

@@ -0,0 +1,79 @@
/*
* Bittorrent Client using Qt4 and libtorrent.
* Copyright (C) 2010 Christophe Dumez, Arnaud Demaiziere
*
* 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.
*
* In addition, as a special exception, the copyright holders give permission to
* link this program with the OpenSSL project's "OpenSSL" library (or with
* modified versions of it that use the same license as the "OpenSSL" library),
* and distribute the linked executables. You must obey the GNU General Public
* License in all respects for all of the code used other than "OpenSSL". If you
* modify file(s), you may extend this exception to your version of the file(s),
* but you are not obligated to do so. If you do not wish to do so, delete this
* exception statement from your version.
*
* Contact: chris@qbittorrent.org, arnaud@qbittorrent.org
*/
#ifndef RSSMANAGER_H
#define RSSMANAGER_H
#include <QTimer>
#include <QSharedPointer>
#include "rssfolder.h"
class DownloadThread;
class RssDownloadRuleList;
class RssParser;
class RssManager;
typedef QSharedPointer<RssManager> RssManagerPtr;
class RssManager: public RssFolder {
Q_OBJECT
public:
RssManager();
virtual ~RssManager();
DownloadThread* rssDownloader() const;
RssParser* rssParser() const;
RssDownloadRuleList* downloadRules() const;
public slots:
void loadStreamList();
void saveStreamList() const;
void forwardFeedContentChanged(const QString& url);
void forwardFeedInfosChanged(const QString& url, const QString& displayName, uint unreadCount);
void forwardFeedIconChanged(const QString& url, const QString& iconPath);
void moveFile(const RssFilePtr& file, const RssFolderPtr& destinationFolder);
void updateRefreshInterval(uint val);
signals:
void feedContentChanged(const QString& url);
void feedInfosChanged(const QString& url, const QString& displayName, uint unreadCount);
void feedIconChanged(const QString& url, const QString& iconPath);
private:
QTimer m_refreshTimer;
uint m_refreshInterval;
DownloadThread* m_rssDownloader;
RssDownloadRuleList* m_downloadRules;
RssParser* m_rssParser;
};
#endif // RSSMANAGER_H

509
src/gui/rss/rssparser.cpp Normal file
View File

@@ -0,0 +1,509 @@
/*
* Bittorrent Client using Qt4 and libtorrent.
* Copyright (C) 2012 Christophe Dumez
*
* 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.
*
* In addition, as a special exception, the copyright holders give permission to
* link this program with the OpenSSL project's "OpenSSL" library (or with
* modified versions of it that use the same license as the "OpenSSL" library),
* and distribute the linked executables. You must obey the GNU General Public
* License in all respects for all of the code used other than "OpenSSL". If you
* modify file(s), you may extend this exception to your version of the file(s),
* but you are not obligated to do so. If you do not wish to do so, delete this
* exception statement from your version.
*
* Contact : chris@qbittorrent.org
*/
#include "rssparser.h"
#include "downloadthread.h"
#include "fs_utils.h"
#include <QDebug>
#include <QFile>
#include <QRegExp>
#include <QStringList>
#include <QVariant>
#include <QTextDocument>
struct ParsingJob {
QString feedUrl;
QString filePath;
};
static const char shortDay[][4] = {
"Mon", "Tue", "Wed",
"Thu", "Fri", "Sat",
"Sun"
};
static const char longDay[][10] = {
"Monday", "Tuesday", "Wednesday",
"Thursday", "Friday", "Saturday",
"Sunday"
};
static const char shortMonth[][4] = {
"Jan", "Feb", "Mar", "Apr",
"May", "Jun", "Jul", "Aug",
"Sep", "Oct", "Nov", "Dec"
};
static const char longMonth[][10] = {
"January", "February", "March",
"April", "May", "June",
"July", "August", "September",
"October", "November", "December"
};
// Ported to Qt4 from KDElibs4
QDateTime RssParser::parseDate(const QString &string) {
const QString str = string.trimmed();
if (str.isEmpty())
return QDateTime::currentDateTime();
int nyear = 6; // indexes within string to values
int nmonth = 4;
int nday = 2;
int nwday = 1;
int nhour = 7;
int nmin = 8;
int nsec = 9;
// Also accept obsolete form "Weekday, DD-Mon-YY HH:MM:SS ±hhmm"
QRegExp rx("^(?:([A-Z][a-z]+),\\s*)?(\\d{1,2})(\\s+|-)([^-\\s]+)(\\s+|-)(\\d{2,4})\\s+(\\d\\d):(\\d\\d)(?::(\\d\\d))?\\s+(\\S+)$");
QStringList parts;
if (!str.indexOf(rx)) {
// Check that if date has '-' separators, both separators are '-'.
parts = rx.capturedTexts();
bool h1 = (parts[3] == QLatin1String("-"));
bool h2 = (parts[5] == QLatin1String("-"));
if (h1 != h2)
return QDateTime::currentDateTime();
} else {
// Check for the obsolete form "Wdy Mon DD HH:MM:SS YYYY"
rx = QRegExp("^([A-Z][a-z]+)\\s+(\\S+)\\s+(\\d\\d)\\s+(\\d\\d):(\\d\\d):(\\d\\d)\\s+(\\d\\d\\d\\d)$");
if (str.indexOf(rx))
return QDateTime::currentDateTime();
nyear = 7;
nmonth = 2;
nday = 3;
nwday = 1;
nhour = 4;
nmin = 5;
nsec = 6;
parts = rx.capturedTexts();
}
bool ok[4];
const int day = parts[nday].toInt(&ok[0]);
int year = parts[nyear].toInt(&ok[1]);
const int hour = parts[nhour].toInt(&ok[2]);
const int minute = parts[nmin].toInt(&ok[3]);
if (!ok[0] || !ok[1] || !ok[2] || !ok[3])
return QDateTime::currentDateTime();
int second = 0;
if (!parts[nsec].isEmpty()) {
second = parts[nsec].toInt(&ok[0]);
if (!ok[0])
return QDateTime::currentDateTime();
}
bool leapSecond = (second == 60);
if (leapSecond)
second = 59; // apparently a leap second - validate below, once time zone is known
int month = 0;
for ( ; month < 12 && parts[nmonth] != shortMonth[month]; ++month) ;
int dayOfWeek = -1;
if (!parts[nwday].isEmpty()) {
// Look up the weekday name
while (++dayOfWeek < 7 && shortDay[dayOfWeek] != parts[nwday]) ;
if (dayOfWeek >= 7)
for (dayOfWeek = 0; dayOfWeek < 7 && longDay[dayOfWeek] != parts[nwday]; ++dayOfWeek) ;
}
// if (month >= 12 || dayOfWeek >= 7
// || (dayOfWeek < 0 && format == RFCDateDay))
// return QDateTime;
int i = parts[nyear].size();
if (i < 4) {
// It's an obsolete year specification with less than 4 digits
year += (i == 2 && year < 50) ? 2000: 1900;
}
// Parse the UTC offset part
int offset = 0; // set default to '-0000'
bool negOffset = false;
if (parts.count() > 10) {
rx = QRegExp("^([+-])(\\d\\d)(\\d\\d)$");
if (!parts[10].indexOf(rx)) {
// It's a UTC offset ±hhmm
parts = rx.capturedTexts();
offset = parts[2].toInt(&ok[0]) * 3600;
int offsetMin = parts[3].toInt(&ok[1]);
if (!ok[0] || !ok[1] || offsetMin > 59)
return QDateTime();
offset += offsetMin * 60;
negOffset = (parts[1] == QLatin1String("-"));
if (negOffset)
offset = -offset;
} else {
// Check for an obsolete time zone name
QByteArray zone = parts[10].toLatin1();
if (zone.length() == 1 && isalpha(zone[0]) && toupper(zone[0]) != 'J')
negOffset = true; // military zone: RFC 2822 treats as '-0000'
else if (zone != "UT" && zone != "GMT") { // treated as '+0000'
offset = (zone == "EDT") ? -4*3600
: (zone == "EST" || zone == "CDT") ? -5*3600
: (zone == "CST" || zone == "MDT") ? -6*3600
: (zone == "MST" || zone == "PDT") ? -7*3600
: (zone == "PST") ? -8*3600
: 0;
if (!offset) {
// Check for any other alphabetic time zone
bool nonalpha = false;
for (int i = 0, end = zone.size(); i < end && !nonalpha; ++i)
nonalpha = !isalpha(zone[i]);
if (nonalpha)
return QDateTime();
// TODO: Attempt to recognize the time zone abbreviation?
negOffset = true; // unknown time zone: RFC 2822 treats as '-0000'
}
}
}
}
QDate qdate(year, month+1, day); // convert date, and check for out-of-range
if (!qdate.isValid())
return QDateTime::currentDateTime();
QTime qTime(hour, minute, second);
QDateTime result(qdate, qTime, Qt::UTC);
if (offset)
result = result.addSecs(-offset);
if (!result.isValid())
return QDateTime::currentDateTime(); // invalid date/time
if (leapSecond) {
// Validate a leap second time. Leap seconds are inserted after 23:59:59 UTC.
// Convert the time to UTC and check that it is 00:00:00.
if ((hour*3600 + minute*60 + 60 - offset + 86400*5) % 86400) // (max abs(offset) is 100 hours)
return QDateTime::currentDateTime(); // the time isn't the last second of the day
}
return result;
}
RssParser::RssParser(QObject *parent) :
QThread(parent), m_running(true)
{
start();
}
RssParser::~RssParser()
{
m_running = false;
m_waitCondition.wakeOne();
wait();
}
void RssParser::parseRssFile(const QString& feedUrl, const QString& filePath)
{
qDebug() << Q_FUNC_INFO << feedUrl << filePath;
m_mutex.lock();
ParsingJob job = { feedUrl, fsutils::fromNativePath(filePath) };
m_queue.enqueue(job);
// Wake up thread.
if (m_queue.count() == 1) {
qDebug() << Q_FUNC_INFO << "Waking up thread";
m_waitCondition.wakeOne();
}
m_mutex.unlock();
}
void RssParser::clearFeedData(const QString &feedUrl)
{
m_mutex.lock();
m_lastBuildDates.remove(feedUrl);
m_mutex.unlock();
}
void RssParser::run()
{
while (m_running) {
m_mutex.lock();
if (!m_queue.empty()) {
ParsingJob job = m_queue.dequeue();
m_mutex.unlock();
parseFeed(job);
} else {
qDebug() << Q_FUNC_INFO << "Thread is waiting.";
m_waitCondition.wait(&m_mutex);
qDebug() << Q_FUNC_INFO << "Thread woke up.";
m_mutex.unlock();
}
}
}
void RssParser::parseRssArticle(QXmlStreamReader& xml, const QString& feedUrl)
{
QVariantHash article;
while(!xml.atEnd()) {
xml.readNext();
if(xml.isEndElement() && xml.name() == "item")
break;
if (xml.isStartElement()) {
if (xml.name() == "title")
article["title"] = xml.readElementText();
else if (xml.name() == "enclosure") {
if (xml.attributes().value("type") == "application/x-bittorrent")
article["torrent_url"] = xml.attributes().value("url").toString();
}
else if (xml.name() == "link")
article["news_link"] = xml.readElementText();
else if (xml.name() == "description")
article["description"] = xml.readElementText();
else if (xml.name() == "pubDate")
article["date"] = parseDate(xml.readElementText());
else if (xml.name() == "author")
article["author"] = xml.readElementText();
else if (xml.name() == "guid")
article["id"] = xml.readElementText();
}
}
if (!article.contains("id")) {
// Item does not have a guid, fall back to some other identifier
const QString link = article.value("news_link").toString();
if (!link.isEmpty())
article["id"] = link;
else {
const QString title = article.value("title").toString();
if (!title.isEmpty())
article["id"] = title;
else {
qWarning() << "Item has no guid, link or title, ignoring it...";
return;
}
}
}
emit newArticle(feedUrl, article);
}
void RssParser::parseRSSChannel(QXmlStreamReader& xml, const QString& feedUrl)
{
qDebug() << Q_FUNC_INFO << feedUrl;
Q_ASSERT(xml.isStartElement() && xml.name() == "channel");
while(!xml.atEnd()) {
xml.readNext();
if (xml.isStartElement()) {
if (xml.name() == "title") {
QString title = xml.readElementText();
emit feedTitle(feedUrl, title);
}
else if (xml.name() == "lastBuildDate") {
QString lastBuildDate = xml.readElementText();
if (!lastBuildDate.isEmpty()) {
QMutexLocker locker(&m_mutex);
if (m_lastBuildDates.value(feedUrl, "") == lastBuildDate) {
qDebug() << "The RSS feed has not changed since last time, aborting parsing.";
return;
}
m_lastBuildDates[feedUrl] = lastBuildDate;
}
}
else if (xml.name() == "item") {
parseRssArticle(xml, feedUrl);
}
}
}
}
void RssParser::parseAtomArticle(QXmlStreamReader& xml, const QString& feedUrl, const QString& baseUrl)
{
QVariantHash article;
bool double_content = false;
while(!xml.atEnd()) {
xml.readNext();
if(xml.isEndElement() && xml.name() == "entry")
break;
if (xml.isStartElement()) {
if (xml.name() == "title") {
// Workaround for CDATA (QString cannot parse html escapes on it's own)
QTextDocument doc;
doc.setHtml(xml.readElementText());
article["title"] = doc.toPlainText();
}
else if (xml.name() == "link") {
QString theLink = ( xml.attributes().isEmpty() ?
xml.readElementText() :
xml.attributes().value("href").toString() );
// Atom feeds can have relative links, work around this and
// take the stress of figuring article full URI from UI
// Assemble full URI
article["news_link"] = ( baseUrl.isEmpty() ?
theLink :
baseUrl + theLink );
}
else if (xml.name() == "summary" || xml.name() == "content"){
if(double_content) { // Duplicate content -> ignore
xml.readNext();
while(xml.name() != "summary" && xml.name() != "content")
xml.readNext();
continue;
}
// Try to also parse broken articles, which don't use html '&' escapes
// Actually works great for non-broken content too
QString feedText = xml.readElementText(QXmlStreamReader::IncludeChildElements);
if (!feedText.isEmpty())
article["description"] = feedText;
double_content = true;
}
else if (xml.name() == "updated"){
// ATOM uses standard compliant date, don't do fancy stuff
QDateTime articleDate = QDateTime::fromString(xml.readElementText(), Qt::ISODate);
article["date"] = ( articleDate.isValid() ?
articleDate :
QDateTime::currentDateTime() );
}
else if (xml.name() == "author") {
xml.readNext();
while(xml.name() != "author") {
if(xml.name() == "name")
article["author"] = xml.readElementText();
xml.readNext();
}
}
else if (xml.name() == "id")
article["id"] = xml.readElementText();
}
}
if (!article.contains("id")) {
// Item does not have a guid, fall back to some other identifier
const QString link = article.value("news_link").toString();
if (!link.isEmpty())
article["id"] = link;
else {
const QString title = article.value("title").toString();
if (!title.isEmpty())
article["id"] = title;
else {
qWarning() << "Item has no guid, link or title, ignoring it...";
return;
}
}
}
emit newArticle(feedUrl, article);
}
void RssParser::parseAtomChannel(QXmlStreamReader& xml, const QString& feedUrl)
{
qDebug() << Q_FUNC_INFO << feedUrl;
Q_ASSERT(xml.isStartElement() && xml.name() == "feed");
QString baseURL = xml.attributes().value("xml:base").toString();
while(!xml.atEnd()) {
xml.readNext();
if (xml.isStartElement()) {
if (xml.name() == "title") {
QString title = xml.readElementText();
emit feedTitle(feedUrl, title);
}
else if (xml.name() == "updated") {
QString lastBuildDate = xml.readElementText();
if (!lastBuildDate.isEmpty()) {
QMutexLocker locker(&m_mutex);
if (m_lastBuildDates.value(feedUrl) == lastBuildDate) {
qDebug() << "The RSS feed has not changed since last time, aborting parsing.";
return;
}
m_lastBuildDates[feedUrl] = lastBuildDate;
}
}
else if (xml.name() == "entry") {
parseAtomArticle(xml, feedUrl, baseURL);
}
}
}
}
// read and create items from a rss document
void RssParser::parseFeed(const ParsingJob& job)
{
qDebug() << Q_FUNC_INFO << job.feedUrl << job.filePath;
QFile fileRss(job.filePath);
if (!fileRss.open(QIODevice::ReadOnly | QIODevice::Text)) {
reportFailure(job, tr("Failed to open downloaded RSS file."));
return;
}
QXmlStreamReader xml(&fileRss);
bool found_channel = false;
while (xml.readNextStartElement()) {
if (xml.name() == "rss") {
// Find channels
while (xml.readNextStartElement()) {
if (xml.name() == "channel") {
parseRSSChannel(xml, job.feedUrl);
found_channel = true;
break;
} else {
qDebug() << "Skip rss item: " << xml.name();
xml.skipCurrentElement();
}
}
break;
}
else if (xml.name() == "feed") { // Atom feed
parseAtomChannel(xml, job.feedUrl);
found_channel = true;
break;
} else {
qDebug() << "Skip root item: " << xml.name();
xml.skipCurrentElement();
}
}
if (xml.hasError()) {
reportFailure(job, xml.errorString());
return;
}
if (!found_channel) {
reportFailure(job, tr("Invalid RSS feed at %1.").arg(job.feedUrl));
return;
}
// Clean up
fileRss.close();
emit feedParsingFinished(job.feedUrl, QString());
fsutils::forceRemove(job.filePath);
}
void RssParser::reportFailure(const ParsingJob& job, const QString& error)
{
emit feedParsingFinished(job.feedUrl, error);
fsutils::forceRemove(job.filePath);
}

77
src/gui/rss/rssparser.h Normal file
View File

@@ -0,0 +1,77 @@
/*
* Bittorrent Client using Qt4 and libtorrent.
* Copyright (C) 2012 Christophe Dumez
*
* 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.
*
* In addition, as a special exception, the copyright holders give permission to
* link this program with the OpenSSL project's "OpenSSL" library (or with
* modified versions of it that use the same license as the "OpenSSL" library),
* and distribute the linked executables. You must obey the GNU General Public
* License in all respects for all of the code used other than "OpenSSL". If you
* modify file(s), you may extend this exception to your version of the file(s),
* but you are not obligated to do so. If you do not wish to do so, delete this
* exception statement from your version.
*
* Contact : chris@qbittorrent.org
*/
#ifndef RSSPARSER_H
#define RSSPARSER_H
#include "rssarticle.h"
#include <QMutex>
#include <QQueue>
#include <QThread>
#include <QWaitCondition>
struct ParsingJob;
class RssParser : public QThread
{
Q_OBJECT
public:
explicit RssParser(QObject *parent = 0);
virtual ~RssParser();
signals:
void newArticle(const QString& feedUrl, const QVariantHash& rssArticle);
void feedTitle(const QString& feedUrl, const QString& title);
void feedParsingFinished(const QString& feedUrl, const QString& error);
public slots:
void parseRssFile(const QString& feedUrl, const QString& filePath);
void clearFeedData(const QString& feedUrl);
protected:
virtual void run();
static QDateTime parseDate(const QString& string);
void parseRssArticle(QXmlStreamReader& xml, const QString& feedUrl);
void parseRSSChannel(QXmlStreamReader& xml, const QString& feedUrl);
void parseAtomArticle(QXmlStreamReader& xml, const QString& feedUrl, const QString& baseUrl);
void parseAtomChannel(QXmlStreamReader& xml, const QString& feedUrl);
void parseFeed(const ParsingJob& job);
void reportFailure(const ParsingJob& job, const QString& error);
private:
bool m_running;
QMutex m_mutex;
QQueue<ParsingJob> m_queue;
QWaitCondition m_waitCondition;
QHash<QString/*feedUrl*/, QString/*lastBuildDate*/> m_lastBuildDates; // Optimization
};
#endif // RSSPARSER_H

View File

@@ -0,0 +1,57 @@
/*
* Bittorrent Client using Qt4 and libtorrent.
* Copyright (C) 2010 Christophe Dumez
*
* 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.
*
* In addition, as a special exception, the copyright holders give permission to
* link this program with the OpenSSL project's "OpenSSL" library (or with
* modified versions of it that use the same license as the "OpenSSL" library),
* and distribute the linked executables. You must obey the GNU General Public
* License in all respects for all of the code used other than "OpenSSL". If you
* modify file(s), you may extend this exception to your version of the file(s),
* but you are not obligated to do so. If you do not wish to do so, delete this
* exception statement from your version.
*
* Contact : chris@qbittorrent.org
*/
#include "rsssettingsdlg.h"
#include "ui_rsssettingsdlg.h"
#include "preferences.h"
RssSettingsDlg::RssSettingsDlg(QWidget *parent) :
QDialog(parent),
ui(new Ui::RssSettingsDlg)
{
ui->setupUi(this);
// Load settings
const Preferences* const pref = Preferences::instance();
ui->spinRSSRefresh->setValue(pref->getRSSRefreshInterval());
ui->spinRSSMaxArticlesPerFeed->setValue(pref->getRSSMaxArticlesPerFeed());
}
RssSettingsDlg::~RssSettingsDlg()
{
qDebug("Deleting the RSS settings dialog");
delete ui;
}
void RssSettingsDlg::on_buttonBox_accepted() {
// Save settings
Preferences* const pref = Preferences::instance();
pref->setRSSRefreshInterval(ui->spinRSSRefresh->value());
pref->setRSSMaxArticlesPerFeed(ui->spinRSSMaxArticlesPerFeed->value());
}

View File

@@ -0,0 +1,58 @@
/*
* Bittorrent Client using Qt4 and libtorrent.
* Copyright (C) 2010 Christophe Dumez
*
* 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.
*
* In addition, as a special exception, the copyright holders give permission to
* link this program with the OpenSSL project's "OpenSSL" library (or with
* modified versions of it that use the same license as the "OpenSSL" library),
* and distribute the linked executables. You must obey the GNU General Public
* License in all respects for all of the code used other than "OpenSSL". If you
* modify file(s), you may extend this exception to your version of the file(s),
* but you are not obligated to do so. If you do not wish to do so, delete this
* exception statement from your version.
*
* Contact : chris@qbittorrent.org
*/
#ifndef RSSSETTINGSDLG_H
#define RSSSETTINGSDLG_H
#include <QDialog>
QT_BEGIN_NAMESPACE
namespace Ui {
class RssSettingsDlg;
}
QT_END_NAMESPACE
class RssSettingsDlg : public QDialog
{
Q_OBJECT
public:
explicit RssSettingsDlg(QWidget *parent = 0);
~RssSettingsDlg();
protected slots:
void on_buttonBox_accepted();
private:
Ui::RssSettingsDlg *ui;
};
#endif // RSSSETTINGS_H

View File

@@ -0,0 +1,158 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>RssSettingsDlg</class>
<widget class="QDialog" name="RssSettingsDlg">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>502</width>
<height>137</height>
</rect>
</property>
<property name="windowTitle">
<string>RSS Reader Settings</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0" rowspan="2">
<widget class="QLabel" name="label_4">
<property name="minimumSize">
<size>
<width>48</width>
<height>48</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>48</width>
<height>48</height>
</size>
</property>
<property name="text">
<string/>
</property>
<property name="pixmap">
<pixmap resource="../icons.qrc">:/icons/oxygen/application-rss+xml.png</pixmap>
</property>
<property name="scaledContents">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QLabel" name="label">
<property name="text">
<string>RSS feeds refresh interval:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="0" column="3">
<widget class="QSpinBox" name="spinRSSRefresh">
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>999999</number>
</property>
<property name="value">
<number>5</number>
</property>
</widget>
</item>
<item row="0" column="4">
<widget class="QLabel" name="label_2">
<property name="text">
<string>minutes</string>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Maximum number of articles per feed:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="1" column="3">
<widget class="QSpinBox" name="spinRSSMaxArticlesPerFeed">
<property name="maximum">
<number>9999</number>
</property>
<property name="value">
<number>100</number>
</property>
</widget>
</item>
<item row="0" column="1">
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources>
<include location="../icons.qrc"/>
</resources>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>RssSettingsDlg</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>RssSettingsDlg</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>