mirror of
https://github.com/qbittorrent/qBittorrent.git
synced 2025-12-20 23:47:23 -06:00
Keep reorganizing code (RSS)
This commit is contained in:
221
src/rss/feedList.h
Normal file
221
src/rss/feedList.h
Normal file
@@ -0,0 +1,221 @@
|
||||
#ifndef FEEDLIST_H
|
||||
#define FEEDLIST_H
|
||||
|
||||
#include <QTreeWidget>
|
||||
#include <QTreeWidgetItem>
|
||||
#include <QDropEvent>
|
||||
#include <QDragMoveEvent>
|
||||
#include <QStringList>
|
||||
#include <QHash>
|
||||
#include <QUrl>
|
||||
#include "rss.h"
|
||||
|
||||
class FeedList: public QTreeWidget {
|
||||
Q_OBJECT
|
||||
|
||||
private:
|
||||
RssManager *rssmanager;
|
||||
QHash<QTreeWidgetItem*, RssFile*> mapping;
|
||||
QHash<QString, QTreeWidgetItem*> feeds_items;
|
||||
QTreeWidgetItem* current_feed;
|
||||
QTreeWidgetItem *unread_item;
|
||||
|
||||
public:
|
||||
FeedList(QWidget *parent, RssManager *rssmanager): QTreeWidget(parent), rssmanager(rssmanager) {
|
||||
setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
setDragDropMode(QAbstractItemView::InternalMove);
|
||||
setSelectionMode(QAbstractItemView::ExtendedSelection);
|
||||
setColumnCount(1);
|
||||
QTreeWidgetItem *___qtreewidgetitem = headerItem();
|
||||
___qtreewidgetitem->setText(0, QApplication::translate("RSS", "RSS feeds", 0, QApplication::UnicodeUTF8));
|
||||
unread_item = new QTreeWidgetItem(this);
|
||||
unread_item->setText(0, tr("Unread") + QString::fromUtf8(" (") + QString::number(rssmanager->getNbUnRead(), 10)+ QString(")"));
|
||||
unread_item->setData(0,Qt::DecorationRole, QVariant(QIcon(":/Icons/oxygen/mail-folder-inbox.png")));
|
||||
itemAdded(unread_item, rssmanager);
|
||||
connect(this, SIGNAL(currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)), this, SLOT(updateCurrentFeed(QTreeWidgetItem*)));
|
||||
setCurrentItem(unread_item);
|
||||
}
|
||||
|
||||
~FeedList() {
|
||||
disconnect(this, SIGNAL(currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)), this, SLOT(updateCurrentFeed(QTreeWidgetItem*)));
|
||||
delete unread_item;
|
||||
}
|
||||
|
||||
void itemAdded(QTreeWidgetItem *item, RssFile* file) {
|
||||
mapping[item] = file;
|
||||
if(file->getType() == RssFile::STREAM) {
|
||||
feeds_items[file->getID()] = item;
|
||||
}
|
||||
}
|
||||
|
||||
void itemAboutToBeRemoved(QTreeWidgetItem *item) {
|
||||
RssFile* file = mapping.take(item);
|
||||
if(file->getType() == RssFile::STREAM) {
|
||||
feeds_items.remove(file->getID());
|
||||
} else {
|
||||
QList<RssStream*> feeds = ((RssFolder*)file)->getAllFeeds();
|
||||
foreach(RssStream* feed, feeds) {
|
||||
feeds_items.remove(feed->getID());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool hasFeed(QString url) const {
|
||||
return feeds_items.contains(QUrl(url).toString());
|
||||
}
|
||||
|
||||
QList<QTreeWidgetItem*> getAllFeedItems() const {
|
||||
return feeds_items.values();
|
||||
}
|
||||
|
||||
QTreeWidgetItem* getUnreadItem() const {
|
||||
return unread_item;
|
||||
}
|
||||
|
||||
QStringList getItemPath(QTreeWidgetItem* item) const {
|
||||
QStringList path;
|
||||
if(item) {
|
||||
if(item->parent())
|
||||
path << getItemPath(item->parent());
|
||||
path.append(getRSSItem(item)->getID());
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
QList<QTreeWidgetItem*> getAllOpenFolders(QTreeWidgetItem *parent=0) 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(getItemType(item) == RssFile::FOLDER && 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*> getAllFeedItems(QTreeWidgetItem* folder) {
|
||||
QList<QTreeWidgetItem*> feeds;
|
||||
int nbChildren = folder->childCount();
|
||||
for(int i=0; i<nbChildren; ++i) {
|
||||
QTreeWidgetItem *item = folder->child(i);
|
||||
if(getItemType(item) == RssFile::STREAM) {
|
||||
feeds << item;
|
||||
} else {
|
||||
feeds << getAllFeedItems(item);
|
||||
}
|
||||
}
|
||||
return feeds;
|
||||
}
|
||||
|
||||
RssFile* getRSSItem(QTreeWidgetItem *item) const {
|
||||
return mapping.value(item, 0);
|
||||
}
|
||||
|
||||
RssFile::FileType getItemType(QTreeWidgetItem *item) const {
|
||||
return mapping.value(item)->getType();
|
||||
}
|
||||
|
||||
QString getItemID(QTreeWidgetItem *item) const {
|
||||
return mapping.value(item)->getID();
|
||||
}
|
||||
|
||||
QTreeWidgetItem* getTreeItemFromUrl(QString url) const{
|
||||
return feeds_items.value(url, 0);
|
||||
}
|
||||
|
||||
RssStream* getRSSItemFromUrl(QString url) const {
|
||||
return (RssStream*)getRSSItem(getTreeItemFromUrl(url));
|
||||
}
|
||||
|
||||
QTreeWidgetItem* currentItem() const {
|
||||
return current_feed;
|
||||
}
|
||||
|
||||
QTreeWidgetItem* currentFeed() const {
|
||||
return current_feed;
|
||||
}
|
||||
|
||||
signals:
|
||||
void foldersAltered(QList<QTreeWidgetItem*> folders);
|
||||
void overwriteAttempt(QString filename);
|
||||
|
||||
protected slots:
|
||||
void updateCurrentFeed(QTreeWidgetItem* new_item) {
|
||||
if(!new_item) return;
|
||||
if(!mapping.contains(new_item)) return;
|
||||
if((getItemType(new_item) == RssFile::STREAM) || new_item == unread_item)
|
||||
current_feed = new_item;
|
||||
}
|
||||
|
||||
protected:
|
||||
void dragMoveEvent(QDragMoveEvent * event) {
|
||||
QTreeWidgetItem *item = itemAt(event->pos());
|
||||
if(item == unread_item) {
|
||||
event->ignore();
|
||||
} else {
|
||||
if(item && getItemType(item) != RssFile::FOLDER)
|
||||
event->ignore();
|
||||
else {
|
||||
if(selectedItems().contains(unread_item)) {
|
||||
event->ignore();
|
||||
} else {
|
||||
QTreeWidget::dragMoveEvent(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void dropEvent(QDropEvent *event) {
|
||||
qDebug("dropEvent");
|
||||
QList<QTreeWidgetItem*> folders_altered;
|
||||
QTreeWidgetItem *dest_folder_item = itemAt(event->pos());
|
||||
RssFolder *dest_folder;
|
||||
if(dest_folder_item) {
|
||||
dest_folder = (RssFolder*)getRSSItem(dest_folder_item);
|
||||
folders_altered << dest_folder_item;
|
||||
} else {
|
||||
dest_folder = rssmanager;
|
||||
}
|
||||
QList<QTreeWidgetItem *> src_items = selectedItems();
|
||||
// Check if there is not going to overwrite another file
|
||||
foreach(QTreeWidgetItem *src_item, src_items) {
|
||||
RssFile *file = getRSSItem(src_item);
|
||||
if(dest_folder->hasChild(file->getID())) {
|
||||
emit overwriteAttempt(file->getID());
|
||||
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
|
||||
RssFile *file = getRSSItem(src_item);
|
||||
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);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
#endif // FEEDLIST_H
|
||||
514
src/rss/feeddownloader.h
Normal file
514
src/rss/feeddownloader.h
Normal file
@@ -0,0 +1,514 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt4 and libtorrent.
|
||||
* Copyright (C) 2006 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 FEEDDOWNLOADER_H
|
||||
#define FEEDDOWNLOADER_H
|
||||
|
||||
#include <QString>
|
||||
#include <QListWidget>
|
||||
#include <QListWidgetItem>
|
||||
#include <QInputDialog>
|
||||
#include <QMessageBox>
|
||||
#include <QRegExp>
|
||||
#include <QMenu>
|
||||
#include <QFile>
|
||||
#include <QDataStream>
|
||||
#include <QFileDialog>
|
||||
|
||||
#include "qbtsession.h"
|
||||
#include "ui_feeddownloader.h"
|
||||
#include "qinisettings.h"
|
||||
|
||||
#if QT_VERSION >= 0x040500
|
||||
#include <QHash>
|
||||
#else
|
||||
#include <QMap>
|
||||
#define QHash QMap
|
||||
#define toHash toMap
|
||||
#endif
|
||||
|
||||
class FeedFilter: public QHash<QString, QVariant> {
|
||||
private:
|
||||
bool valid;
|
||||
public:
|
||||
FeedFilter():valid(true) {}
|
||||
FeedFilter(bool valid): valid(valid) {}
|
||||
FeedFilter(QHash<QString, QVariant> filter): QHash<QString, QVariant>(filter), valid(true) {}
|
||||
|
||||
bool matches(QString s) {
|
||||
QStringList match_tokens = getMatchingTokens();
|
||||
foreach(const QString& token, match_tokens) {
|
||||
if(token.isEmpty() || token == "")
|
||||
continue;
|
||||
QRegExp reg(token, Qt::CaseInsensitive, QRegExp::Wildcard);
|
||||
//reg.setMinimal(false);
|
||||
if(reg.indexIn(s) < 0) return false;
|
||||
}
|
||||
qDebug("Checking not matching tokens");
|
||||
// Checking not matching
|
||||
QStringList notmatch_tokens = getNotMatchingTokens();
|
||||
foreach(const QString& token, notmatch_tokens) {
|
||||
if(token.isEmpty()) continue;
|
||||
QRegExp reg(token, Qt::CaseInsensitive, QRegExp::Wildcard);
|
||||
if(reg.indexIn(s) > -1) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool isValid() const {
|
||||
return valid;
|
||||
}
|
||||
|
||||
QStringList getMatchingTokens() const {
|
||||
QString matches = this->value("matches", "").toString();
|
||||
return matches.split(" ");
|
||||
}
|
||||
|
||||
QString getMatchingTokens_str() const {
|
||||
return this->value("matches", "").toString();
|
||||
}
|
||||
|
||||
void setMatchingTokens(QString tokens) {
|
||||
tokens = tokens.trimmed();
|
||||
if(tokens.isEmpty())
|
||||
(*this)["matches"] = "";
|
||||
else
|
||||
(*this)["matches"] = tokens;
|
||||
}
|
||||
|
||||
QStringList getNotMatchingTokens() const {
|
||||
QString notmatching = this->value("not", "").toString();
|
||||
return notmatching.split(QRegExp("[\\s|]"));
|
||||
}
|
||||
|
||||
QString getNotMatchingTokens_str() const {
|
||||
return this->value("not", "").toString();
|
||||
}
|
||||
|
||||
void setNotMatchingTokens(QString tokens) {
|
||||
(*this)["not"] = tokens.trimmed();
|
||||
}
|
||||
|
||||
QString getSavePath() const {
|
||||
return this->value("save_path", "").toString();
|
||||
}
|
||||
|
||||
void setSavePath(QString save_path) {
|
||||
(*this)["save_path"] = save_path;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
class FeedFilters : public QHash<QString, QVariant> {
|
||||
|
||||
private:
|
||||
QString feed_url;
|
||||
|
||||
public:
|
||||
FeedFilters() {}
|
||||
FeedFilters(QString feed_url, QHash<QString, QVariant> filters): QHash<QString, QVariant>(filters), feed_url(feed_url) {}
|
||||
|
||||
bool hasFilter(QString name) const {
|
||||
return this->contains(name);
|
||||
}
|
||||
|
||||
FeedFilter* matches(QString s) {
|
||||
if(!isDownloadingEnabled()) return 0;
|
||||
if(this->size() == 0) return new FeedFilter(false);
|
||||
foreach(QVariant var_hash_filter, this->values()) {
|
||||
QHash<QString, QVariant> hash_filter = var_hash_filter.toHash();
|
||||
FeedFilter *filter = new FeedFilter(hash_filter);
|
||||
if(filter->matches(s))
|
||||
return filter;
|
||||
else
|
||||
delete filter;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
QStringList names() const {
|
||||
return this->keys();
|
||||
}
|
||||
|
||||
FeedFilter getFilter(QString name) const {
|
||||
if(this->contains(name))
|
||||
return FeedFilter(this->value(name).toHash());
|
||||
return FeedFilter();
|
||||
}
|
||||
|
||||
void setFilter(QString name, FeedFilter f) {
|
||||
(*this)[name] = f;
|
||||
}
|
||||
|
||||
bool isDownloadingEnabled() const {
|
||||
QIniSettings qBTRSS("qBittorrent", "qBittorrent-rss");
|
||||
QHash<QString, QVariant> feeds_w_downloader = qBTRSS.value("downloader_on", QHash<QString, QVariant>()).toHash();
|
||||
return feeds_w_downloader.value(feed_url, false).toBool();
|
||||
}
|
||||
|
||||
void setDownloadingEnabled(bool enabled) {
|
||||
QIniSettings qBTRSS("qBittorrent", "qBittorrent-rss");
|
||||
QHash<QString, QVariant> feeds_w_downloader = qBTRSS.value("downloader_on", QHash<QString, QVariant>()).toHash();
|
||||
feeds_w_downloader[feed_url] = enabled;
|
||||
qBTRSS.setValue("downloader_on", feeds_w_downloader);
|
||||
}
|
||||
|
||||
static FeedFilters getFeedFilters(QString url) {
|
||||
QIniSettings qBTRSS("qBittorrent", "qBittorrent-rss");
|
||||
QHash<QString, QVariant> all_feeds_filters = qBTRSS.value("feed_filters", QHash<QString, QVariant>()).toHash();
|
||||
return FeedFilters(url, all_feeds_filters.value(url, QHash<QString, QVariant>()).toHash());
|
||||
}
|
||||
|
||||
void rename(QString old_name, QString new_name) {
|
||||
Q_ASSERT(this->contains(old_name));
|
||||
(*this)[new_name] = this->take(old_name);
|
||||
}
|
||||
|
||||
bool serialize(QString path) {
|
||||
QFile f(path);
|
||||
if(f.open(QIODevice::WriteOnly)) {
|
||||
QDataStream out(&f);
|
||||
out.setVersion(QDataStream::Qt_4_3);
|
||||
out << (QHash<QString, QVariant>)(*this);
|
||||
f.close();
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool unserialize(QString path) {
|
||||
QFile f(path);
|
||||
if(f.open(QIODevice::ReadOnly)) {
|
||||
QDataStream in(&f);
|
||||
in.setVersion(QDataStream::Qt_4_3);
|
||||
QHash<QString, QVariant> tmp;
|
||||
in >> tmp;
|
||||
qDebug("Unserialized %d filters", tmp.size());
|
||||
foreach(const QString& key, tmp.keys()) {
|
||||
(*this)[key] = tmp[key];
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void save() {
|
||||
QIniSettings qBTRSS("qBittorrent", "qBittorrent-rss");
|
||||
QHash<QString, QVariant> all_feeds_filters = qBTRSS.value("feed_filters", QHash<QString, QVariant>()).toHash();
|
||||
qDebug("Saving filters for feed: %s (%d filters)", qPrintable(feed_url), (*this).size());
|
||||
all_feeds_filters[feed_url] = *this;
|
||||
qBTRSS.setValue("feed_filters", all_feeds_filters);
|
||||
}
|
||||
};
|
||||
|
||||
class FeedDownloaderDlg : public QDialog, private Ui_FeedDownloader{
|
||||
Q_OBJECT
|
||||
|
||||
private:
|
||||
QString feed_url;
|
||||
QString feed_name;
|
||||
FeedFilters filters;
|
||||
Bittorrent *BTSession;
|
||||
QString selected_filter; // name
|
||||
|
||||
public:
|
||||
FeedDownloaderDlg(QWidget *parent, QString feed_url, QString feed_name, Bittorrent* BTSession): QDialog(parent), feed_url(feed_url), feed_name(feed_name), BTSession(BTSession), selected_filter(QString::null){
|
||||
setupUi(this);
|
||||
setAttribute(Qt::WA_DeleteOnClose);
|
||||
Q_ASSERT(!feed_name.isEmpty());
|
||||
rssfeed_lbl->setText(feed_name);
|
||||
filters = FeedFilters::getFeedFilters(feed_url);
|
||||
// Connect Signals/Slots
|
||||
connect(filtersList, SIGNAL(currentItemChanged(QListWidgetItem* , QListWidgetItem *)), this, SLOT(showFilterSettings(QListWidgetItem *)));
|
||||
connect(filtersList, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(displayFiltersListMenu(const QPoint&)));
|
||||
connect(actionAdd_filter, SIGNAL(triggered()), this, SLOT(addFilter()));
|
||||
connect(actionRemove_filter, SIGNAL(triggered()), this, SLOT(deleteFilter()));
|
||||
connect(actionRename_filter, SIGNAL(triggered()), this, SLOT(renameFilter()));
|
||||
connect(del_button, SIGNAL(clicked(bool)), this, SLOT(deleteFilter()));
|
||||
connect(add_button, SIGNAL(clicked(bool)), this, SLOT(addFilter()));
|
||||
connect(enableDl_cb, SIGNAL(stateChanged(int)), this, SLOT(enableFilterBox(int)));
|
||||
// Restore saved info
|
||||
enableDl_cb->setChecked(filters.isDownloadingEnabled());
|
||||
fillFiltersList();
|
||||
if(filters.size() > 0) {
|
||||
// Select first filter
|
||||
filtersList->setCurrentItem(filtersList->item(0));
|
||||
//showFilterSettings(filtersList->item(0));
|
||||
}
|
||||
// Show
|
||||
show();
|
||||
}
|
||||
|
||||
~FeedDownloaderDlg() {
|
||||
if(enableDl_cb->isChecked())
|
||||
emit filteringEnabled();
|
||||
// Make sure we save everything
|
||||
saveCurrentFilterSettings();
|
||||
filters.save();
|
||||
}
|
||||
|
||||
protected slots:
|
||||
void saveCurrentFilterSettings() {
|
||||
if(!selected_filter.isEmpty()) {
|
||||
FeedFilter filter = filters.getFilter(selected_filter);
|
||||
filter.setMatchingTokens(match_line->text());
|
||||
filter.setNotMatchingTokens(notmatch_line->text());
|
||||
QString save_path = savepath_line->text().trimmed();
|
||||
if(save_path.isEmpty())
|
||||
save_path = BTSession->getDefaultSavePath();
|
||||
filter.setSavePath(save_path);
|
||||
// Save updated filter
|
||||
filters.setFilter(selected_filter, filter);
|
||||
}
|
||||
}
|
||||
|
||||
void on_browse_button_clicked() {
|
||||
QString default_path = savepath_line->text();
|
||||
if(default_path.isEmpty() || !QDir(default_path).exists()) {
|
||||
default_path = QDir::homePath();
|
||||
}
|
||||
QString dir = QFileDialog::getExistingDirectory(this, tr("Choose save path"), QDir::homePath());
|
||||
if(!dir.isNull() && QDir(dir).exists()) {
|
||||
savepath_line->setText(dir);
|
||||
}
|
||||
}
|
||||
|
||||
void fillFiltersList() {
|
||||
// Fill filter list
|
||||
foreach(QString filter_name, filters.names()) {
|
||||
new QListWidgetItem(filter_name, filtersList);
|
||||
}
|
||||
}
|
||||
|
||||
void displayFiltersListMenu(const QPoint&) {
|
||||
QMenu myFiltersListMenu(this);
|
||||
if(filtersList->selectedItems().size() > 0) {
|
||||
myFiltersListMenu.addAction(actionRename_filter);
|
||||
myFiltersListMenu.addAction(actionRemove_filter);
|
||||
} else {
|
||||
myFiltersListMenu.addAction(actionAdd_filter);
|
||||
}
|
||||
// Call menu
|
||||
myFiltersListMenu.exec(QCursor::pos());
|
||||
}
|
||||
|
||||
void showFilterSettings(QListWidgetItem *item) {
|
||||
// First, save current filter settings
|
||||
saveCurrentFilterSettings();
|
||||
// Clear all fields
|
||||
clearFields();
|
||||
if(!item) {
|
||||
qDebug("No new selected item");
|
||||
return;
|
||||
}
|
||||
// Actually show filter settings
|
||||
QString filter_name = item->text();
|
||||
FeedFilter filter = filters.getFilter(filter_name);
|
||||
filterSettingsBox->setEnabled(true);
|
||||
match_line->setText(filter.getMatchingTokens_str());
|
||||
if(match_line->text().trimmed().isEmpty()) {
|
||||
match_line->setText(filter_name);
|
||||
}
|
||||
notmatch_line->setText(filter.getNotMatchingTokens_str());
|
||||
QString save_path = filter.getSavePath();
|
||||
if(save_path.isEmpty())
|
||||
save_path = BTSession->getDefaultSavePath();
|
||||
savepath_line->setText(save_path);
|
||||
// Update selected filter
|
||||
selected_filter = filter_name;
|
||||
}
|
||||
|
||||
void deleteFilter() {
|
||||
QList<QListWidgetItem *> items = filtersList->selectedItems();
|
||||
if(items.size() == 1) {
|
||||
QListWidgetItem * item = items.first();
|
||||
filters.remove(item->text());
|
||||
selected_filter = QString::null;
|
||||
delete item;
|
||||
// Reset Filter settings view
|
||||
if(filters.size() == 0) {
|
||||
clearFields();
|
||||
filterSettingsBox->setEnabled(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void renameFilter() {
|
||||
QList<QListWidgetItem *> items = filtersList->selectedItems();
|
||||
if(items.size() == 1) {
|
||||
QListWidgetItem *item = items.first();
|
||||
QString current_name = item->text();
|
||||
QString new_name;
|
||||
bool validated = false;
|
||||
do {
|
||||
new_name = askFilterName(current_name);
|
||||
if(new_name.isNull() || new_name == current_name) return;
|
||||
if(!filters.hasFilter(new_name)) {
|
||||
validated = true;
|
||||
} else {
|
||||
QMessageBox::warning(0, tr("Invalid filter name"), tr("This filter name is already in use."));
|
||||
}
|
||||
}while(!validated);
|
||||
// Save the current filter
|
||||
saveCurrentFilterSettings();
|
||||
// Rename the filter
|
||||
filters.rename(current_name, new_name);
|
||||
if(selected_filter == current_name)
|
||||
selected_filter = new_name;
|
||||
item->setText(new_name);
|
||||
}
|
||||
}
|
||||
|
||||
void enableFilterBox(int state) {
|
||||
if(state == Qt::Checked) {
|
||||
filtersBox->setEnabled(true);
|
||||
filters.setDownloadingEnabled(true);
|
||||
} else {
|
||||
filtersBox->setEnabled(false);
|
||||
filters.setDownloadingEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
QString askFilterName(QString name=QString::null) {
|
||||
QString name_prop;
|
||||
if(name.isEmpty())
|
||||
name_prop = tr("New filter");
|
||||
else
|
||||
name_prop = name;
|
||||
QString new_name;
|
||||
bool validated = false;
|
||||
do {
|
||||
bool ok;
|
||||
new_name = QInputDialog::getText(this, tr("Please choose a name for this filter"), tr("Filter name:"), QLineEdit::Normal, name_prop, &ok);
|
||||
if(!ok) {
|
||||
return QString::null;
|
||||
}
|
||||
// Validate filter name
|
||||
new_name = new_name.trimmed();
|
||||
if(new_name.isEmpty()) {
|
||||
// Cannot be left empty
|
||||
QMessageBox::warning(0, tr("Invalid filter name"), tr("The filter name cannot be left empty."));
|
||||
} else {
|
||||
validated = true;
|
||||
}
|
||||
} while(!validated);
|
||||
return new_name;
|
||||
}
|
||||
|
||||
void addFilter() {
|
||||
QString filter_name = QString::null;
|
||||
bool validated = false;
|
||||
do {
|
||||
filter_name = askFilterName();
|
||||
if(filter_name.isNull()) return;
|
||||
if(filters.hasFilter(filter_name)) {
|
||||
// Filter alread exists
|
||||
QMessageBox::warning(0, tr("Invalid filter name"), tr("This filter name is already in use."));
|
||||
} else {
|
||||
validated = true;
|
||||
}
|
||||
}while(!validated);
|
||||
QListWidgetItem *it = new QListWidgetItem(filter_name, filtersList);
|
||||
filtersList->setCurrentItem(it);
|
||||
//showFilterSettings(it);
|
||||
}
|
||||
|
||||
void clearFields() {
|
||||
match_line->clear();
|
||||
notmatch_line->clear();
|
||||
savepath_line->clear();
|
||||
test_res_lbl->setText("");
|
||||
test_line->clear();
|
||||
}
|
||||
|
||||
void on_testButton_clicked(bool) {
|
||||
test_res_lbl->clear();
|
||||
if(selected_filter.isEmpty()) {
|
||||
qDebug("No filter is selected!!!");
|
||||
return;
|
||||
}
|
||||
QString s = test_line->text().trimmed();
|
||||
if(s.isEmpty()) {
|
||||
QMessageBox::warning(0, tr("Filter testing error"), tr("Please specify a test torrent name."));
|
||||
return;
|
||||
}
|
||||
// Get current filter
|
||||
saveCurrentFilterSettings();
|
||||
FeedFilter f = filters.getFilter(selected_filter);
|
||||
if(f.matches(s))
|
||||
test_res_lbl->setText("<b><font color=\"green\">"+tr("matches")+"</font></b>");
|
||||
else
|
||||
test_res_lbl->setText("<b><font color=\"red\">"+tr("does not match")+"</font></b>");
|
||||
}
|
||||
|
||||
void on_importButton_clicked(bool) {
|
||||
QString source = QFileDialog::getOpenFileName(0, tr("Select file to import"), QDir::homePath(), tr("Filters Files")+QString::fromUtf8(" (*.filters)"));
|
||||
if(source.isEmpty()) return;
|
||||
if(filters.unserialize(source)) {
|
||||
// Clean up first
|
||||
clearFields();
|
||||
filtersList->clear();
|
||||
selected_filter = QString::null;
|
||||
fillFiltersList();
|
||||
if(filters.size() > 0)
|
||||
filtersList->setCurrentItem(filtersList->item(0));
|
||||
QMessageBox::information(0, tr("Import successful"), tr("Filters import was successful."));
|
||||
} else {
|
||||
QMessageBox::warning(0, tr("Import failure"), tr("Filters could not be imported due to an I/O error."));
|
||||
}
|
||||
}
|
||||
|
||||
void on_exportButton_clicked(bool) {
|
||||
QString destination = QFileDialog::getSaveFileName(this, tr("Select destination file"), QDir::homePath(), tr("Filters Files")+QString::fromUtf8(" (*.filters)"));
|
||||
if(destination.isEmpty()) return;
|
||||
// Append file extension
|
||||
if(!destination.endsWith(".filters"))
|
||||
destination += ".filters";
|
||||
/*if(QFile::exists(destination)) {
|
||||
int ret = QMessageBox::question(0, tr("Overwriting confirmation"), tr("Are you sure you want to overwrite existing file?"), QMessageBox::Yes|QMessageBox::No);
|
||||
if(ret != QMessageBox::Yes) return;
|
||||
}*/
|
||||
if(filters.serialize(destination))
|
||||
QMessageBox::information(0, tr("Export successful"), tr("Filters export was successful."));
|
||||
else
|
||||
QMessageBox::warning(0, tr("Export failure"), tr("Filters could not be exported due to an I/O error."));
|
||||
}
|
||||
|
||||
signals:
|
||||
void filteringEnabled();
|
||||
|
||||
};
|
||||
|
||||
#undef QHash
|
||||
#undef toHash
|
||||
|
||||
#endif // FEEDDOWNLOADER_H
|
||||
716
src/rss/rss.cpp
Normal file
716
src/rss/rss.cpp
Normal file
@@ -0,0 +1,716 @@
|
||||
/*
|
||||
* 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 "rss.h"
|
||||
#include "preferences.h"
|
||||
|
||||
#if QT_VERSION < 0x040500
|
||||
#include <QMap>
|
||||
#define QHash QMap
|
||||
#define toHash toMap
|
||||
#else
|
||||
#include <QHash>
|
||||
#endif
|
||||
|
||||
/** RssFolder **/
|
||||
|
||||
RssFolder::RssFolder(RssFolder *parent, RssManager *rssmanager, Bittorrent *BTSession, QString name): parent(parent), rssmanager(rssmanager), BTSession(BTSession), name(name) {
|
||||
downloader = new downloadThread(this);
|
||||
connect(downloader, SIGNAL(downloadFinished(QString, QString)), this, SLOT(processFinishedDownload(QString, QString)));
|
||||
connect(downloader, SIGNAL(downloadFailure(QString, QString)), this, SLOT(handleDownloadFailure(QString, QString)));
|
||||
}
|
||||
|
||||
RssFolder::~RssFolder() {
|
||||
qDebug("Deleting a RSS folder, removing elements");
|
||||
qDeleteAll(this->values());
|
||||
qDebug("Deleting downloader thread");
|
||||
delete downloader;
|
||||
qDebug("Downloader thread removed");
|
||||
}
|
||||
|
||||
unsigned int RssFolder::getNbUnRead() const {
|
||||
unsigned int nb_unread = 0;
|
||||
foreach(RssFile *file, this->values()) {
|
||||
nb_unread += file->getNbUnRead();
|
||||
}
|
||||
return nb_unread;
|
||||
}
|
||||
|
||||
RssFile::FileType RssFolder::getType() const {
|
||||
return RssFile::FOLDER;
|
||||
}
|
||||
|
||||
void RssFolder::refreshAll(){
|
||||
qDebug("Refreshing all rss feeds");
|
||||
const QList<RssFile*> items = this->values();
|
||||
for(int i=0; i<items.size(); ++i) {
|
||||
//foreach(RssFile *item, *this){
|
||||
RssFile *item = items.at(i);
|
||||
if(item->getType() == RssFile::STREAM) {
|
||||
RssStream* stream = (RssStream*) item;
|
||||
QString url = stream->getUrl();
|
||||
if(stream->isLoading()) return;
|
||||
stream->setLoading(true);
|
||||
downloader->downloadUrl(url);
|
||||
if(!stream->hasCustomIcon()){
|
||||
downloader->downloadUrl(stream->getIconUrl());
|
||||
}
|
||||
} else {
|
||||
RssFolder *folder = (RssFolder*)item;
|
||||
folder->refreshAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RssFolder::removeFile(QString ID) {
|
||||
if(this->contains(ID)) {
|
||||
RssFile* child = this->take(ID);
|
||||
child->removeAllSettings();
|
||||
child->removeAllItems();
|
||||
delete child;
|
||||
}
|
||||
}
|
||||
|
||||
RssFolder* RssFolder::addFolder(QString name) {
|
||||
RssFolder *subfolder;
|
||||
if(!this->contains(name)) {
|
||||
subfolder = new RssFolder(this, rssmanager, BTSession, name);
|
||||
(*this)[name] = subfolder;
|
||||
} else {
|
||||
subfolder = (RssFolder*)this->value(name);
|
||||
}
|
||||
return subfolder;
|
||||
}
|
||||
|
||||
RssStream* RssFolder::addStream(QString url) {
|
||||
RssStream* stream = new RssStream(this, rssmanager, BTSession, url);
|
||||
Q_ASSERT(!this->contains(stream->getUrl()));
|
||||
(*this)[stream->getUrl()] = stream;
|
||||
refreshStream(stream->getUrl());
|
||||
return stream;
|
||||
}
|
||||
|
||||
// Refresh All Children
|
||||
void RssFolder::refresh() {
|
||||
foreach(RssFile *child, this->values()) {
|
||||
// Little optimization child->refresh() would work too
|
||||
if(child->getType() == RssFile::STREAM)
|
||||
refreshStream(child->getID());
|
||||
else
|
||||
child->refresh();
|
||||
}
|
||||
}
|
||||
|
||||
QList<RssItem*> RssFolder::getNewsList() const {
|
||||
QList<RssItem*> news;
|
||||
foreach(RssFile *child, this->values()) {
|
||||
news << child->getNewsList();
|
||||
}
|
||||
return news;
|
||||
}
|
||||
|
||||
QList<RssItem*> RssFolder::getUnreadNewsList() const {
|
||||
QList<RssItem*> unread_news;
|
||||
foreach(RssFile *child, this->values()) {
|
||||
unread_news << child->getUnreadNewsList();
|
||||
}
|
||||
return unread_news;
|
||||
}
|
||||
|
||||
void RssFolder::refreshStream(QString url) {
|
||||
qDebug("Refreshing feed: %s", url.toLocal8Bit().data());
|
||||
Q_ASSERT(this->contains(url));
|
||||
RssStream *stream = (RssStream*)this->value(url);
|
||||
if(stream->isLoading()) {
|
||||
qDebug("Stream %s is already being loaded...", stream->getUrl().toLocal8Bit().data());
|
||||
return;
|
||||
}
|
||||
stream->setLoading(true);
|
||||
qDebug("stream %s : loaded=true", stream->getUrl().toLocal8Bit().data());
|
||||
downloader->downloadUrl(url);
|
||||
if(!stream->hasCustomIcon()){
|
||||
downloader->downloadUrl(stream->getIconUrl());
|
||||
}else{
|
||||
qDebug("No need to download this feed's icon, it was already downloaded");
|
||||
}
|
||||
}
|
||||
|
||||
QList<RssFile*> RssFolder::getContent() const {
|
||||
return this->values();
|
||||
}
|
||||
|
||||
unsigned int RssFolder::getNbFeeds() const {
|
||||
unsigned int nbFeeds = 0;
|
||||
foreach(RssFile* item, this->values()) {
|
||||
if(item->getType() == RssFile::FOLDER)
|
||||
nbFeeds += ((RssFolder*)item)->getNbFeeds();
|
||||
else
|
||||
nbFeeds += 1;
|
||||
}
|
||||
return nbFeeds;
|
||||
}
|
||||
|
||||
void RssFolder::processFinishedDownload(QString url, QString path) {
|
||||
if(url.endsWith("favicon.ico")){
|
||||
// Icon downloaded
|
||||
QImage fileIcon;
|
||||
if(fileIcon.load(path)) {
|
||||
QList<RssStream*> res = findFeedsWithIcon(url);
|
||||
RssStream* stream;
|
||||
foreach(stream, res){
|
||||
stream->setIconPath(path);
|
||||
if(!stream->isLoading())
|
||||
rssmanager->forwardFeedIconChanged(stream->getUrl(), stream->getIconPath());
|
||||
}
|
||||
}else{
|
||||
qDebug("Unsupported icon format at %s", (const char*)url.toLocal8Bit());
|
||||
}
|
||||
return;
|
||||
}
|
||||
RssStream *stream = (RssStream*)this->value(url, 0);
|
||||
if(!stream){
|
||||
qDebug("This rss stream was deleted in the meantime, nothing to update");
|
||||
return;
|
||||
}
|
||||
stream->processDownloadedFile(path);
|
||||
stream->setLoading(false);
|
||||
qDebug("stream %s : loaded=false", stream->getUrl().toLocal8Bit().data());
|
||||
// If the feed has no alias, then we use the title as Alias
|
||||
// this is more user friendly
|
||||
if(stream->getName().isEmpty()){
|
||||
if(!stream->getTitle().isEmpty())
|
||||
stream->rename(stream->getTitle());
|
||||
}
|
||||
rssmanager->forwardFeedInfosChanged(url, stream->getName(), stream->getNbUnRead());
|
||||
}
|
||||
|
||||
void RssFolder::handleDownloadFailure(QString url, QString reason) {
|
||||
if(url.endsWith("favicon.ico")){
|
||||
// Icon download failure
|
||||
qDebug("Could not download icon at %s, reason: %s", (const char*)url.toLocal8Bit(), (const char*)reason.toLocal8Bit());
|
||||
return;
|
||||
}
|
||||
RssStream *stream = (RssStream*)this->value(url, 0);
|
||||
if(!stream){
|
||||
qDebug("This rss stream was deleted in the meantime, nothing to update");
|
||||
return;
|
||||
}
|
||||
stream->setLoading(false);
|
||||
qDebug("Could not download Rss at %s, reason: %s", (const char*)url.toLocal8Bit(), (const char*)reason.toLocal8Bit());
|
||||
stream->setDownloadFailed();
|
||||
rssmanager->forwardFeedInfosChanged(url, stream->getName(), stream->getNbUnRead());
|
||||
}
|
||||
|
||||
QList<RssStream*> RssFolder::findFeedsWithIcon(QString icon_url) const {
|
||||
QList<RssStream*> res;
|
||||
RssFile* item;
|
||||
foreach(item, this->values()){
|
||||
if(item->getType() == RssFile::STREAM && ((RssStream*)item)->getIconUrl() == icon_url)
|
||||
res << (RssStream*)item;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
QString RssFolder::getName() const {
|
||||
return name;
|
||||
}
|
||||
|
||||
void RssFolder::rename(QString new_name) {
|
||||
Q_ASSERT(!parent->contains(new_name));
|
||||
if(!parent->contains(new_name)) {
|
||||
// Update parent
|
||||
(*parent)[new_name] = parent->take(name);
|
||||
// Actually rename
|
||||
name = new_name;
|
||||
}
|
||||
}
|
||||
|
||||
void RssFolder::markAllAsRead() {
|
||||
foreach(RssFile *item, this->values()) {
|
||||
item->markAllAsRead();
|
||||
}
|
||||
}
|
||||
|
||||
QList<RssStream*> RssFolder::getAllFeeds() const {
|
||||
QList<RssStream*> streams;
|
||||
foreach(RssFile *item, this->values()) {
|
||||
if(item->getType() == RssFile::STREAM) {
|
||||
streams << ((RssStream*)item);
|
||||
} else {
|
||||
foreach(RssStream* stream, ((RssFolder*)item)->getAllFeeds()) {
|
||||
streams << stream;
|
||||
}
|
||||
}
|
||||
}
|
||||
return streams;
|
||||
}
|
||||
|
||||
void RssFolder::addFile(RssFile * item) {
|
||||
if(item->getType() == RssFile::STREAM) {
|
||||
Q_ASSERT(!this->contains(((RssStream*)item)->getUrl()));
|
||||
(*this)[((RssStream*)item)->getUrl()] = item;
|
||||
qDebug("Added feed %s to folder ./%s", ((RssStream*)item)->getUrl().toLocal8Bit().data(), name.toLocal8Bit().data());
|
||||
} else {
|
||||
Q_ASSERT(!this->contains(((RssFolder*)item)->getName()));
|
||||
(*this)[((RssFolder*)item)->getName()] = item;
|
||||
qDebug("Added folder %s to folder ./%s", ((RssFolder*)item)->getName().toLocal8Bit().data(), name.toLocal8Bit().data());
|
||||
}
|
||||
// Update parent
|
||||
item->setParent(this);
|
||||
}
|
||||
|
||||
/** RssManager **/
|
||||
|
||||
RssManager::RssManager(Bittorrent *BTSession): RssFolder(0, this, BTSession, QString::null) {
|
||||
loadStreamList();
|
||||
connect(&newsRefresher, SIGNAL(timeout()), this, SLOT(refreshAll()));
|
||||
refreshInterval = Preferences::getRSSRefreshInterval();
|
||||
newsRefresher.start(refreshInterval*60000);
|
||||
}
|
||||
|
||||
RssManager::~RssManager(){
|
||||
qDebug("Deleting RSSManager");
|
||||
saveStreamList();
|
||||
qDebug("RSSManager deleted");
|
||||
}
|
||||
|
||||
void RssManager::updateRefreshInterval(unsigned int val){
|
||||
if(refreshInterval != val) {
|
||||
refreshInterval = val;
|
||||
newsRefresher.start(refreshInterval*60000);
|
||||
qDebug("New RSS refresh interval is now every %dmin", refreshInterval);
|
||||
}
|
||||
}
|
||||
|
||||
void RssManager::loadStreamList(){
|
||||
QIniSettings settings("qBittorrent", "qBittorrent");
|
||||
QStringList streamsUrl = settings.value("Rss/streamList").toStringList();
|
||||
QStringList aliases = settings.value("Rss/streamAlias").toStringList();
|
||||
if(streamsUrl.size() != aliases.size()){
|
||||
std::cerr << "Corrupted Rss list, not loading it\n";
|
||||
return;
|
||||
}
|
||||
unsigned int i = 0;
|
||||
foreach(QString s, streamsUrl){
|
||||
QStringList path = s.split("\\");
|
||||
if(path.empty()) continue;
|
||||
QString feed_url = path.takeLast();
|
||||
// Create feed path (if it does not exists)
|
||||
RssFolder * feed_parent = this;
|
||||
foreach(QString folder_name, path) {
|
||||
feed_parent = feed_parent->addFolder(folder_name);
|
||||
}
|
||||
// Create feed
|
||||
RssStream *stream = feed_parent->addStream(feed_url);
|
||||
QString alias = aliases.at(i);
|
||||
if(!alias.isEmpty()) {
|
||||
stream->rename(alias);
|
||||
}
|
||||
++i;
|
||||
}
|
||||
qDebug("NB RSS streams loaded: %d", streamsUrl.size());
|
||||
}
|
||||
|
||||
void RssManager::forwardFeedInfosChanged(QString url, QString aliasOrUrl, unsigned int nbUnread) {
|
||||
emit feedInfosChanged(url, aliasOrUrl, nbUnread);
|
||||
}
|
||||
|
||||
void RssManager::forwardFeedIconChanged(QString url, QString icon_path) {
|
||||
emit feedIconChanged(url, icon_path);
|
||||
}
|
||||
|
||||
void RssManager::moveFile(RssFile* file, RssFolder* dest_folder) {
|
||||
RssFolder* src_folder = file->getParent();
|
||||
if(dest_folder != src_folder) {
|
||||
// Copy to new Folder
|
||||
dest_folder->addFile(file);
|
||||
// Remove reference in old folder
|
||||
src_folder->remove(file->getID());
|
||||
} else {
|
||||
qDebug("Nothing to move, same destination folder");
|
||||
}
|
||||
}
|
||||
|
||||
void RssManager::saveStreamList(){
|
||||
QStringList streamsUrl;
|
||||
QStringList aliases;
|
||||
const QList<RssStream*> streams = getAllFeeds();
|
||||
foreach(const RssStream *stream, streams) {
|
||||
QString stream_path = stream->getPath().join("\\");
|
||||
if(stream_path.isNull()) {
|
||||
stream_path = "";
|
||||
}
|
||||
qDebug("Saving stream path: %s", qPrintable(stream_path));
|
||||
streamsUrl << stream_path;
|
||||
aliases << stream->getName();
|
||||
}
|
||||
QIniSettings settings("qBittorrent", "qBittorrent");
|
||||
settings.beginGroup("Rss");
|
||||
// FIXME: Empty folder are not saved
|
||||
settings.setValue("streamList", streamsUrl);
|
||||
settings.setValue("streamAlias", aliases);
|
||||
settings.endGroup();
|
||||
}
|
||||
|
||||
/** RssStream **/
|
||||
|
||||
RssStream::RssStream(RssFolder* parent, RssManager *rssmanager, Bittorrent *BTSession, QString _url): parent(parent), rssmanager(rssmanager), BTSession(BTSession), alias(""), iconPath(":/Icons/rss16.png"), refreshed(false), downloadFailure(false), currently_loading(false) {
|
||||
qDebug("RSSStream constructed");
|
||||
QIniSettings qBTRSS("qBittorrent", "qBittorrent-rss");
|
||||
url = QUrl(_url).toString();
|
||||
QHash<QString, QVariant> all_old_items = qBTRSS.value("old_items", QHash<QString, QVariant>()).toHash();
|
||||
QVariantList old_items = all_old_items.value(url, QVariantList()).toList();
|
||||
qDebug("Loading %d old items for feed %s", old_items.size(), getName().toLocal8Bit().data());
|
||||
foreach(const QVariant &var_it, old_items) {
|
||||
QHash<QString, QVariant> item = var_it.toHash();
|
||||
RssItem *rss_item = RssItem::fromHash(this, item);
|
||||
if(rss_item->isValid()) {
|
||||
(*this)[rss_item->getId()] = rss_item;
|
||||
} else {
|
||||
delete rss_item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RssStream::~RssStream(){
|
||||
qDebug("Deleting a RSS stream: %s", getName().toLocal8Bit().data());
|
||||
if(refreshed) {
|
||||
QIniSettings qBTRSS("qBittorrent", "qBittorrent-rss");
|
||||
QVariantList old_items;
|
||||
foreach(RssItem *item, this->values()) {
|
||||
old_items << item->toHash();
|
||||
}
|
||||
qDebug("Saving %d old items for feed %s", old_items.size(), getName().toLocal8Bit().data());
|
||||
QHash<QString, QVariant> all_old_items = qBTRSS.value("old_items", QHash<QString, QVariant>()).toHash();
|
||||
all_old_items[url] = old_items;
|
||||
qBTRSS.setValue("old_items", all_old_items);
|
||||
}
|
||||
qDebug("Removing all item from feed");
|
||||
removeAllItems();
|
||||
qDebug("All items were removed");
|
||||
if(QFile::exists(filePath))
|
||||
misc::safeRemove(filePath);
|
||||
if(QFile::exists(iconPath) && !iconPath.startsWith(":/"))
|
||||
misc::safeRemove(iconPath);
|
||||
}
|
||||
|
||||
RssFile::FileType RssStream::getType() const {
|
||||
return RssFile::STREAM;
|
||||
}
|
||||
|
||||
void RssStream::refresh() {
|
||||
parent->refreshStream(url);
|
||||
}
|
||||
|
||||
// delete all the items saved
|
||||
void RssStream::removeAllItems() {
|
||||
qDeleteAll(this->values());
|
||||
this->clear();
|
||||
}
|
||||
|
||||
void RssStream::removeAllSettings() {
|
||||
QIniSettings qBTRSS("qBittorrent", "qBittorrent-rss");
|
||||
QHash<QString, QVariant> feeds_w_downloader = qBTRSS.value("downloader_on", QHash<QString, QVariant>()).toHash();
|
||||
if(feeds_w_downloader.contains(url)) {
|
||||
feeds_w_downloader.remove(url);
|
||||
qBTRSS.setValue("downloader_on", feeds_w_downloader);
|
||||
}
|
||||
QHash<QString, QVariant> all_feeds_filters = qBTRSS.value("feed_filters", QHash<QString, QVariant>()).toHash();
|
||||
if(all_feeds_filters.contains(url)) {
|
||||
all_feeds_filters.remove(url);
|
||||
qBTRSS.setValue("feed_filters", all_feeds_filters);
|
||||
}
|
||||
}
|
||||
|
||||
bool RssStream::itemAlreadyExists(QString name) {
|
||||
return this->contains(name);
|
||||
}
|
||||
|
||||
void RssStream::setLoading(bool val) {
|
||||
currently_loading = val;
|
||||
}
|
||||
|
||||
bool RssStream::isLoading() {
|
||||
return currently_loading;
|
||||
}
|
||||
|
||||
QString RssStream::getTitle() const{
|
||||
return title;
|
||||
}
|
||||
|
||||
void RssStream::rename(QString new_name){
|
||||
qDebug("Renaming stream to %s", new_name.toLocal8Bit().data());
|
||||
alias = new_name;
|
||||
}
|
||||
|
||||
// Return the alias if the stream has one, the url if it has no alias
|
||||
QString RssStream::getName() const{
|
||||
if(!alias.isEmpty()) {
|
||||
//qDebug("getName() returned alias: %s", (const char*)alias.toLocal8Bit());
|
||||
return alias;
|
||||
}
|
||||
if(!title.isEmpty()) {
|
||||
//qDebug("getName() returned title: %s", (const char*)title.toLocal8Bit());
|
||||
return title;
|
||||
}
|
||||
//qDebug("getName() returned url: %s", (const char*)url.toLocal8Bit());
|
||||
return url;
|
||||
}
|
||||
|
||||
QString RssStream::getLink() const{
|
||||
return link;
|
||||
}
|
||||
|
||||
QString RssStream::getUrl() const{
|
||||
return url;
|
||||
}
|
||||
|
||||
QString RssStream::getDescription() const{
|
||||
return description;
|
||||
}
|
||||
|
||||
QString RssStream::getImage() const{
|
||||
return image;
|
||||
}
|
||||
|
||||
QString RssStream::getFilePath() const{
|
||||
return filePath;
|
||||
}
|
||||
|
||||
QString RssStream::getIconPath() const{
|
||||
if(downloadFailure)
|
||||
return ":/Icons/oxygen/unavailable.png";
|
||||
return iconPath;
|
||||
}
|
||||
|
||||
bool RssStream::hasCustomIcon() const{
|
||||
return !iconPath.startsWith(":/");
|
||||
}
|
||||
|
||||
void RssStream::setIconPath(QString path) {
|
||||
iconPath = path;
|
||||
}
|
||||
|
||||
RssItem* RssStream::getItem(QString id) const{
|
||||
return this->value(id);
|
||||
}
|
||||
|
||||
unsigned int RssStream::getNbNews() const{
|
||||
return this->size();
|
||||
}
|
||||
|
||||
void RssStream::markAllAsRead() {
|
||||
foreach(RssItem *item, this->values()){
|
||||
item->setRead();
|
||||
}
|
||||
rssmanager->forwardFeedInfosChanged(url, getName(), 0);
|
||||
}
|
||||
|
||||
unsigned int RssStream::getNbUnRead() const{
|
||||
unsigned int nbUnread=0;
|
||||
foreach(RssItem *item, this->values()) {
|
||||
if(!item->isRead())
|
||||
++nbUnread;
|
||||
}
|
||||
return nbUnread;
|
||||
}
|
||||
|
||||
QList<RssItem*> RssStream::getNewsList() const{
|
||||
return this->values();
|
||||
}
|
||||
|
||||
QList<RssItem*> RssStream::getUnreadNewsList() const {
|
||||
QList<RssItem*> unread_news;
|
||||
foreach(RssItem *item, this->values()) {
|
||||
if(!item->isRead())
|
||||
unread_news << item;
|
||||
}
|
||||
return unread_news;
|
||||
}
|
||||
|
||||
// download the icon from the adress
|
||||
QString RssStream::getIconUrl() {
|
||||
QUrl siteUrl(url);
|
||||
return QString::fromUtf8("http://")+siteUrl.host()+QString::fromUtf8("/favicon.ico");
|
||||
}
|
||||
|
||||
// read and create items from a rss document
|
||||
short RssStream::readDoc(QIODevice* device) {
|
||||
qDebug("Parsing RSS file...");
|
||||
QXmlStreamReader xml(device);
|
||||
// is it a rss file ?
|
||||
if (xml.atEnd()) {
|
||||
qDebug("ERROR: Could not parse RSS file");
|
||||
return -1;
|
||||
}
|
||||
while (!xml.atEnd()) {
|
||||
xml.readNext();
|
||||
if(xml.isStartElement()) {
|
||||
if(xml.name() != "rss") {
|
||||
qDebug("ERROR: this is not a rss file, root tag is <%s>", qPrintable(xml.name().toString()));
|
||||
return -1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Read channels
|
||||
while(!xml.atEnd()) {
|
||||
xml.readNext();
|
||||
|
||||
if(xml.isEndElement())
|
||||
break;
|
||||
|
||||
if(xml.isStartElement()) {
|
||||
//qDebug("xml.name() == %s", qPrintable(xml.name().toString()));
|
||||
if(xml.name() == "channel") {
|
||||
qDebug("in channel");
|
||||
|
||||
// Parse channel content
|
||||
while(!xml.atEnd()) {
|
||||
xml.readNext();
|
||||
|
||||
if(xml.isEndElement() && xml.name() == "channel") {
|
||||
break;
|
||||
}
|
||||
|
||||
if(xml.isStartElement()) {
|
||||
//qDebug("xml.name() == %s", qPrintable(xml.name().toString()));
|
||||
if(xml.name() == "title") {
|
||||
title = xml.readElementText();
|
||||
if(alias == getUrl())
|
||||
rename(title);
|
||||
}
|
||||
else if(xml.name() == "link") {
|
||||
link = xml.readElementText();
|
||||
}
|
||||
else if(xml.name() == "description") {
|
||||
description = xml.readElementText();
|
||||
}
|
||||
else if(xml.name() == "image") {
|
||||
image = xml.attributes().value("url").toString();
|
||||
}
|
||||
else if(xml.name() == "item") {
|
||||
RssItem * item = new RssItem(this, xml);
|
||||
if(item->isValid() && !itemAlreadyExists(item->getId())) {
|
||||
this->insert(item->getId(), item);
|
||||
} else {
|
||||
delete item;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resizeList();
|
||||
|
||||
// RSS Feed Downloader
|
||||
foreach(RssItem* item, values()) {
|
||||
if(item->isRead()) continue;
|
||||
QString torrent_url;
|
||||
if(item->has_attachment())
|
||||
torrent_url = item->getTorrentUrl();
|
||||
else
|
||||
torrent_url = item->getLink();
|
||||
// Check if the item should be automatically downloaded
|
||||
FeedFilter * matching_filter = FeedFilters::getFeedFilters(url).matches(item->getTitle());
|
||||
if(matching_filter != 0) {
|
||||
// Download the torrent
|
||||
BTSession->addConsoleMessage(tr("Automatically downloading %1 torrent from %2 RSS feed...").arg(item->getTitle()).arg(getName()));
|
||||
if(matching_filter->isValid()) {
|
||||
QString save_path = matching_filter->getSavePath();
|
||||
if(save_path.isEmpty())
|
||||
BTSession->downloadUrlAndSkipDialog(torrent_url);
|
||||
else
|
||||
BTSession->downloadUrlAndSkipDialog(torrent_url, save_path);
|
||||
} else {
|
||||
// All torrents are downloaded from this feed
|
||||
BTSession->downloadUrlAndSkipDialog(torrent_url);
|
||||
}
|
||||
// Item was downloaded, consider it as Read
|
||||
item->setRead();
|
||||
// Clean up
|
||||
delete matching_filter;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void RssStream::resizeList() {
|
||||
unsigned int max_articles = Preferences::getRSSMaxArticlesPerFeed();
|
||||
unsigned int nb_articles = this->size();
|
||||
if(nb_articles > max_articles) {
|
||||
QList<RssItem*> listItem = RssManager::sortNewsList(this->values());
|
||||
int excess = nb_articles - max_articles;
|
||||
for(int i=0; i<excess; ++i){
|
||||
RssItem *lastItem = listItem.takeLast();
|
||||
delete this->take(lastItem->getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// existing and opening test after download
|
||||
short RssStream::openRss(){
|
||||
qDebug("openRss() called");
|
||||
QFile fileRss(filePath);
|
||||
if(!fileRss.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||
qDebug("openRss error: open failed, no file or locked, %s", (const char*)filePath.toLocal8Bit());
|
||||
if(QFile::exists(filePath)) {
|
||||
fileRss.remove();
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// start reading the xml
|
||||
short return_lecture = readDoc(&fileRss);
|
||||
fileRss.close();
|
||||
if(QFile::exists(filePath)) {
|
||||
fileRss.remove();
|
||||
}
|
||||
return return_lecture;
|
||||
}
|
||||
|
||||
// read and store the downloaded rss' informations
|
||||
void RssStream::processDownloadedFile(QString file_path) {
|
||||
filePath = file_path;
|
||||
downloadFailure = false;
|
||||
if(openRss() >= 0) {
|
||||
refreshed = true;
|
||||
} else {
|
||||
qDebug("OpenRss: Feed update Failed");
|
||||
}
|
||||
}
|
||||
|
||||
void RssStream::setDownloadFailed(){
|
||||
downloadFailure = true;
|
||||
}
|
||||
|
||||
553
src/rss/rss.h
Normal file
553
src/rss/rss.h
Normal file
@@ -0,0 +1,553 @@
|
||||
/*
|
||||
* 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_H
|
||||
#define RSS_H
|
||||
|
||||
#include <QFile>
|
||||
#include <QList>
|
||||
#include <QTemporaryFile>
|
||||
#include <QXmlStreamReader>
|
||||
#include <QTime>
|
||||
#include <QUrl>
|
||||
#include <QTimer>
|
||||
#include <QImage>
|
||||
#include <QDateTime>
|
||||
#include <QTimer>
|
||||
#include <QUrl>
|
||||
|
||||
#include "misc.h"
|
||||
#include "feeddownloader.h"
|
||||
#include "qbtsession.h"
|
||||
#include "downloadthread.h"
|
||||
|
||||
#if QT_VERSION >= 0x040500
|
||||
#include <QHash>
|
||||
#else
|
||||
#include <QMap>
|
||||
#define QHash QMap
|
||||
#define toHash toMap
|
||||
#endif
|
||||
|
||||
class RssManager;
|
||||
class RssFile; // Folder or Stream
|
||||
class RssFolder;
|
||||
class RssStream;
|
||||
class RssItem;
|
||||
|
||||
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"
|
||||
};
|
||||
|
||||
class RssFile: public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
enum FileType {STREAM, FOLDER};
|
||||
|
||||
RssFile(): QObject() {}
|
||||
virtual ~RssFile() {}
|
||||
|
||||
virtual unsigned int getNbUnRead() const = 0;
|
||||
virtual FileType getType() const = 0;
|
||||
virtual QString getName() const = 0;
|
||||
virtual QString getID() const = 0;
|
||||
virtual void removeAllItems() = 0;
|
||||
virtual void rename(QString new_name) = 0;
|
||||
virtual void markAllAsRead() = 0;
|
||||
virtual RssFolder* getParent() const = 0;
|
||||
virtual void setParent(RssFolder*) = 0;
|
||||
virtual void refresh() = 0;
|
||||
virtual void removeAllSettings() = 0;
|
||||
virtual QList<RssItem*> getNewsList() const = 0;
|
||||
virtual QList<RssItem*> getUnreadNewsList() const = 0;
|
||||
QStringList getPath() const {
|
||||
QStringList path;
|
||||
if(getParent()) {
|
||||
path = ((RssFile*)getParent())->getPath();
|
||||
path.append(getID());
|
||||
}
|
||||
return path;
|
||||
}
|
||||
};
|
||||
|
||||
// Item of a rss stream, single information
|
||||
class RssItem: public QObject {
|
||||
Q_OBJECT
|
||||
private:
|
||||
RssStream* parent;
|
||||
QString id;
|
||||
QString title;
|
||||
QString torrent_url;
|
||||
QString news_link;
|
||||
QString description;
|
||||
QDateTime date;
|
||||
QString author;
|
||||
|
||||
|
||||
bool is_valid;
|
||||
bool read;
|
||||
|
||||
protected:
|
||||
// Ported to Qt4 from KDElibs4
|
||||
QDateTime parseDate(const QString &string) {
|
||||
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();
|
||||
QDateTime result(qdate, QTime(hour, minute, second));
|
||||
if (!result.isValid()
|
||||
|| (dayOfWeek >= 0 && result.date().dayOfWeek() != dayOfWeek+1))
|
||||
return QDateTime::currentDateTime(); // invalid date/time, or weekday doesn't correspond with date
|
||||
if (!offset) {
|
||||
result.setTimeSpec(Qt::UTC);
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
public:
|
||||
// public constructor
|
||||
RssItem(RssStream* parent, QXmlStreamReader& xml): parent(parent), read(false) {
|
||||
is_valid = false;
|
||||
torrent_url = QString::null;
|
||||
news_link = QString::null;
|
||||
title = QString::null;
|
||||
while(!xml.atEnd()) {
|
||||
xml.readNext();
|
||||
|
||||
if(xml.isEndElement() && xml.name() == "item")
|
||||
break;
|
||||
|
||||
if(xml.isStartElement()) {
|
||||
if(xml.name() == "title") {
|
||||
title = xml.readElementText();
|
||||
}
|
||||
else if(xml.name() == "enclosure") {
|
||||
if(xml.attributes().value("type") == "application/x-bittorrent") {
|
||||
torrent_url = xml.attributes().value("url").toString();
|
||||
}
|
||||
}
|
||||
else if(xml.name() == "link") {
|
||||
news_link = xml.readElementText();
|
||||
if(id.isEmpty())
|
||||
id = news_link;
|
||||
}
|
||||
else if(xml.name() == "description") {
|
||||
description = xml.readElementText();
|
||||
}
|
||||
else if(xml.name() == "pubDate") {
|
||||
date = parseDate(xml.readElementText());
|
||||
}
|
||||
else if(xml.name() == "author") {
|
||||
author = xml.readElementText();
|
||||
}
|
||||
else if(xml.name() == "guid") {
|
||||
id = xml.readElementText();
|
||||
}
|
||||
}
|
||||
}
|
||||
if(!id.isEmpty())
|
||||
is_valid = true;
|
||||
}
|
||||
|
||||
RssItem(RssStream* parent, QString _id, QString _title, QString _torrent_url, QString _news_link, QString _description, QDateTime _date, QString _author, bool _read):
|
||||
parent(parent), id(_id), title(_title), torrent_url(_torrent_url), news_link(_news_link), description(_description), date(_date), author(_author), read(_read){
|
||||
if(id.isEmpty())
|
||||
id = news_link;
|
||||
if(!id.isEmpty()) {
|
||||
is_valid = true;
|
||||
} else {
|
||||
std::cerr << "ERROR: an invalid RSS item was saved" << std::endl;
|
||||
is_valid = false;
|
||||
}
|
||||
}
|
||||
|
||||
~RssItem(){
|
||||
}
|
||||
|
||||
bool has_attachment() const {
|
||||
return !torrent_url.isEmpty();
|
||||
}
|
||||
|
||||
QString getId() const { return id; }
|
||||
|
||||
QHash<QString, QVariant> toHash() const {
|
||||
QHash<QString, QVariant> item;
|
||||
item["title"] = title;
|
||||
item["id"] = id;
|
||||
item["torrent_url"] = torrent_url;
|
||||
item["news_link"] = news_link;
|
||||
item["description"] = description;
|
||||
item["date"] = date;
|
||||
item["author"] = author;
|
||||
item["read"] = read;
|
||||
return item;
|
||||
}
|
||||
|
||||
static RssItem* fromHash(RssStream* parent, const QHash<QString, QVariant> &h) {
|
||||
return new RssItem(parent, h.value("id", "").toString(), h["title"].toString(), h["torrent_url"].toString(), h["news_link"].toString(),
|
||||
h["description"].toString(), h["date"].toDateTime(), h["author"].toString(), h["read"].toBool());
|
||||
}
|
||||
|
||||
RssStream* getParent() const {
|
||||
return parent;
|
||||
}
|
||||
|
||||
bool isValid() const {
|
||||
return is_valid;
|
||||
}
|
||||
|
||||
QString getTitle() const{
|
||||
return title;
|
||||
}
|
||||
|
||||
QString getAuthor() const {
|
||||
return author;
|
||||
}
|
||||
|
||||
QString getTorrentUrl() const{
|
||||
return torrent_url;
|
||||
}
|
||||
|
||||
QString getLink() const {
|
||||
return news_link;
|
||||
}
|
||||
|
||||
QString getDescription() const{
|
||||
if(description.isEmpty())
|
||||
return tr("No description available");
|
||||
return description;
|
||||
}
|
||||
|
||||
QDateTime getDate() const {
|
||||
return date;
|
||||
}
|
||||
|
||||
bool isRead() const{
|
||||
return read;
|
||||
}
|
||||
|
||||
void setRead(){
|
||||
read = true;
|
||||
}
|
||||
};
|
||||
|
||||
// Rss stream, loaded form an xml file
|
||||
class RssStream: public RssFile, public QHash<QString, RssItem*> {
|
||||
Q_OBJECT
|
||||
|
||||
private:
|
||||
RssFolder *parent;
|
||||
RssManager *rssmanager;
|
||||
Bittorrent *BTSession;
|
||||
QString title;
|
||||
QString link;
|
||||
QString description;
|
||||
QString image;
|
||||
QString url;
|
||||
QString alias;
|
||||
QString filePath;
|
||||
QString iconPath;
|
||||
bool read;
|
||||
bool refreshed;
|
||||
bool downloadFailure;
|
||||
bool currently_loading;
|
||||
|
||||
public slots:
|
||||
void processDownloadedFile(QString file_path);
|
||||
void setDownloadFailed();
|
||||
|
||||
public:
|
||||
RssStream(RssFolder* parent, RssManager *rssmanager, Bittorrent *BTSession, QString _url);
|
||||
~RssStream();
|
||||
RssFolder* getParent() const { return parent; }
|
||||
void setParent(RssFolder* _parent) { parent = _parent; }
|
||||
FileType getType() const;
|
||||
void refresh();
|
||||
QString getID() const { return url; }
|
||||
void removeAllItems();
|
||||
void removeAllSettings();
|
||||
bool itemAlreadyExists(QString hash);
|
||||
void setLoading(bool val);
|
||||
bool isLoading();
|
||||
QString getTitle() const;
|
||||
void rename(QString _alias);
|
||||
QString getName() const;
|
||||
QString getLink() const;
|
||||
QString getUrl() const;
|
||||
QString getDescription() const;
|
||||
QString getImage() const;
|
||||
QString getFilePath() const;
|
||||
QString getIconPath() const;
|
||||
bool hasCustomIcon() const;
|
||||
void setIconPath(QString path);
|
||||
RssItem* getItem(QString name) const;
|
||||
unsigned int getNbNews() const;
|
||||
void markAllAsRead();
|
||||
unsigned int getNbUnRead() const;
|
||||
QList<RssItem*> getNewsList() const;
|
||||
QList<RssItem*> getUnreadNewsList() const;
|
||||
QString getIconUrl();
|
||||
|
||||
private:
|
||||
short readDoc(QIODevice* device);
|
||||
void resizeList();
|
||||
short openRss();
|
||||
};
|
||||
|
||||
class RssFolder: public RssFile, public QHash<QString, RssFile*> {
|
||||
Q_OBJECT
|
||||
|
||||
private:
|
||||
RssFolder *parent;
|
||||
RssManager *rssmanager;
|
||||
downloadThread *downloader;
|
||||
Bittorrent *BTSession;
|
||||
QString name;
|
||||
|
||||
public:
|
||||
RssFolder(RssFolder *parent, RssManager *rssmanager, Bittorrent *BTSession, QString name);
|
||||
~RssFolder();
|
||||
RssFolder* getParent() const { return parent; }
|
||||
void setParent(RssFolder* _parent) { parent = _parent; }
|
||||
unsigned int getNbUnRead() const;
|
||||
FileType getType() const;
|
||||
RssStream* addStream(QString url);
|
||||
RssFolder* addFolder(QString name);
|
||||
QList<RssStream*> findFeedsWithIcon(QString icon_url) const;
|
||||
unsigned int getNbFeeds() const;
|
||||
QList<RssFile*> getContent() const;
|
||||
QList<RssStream*> getAllFeeds() const;
|
||||
QString getName() const;
|
||||
QString getID() const { return name; }
|
||||
bool hasChild(QString ID) { return this->contains(ID); }
|
||||
QList<RssItem*> getNewsList() const;
|
||||
QList<RssItem*> getUnreadNewsList() const;
|
||||
void removeAllSettings() {
|
||||
foreach(RssFile* child, values()) {
|
||||
child->removeAllSettings();
|
||||
}
|
||||
}
|
||||
|
||||
void removeAllItems() {
|
||||
foreach(RssFile* child, values()) {
|
||||
child->removeAllItems();
|
||||
}
|
||||
qDeleteAll(values());
|
||||
clear();
|
||||
}
|
||||
|
||||
public slots:
|
||||
void refreshAll();
|
||||
void addFile(RssFile * item);
|
||||
void removeFile(QString ID);
|
||||
void refresh();
|
||||
void refreshStream(QString url);
|
||||
void processFinishedDownload(QString url, QString path);
|
||||
void handleDownloadFailure(QString url, QString reason);
|
||||
void rename(QString new_name);
|
||||
void markAllAsRead();
|
||||
};
|
||||
|
||||
class RssManager: public RssFolder{
|
||||
Q_OBJECT
|
||||
|
||||
private:
|
||||
QTimer newsRefresher;
|
||||
unsigned int refreshInterval;
|
||||
Bittorrent *BTSession;
|
||||
|
||||
signals:
|
||||
void feedInfosChanged(QString url, QString aliasOrUrl, unsigned int nbUnread);
|
||||
void feedIconChanged(QString url, QString icon_path);
|
||||
|
||||
public slots:
|
||||
void loadStreamList();
|
||||
void saveStreamList();
|
||||
void forwardFeedInfosChanged(QString url, QString aliasOrUrl, unsigned int nbUnread);
|
||||
void forwardFeedIconChanged(QString url, QString icon_path);
|
||||
void moveFile(RssFile* file, RssFolder* dest_folder);
|
||||
void updateRefreshInterval(unsigned int val);
|
||||
|
||||
public:
|
||||
RssManager(Bittorrent *BTSession);
|
||||
~RssManager();
|
||||
static void insertSortElem(QList<RssItem*> &list, RssItem *item) {
|
||||
int i = 0;
|
||||
while(i < list.size() && item->getDate() < list.at(i)->getDate()) {
|
||||
++i;
|
||||
}
|
||||
list.insert(i, item);
|
||||
}
|
||||
|
||||
static QList<RssItem*> sortNewsList(const QList<RssItem*>& news_list) {
|
||||
QList<RssItem*> new_list;
|
||||
foreach(RssItem *item, news_list) {
|
||||
insertSortElem(new_list, item);
|
||||
}
|
||||
return new_list;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
#endif
|
||||
19
src/rss/rss.pri
Normal file
19
src/rss/rss.pri
Normal file
@@ -0,0 +1,19 @@
|
||||
INCLUDEPATH += $$PWD
|
||||
|
||||
!contains(DEFINES, DISABLE_GUI) {
|
||||
|
||||
HEADERS += $$PWD/rss.h \
|
||||
$$PWD/rss_imp.h \
|
||||
$$PWD/rsssettings.h \
|
||||
$$PWD/feeddownloader.h \
|
||||
$$PWD/feedList.h \
|
||||
|
||||
SOURCES += $$PWD/rss.cpp \
|
||||
$$PWD/rss_imp.cpp \
|
||||
$$PWD/rsssettings.cpp
|
||||
|
||||
FORMS += $$PWD/ui/rss.ui \
|
||||
$$PWD/ui/feeddownloader.ui \
|
||||
$$PWD/ui/rsssettings.ui
|
||||
|
||||
}
|
||||
649
src/rss/rss_imp.cpp
Normal file
649
src/rss/rss_imp.cpp
Normal file
@@ -0,0 +1,649 @@
|
||||
/*
|
||||
* 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 <QInputDialog>
|
||||
#include <QMenu>
|
||||
#include <QStandardItemModel>
|
||||
#include <QMessageBox>
|
||||
#include <QString>
|
||||
#include <QClipboard>
|
||||
#include <QDragMoveEvent>
|
||||
|
||||
#include "rss_imp.h"
|
||||
#include "feeddownloader.h"
|
||||
#include "feedList.h"
|
||||
#include "qbtsession.h"
|
||||
#include "cookiesdlg.h"
|
||||
#include "preferences.h"
|
||||
#include "rsssettings.h"
|
||||
|
||||
enum NewsCols { NEWS_ICON, NEWS_TITLE_COL, NEWS_URL_COL, NEWS_ID };
|
||||
|
||||
// display a right-click menu
|
||||
void RSSImp::displayRSSListMenu(const QPoint& pos){
|
||||
if(!listStreams->indexAt(pos).isValid()) {
|
||||
// No item under the mouse, clear selection
|
||||
listStreams->clearSelection();
|
||||
}
|
||||
QMenu myRSSListMenu(this);
|
||||
QList<QTreeWidgetItem*> selectedItems = listStreams->selectedItems();
|
||||
if(selectedItems.size() > 0) {
|
||||
myRSSListMenu.addAction(actionUpdate);
|
||||
myRSSListMenu.addAction(actionMark_items_read);
|
||||
myRSSListMenu.addSeparator();
|
||||
if(selectedItems.size() == 1) {
|
||||
if(listStreams->getRSSItem(selectedItems.first()) != rssmanager) {
|
||||
myRSSListMenu.addAction(actionRename);
|
||||
myRSSListMenu.addAction(actionDelete);
|
||||
myRSSListMenu.addSeparator();
|
||||
if(listStreams->getItemType(selectedItems.first()) == RssFile::FOLDER) {
|
||||
myRSSListMenu.addAction(actionNew_folder);
|
||||
} else {
|
||||
myRSSListMenu.addAction(actionManage_cookies);
|
||||
}
|
||||
}
|
||||
}
|
||||
myRSSListMenu.addAction(actionNew_subscription);
|
||||
if(listStreams->getItemType(selectedItems.first()) == RssFile::STREAM) {
|
||||
myRSSListMenu.addSeparator();
|
||||
myRSSListMenu.addAction(actionCopy_feed_URL);
|
||||
if(selectedItems.size() == 1) {
|
||||
myRSSListMenu.addSeparator();
|
||||
myRSSListMenu.addAction(actionRSS_feed_downloader);
|
||||
}
|
||||
}
|
||||
}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<QTreeWidgetItem*> selectedItems = listNews->selectedItems();
|
||||
if(selectedItems.size() > 0) {
|
||||
bool has_attachment = false;
|
||||
foreach(QTreeWidgetItem *item, selectedItems) {
|
||||
qDebug("text(3) URL: %s", qPrintable(item->text(NEWS_URL_COL)));
|
||||
qDebug("text(2) TITLE: %s", qPrintable(item->text(NEWS_TITLE_COL)));
|
||||
if(listStreams->getRSSItemFromUrl(item->text(NEWS_URL_COL))->getItem(item->text(NEWS_ID))->has_attachment()) {
|
||||
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(!listStreams->selectedItems().empty());
|
||||
// Get feed hostname
|
||||
QString feed_url = listStreams->getItemID(listStreams->selectedItems().first());
|
||||
QString feed_hostname = QUrl::fromEncoded(feed_url.toLocal8Bit()).host();
|
||||
qDebug("RSS Feed hostname is: %s", qPrintable(feed_hostname));
|
||||
Q_ASSERT(!feed_hostname.isEmpty());
|
||||
bool ok = false;
|
||||
QList<QByteArray> raw_cookies = CookiesDlg::askForCookies(this, Preferences::getHostNameCookies(feed_hostname), &ok);
|
||||
if(ok) {
|
||||
Preferences::setHostNameCookies(feed_hostname, raw_cookies);
|
||||
}
|
||||
}
|
||||
|
||||
void RSSImp::askNewFolder() {
|
||||
QTreeWidgetItem *parent_item = 0;
|
||||
RssFolder *rss_parent;
|
||||
if(listStreams->selectedItems().size() > 0) {
|
||||
parent_item = listStreams->selectedItems().at(0);
|
||||
rss_parent = (RssFolder*)listStreams->getRSSItem(parent_item);
|
||||
Q_ASSERT(rss_parent->getType() == RssFile::FOLDER);
|
||||
} else {
|
||||
rss_parent = rssmanager;
|
||||
}
|
||||
bool ok;
|
||||
QString new_name = QInputDialog::getText(this, tr("Please choose a folder name"), tr("Folder name:"), QLineEdit::Normal, tr("New folder"), &ok);
|
||||
if(ok) {
|
||||
RssFolder* new_folder = rss_parent->addFolder(new_name);
|
||||
QTreeWidgetItem* folder_item;
|
||||
if(parent_item)
|
||||
folder_item = new QTreeWidgetItem(parent_item);
|
||||
else
|
||||
folder_item = new QTreeWidgetItem(listStreams);
|
||||
// Notify TreeWidget
|
||||
listStreams->itemAdded(folder_item, new_folder);
|
||||
// Set Text
|
||||
folder_item->setText(0, new_folder->getName() + QString::fromUtf8(" (0)"));
|
||||
folder_item->setData(0,Qt::DecorationRole, QVariant(QIcon(":/Icons/oxygen/folder.png")));
|
||||
// Expand parent folder to display new folder
|
||||
if(parent_item)
|
||||
parent_item->setExpanded(true);
|
||||
rssmanager->saveStreamList();
|
||||
}
|
||||
}
|
||||
|
||||
void RSSImp::displayOverwriteError(QString filename) {
|
||||
QMessageBox::warning(this, tr("Overwrite attempt"),
|
||||
tr("You cannot overwrite %1 item.", "You cannot overwrite myFolder item.").arg(filename),
|
||||
QMessageBox::Ok);
|
||||
}
|
||||
|
||||
// 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 = listStreams->selectedItems();
|
||||
if(!selected_items.empty()) {
|
||||
parent_item = selected_items.first();
|
||||
// Consider the case where the user clicked on Unread item
|
||||
if(parent_item == listStreams->getUnreadItem()) {
|
||||
parent_item = 0;
|
||||
} else {
|
||||
if(listStreams->getItemType(parent_item) != RssFile::FOLDER)
|
||||
parent_item = parent_item->parent();
|
||||
}
|
||||
}
|
||||
RssFolder *rss_parent;
|
||||
if(parent_item) {
|
||||
rss_parent = (RssFolder*)listStreams->getRSSItem(parent_item);
|
||||
} else {
|
||||
rss_parent = 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 = QInputDialog::getText(this, tr("Please type a rss stream url"), tr("Stream URL:"), QLineEdit::Normal, default_url, &ok);
|
||||
if(ok) {
|
||||
newUrl = newUrl.trimmed();
|
||||
if(!newUrl.isEmpty()){
|
||||
if(listStreams->hasFeed(newUrl)) {
|
||||
QMessageBox::warning(this, tr("qBittorrent"),
|
||||
tr("This rss feed is already in the list."),
|
||||
QMessageBox::Ok);
|
||||
return;
|
||||
}
|
||||
RssStream *stream = rss_parent->addStream(newUrl);
|
||||
// Create TreeWidget item
|
||||
QTreeWidgetItem* item;
|
||||
if(parent_item)
|
||||
item = new QTreeWidgetItem(parent_item);
|
||||
else
|
||||
item = new QTreeWidgetItem(listStreams);
|
||||
// Notify TreeWidget
|
||||
listStreams->itemAdded(item, stream);
|
||||
// Set text
|
||||
item->setText(0, stream->getName() + QString::fromUtf8(" (0)"));
|
||||
item->setData(0,Qt::DecorationRole, QVariant(QIcon(":/Icons/loading.png")));
|
||||
stream->refresh();
|
||||
rssmanager->saveStreamList();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// delete a stream by a button
|
||||
void RSSImp::deleteSelectedItems() {
|
||||
QList<QTreeWidgetItem*> selectedItems = listStreams->selectedItems();
|
||||
if(selectedItems.size() == 0) 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
|
||||
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) {
|
||||
foreach(QTreeWidgetItem *item, selectedItems){
|
||||
if(listStreams->currentFeed() == item){
|
||||
textBrowser->clear();
|
||||
previous_news = 0;
|
||||
listNews->clear();
|
||||
}
|
||||
RssFile *rss_item = listStreams->getRSSItem(item);
|
||||
// Notify TreeWidget
|
||||
listStreams->itemAboutToBeRemoved(item);
|
||||
// Actually delete the item
|
||||
rss_item->getParent()->removeFile(rss_item->getID());
|
||||
delete item;
|
||||
}
|
||||
rssmanager->saveStreamList();
|
||||
// Update Unread items
|
||||
updateItemInfos(listStreams->getUnreadItem());
|
||||
}
|
||||
}
|
||||
|
||||
void RSSImp::loadFoldersOpenState() {
|
||||
QIniSettings settings("qBittorrent", "qBittorrent");
|
||||
settings.beginGroup("Rss");
|
||||
QStringList open_folders = settings.value("open_folders", QStringList()).toStringList();
|
||||
settings.endGroup();
|
||||
foreach(QString var_path, open_folders) {
|
||||
QStringList path = var_path.split("\\");
|
||||
QTreeWidgetItem *parent = 0;
|
||||
foreach(QString name, path) {
|
||||
int nbChildren = 0;
|
||||
if(parent)
|
||||
nbChildren = parent->childCount();
|
||||
else
|
||||
nbChildren = listStreams->topLevelItemCount();
|
||||
for(int i=0; i<nbChildren; ++i) {
|
||||
QTreeWidgetItem* child;
|
||||
if(parent)
|
||||
child = parent->child(i);
|
||||
else
|
||||
child = listStreams->topLevelItem(i);
|
||||
if(listStreams->getRSSItem(child)->getID() == name) {
|
||||
parent = child;
|
||||
parent->setExpanded(true);
|
||||
qDebug("expanding folder %s", qPrintable(name));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RSSImp::saveFoldersOpenState() {
|
||||
QStringList open_folders;
|
||||
QList<QTreeWidgetItem*> items = listStreams->getAllOpenFolders();
|
||||
foreach(QTreeWidgetItem* item, items) {
|
||||
QString path = listStreams->getItemPath(item).join("\\");
|
||||
qDebug("saving open folder: %s", qPrintable(path));
|
||||
open_folders << path;
|
||||
}
|
||||
QIniSettings settings("qBittorrent", "qBittorrent");
|
||||
settings.beginGroup("Rss");
|
||||
settings.setValue("open_folders", open_folders);
|
||||
settings.endGroup();
|
||||
}
|
||||
|
||||
// refresh all streams by a button
|
||||
void RSSImp::on_updateAllButton_clicked() {
|
||||
foreach(QTreeWidgetItem *item, listStreams->getAllFeedItems()) {
|
||||
item->setData(0,Qt::DecorationRole, QVariant(QIcon(":/Icons/loading.png")));
|
||||
}
|
||||
rssmanager->refreshAll();
|
||||
}
|
||||
|
||||
void RSSImp::downloadTorrent() {
|
||||
QList<QTreeWidgetItem *> selected_items = listNews->selectedItems();
|
||||
foreach(const QTreeWidgetItem* item, selected_items) {
|
||||
RssItem* article = listStreams->getRSSItemFromUrl(item->text(NEWS_URL_COL))->getItem(item->text(NEWS_ID));
|
||||
if(article->has_attachment()) {
|
||||
BTSession->downloadFromUrl(article->getTorrentUrl());
|
||||
} else {
|
||||
BTSession->downloadFromUrl(article->getLink());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// open the url of the news in a browser
|
||||
void RSSImp::openNewsUrl() {
|
||||
QList<QTreeWidgetItem *> selected_items = listNews->selectedItems();
|
||||
foreach(const QTreeWidgetItem* item, selected_items) {
|
||||
RssItem* news = listStreams->getRSSItemFromUrl(item->text(NEWS_URL_COL))->getItem(item->text(NEWS_ID));
|
||||
QString link = news->getLink();
|
||||
if(!link.isEmpty())
|
||||
QDesktopServices::openUrl(QUrl(link));
|
||||
}
|
||||
}
|
||||
|
||||
//right-click on stream : give it an alias
|
||||
void RSSImp::renameFiles() {
|
||||
QList<QTreeWidgetItem*> selectedItems = listStreams->selectedItems();
|
||||
Q_ASSERT(selectedItems.size() == 1);
|
||||
QTreeWidgetItem *item = selectedItems.at(0);
|
||||
RssFile *rss_item = listStreams->getRSSItem(item);
|
||||
bool ok;
|
||||
QString newName;
|
||||
do {
|
||||
newName = QInputDialog::getText(this, tr("Please choose a new name for this RSS feed"), tr("New feed name:"), QLineEdit::Normal, listStreams->getRSSItem(item)->getName(), &ok);
|
||||
// Check if name is already taken
|
||||
if(ok) {
|
||||
if(rss_item->getParent()->contains(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 = listStreams->selectedItems();
|
||||
foreach(QTreeWidgetItem* item, selectedItems){
|
||||
RssFile* file = listStreams->getRSSItem(item);
|
||||
// Update icons
|
||||
if(item == listStreams->getUnreadItem()) {
|
||||
foreach(QTreeWidgetItem *feed, listStreams->getAllFeedItems()) {
|
||||
feed->setData(0,Qt::DecorationRole, QVariant(QIcon(":/Icons/loading.png")));
|
||||
}
|
||||
file->refresh();
|
||||
break;
|
||||
} else {
|
||||
if(file->getType() == RssFile::STREAM) {
|
||||
item->setData(0,Qt::DecorationRole, QVariant(QIcon(":/Icons/loading.png")));
|
||||
} else {
|
||||
// Update feeds in the folder
|
||||
foreach(QTreeWidgetItem *feed, listStreams->getAllFeedItems(item)) {
|
||||
feed->setData(0,Qt::DecorationRole, QVariant(QIcon(":/Icons/loading.png")));
|
||||
}
|
||||
}
|
||||
}
|
||||
// Actually refresh
|
||||
file->refresh();
|
||||
}
|
||||
}
|
||||
|
||||
void RSSImp::copySelectedFeedsURL() {
|
||||
QStringList URLs;
|
||||
QList<QTreeWidgetItem*> selectedItems = listStreams->selectedItems();
|
||||
QTreeWidgetItem* item;
|
||||
foreach(item, selectedItems){
|
||||
if(listStreams->getItemType(item) == RssFile::STREAM)
|
||||
URLs << listStreams->getItemID(item);
|
||||
}
|
||||
qApp->clipboard()->setText(URLs.join("\n"));
|
||||
}
|
||||
|
||||
void RSSImp::showFeedDownloader() {
|
||||
QTreeWidgetItem* item = listStreams->selectedItems()[0];
|
||||
RssFile* rss_item = listStreams->getRSSItem(item);
|
||||
if(rss_item->getType() == RssFile::STREAM) {
|
||||
FeedDownloaderDlg* feedDownloader = new FeedDownloaderDlg(this, listStreams->getItemID(item), rss_item->getName(), BTSession);
|
||||
connect(feedDownloader, SIGNAL(filteringEnabled()), this, SLOT(on_updateAllButton_clicked()));
|
||||
}
|
||||
}
|
||||
|
||||
void RSSImp::on_markReadButton_clicked() {
|
||||
QList<QTreeWidgetItem*> selectedItems = listStreams->selectedItems();
|
||||
QTreeWidgetItem* item;
|
||||
foreach(item, selectedItems){
|
||||
RssFile *rss_item = listStreams->getRSSItem(item);
|
||||
rss_item->markAllAsRead();
|
||||
updateItemInfos(item);
|
||||
}
|
||||
if(selectedItems.size())
|
||||
refreshNewsList(listStreams->currentItem());
|
||||
}
|
||||
|
||||
void RSSImp::fillFeedsList(QTreeWidgetItem *parent, RssFolder *rss_parent) {
|
||||
QList<RssFile*> children;
|
||||
if(parent) {
|
||||
children = rss_parent->getContent();
|
||||
} else {
|
||||
children = rssmanager->getContent();
|
||||
}
|
||||
foreach(RssFile* rss_child, children){
|
||||
QTreeWidgetItem* item;
|
||||
if(!parent)
|
||||
item = new QTreeWidgetItem(listStreams);
|
||||
else
|
||||
item = new QTreeWidgetItem(parent);
|
||||
item->setData(0, Qt::DisplayRole, QVariant(rss_child->getName()+ QString::fromUtf8(" (")+QString::number(rss_child->getNbUnRead(), 10)+QString(")")));
|
||||
// Notify TreeWidget of item addition
|
||||
listStreams->itemAdded(item, rss_child);
|
||||
// Set Icon
|
||||
if(rss_child->getType() == RssFile::STREAM) {
|
||||
item->setData(0,Qt::DecorationRole, QVariant(QIcon(QString::fromUtf8(":/Icons/loading.png"))));
|
||||
} else {
|
||||
item->setData(0,Qt::DecorationRole, QVariant(QIcon(QString::fromUtf8(":/Icons/oxygen/folder.png"))));
|
||||
// Recurvive call to load sub folders/files
|
||||
fillFeedsList(item, (RssFolder*)rss_child);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// fills the newsList
|
||||
void RSSImp::refreshNewsList(QTreeWidgetItem* item) {
|
||||
if(!item) {
|
||||
listNews->clear();
|
||||
return;
|
||||
}
|
||||
|
||||
RssFile *rss_item = listStreams->getRSSItem(item);
|
||||
if(!rss_item) return;
|
||||
|
||||
qDebug("Getting the list of news");
|
||||
QList<RssItem*> news;
|
||||
if(rss_item == rssmanager)
|
||||
news = RssManager::sortNewsList(rss_item->getUnreadNewsList());
|
||||
else if(rss_item)
|
||||
news = RssManager::sortNewsList(rss_item->getNewsList());
|
||||
// Clear the list first
|
||||
textBrowser->clear();
|
||||
previous_news = 0;
|
||||
listNews->clear();
|
||||
qDebug("Got the list of news");
|
||||
foreach(RssItem* article, news){
|
||||
QTreeWidgetItem* it = new QTreeWidgetItem(listNews);
|
||||
it->setText(NEWS_TITLE_COL, article->getTitle());
|
||||
it->setText(NEWS_URL_COL, article->getParent()->getUrl());
|
||||
it->setText(NEWS_ID, article->getId());
|
||||
if(article->isRead()){
|
||||
it->setData(NEWS_TITLE_COL, Qt::ForegroundRole, QVariant(QColor("grey")));
|
||||
it->setData(NEWS_ICON, Qt::DecorationRole, QVariant(QIcon(":/Icons/sphere.png")));
|
||||
}else{
|
||||
it->setData(NEWS_TITLE_COL, Qt::ForegroundRole, QVariant(QColor("blue")));
|
||||
it->setData(NEWS_ICON, Qt::DecorationRole, QVariant(QIcon(":/Icons/sphere2.png")));
|
||||
}
|
||||
}
|
||||
qDebug("Added all news to the GUI");
|
||||
qDebug("First news selected");
|
||||
}
|
||||
|
||||
// display a news
|
||||
void RSSImp::refreshTextBrowser() {
|
||||
QList<QTreeWidgetItem*> selection = listNews->selectedItems();
|
||||
if(selection.empty()) return;
|
||||
Q_ASSERT(selection.size() == 1);
|
||||
QTreeWidgetItem *item = selection.first();
|
||||
if(item == previous_news) return;
|
||||
// Stop displaying previous news if necessary
|
||||
if(listStreams->currentFeed() == listStreams->getUnreadItem()) {
|
||||
if(previous_news) {
|
||||
delete listNews->takeTopLevelItem(listNews->indexOfTopLevelItem(previous_news));
|
||||
}
|
||||
previous_news = item;
|
||||
}
|
||||
RssStream *stream = listStreams->getRSSItemFromUrl(item->text(NEWS_URL_COL));
|
||||
RssItem* article = stream->getItem(item->text(NEWS_ID));
|
||||
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->getTitle() + "</div>";
|
||||
if(article->getDate().isValid()) {
|
||||
html += "<div style='background-color: #efefef;'><b>"+tr("Date: ")+"</b>"+article->getDate().toLocalTime().toString(Qt::SystemLocaleLongDate)+"</div>";
|
||||
}
|
||||
if(!article->getAuthor().isEmpty()) {
|
||||
html += "<div style='background-color: #efefef;'><b>"+tr("Author: ")+"</b>"+article->getAuthor()+"</div>";
|
||||
}
|
||||
html += "</div>";
|
||||
html += "<divstyle='margin-left: 5px; margin-right: 5px;'>"+article->getDescription()+"</div>";
|
||||
textBrowser->setHtml(html);
|
||||
article->setRead();
|
||||
item->setData(NEWS_TITLE_COL, Qt::ForegroundRole, QVariant(QColor("grey")));
|
||||
item->setData(0, Qt::DecorationRole, QVariant(QIcon(":/Icons/sphere.png")));
|
||||
// Decrement feed nb unread news
|
||||
updateItemInfos(listStreams->getUnreadItem());
|
||||
updateItemInfos(listStreams->getTreeItemFromUrl(item->text(NEWS_URL_COL)));
|
||||
}
|
||||
|
||||
void RSSImp::saveSlidersPosition() {
|
||||
// Remember sliders positions
|
||||
QIniSettings settings("qBittorrent", "qBittorrent");
|
||||
settings.setValue("rss/splitter_h", splitter_h->saveState());
|
||||
settings.setValue("rss/splitter_v", splitter_v->saveState());
|
||||
qDebug("Splitters position saved");
|
||||
}
|
||||
|
||||
void RSSImp::restoreSlidersPosition() {
|
||||
QIniSettings settings("qBittorrent", "qBittorrent");
|
||||
QByteArray pos_h = settings.value("rss/splitter_h", QByteArray()).toByteArray();
|
||||
if(!pos_h.isNull()) {
|
||||
splitter_h->restoreState(pos_h);
|
||||
}
|
||||
QByteArray pos_v = settings.value("rss/splitter_v", QByteArray()).toByteArray();
|
||||
if(!pos_v.isNull()) {
|
||||
splitter_v->restoreState(pos_v);
|
||||
}
|
||||
}
|
||||
|
||||
void RSSImp::updateItemsInfos(QList<QTreeWidgetItem *> items) {
|
||||
foreach(QTreeWidgetItem* item, items) {
|
||||
updateItemInfos(item);
|
||||
}
|
||||
}
|
||||
|
||||
void RSSImp::updateItemInfos(QTreeWidgetItem *item) {
|
||||
RssFile *rss_item = listStreams->getRSSItem(item);
|
||||
QString name;
|
||||
if(rss_item == rssmanager)
|
||||
name = tr("Unread");
|
||||
else
|
||||
name = rss_item->getName();
|
||||
item->setText(0, name + QString::fromUtf8(" (") + QString::number(rss_item->getNbUnRead(), 10)+ QString(")"));
|
||||
// If item has a parent, update it too
|
||||
if(item->parent())
|
||||
updateItemInfos(item->parent());
|
||||
}
|
||||
|
||||
void RSSImp::updateFeedIcon(QString url, QString icon_path){
|
||||
QTreeWidgetItem *item = listStreams->getTreeItemFromUrl(url);
|
||||
item->setData(0,Qt::DecorationRole, QVariant(QIcon(icon_path)));
|
||||
}
|
||||
|
||||
void RSSImp::updateFeedInfos(QString url, QString aliasOrUrl, unsigned int nbUnread){
|
||||
QTreeWidgetItem *item = listStreams->getTreeItemFromUrl(url);
|
||||
RssStream *stream = (RssStream*)listStreams->getRSSItem(item);
|
||||
item->setText(0, aliasOrUrl + QString::fromUtf8(" (") + QString::number(nbUnread, 10)+ QString(")"));
|
||||
if(!stream->isLoading())
|
||||
item->setData(0,Qt::DecorationRole, QVariant(QIcon(stream->getIconPath())));
|
||||
// Update parent
|
||||
if(item->parent())
|
||||
updateItemInfos(item->parent());
|
||||
// Update Unread item
|
||||
updateItemInfos(listStreams->getUnreadItem());
|
||||
// If the feed is selected, update the displayed news
|
||||
if(listStreams->currentItem() == item ){
|
||||
refreshNewsList(item);
|
||||
} else {
|
||||
// Update unread items
|
||||
if(listStreams->currentItem() == listStreams->getUnreadItem()) {
|
||||
refreshNewsList(listStreams->getUnreadItem());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RSSImp::updateRefreshInterval(unsigned int val) {
|
||||
rssmanager->updateRefreshInterval(val);
|
||||
}
|
||||
|
||||
RSSImp::RSSImp(Bittorrent *BTSession) : QWidget(), BTSession(BTSession){
|
||||
setupUi(this);
|
||||
|
||||
rssmanager = new RssManager(BTSession);
|
||||
|
||||
listStreams = new FeedList(splitter_h, rssmanager);
|
||||
splitter_h->insertWidget(0, listStreams);
|
||||
listNews->hideColumn(NEWS_URL_COL);
|
||||
listNews->setColumnWidth(0, 16);
|
||||
|
||||
fillFeedsList();
|
||||
refreshNewsList(listStreams->currentItem());
|
||||
|
||||
loadFoldersOpenState();
|
||||
connect(rssmanager, SIGNAL(feedInfosChanged(QString, QString, unsigned int)), this, SLOT(updateFeedInfos(QString, QString, unsigned int)));
|
||||
connect(rssmanager, SIGNAL(feedIconChanged(QString, QString)), this, SLOT(updateFeedIcon(QString, QString)));
|
||||
|
||||
connect(listStreams, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(displayRSSListMenu(const QPoint&)));
|
||||
connect(listNews, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(displayItemsListMenu(const QPoint&)));
|
||||
|
||||
// Feeds list actions
|
||||
connect(actionDelete, SIGNAL(triggered()), this, SLOT(deleteSelectedItems()));
|
||||
connect(actionRename, SIGNAL(triggered()), this, SLOT(renameFiles()));
|
||||
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(on_updateAllButton_clicked()));
|
||||
connect(actionCopy_feed_URL, SIGNAL(triggered()), this, SLOT(copySelectedFeedsURL()));
|
||||
connect(actionRSS_feed_downloader, SIGNAL(triggered()), this, SLOT(showFeedDownloader()));
|
||||
connect(actionMark_items_read, SIGNAL(triggered()), this, SLOT(on_markReadButton_clicked()));
|
||||
// News list actions
|
||||
connect(actionOpen_news_URL, SIGNAL(triggered()), this, SLOT(openNewsUrl()));
|
||||
connect(actionDownload_torrent, SIGNAL(triggered()), this, SLOT(downloadTorrent()));
|
||||
|
||||
connect(listStreams, SIGNAL(currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)), this, SLOT(refreshNewsList(QTreeWidgetItem*)));
|
||||
connect(listStreams, SIGNAL(foldersAltered(QList<QTreeWidgetItem*>)), this, SLOT(updateItemsInfos(QList<QTreeWidgetItem*>)));
|
||||
connect(listStreams, SIGNAL(overwriteAttempt(QString)), this, SLOT(displayOverwriteError(QString)));
|
||||
|
||||
connect(listNews, SIGNAL(itemSelectionChanged()), this, SLOT(refreshTextBrowser()));
|
||||
connect(listNews, SIGNAL(itemDoubleClicked(QTreeWidgetItem *, int)), this, SLOT(downloadTorrent()));
|
||||
|
||||
// Refresh all feeds
|
||||
rssmanager->refreshAll();
|
||||
// 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 listStreams;
|
||||
delete rssmanager;
|
||||
qDebug("RSSImp deleted");
|
||||
}
|
||||
|
||||
|
||||
void RSSImp::on_settingsButton_clicked() {
|
||||
RssSettings rssSettingsDlg(this);
|
||||
if(rssSettingsDlg.exec())
|
||||
updateRefreshInterval(Preferences::getRefreshInterval());
|
||||
}
|
||||
96
src/rss/rss_imp.h
Normal file
96
src/rss/rss_imp.h
Normal file
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* 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 "ui_rss.h"
|
||||
#include "rss.h"
|
||||
|
||||
class Bittorrent;
|
||||
class FeedList;
|
||||
class QTreeWidgetItem;
|
||||
|
||||
class RSSImp : public QWidget, public Ui::RSS{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
RSSImp(Bittorrent *BTSession);
|
||||
~RSSImp();
|
||||
|
||||
public slots:
|
||||
void deleteSelectedItems();
|
||||
void updateRefreshInterval(unsigned int val);
|
||||
|
||||
protected slots:
|
||||
void on_newFeedButton_clicked();
|
||||
void on_updateAllButton_clicked();
|
||||
void on_markReadButton_clicked();
|
||||
void displayRSSListMenu(const QPoint&);
|
||||
void displayItemsListMenu(const QPoint&);
|
||||
void renameFiles();
|
||||
void refreshSelectedItems();
|
||||
void copySelectedFeedsURL();
|
||||
void refreshNewsList(QTreeWidgetItem* item);
|
||||
void refreshTextBrowser();
|
||||
void updateFeedIcon(QString url, QString icon_path);
|
||||
void updateFeedInfos(QString url, QString aliasOrUrl, unsigned int nbUnread);
|
||||
void updateItemsInfos(QList<QTreeWidgetItem*> items);
|
||||
void updateItemInfos(QTreeWidgetItem *item);
|
||||
void openNewsUrl();
|
||||
void downloadTorrent();
|
||||
void fillFeedsList(QTreeWidgetItem *parent=0, RssFolder *rss_parent=0);
|
||||
void saveSlidersPosition();
|
||||
void restoreSlidersPosition();
|
||||
void showFeedDownloader();
|
||||
void askNewFolder();
|
||||
void saveFoldersOpenState();
|
||||
void loadFoldersOpenState();
|
||||
void displayOverwriteError(QString filename);
|
||||
void on_actionManage_cookies_triggered();
|
||||
void on_settingsButton_clicked();
|
||||
|
||||
private:
|
||||
RssManager *rssmanager;
|
||||
Bittorrent *BTSession;
|
||||
FeedList *listStreams;
|
||||
QTreeWidgetItem* previous_news;
|
||||
|
||||
};
|
||||
|
||||
#if QT_VERSION < 0x040500
|
||||
#undef QHash
|
||||
#undef toHash
|
||||
#endif
|
||||
|
||||
#endif
|
||||
54
src/rss/rsssettings.cpp
Normal file
54
src/rss/rsssettings.cpp
Normal file
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* 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 "rsssettings.h"
|
||||
#include "ui_rsssettings.h"
|
||||
#include "preferences.h"
|
||||
|
||||
RssSettings::RssSettings(QWidget *parent) :
|
||||
QDialog(parent),
|
||||
ui(new Ui::RssSettings)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
// Load settings
|
||||
ui->spinRSSRefresh->setValue(Preferences::getRSSRefreshInterval());
|
||||
ui->spinRSSMaxArticlesPerFeed->setValue(Preferences::getRSSMaxArticlesPerFeed());
|
||||
}
|
||||
|
||||
RssSettings::~RssSettings()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void RssSettings::on_buttonBox_accepted() {
|
||||
// Save settings
|
||||
Preferences::setRSSRefreshInterval(ui->spinRSSRefresh->value());
|
||||
Preferences::setRSSMaxArticlesPerFeed(ui->spinRSSMaxArticlesPerFeed->value());
|
||||
}
|
||||
56
src/rss/rsssettings.h
Normal file
56
src/rss/rsssettings.h
Normal file
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* 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 RSSSETTINGS_H
|
||||
#define RSSSETTINGS_H
|
||||
|
||||
#include <QDialog>
|
||||
|
||||
namespace Ui {
|
||||
class RssSettings;
|
||||
}
|
||||
|
||||
class RssSettings : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit RssSettings(QWidget *parent = 0);
|
||||
~RssSettings();
|
||||
|
||||
protected slots:
|
||||
void on_buttonBox_accepted();
|
||||
|
||||
private:
|
||||
Ui::RssSettings *ui;
|
||||
|
||||
};
|
||||
|
||||
#endif // RSSSETTINGS_H
|
||||
549
src/rss/ui/feeddownloader.ui
Normal file
549
src/rss/ui/feeddownloader.ui
Normal file
@@ -0,0 +1,549 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>FeedDownloader</class>
|
||||
<widget class="QDialog" name="FeedDownloader">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>737</width>
|
||||
<height>415</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>RSS Feed downloader</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_7">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_4">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>16</pointsize>
|
||||
<weight>75</weight>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>RSS feed:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="rssfeed_lbl">
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>16</pointsize>
|
||||
<weight>75</weight>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Feed name</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<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>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="enableDl_cb">
|
||||
<property name="text">
|
||||
<string>Automatically download torrents from this feed</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="filtersBox">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<weight>75</weight>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>Download filters</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_6">
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="font">
|
||||
<font>
|
||||
<weight>75</weight>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Filters:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QListWidget" name="filtersList">
|
||||
<property name="contextMenuPolicy">
|
||||
<enum>Qt::CustomContextMenu</enum>
|
||||
</property>
|
||||
<property name="sortingEnabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_5">
|
||||
<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>
|
||||
<item>
|
||||
<widget class="QPushButton" name="del_button">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../icons.qrc">
|
||||
<normaloff>:/Icons/oxygen/list-remove.png</normaloff>:/Icons/oxygen/list-remove.png</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_4">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Fixed</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>10</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="add_button">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../icons.qrc">
|
||||
<normaloff>:/Icons/oxygen/list-add.png</normaloff>:/Icons/oxygen/list-add.png</iconset>
|
||||
</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>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="filterSettingsBox">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<weight>75</weight>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="autoFillBackground">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>Filter settings</string>
|
||||
</property>
|
||||
<property name="flat">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_9">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="font">
|
||||
<font>
|
||||
<weight>50</weight>
|
||||
<bold>false</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Matches:</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="font">
|
||||
<font>
|
||||
<weight>50</weight>
|
||||
<bold>false</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Does not match:</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="font">
|
||||
<font>
|
||||
<weight>50</weight>
|
||||
<bold>false</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Destination folder:</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_4">
|
||||
<item>
|
||||
<widget class="QLineEdit" name="match_line">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="notmatch_line"/>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QLineEdit" name="savepath_line">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>300</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="browse_button">
|
||||
<property name="font">
|
||||
<font>
|
||||
<weight>50</weight>
|
||||
<bold>false</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="testerBox">
|
||||
<property name="title">
|
||||
<string>Filter testing</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_8">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_9">
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_5">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_6">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>28</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<weight>50</weight>
|
||||
<bold>false</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Torrent title:</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_7">
|
||||
<property name="font">
|
||||
<font>
|
||||
<weight>50</weight>
|
||||
<bold>false</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Result:</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_6">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_8">
|
||||
<item>
|
||||
<widget class="QLineEdit" name="test_line"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="testButton">
|
||||
<property name="font">
|
||||
<font>
|
||||
<weight>50</weight>
|
||||
<bold>false</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Test</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_7">
|
||||
<item>
|
||||
<widget class="QLabel" name="test_res_lbl">
|
||||
<property name="font">
|
||||
<font>
|
||||
<weight>50</weight>
|
||||
<bold>false</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_6">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>9</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_10">
|
||||
<item>
|
||||
<widget class="QPushButton" name="importButton">
|
||||
<property name="text">
|
||||
<string>Import...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="exportButton">
|
||||
<property name="text">
|
||||
<string>Export...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</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>
|
||||
<action name="actionRename_filter">
|
||||
<property name="icon">
|
||||
<iconset resource="../icons.qrc">
|
||||
<normaloff>:/Icons/oxygen/edit_clear.png</normaloff>:/Icons/oxygen/edit_clear.png</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Rename filter</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Rename filter</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionRemove_filter">
|
||||
<property name="icon">
|
||||
<iconset resource="../icons.qrc">
|
||||
<normaloff>:/Icons/oxygen/list-remove.png</normaloff>:/Icons/oxygen/list-remove.png</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Remove filter</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Remove filter</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionAdd_filter">
|
||||
<property name="icon">
|
||||
<iconset resource="../icons.qrc">
|
||||
<normaloff>:/Icons/oxygen/list-add.png</normaloff>:/Icons/oxygen/list-add.png</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Add filter</string>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<resources>
|
||||
<include location="../icons.qrc"/>
|
||||
</resources>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>FeedDownloader</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>FeedDownloader</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>
|
||||
310
src/rss/ui/rss.ui
Normal file
310
src/rss/ui/rss.ui
Normal file
@@ -0,0 +1,310 @@
|
||||
<?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="text">
|
||||
<string>New subscription</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../icons.qrc">
|
||||
<normaloff>:/Icons/oxygen/subscribe.png</normaloff>:/Icons/oxygen/subscribe.png</iconset>
|
||||
</property>
|
||||
<property name="toolButtonStyle">
|
||||
<enum>Qt::ToolButtonTextBesideIcon</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="markReadButton">
|
||||
<property name="text">
|
||||
<string>Mark items read</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../icons.qrc">
|
||||
<normaloff>:/Icons/oxygen/button_ok.png</normaloff>:/Icons/oxygen/button_ok.png</iconset>
|
||||
</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="icon">
|
||||
<iconset resource="../icons.qrc">
|
||||
<normaloff>:/Icons/oxygen/view-refresh.png</normaloff>:/Icons/oxygen/view-refresh.png</iconset>
|
||||
</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="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><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
|
||||
<html><head><meta name="qrichtext" content="1" /><style type="text/css">
|
||||
p, li { white-space: pre-wrap; }
|
||||
</style></head><body style=" font-family:'Sans'; font-size:10pt; font-weight:400; font-style:normal;">
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Torrents:</span> <span style=" font-style:italic;">(double-click to download)</span></p></body></html></string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSplitter" name="splitter_v">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<widget class="QTreeWidget" name="listNews">
|
||||
<property name="contextMenuPolicy">
|
||||
<enum>Qt::CustomContextMenu</enum>
|
||||
</property>
|
||||
<property name="selectionMode">
|
||||
<enum>QAbstractItemView::SingleSelection</enum>
|
||||
</property>
|
||||
<property name="selectionBehavior">
|
||||
<enum>QAbstractItemView::SelectItems</enum>
|
||||
</property>
|
||||
<property name="rootIsDecorated">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="itemsExpandable">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="allColumnsShowFocus">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="expandsOnDoubleClick">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<attribute name="headerVisible">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
<attribute name="headerStretchLastSection">
|
||||
<bool>true</bool>
|
||||
</attribute>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string notr="true">Bullet</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Article title</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Feed URL</string>
|
||||
</property>
|
||||
</column>
|
||||
</widget>
|
||||
<widget class="QTextBrowser" name="textBrowser"/>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
<action name="actionDelete">
|
||||
<property name="icon">
|
||||
<iconset resource="../icons.qrc">
|
||||
<normaloff>:/Icons/oxygen/remove.png</normaloff>:/Icons/oxygen/remove.png</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Delete</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Delete</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionRename">
|
||||
<property name="icon">
|
||||
<iconset resource="../icons.qrc">
|
||||
<normaloff>:/Icons/oxygen/edit_clear.png</normaloff>:/Icons/oxygen/edit_clear.png</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Rename...</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Rename</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionUpdate">
|
||||
<property name="icon">
|
||||
<iconset resource="../icons.qrc">
|
||||
<normaloff>:/Icons/oxygen/view-refresh.png</normaloff>:/Icons/oxygen/view-refresh.png</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Update</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Update</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionNew_subscription">
|
||||
<property name="icon">
|
||||
<iconset resource="../icons.qrc">
|
||||
<normaloff>:/Icons/oxygen/subscribe16.png</normaloff>:/Icons/oxygen/subscribe16.png</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>New subscription...</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionUpdate_all_feeds">
|
||||
<property name="icon">
|
||||
<iconset resource="../icons.qrc">
|
||||
<normaloff>:/Icons/oxygen/view-refresh.png</normaloff>:/Icons/oxygen/view-refresh.png</iconset>
|
||||
</property>
|
||||
<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="icon">
|
||||
<iconset resource="../icons.qrc">
|
||||
<normaloff>:/Icons/oxygen/button_ok.png</normaloff>:/Icons/oxygen/button_ok.png</iconset>
|
||||
</property>
|
||||
<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="icon">
|
||||
<iconset resource="../icons.qrc">
|
||||
<normaloff>:/Icons/oxygen/download.png</normaloff>:/Icons/oxygen/download.png</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Download torrent</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionOpen_news_URL">
|
||||
<property name="icon">
|
||||
<iconset resource="../icons.qrc">
|
||||
<normaloff>:/Icons/url.png</normaloff>:/Icons/url.png</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Open news URL</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionCopy_feed_URL">
|
||||
<property name="icon">
|
||||
<iconset resource="../icons.qrc">
|
||||
<normaloff>:/Icons/oxygen/edit-copy.png</normaloff>:/Icons/oxygen/edit-copy.png</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Copy feed URL</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionRSS_feed_downloader">
|
||||
<property name="icon">
|
||||
<iconset resource="../icons.qrc">
|
||||
<normaloff>:/Icons/oxygen/download.png</normaloff>:/Icons/oxygen/download.png</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>RSS feed downloader...</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionNew_folder">
|
||||
<property name="icon">
|
||||
<iconset resource="../icons.qrc">
|
||||
<normaloff>:/Icons/oxygen/folder-new.png</normaloff>:/Icons/oxygen/folder-new.png</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>New folder...</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionManage_cookies">
|
||||
<property name="icon">
|
||||
<iconset resource="../icons.qrc">
|
||||
<normaloff>:/Icons/oxygen/cookies.png</normaloff>:/Icons/oxygen/cookies.png</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Manage cookies...</string>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<resources>
|
||||
<include location="../icons.qrc"/>
|
||||
</resources>
|
||||
<connections/>
|
||||
</ui>
|
||||
158
src/rss/ui/rsssettings.ui
Normal file
158
src/rss/ui/rsssettings.ui
Normal file
@@ -0,0 +1,158 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>RssSettings</class>
|
||||
<widget class="QDialog" name="RssSettings">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>468</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/rss32.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>RssSettings</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>RssSettings</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>
|
||||
Reference in New Issue
Block a user