diff options
Diffstat (limited to 'plugins/rssfeed')
37 files changed, 8474 insertions, 0 deletions
diff --git a/plugins/rssfeed/Makefile.am b/plugins/rssfeed/Makefile.am new file mode 100644 index 0000000..c295db5 --- /dev/null +++ b/plugins/rssfeed/Makefile.am @@ -0,0 +1,30 @@ +INCLUDES = -I$(srcdir)/../../libktorrent $(all_includes) +METASOURCES = AUTO +kde_module_LTLIBRARIES = ktrssfeedplugin.la + + +# LD flags for the plugin +# -module says: this is a module, i.e. something you're going to dlopen +# so e.g. it has no version number like a normal shared lib would have. +ktrssfeedplugin_la_LDFLAGS = -module $(KDE_PLUGIN) $(all_libraries) + +# rc file containing the GUI for the plugin +# pluginsdir = $(kde_datadir)/ktrssfeedplugin +# plugins_DATA = ktrssfeedpluginui.rc + +# Install the desktop file needed to detect the plugin + +rcdir = $(kde_datadir)/ktorrent + +kde_kcfg_DATA = ktrssfeedplugin.kcfg +kde_services_DATA = ktrssfeedplugin.desktop + +noinst_HEADERS = rssfeedplugin.h rssfeedmanager.h rssfeed.h rssfilter.h \ + rssarticle.h rsslinkdownloader.h +ktrssfeedplugin_la_SOURCES = rssfeedplugin.cpp rssfeedmanager.cpp \ + rssfeedwidget.ui rssfeed.cpp rssfilter.cpp rssarticle.cpp rsslinkdownloader.cpp +ktrssfeedplugin_la_LIBADD = $(LIB_KIO) $(LIB_QT) rss/librsslocal.la \ + ../../libktorrent/libktorrent.la $(LIB_KDECORE) $(LIB_KDEUI) $(LIB_KHTML) $(LIB_KPARTS) + +SUBDIRS = rss +KDE_CXXFLAGS = $(USE_EXCEPTIONS) $(USE_RTTI) diff --git a/plugins/rssfeed/ktrssfeedplugin.desktop b/plugins/rssfeed/ktrssfeedplugin.desktop new file mode 100644 index 0000000..a26c08b --- /dev/null +++ b/plugins/rssfeed/ktrssfeedplugin.desktop @@ -0,0 +1,22 @@ +[Desktop Entry] +Name=RssFeedPlugin +Name[bg]=Приставка за RssFeed +Name[de]=RSS-Nachrichtenquellen-Modul +Name[el]=Πρόσθετο ροών Rss +Name[et]=RSS-kanali plugin +Name[it]=Plugin Fonti notizie +Name[nb]=RSS-modul +Name[nds]=RSS-Moduul +Name[pl]=Wtyczka kanały RSS +Name[pt_BR]=Plugin de RssFeed +Name[sk]=RssFeed Plugin +Name[sr]=Прикључак Rss довода +Name[sr@Latn]=Priključak Rss dovoda +Name[sv]=RSS-kanalinsticksprogram +Name[tr]=RSS Besleme Eklentisi +Name[uk]=Втулок подач RSS +Name[xx]=xxRssFeedPluginxx +Name[zh_CN]=RSS 种子插件 +ServiceTypes=KTorrent/Plugin +Type=Service +X-KDE-Library=ktrssfeedplugin diff --git a/plugins/rssfeed/ktrssfeedplugin.kcfg b/plugins/rssfeed/ktrssfeedplugin.kcfg new file mode 100644 index 0000000..86499cb --- /dev/null +++ b/plugins/rssfeed/ktrssfeedplugin.kcfg @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<kcfg xmlns="http://www.kde.org/standards/kcfg/1.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://www.kde.org/standards/kcfg/1.0 + http://www.kde.org/standards/kcfg/1.0/kcfg.xsd" > + + <kcfgfile name="ktrssfeedpluginrc"/> + <group name="general"> + <entry name="default" type="Int"> + <label>default</label> + <default>0</default> + </entry> + </group> +</kcfg> diff --git a/plugins/rssfeed/rss/COPYING b/plugins/rssfeed/rss/COPYING new file mode 100644 index 0000000..cca2a5c --- /dev/null +++ b/plugins/rssfeed/rss/COPYING @@ -0,0 +1,20 @@ +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/plugins/rssfeed/rss/Makefile.am b/plugins/rssfeed/rss/Makefile.am new file mode 100644 index 0000000..75b570a --- /dev/null +++ b/plugins/rssfeed/rss/Makefile.am @@ -0,0 +1,20 @@ +INCLUDES = \ + -I$(top_srcdir)/src \ + $(all_includes) + +noinst_LTLIBRARIES = \ + librsslocal.la + +noinst_HEADERS = article.h document.h global.h image.h textinput.h \ + loader.h librss.h + +librsslocal_la_SOURCES = article.cpp document.cpp image.cpp textinput.cpp \ + tools_p.cpp loader.cpp + +librsslocal_la_METASOURCES = AUTO + +check_PROGRAMS = testlibrss +testlibrss_SOURCES = testlibrss.cpp +testlibrss_LDFLAGS = $(all_libraries) +testlibrss_LDADD = librsslocal.la $(LIB_KIO) +KDE_CXXFLAGS = $(USE_EXCEPTIONS) $(USE_RTTI) diff --git a/plugins/rssfeed/rss/README b/plugins/rssfeed/rss/README new file mode 100644 index 0000000..77d9450 --- /dev/null +++ b/plugins/rssfeed/rss/README @@ -0,0 +1,6 @@ +This is NOT original librss by Frerich Raabe, though based on it. + +This version is supposed to be called libsyndication but is not renamed to relieve packaging burden a bit +(honestly, we just didn't yet get to it). + +Please DO NOT report any bugs about it to Frerich, since he most probably did not introduce the found bugs. diff --git a/plugins/rssfeed/rss/article.cpp b/plugins/rssfeed/rss/article.cpp new file mode 100644 index 0000000..571490e --- /dev/null +++ b/plugins/rssfeed/rss/article.cpp @@ -0,0 +1,270 @@ +/* + * article.cpp + * + * Copyright (c) 2001, 2002, 2003, 2004 Frerich Raabe <raabe@kde.org> + * + * 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. For licensing and distribution details, check the + * accompanying file 'COPYING'. + */ +#include "article.h" +#include "tools_p.h" + +#include <kdebug.h> +#include <krfcdate.h> +#include <kurl.h> +#include <kurllabel.h> +#include <kmdcodec.h> + +#include <qdatetime.h> +#include <qdom.h> + +using namespace RSS; +namespace RSS +{ + KMD5 md5Machine; +} + +struct Article::Private : public Shared +{ + QString title; + KURL link; + QString description; + QDateTime pubDate; + QString guid; + bool guidIsPermaLink; + MetaInfoMap meta; + KURL commentsLink; + int numComments; +}; + +Article::Article() : d(new Private) +{ +} + +Article::Article(const Article &other) : d(0) +{ + *this = other; +} + +Article::Article(const QDomNode &node, Format format) : d(new Private) +{ + QString elemText; + + d->numComments=0; + + if (!(elemText = extractNode(node, QString::fromLatin1("title"))).isNull()) + d->title = elemText; + + + QDomNode n; + bool foundTorrentEnclosure = false; + for (n = node.firstChild(); !n.isNull(); n = n.nextSibling()) { + const QDomElement e = n.toElement(); + if ( (e.tagName()==QString::fromLatin1("enclosure") ) ) + { + QString enclosureAttr = e.attribute(QString::fromLatin1("type")); + if (!enclosureAttr.isNull() ) + { + if (enclosureAttr == "application/x-bittorrent") + { + enclosureAttr = e.attribute(QString::fromLatin1("url")); + if (!enclosureAttr.isNull() ) + { + d->link=enclosureAttr; + foundTorrentEnclosure = true; + break; + } + } + } + } + } + + if (!foundTorrentEnclosure) + { + if (format==AtomFeed) + { + QDomNode n; + for (n = node.firstChild(); !n.isNull(); n = n.nextSibling()) { + const QDomElement e = n.toElement(); + if ( (e.tagName()==QString::fromLatin1("link")) && + (e.attribute(QString::fromLatin1("rel"))==QString::fromLatin1("alternate"))) + { + d->link=n.toElement().attribute(QString::fromLatin1("href")); + break; + } + } + } + else + { + if (!(elemText = extractNode(node, QString::fromLatin1("link"))).isNull()) + d->link = elemText; + } + } + + + // prefer content/content:encoded over summary/description for feeds that provide it + QString tagName=(format==AtomFeed)? QString::fromLatin1("content"): QString::fromLatin1("content:encoded"); + + if (!(elemText = extractNode(node, tagName, false)).isNull()) + d->description = elemText; + + if (d->description.isEmpty()) + { + if (!(elemText = extractNode(node, QString::fromLatin1("body"), false)).isNull()) + d->description = elemText; + + if (d->description.isEmpty()) // 3rd try: see http://www.intertwingly.net/blog/1299.html + { + if (!(elemText = extractNode(node, QString::fromLatin1((format==AtomFeed)? "summary" : "description"), false)).isNull()) + d->description = elemText; + } + } + + if (!(elemText = extractNode(node, QString::fromLatin1((format==AtomFeed)? "created": "pubDate"))).isNull()) + { + time_t _time; + if (format==AtomFeed) + _time = parseISO8601Date(elemText); + else + _time = KRFCDate::parseDate(elemText); + + // 0 means invalid, not epoch (it returns epoch+1 when it parsed epoch, see the KRFCDate::parseDate() docs) + if (_time != 0) + d->pubDate.setTime_t(_time); + } + if (!(elemText = extractNode(node, QString::fromLatin1("dc:date"))).isNull()) + { + time_t _time = parseISO8601Date(elemText); + + // 0 means invalid, not epoch (it returns epoch+1 when it parsed epoch, see the KRFCDate::parseDate() docs) + if (_time != 0) + d->pubDate.setTime_t(_time); + } + + //no luck so far - so let's set it to the current time + if (!d->pubDate.isValid()) + { + d->pubDate = QDateTime::currentDateTime(); + } + + + if (!(elemText = extractNode(node, QString::fromLatin1("wfw:comment"))).isNull()) { + d->commentsLink = elemText; + } + + if (!(elemText = extractNode(node, QString::fromLatin1("slash:comments"))).isNull()) { + d->numComments = elemText.toInt(); + } + + tagName=(format==AtomFeed)? QString::fromLatin1("id"): QString::fromLatin1("guid"); + n = node.namedItem(tagName); + if (!n.isNull()) { + d->guidIsPermaLink = (format==AtomFeed)? false : true; + if (n.toElement().attribute(QString::fromLatin1("isPermaLink"), "true") == "false") d->guidIsPermaLink = false; + + if (!(elemText = extractNode(node, tagName)).isNull()) + d->guid = elemText; + } + + if(d->guid.isEmpty()) { + d->guidIsPermaLink = false; + + md5Machine.reset(); + QDomNode n(node); + md5Machine.update(d->title.utf8()); + md5Machine.update(d->description.utf8()); + d->guid = QString(md5Machine.hexDigest().data()); + d->meta[QString::fromLatin1("guidIsHash")] = QString::fromLatin1("true"); + } + + for (QDomNode i = node.firstChild(); !i.isNull(); i = i.nextSibling()) + { + if (i.isElement() && i.toElement().tagName() == QString::fromLatin1("metaInfo:meta")) + { + QString type = i.toElement().attribute(QString::fromLatin1("type")); + d->meta[type] = i.toElement().text(); + } + } +} + +Article::~Article() +{ + if (d->deref()) + delete d; +} + +QString Article::title() const +{ + return d->title; +} + +const KURL &Article::link() const +{ + return d->link; +} + +QString Article::description() const +{ + return d->description; +} + +QString Article::guid() const +{ + return d->guid; +} + +bool Article::guidIsPermaLink() const +{ + return d->guidIsPermaLink; +} + +const QDateTime &Article::pubDate() const +{ + return d->pubDate; +} + +const KURL &Article::commentsLink() const +{ + return d->commentsLink; +} + +int Article::comments() const +{ + return d->numComments; +} + + +QString Article::meta(const QString &key) const +{ + return d->meta[key]; +} + +KURLLabel *Article::widget(QWidget *parent, const char *name) const +{ + KURLLabel *label = new KURLLabel(d->link.url(), d->title, parent, name); + label->setUseTips(true); + if (!d->description.isNull()) + label->setTipText(d->description); + + return label; +} + +Article &Article::operator=(const Article &other) +{ + if (this != &other) { + other.d->ref(); + if (d && d->deref()) + delete d; + d = other.d; + } + return *this; +} + +bool Article::operator==(const Article &other) const +{ + return d->guid == other.guid(); +} + +// vim:noet:ts=4 diff --git a/plugins/rssfeed/rss/article.h b/plugins/rssfeed/rss/article.h new file mode 100644 index 0000000..bab7a38 --- /dev/null +++ b/plugins/rssfeed/rss/article.h @@ -0,0 +1,159 @@ +/* + * article.h + * + * Copyright (c) 2001, 2002, 2003 Frerich Raabe <raabe@kde.org> + * + * 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. For licensing and distribution details, check the + * accompanying file 'COPYING'. + */ +#ifndef LIBRSS_ARTICLE_H +#define LIBRSS_ARTICLE_H + +#include <qmap.h> + +#include "global.h" + +class QDateTime; +class QDomNode; +template <class> class QValueList; +class QString; +class QWidget; +class KURL; +class KURLLabel; + +namespace RSS +{ + /** + * Represents an article as stored in a RSS file. You don't have to + * instantiate one of these yourself, the common way to access instances + * is via Document::articles(). + * @see Document::articles() + */ + class Article + { + public: + /** + * A list of articles. + */ + typedef QValueList<Article> List; + + /** + * Default constructor. + */ + Article(); + + /** + * Copy constructor. + * @param other The Article object to copy. + */ + Article(const Article &other); + + /** + * Constructs an Article from a piece of RSS markup. + * @param node A QDomNode which references the DOM leaf to be used + * for constructing the Article. + */ + Article(const QDomNode &node, Format format); + + /** + * Assignment operator. + * @param other The Article object to clone. + * @return A reference to the cloned Article object. + */ + Article &operator=(const Article &other); + + /** + * Compares two articles. Two articles are treated to be identical + * if all their properties (title, link, description etc.) are + * equal. + * @param other The article this article should be compared with. + * @return Whether the two articles are equal. + */ + bool operator==(const Article &other) const; + + /** + * Convenience method. Simply calls !operator==(). + * @param other The article this article should be compared with. + * @return Whether the two articles are unequal. + */ + bool operator!=(const Article &other) const { return !operator==(other); } + + /** + * Destructor. + */ + virtual ~Article(); + + /** + * RSS 0.90 and upwards + * @return The headline of this article, or QString::null if + * no headline was available. + */ + QString title() const; + + /** + * RSS 0.90 and upwards + * @return A URL referencing the complete text for this article, + * or an empty KURL if no link was available. + * Note that the RSS 0.91 Specification dictates that URLs not + * starting with "http://" or "ftp://" are considered invalid. + */ + const KURL &link() const; + + /** + * RSS 0.91 and upwards + * @return A story synopsis, or QString::null if no description + * was available. + */ + QString description() const; + + /** + * RSS 2.0 and upwards + * @return An article GUID (globally unique identifier). + */ + QString guid() const; + + /** + * RSS 2.0 and upwards + * @return If this article GUID is permalink. Has no meaning when guid() is QString::null. + */ + bool guidIsPermaLink() const; + + /** + * RSS 2.0 and upwards + * @return The date when the article was published. + */ + const QDateTime &pubDate() const; + + const KURL &commentsLink() const; + int comments() const; + + QString meta(const QString &key) const; + + /** + * @param parent The parent widget for the KURLLabel. + * @param name A name for the widget which will be used internally. + * @return a widget (a KURLLabel in this case) for the Article. + * This makes building a user-interface which contains the + * information in this Article object more convenient. + * The returned KURLLabel's caption will be the title(), clicking + * on it will emit the URL link(), and it has a QToolTip attached + * to it which displays the description() (in case it has one, + * if there is no description, the URL which the label links to + * will be used). + * Note that you have to delete the KURLLabel object returned by + * this method yourself. + */ + KURLLabel *widget(QWidget *parent = 0, const char *name = 0) const; + + typedef QMap<QString, QString> MetaInfoMap; + + private: + struct Private; + Private *d; + }; +} + +#endif // LIBRSS_ARTICLE_H +// vim: noet:ts=4 diff --git a/plugins/rssfeed/rss/document.cpp b/plugins/rssfeed/rss/document.cpp new file mode 100644 index 0000000..be353e4 --- /dev/null +++ b/plugins/rssfeed/rss/document.cpp @@ -0,0 +1,619 @@ +/* + * document.cpp + * + * Copyright (c) 2001, 2002, 2003 Frerich Raabe <raabe@kde.org> + * + * 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. For licensing and distribution details, check the + * accompanying file 'COPYING'. + * + */ +#include "document.h" +#include "article.h" +#include "image.h" +#include "textinput.h" +#include "tools_p.h" + +#include <krfcdate.h> +#include <kurl.h> + +#include <qdatetime.h> +#include <qdom.h> +#include <qptrlist.h> + +using namespace RSS; + +struct Document::Private : public Shared +{ + Private() : version(v0_90), image(NULL), textInput(NULL), language(en) + { + format=UnknownFormat; + valid=false; + ttl=-1; + } + + ~Private() + { + delete textInput; + delete image; + } + + Version version; + QString title; + QString description; + KURL link; + Image *image; + TextInput *textInput; + Article::List articles; + Language language; + Format format; + QString copyright; + QDateTime pubDate; + QDateTime lastBuildDate; + QString rating; + KURL docs; + int ttl; + QString managingEditor; + QString webMaster; + HourList skipHours; + DayList skipDays; + bool valid; +}; + +Document::Document() : d(new Private) +{ +} + +Document::Document(const Document &other) : d(0) +{ + *this = other; +} + +Document::Document(const QDomDocument &doc) : d(new Private) +{ + QString elemText; + QDomNode rootNode = doc.documentElement(); + + // Determine the version of the present RSS markup. + QString attr; + + // we should probably check that it ISN'T feed or rss, rather than check if it is xhtml + if (rootNode.toElement().tagName()==QString::fromLatin1("html")) + d->valid=false; + else + d->valid=true; + + attr = rootNode.toElement().attribute(QString::fromLatin1("version"), QString::null); + if (!attr.isNull()) { + if (rootNode.toElement().tagName()=="feed") + { + d->format=AtomFeed; + if (attr == QString::fromLatin1("0.3")) + d->version = vAtom_0_3; + else if (attr == QString::fromLatin1("0.2")) /* smt -> review */ + d->version = vAtom_0_2; + else if (attr == QString::fromLatin1("0.1")) /* smt -> review */ + d->version = vAtom_0_1; + } + else + { + d->format=RSSFeed; + if (attr == QString::fromLatin1("0.91")) + d->version = v0_91; + else if (attr == QString::fromLatin1("0.92")) + d->version = v0_92; + else if (attr == QString::fromLatin1("0.93")) + d->version = v0_93; + else if (attr == QString::fromLatin1("0.94")) + d->version = v0_94; + else if (attr.startsWith("2.0") || attr == QString::fromLatin1("2")) // http://www.breuls.org/rss puts 2.00 in version (BR #0000016) + d->version = v2_0; + } + } + + if (d->format==UnknownFormat) + { + attr = rootNode.toElement().attribute(QString::fromLatin1("xmlns"), QString::null); + if (!attr.isNull()) { + /* + * Hardcoding these URLs is actually a bad idea, since the DTD doesn't + * dictate a specific namespace. Still, most RSS files seem to use + * these two, so I'll go for them now. If it turns out that many + * mirrors of this RSS namespace are in use, I'll probably have to + * distinguish the RSS versions by analyzing the relationship between + * the nodes. + */ + if (attr == QString::fromLatin1("http://my.netscape.com/rdf/simple/0.9/")) { + d->format=RSSFeed; + d->version = v0_90; + } + else if (attr == QString::fromLatin1("http://purl.org/rss/1.0/")) { + d->format=RSSFeed; + d->version = v1_0; + } + } + } + + QDomNode channelNode; + + if (d->format == AtomFeed) + channelNode=rootNode; + else + channelNode=rootNode.namedItem(QString::fromLatin1("channel")); + + if (!(elemText = extractNode(channelNode, QString::fromLatin1("title"))).isNull()) + d->title = elemText; + if (!(elemText = extractNode(channelNode, QString::fromLatin1("description"))).isNull()) + d->description = elemText; + if (!(elemText = extractNode(channelNode, QString::fromLatin1("link"))).isNull()) + d->link = elemText; + + + /* This is ugly but necessary since RSS 0.90 and 1.0 have a different parent + * node for <image>, <textinput> and <item> than RSS 0.91-0.94 and RSS 2.0. + */ + QDomNode parentNode; + if (d->version == v0_90 || d->version == v1_0 || d->format == AtomFeed) + parentNode = rootNode; + else + { + // following is a HACK for broken 0.91 feeds like xanga.com's + if (!rootNode.namedItem(QString::fromLatin1("item")).isNull()) + parentNode = rootNode; + else + parentNode = channelNode; + } + + // image and textinput aren't supported by Atom.. handle in case feed provides + QDomNode n = parentNode.namedItem(QString::fromLatin1("image")); + if (!n.isNull()) + d->image = new Image(n); + + n = parentNode.namedItem(QString::fromLatin1("textinput")); + if (!n.isNull()) + d->textInput = new TextInput(n); + + // Our (hopefully faster) version of elementsByTagName() + QString tagName; + if (d->format == AtomFeed) + tagName=QString::fromLatin1("entry"); + else + tagName=QString::fromLatin1("item"); + + for (n = parentNode.firstChild(); !n.isNull(); n = n.nextSibling()) { + const QDomElement e = n.toElement(); + if (e.tagName() == tagName) + d->articles.append(Article(e, d->format)); + } + + if (!(elemText = extractNode(channelNode, QString::fromLatin1("copyright"))).isNull()) + d->copyright = elemText; + + if (d->format == AtomFeed) + elemText = rootNode.toElement().attribute(QString::fromLatin1("xml:lang"), QString::null); + else + elemText = extractNode(channelNode, QString::fromLatin1("language")); + + if (!elemText.isNull()){ + if (elemText == QString::fromLatin1("af")) + d->language = af; + else if (elemText == QString::fromLatin1("sq")) + d->language = sq; + else if (elemText == QString::fromLatin1("eu")) + d->language = eu; + else if (elemText == QString::fromLatin1("be")) + d->language = be; + else if (elemText == QString::fromLatin1("bg")) + d->language = bg; + else if (elemText == QString::fromLatin1("ca")) + d->language = ca; + else if (elemText == QString::fromLatin1("zh-cn")) + d->language = zh_cn; + else if (elemText == QString::fromLatin1("zh-tw")) + d->language = zh_tw; + else if (elemText == QString::fromLatin1("hr")) + d->language = hr; + else if (elemText == QString::fromLatin1("cs")) + d->language = cs; + else if (elemText == QString::fromLatin1("da")) + d->language = da; + else if (elemText == QString::fromLatin1("nl")) + d->language = nl; + else if (elemText == QString::fromLatin1("nl-be")) + d->language = nl_be; + else if (elemText == QString::fromLatin1("nl-nl")) + d->language = nl_nl; + else if (elemText == QString::fromLatin1("en")) + d->language = en; + else if (elemText == QString::fromLatin1("en-au")) + d->language = en_au; + else if (elemText == QString::fromLatin1("en-bz")) + d->language = en_bz; + else if (elemText == QString::fromLatin1("en-ca")) + d->language = en_ca; + else if (elemText == QString::fromLatin1("en-ie")) + d->language = en_ie; + else if (elemText == QString::fromLatin1("en-jm")) + d->language = en_jm; + else if (elemText == QString::fromLatin1("en-nz")) + d->language = en_nz; + else if (elemText == QString::fromLatin1("en-ph")) + d->language = en_ph; + else if (elemText == QString::fromLatin1("en-za")) + d->language = en_za; + else if (elemText == QString::fromLatin1("en-tt")) + d->language = en_tt; + else if (elemText == QString::fromLatin1("en-gb")) + d->language = en_gb; + else if (elemText == QString::fromLatin1("en-us")) + d->language = en_us; + else if (elemText == QString::fromLatin1("en-zw")) + d->language = en_zw; + else if (elemText == QString::fromLatin1("fo")) + d->language = fo; + else if (elemText == QString::fromLatin1("fi")) + d->language = fi; + else if (elemText == QString::fromLatin1("fr")) + d->language = fr; + else if (elemText == QString::fromLatin1("fr-be")) + d->language = fr_be; + else if (elemText == QString::fromLatin1("fr-ca")) + d->language = fr_ca; + else if (elemText == QString::fromLatin1("fr-fr")) + d->language = fr_fr; + else if (elemText == QString::fromLatin1("fr-lu")) + d->language = fr_lu; + else if (elemText == QString::fromLatin1("fr-mc")) + d->language = fr_mc; + else if (elemText == QString::fromLatin1("fr-ch")) + d->language = fr_ch; + else if (elemText == QString::fromLatin1("gl")) + d->language = gl; + else if (elemText == QString::fromLatin1("gd")) + d->language = gd; + else if (elemText == QString::fromLatin1("de")) + d->language = de; + else if (elemText == QString::fromLatin1("de-at")) + d->language = de_at; + else if (elemText == QString::fromLatin1("de-de")) + d->language = de_de; + else if (elemText == QString::fromLatin1("de-li")) + d->language = de_li; + else if (elemText == QString::fromLatin1("de-lu")) + d->language = de_lu; + else if (elemText == QString::fromLatin1("de-ch")) + d->language = de_ch; + else if (elemText == QString::fromLatin1("el")) + d->language = el; + else if (elemText == QString::fromLatin1("hu")) + d->language = hu; + else if (elemText == QString::fromLatin1("is")) + d->language = is; + else if (elemText == QString::fromLatin1("id")) + d->language = id; + else if (elemText == QString::fromLatin1("ga")) + d->language = ga; + else if (elemText == QString::fromLatin1("it")) + d->language = it; + else if (elemText == QString::fromLatin1("it-it")) + d->language = it_it; + else if (elemText == QString::fromLatin1("it-ch")) + d->language = it_ch; + else if (elemText == QString::fromLatin1("ja")) + d->language = ja; + else if (elemText == QString::fromLatin1("ko")) + d->language = ko; + else if (elemText == QString::fromLatin1("mk")) + d->language = mk; + else if (elemText == QString::fromLatin1("no")) + d->language = no; + else if (elemText == QString::fromLatin1("pl")) + d->language = pl; + else if (elemText == QString::fromLatin1("pt")) + d->language = pt; + else if (elemText == QString::fromLatin1("pt-br")) + d->language = pt_br; + else if (elemText == QString::fromLatin1("pt-pt")) + d->language = pt_pt; + else if (elemText == QString::fromLatin1("ro")) + d->language = ro; + else if (elemText == QString::fromLatin1("ro-mo")) + d->language = ro_mo; + else if (elemText == QString::fromLatin1("ro-ro")) + d->language = ro_ro; + else if (elemText == QString::fromLatin1("ru")) + d->language = ru; + else if (elemText == QString::fromLatin1("ru-mo")) + d->language = ru_mo; + else if (elemText == QString::fromLatin1("ru-ru")) + d->language = ru_ru; + else if (elemText == QString::fromLatin1("sr")) + d->language = sr; + else if (elemText == QString::fromLatin1("sk")) + d->language = sk; + else if (elemText == QString::fromLatin1("sl")) + d->language = sl; + else if (elemText == QString::fromLatin1("es")) + d->language = es; + else if (elemText == QString::fromLatin1("es-ar")) + d->language = es_ar; + else if (elemText == QString::fromLatin1("es-bo")) + d->language = es_bo; + else if (elemText == QString::fromLatin1("es-cl")) + d->language = es_cl; + else if (elemText == QString::fromLatin1("es-co")) + d->language = es_co; + else if (elemText == QString::fromLatin1("es-cr")) + d->language = es_cr; + else if (elemText == QString::fromLatin1("es-do")) + d->language = es_do; + else if (elemText == QString::fromLatin1("es-ec")) + d->language = es_ec; + else if (elemText == QString::fromLatin1("es-sv")) + d->language = es_sv; + else if (elemText == QString::fromLatin1("es-gt")) + d->language = es_gt; + else if (elemText == QString::fromLatin1("es-hn")) + d->language = es_hn; + else if (elemText == QString::fromLatin1("es-mx")) + d->language = es_mx; + else if (elemText == QString::fromLatin1("es-ni")) + d->language = es_ni; + else if (elemText == QString::fromLatin1("es-pa")) + d->language = es_pa; + else if (elemText == QString::fromLatin1("es-py")) + d->language = es_py; + else if (elemText == QString::fromLatin1("es-pe")) + d->language = es_pe; + else if (elemText == QString::fromLatin1("es-pr")) + d->language = es_pr; + else if (elemText == QString::fromLatin1("es-es")) + d->language = es_es; + else if (elemText == QString::fromLatin1("es-uy")) + d->language = es_uy; + else if (elemText == QString::fromLatin1("es-ve")) + d->language = es_ve; + else if (elemText == QString::fromLatin1("sv")) + d->language = sv; + else if (elemText == QString::fromLatin1("sv-fi")) + d->language = sv_fi; + else if (elemText == QString::fromLatin1("sv-se")) + d->language = sv_se; + else if (elemText == QString::fromLatin1("tr")) + d->language = tr; + else if (elemText == QString::fromLatin1("uk")) + d->language = uk; + else + d->language = UndefinedLanguage; + } + + if (d->format == AtomFeed) + tagName=QString::fromLatin1("issued"); // atom doesn't specify this for feeds + // but some broken feeds do this + else + tagName=QString::fromLatin1("pubDate"); + + if (!(elemText = extractNode(channelNode, tagName)).isNull()) { + time_t _time; + + if (d->format == AtomFeed) + _time=parseISO8601Date(elemText); + else + _time=KRFCDate::parseDate(elemText); + /* \bug This isn't really the right way since it will set the date to + * Jan 1 1970, 1:00:00 if the passed date was invalid; this means that + * we cannot distinguish between that date, and invalid values. :-/ + */ + d->pubDate.setTime_t(_time); + } + + if (!(elemText = extractNode(channelNode, QString::fromLatin1("dc:date"))).isNull()) { + time_t _time = parseISO8601Date(elemText); + /* \bug This isn't really the right way since it will set the date to + * Jan 1 1970, 1:00:00 if the passed date was invalid; this means that + * we cannot distinguish between that date, and invalid values. :-/ + */ + d->pubDate.setTime_t(_time); + } + + if (d->format == AtomFeed) + tagName=QString::fromLatin1("modified"); + else + tagName=QString::fromLatin1("lastBuildDate"); + if (!(elemText = extractNode(channelNode, tagName)).isNull()) { + time_t _time; + if (d->format == AtomFeed) + _time = parseISO8601Date(elemText); + else + _time = KRFCDate::parseDate(elemText); + d->lastBuildDate.setTime_t(_time); + } + + if (!(elemText = extractNode(channelNode, QString::fromLatin1("rating"))).isNull()) + d->rating = elemText; + if (!(elemText = extractNode(channelNode, QString::fromLatin1("docs"))).isNull()) + d->docs = elemText; + if (!(elemText = extractNode(channelNode, QString::fromLatin1((d->format == AtomFeed) ? "author" : "managingEditor"))).isNull()) + d->managingEditor = elemText; + if (!(elemText = extractNode(channelNode, QString::fromLatin1("webMaster"))).isNull()) + d->webMaster = elemText; + + if (!(elemText = extractNode(channelNode, QString::fromLatin1("ttl"))).isNull()) + d->ttl = elemText.toUInt(); + + n = channelNode.namedItem(QString::fromLatin1("skipHours")); + if (!n.isNull()) + for (QDomElement e = n.firstChild().toElement(); !e.isNull(); e = e.nextSibling().toElement()) + if (e.tagName() == QString::fromLatin1("hour")) + d->skipHours.append(e.text().toUInt()); + + n = channelNode.namedItem(QString::fromLatin1("skipDays")); + if (!n.isNull()) { + Day day; + QString elemText; + for (QDomElement e = n.firstChild().toElement(); !e.isNull(); e = e.nextSibling().toElement()) + if (e.tagName() == QString::fromLatin1("day")) { + elemText = e.text().lower(); + if (elemText == QString::fromLatin1("monday")) + day = Monday; + else if (elemText == QString::fromLatin1("tuesday")) + day = Tuesday; + else if (elemText == QString::fromLatin1("wednesday")) + day = Wednesday; + else if (elemText == QString::fromLatin1("thursday")) + day = Thursday; + else if (elemText == QString::fromLatin1("friday")) + day = Friday; + else if (elemText == QString::fromLatin1("saturday")) + day = Saturday; + else if (elemText == QString::fromLatin1("sunday")) + day = Sunday; + else + day = UndefinedDay; + if (day != UndefinedDay) + d->skipDays.append(day); + } + } +} + +Document::~Document() +{ + if (d->deref()) + delete d; +} + +bool Document::isValid() const +{ + return d->valid; +} + +Version Document::version() const +{ + return d->version; +} + +QString Document::verbVersion() const +{ + switch (d->version) { + case v0_90: return QString::fromLatin1("0.90"); + case v0_91: return QString::fromLatin1("0.91"); + case v0_92: return QString::fromLatin1("0.92"); + case v0_93: return QString::fromLatin1("0.93"); + case v0_94: return QString::fromLatin1("0.94"); + case v1_0: return QString::fromLatin1("1.0"); + case v2_0: return QString::fromLatin1("2.0"); + case vAtom_0_3: return QString::fromLatin1("0.3"); + case vAtom_0_2: return QString::fromLatin1("0.2"); + case vAtom_0_1: return QString::fromLatin1("0.1"); + } + return QString::null; +} + +QString Document::title() const +{ + return d->title; +} + +QString Document::description() const +{ + return d->description; +} + +const KURL &Document::link() const +{ + return d->link; +} + +Image *Document::image() +{ + return d->image; +} + +const Image *Document::image() const +{ + return d->image; +} + +TextInput *Document::textInput() +{ + return d->textInput; +} + +const TextInput *Document::textInput() const +{ + return d->textInput; +} + +const Article::List &Document::articles() const +{ + return d->articles; +} + +Language Document::language() const +{ + return d->language; +} + +QString Document::copyright() const +{ + return d->copyright; +} + +const QDateTime &Document::pubDate() const +{ + return d->pubDate; +} + +const QDateTime &Document::lastBuildDate() const +{ + return d->lastBuildDate; +} + +QString Document::rating() const +{ + return d->rating; +} + +const KURL &Document::docs() const +{ + return d->docs; +} + +QString Document::managingEditor() const +{ + return d->managingEditor; +} + +QString Document::webMaster() const +{ + return d->webMaster; +} + +const HourList &Document::skipHours() const +{ + return d->skipHours; +} + +const DayList &Document::skipDays() const +{ + return d->skipDays; +} + +int Document::ttl() const +{ + return d->ttl; +} + +Document &Document::operator=(const Document &other) +{ + if (this != &other) { + other.d->ref(); + if (d && d->deref()) + delete d; + d = other.d; + } + return *this; +} + +// vim:noet:ts=4 diff --git a/plugins/rssfeed/rss/document.h b/plugins/rssfeed/rss/document.h new file mode 100644 index 0000000..1ead634 --- /dev/null +++ b/plugins/rssfeed/rss/document.h @@ -0,0 +1,237 @@ +/* + * document.h + * + * Copyright (c) 2001, 2002, 2003 Frerich Raabe <raabe@kde.org> + * + * 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. For licensing and distribution details, check the + * accompanying file 'COPYING'. + */ +#ifndef LIBRSS_DOCUMENT_H +#define LIBRSS_DOCUMENT_H + +#include "article.h" +#include "global.h" + +class QDateTime; +class QDomDocument; + +namespace RSS +{ + class Image; + class TextInput; + + /** + * Represents a RSS document and provides all the features and properties + * as stored in it. You usually don't need to instantiate this one yourself + * but rather use Loader::loadFrom() to produce a Document object. + * @see Loader::loadForm() + */ + class Document + { + public: + /** + * Default constructor. + */ + Document(); + + /** + * Copy constructor. + * @param other The Document object to copy. + */ + Document(const Document &other); + + /** + * Constructs a Document from a piece of XML markup. + */ + Document(const QDomDocument &doc); + + /** + * Assignment operator. + * @param other The Document object to clone. + * @return A reference to the cloned Document object. + */ + Document &operator=(const Document &other); + + /** + * Destructor. + */ + ~Document(); + + /** + * @return If document is valid + */ + bool isValid() const; + + /** + * @return The version of this document (one of the values of the + * enum RSS::Version). This value can be used to determine which + * features this RSS document provides. + * @see verbVersion() + */ + Version version() const; + + /** + * Convenience method. Differs from version() only in how the result + * is returned. + * @return A QString representing the verbose version of the + * document. + * @see version() + */ + QString verbVersion() const; + + /** + * RSS 0.90 and upwards + * @return The title of the RSS document, or QString::null if no + * title was available. This is often the name of the news source + * from which the RSS document was retrieved. + */ + QString title() const; + + /** + * RSS 0.90 and upwards + * @return The description of the RSS document, or QString::null + * if no description was available. This is usually a short slogan + * or description of the news source from which the RSS document + * was retrieved. + */ + QString description() const; + + /** + * RSS 0.90 and upwards + * @return A link pointing to some website, or an empty KURL if no + * link was available. This URL mostly points to the homepage of + * the news site from which the RSS document was retrieved. + * Note that the RSS 0.91 Specification dictates that URLs not + * starting with "http://" or "ftp://" are considered invalid. + */ + const KURL &link() const; + + /** + * RSS 0.90 and upwards + * @return An Image object as stored in the RSS document, or a + * null pointer if there was no image available. + * @see Image + */ + Image *image(); + + /** + * A version of the method above, with stricter const-ness. + */ + const Image *image() const; + + /** + * RSS 0.90 and upwards + * @return A TextInput object as stored in the RSS document, or a + * null pointer if there was no text input available. + * @see TextInput + */ + TextInput *textInput(); + + /** + * A version of the method above, with stricter const-ness. + */ + const TextInput *textInput() const; + + /** + * RSS 0.90 and upwards + * @return A list of Article objects as stored in the RSS document, + * or a null pointer if there were no articles available. Every RSS + * DTD requires that there is at least one article defined, so a + * null pointer indicates an invalid RSS file! + * @see Article + */ + const Article::List &articles() const; + + /** + * RSS 0.91 and upwards + * @return The language used in the RSS document (for the article + * headlines etc.). This was originally introduced to assist with + * determining the correct page encoding but acts as a solely + * optional information in this library since you don't have to care + * about the encoding as Unicode is used in the whole library. + * @see RSS::Language + */ + Language language() const; + + /** + * RSS 0.91 and upwards + * @return A copyright of the information contained in the RSS + * document, or QString::null if no copyright is available. + */ + QString copyright() const; + + /** + * RSS 0.91 and upwards + * @return The date when the RSS document was published. + */ + const QDateTime &pubDate() const; + + /** + * RSS 0.91 and upwards. + * @return The last time the channel was modified. + */ + const QDateTime &lastBuildDate() const; + + /** + * RSS 0.91 and upwards + * @return A <a href="http://www.w3.org/PICS/#Specs">PICS</a> + * rating for this page. + */ + QString rating() const; + + /** + * RSS 0.91 and upwards + * @return This tag should contain either a URL that references a + * description of the channel, or a pointer to the documentation + * for the format used in the RSS file. + */ + const KURL &docs() const; + + /** + * RSS 0.91 and upwards + * @return The email address of the managing editor of the site, + * the person to contact for editorial inquiries. The suggested + * format for email addresses in RSS documents is + * bull@mancuso.com (Bull Mancuso). + * @see webMaster() + */ + QString managingEditor() const; + + /** + * RSS 0.91 and upwards + * @return The email address of the webmaster for the site, the + * person to contact if there are technical problems with the + * channel, or QString::null if this information isn't available. + * @see managingEditor() + */ + QString webMaster() const; + + /** + * RSS 0.91 and upwards + * @return A list of hours indicating the hours in the day, GMT, + * when the channel is unlikely to be updated. If this item is + * omitted, the channel is assumed to be updated hourly. Each + * hour should be an integer value between 0 and 23. + * @see skipDays() + */ + const HourList &skipHours() const; + + /** + * RSS 0.91 and upwards + * @return A list of <day>s of the week, in English, indicating + * the days of the week when the RSS document will not be updated. + * @see skipHours(), DayList, Day + */ + const DayList &skipDays() const; + int ttl() const; + + private: + struct Private; + Private *d; + }; +} + +#endif // LIBRSS_DOCUMENT_H +// vim: noet:ts=4 diff --git a/plugins/rssfeed/rss/global.h b/plugins/rssfeed/rss/global.h new file mode 100644 index 0000000..3a954e6 --- /dev/null +++ b/plugins/rssfeed/rss/global.h @@ -0,0 +1,145 @@ +/* + * global.h + * + * Copyright (c) 2001, 2002, 2003 Frerich Raabe <raabe@kde.org> + * + * 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. For licensing and distribution details, check the + * accompanying file 'COPYING'. + */ +#ifndef LIBRSS_GLOBAL_H +#define LIBRSS_GLOBAL_H + +template <class> +class QValueList; + +namespace RSS +{ + /** + * Versions currently supported by this library. This enumeration is + * subject to be extended in the future and used by Document::version() to + * provide an interface to the client using which he can find out what + * version the loaded RSS file actually is. + */ + enum Version { + v0_90, /// RSS v0.90 + v0_91, /// RSS v0.91 + v0_92, /// RSS v0.92 + v0_93, /// RSS v0.93 + v0_94, /// RSS v0.94 + v1_0, /// RSS v1.0 + v2_0, /// RSS v2.0 + vAtom_0_1, /// Atom v0.1 + vAtom_0_2, /// Atom v0.2 + vAtom_0_3 /// Atom v0.3 + }; + + /** + * Possible status values returned by the signal + * Loader::loadingComplete(). + */ + enum Status { + Success, /** + * Nothing went wrong so far, but you still have to check + * what values are returned by the classes since it's not + * guaranteed that the retrieved RSS markup actually + * complies to one of the RSS DTDs.*/ + Aborted, /** the loader was aborted manually + */ + RetrieveError, /** + * Something went wrong while retrieving the RSS data, + * this could be a problem while resolving the host name + * (assuming the source file loader was used) or a + * problem with the program to be executed (in case the + * program loader was used.).*/ + ParseError /** + * The overall format of the RSS markup wasn't XML + * conform. This only indicates that the data wasn't + * valid (for example, if the data returned by a + * DataRetriever isn't well-formed XML). + * @see DataRetriever */ + }; + + /** + * Possible languages which are returned by Document::language(). + */ + enum Language { + UndefinedLanguage, /** Unknown / undefined language */ + + af, /** Afrikaans */ sq, /** Albanian */ + eu, /** Basque */ be, /** Belarusian */ + bg, /** Bulgarian */ ca, /** Catalan */ + zh_cn, /** Chinese (Simplified) */ zh_tw, /** Chinese (Traditional */ + hr, /** Croatian */ cs, /** Czech */ + da, /** Danish */ nl, /** Dutch */ + nl_be, /** Dutch (Belgium) */ nl_nl, /** Dutch (Netherlands) */ + en, /** English */ en_au, /** English (Australia) */ + en_bz, /** English (Belize) */ en_ca, /** English (Canada) */ + en_ie, /** English (Ireland) */ en_jm, /** English (Jamaica) */ + en_nz, /** English (New Zealand) */ en_ph, /** English (Phillipines) */ + en_za, /** English (South Africa) */ en_tt, /** English (Trinidad) */ + en_gb, /** English (Great Britain) */en_us, /** English (United States) */ + en_zw, /** English (Zimbabwe) */ fo, /** Faeroese */ + fi, /** Finnish */ fr, /** French */ + fr_be, /** French (Belgium) */ fr_ca, /** French (Canada) */ + fr_fr, /** French (France) */ fr_lu, /** French (Luxembourg) */ + fr_mc, /** French (Monaco) */ fr_ch, /** French (Switzerland) */ + gl, /** Galician */ gd, /** Gaelic */ + de, /** German */ de_at, /** German (Austria) */ + de_de, /** German (Germany) */ de_li, /** German (Liechtenstein) */ + de_lu, /** German (Luxembourg) */ de_ch, /** German (Switzerland) */ + el, /** Greek */ hu, /** Hungarian */ + is, /** Icelandic */ id, /** Indonesian */ + ga, /** Irish */ it, /** Italian */ + it_it, /** Italian (Italy) */ it_ch, /** Italian (Switzerland) */ + ja, /** Japanese */ ko, /** Korean */ + mk, /** Macedonian */ no, /** Norwegian */ + pl, /** Polish */ pt, /** Portuguese */ + pt_br, /** Portuguese (Brazil) */ pt_pt, /** Portuguese (Portugal) */ + ro, /** Romanian */ ro_mo, /** Romanian (Moldova) */ + ro_ro, /** Romanian (Romania) */ ru, /** Russian */ + ru_mo, /** Russian (Moldova) */ ru_ru, /** Russian (Russia) */ + sr, /** Serbian */ sk, /** Slovak */ + sl, /** Slovenian */ es, /** Spanish */ + es_ar, /** Spanish (Argentina) */ es_bo, /** Spanish (Bolivia) */ + es_cl, /** Spanish (Chile) */ es_co, /** Spanish (Colombia) */ + es_cr, /** Spanish (Costa Rica) */ es_do, /** Spanish (Dominican Rep.) */ + es_ec, /** Spanish (Ecuador) */ es_sv, /** Spanish (El Salvador) */ + es_gt, /** Spanish (Guatemala) */ es_hn, /** Spanish (Honduras) */ + es_mx, /** Spanish (Mexico) */ es_ni, /** Spanish (Nicaragua) */ + es_pa, /** Spanish (Panama) */ es_py, /** Spanish (Paraguay) */ + es_pe, /** Spanish (Peru) */ es_pr, /** Spanish (Puerto Rico) */ + es_es, /** Spanish (Spain) */ es_uy, /** Spanish (Uruguay) */ + es_ve, /** Spanish (Venezuela) */ sv, /** Swedish */ + sv_fi, /** Swedish (Finland) */ sv_se, /** Swedish (Sweden) */ + tr, /** Turkish */ uk /** Ukranian */ + }; + + /** + * Possible values contained in a DayList. + */ + enum Day { + UndefinedDay, + Monday = 1, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday + }; + + enum Format { + UnknownFormat, + AtomFeed, + RSSFeed + }; + + /** + * This type is used by Document::skipDays(). + */ + typedef QValueList<Day> DayList; + + /** + * This type is used by Document::skipHours(). + */ + typedef QValueList<unsigned short> HourList; +} + +#endif // LIBRSS_GLOBAL_H +// vim: noet:ts=4 diff --git a/plugins/rssfeed/rss/image.cpp b/plugins/rssfeed/rss/image.cpp new file mode 100644 index 0000000..33e1544 --- /dev/null +++ b/plugins/rssfeed/rss/image.cpp @@ -0,0 +1,167 @@ +/* + * image.cpp + * + * Copyright (c) 2001, 2002, 2003 Frerich Raabe <raabe@kde.org> + * + * 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. For licensing and distribution details, check the + * accompanying file 'COPYING'. + */ +#include "image.h" +#include "tools_p.h" + +#include <kio/job.h> +#include <kurl.h> + +#include <qbuffer.h> +#include <qdom.h> +#include <qpixmap.h> + +using namespace RSS; + +struct Image::Private : public Shared +{ + Private() : height(31), width(88), pixmapBuffer(NULL), job(NULL) + { } + + QString title; + KURL url; + KURL link; + QString description; + unsigned int height; + unsigned int width; + QBuffer *pixmapBuffer; + KIO::Job *job; +}; + +Image::Image() : QObject(), d(new Private) +{ +} + +Image::Image(const Image &other) : QObject(), d(0) +{ + *this = other; +} + +Image::Image(const QDomNode &node) : QObject(), d(new Private) +{ + QString elemText; + + if (!(elemText = extractNode(node, QString::fromLatin1("title"))).isNull()) + d->title = elemText; + if (!(elemText = extractNode(node, QString::fromLatin1("url"))).isNull()) + d->url = elemText; + if (!(elemText = extractNode(node, QString::fromLatin1("link"))).isNull()) + d->link = elemText; + if (!(elemText = extractNode(node, QString::fromLatin1("description"))).isNull()) + d->description = elemText; + if (!(elemText = extractNode(node, QString::fromLatin1("height"))).isNull()) + d->height = elemText.toUInt(); + if (!(elemText = extractNode(node, QString::fromLatin1("width"))).isNull()) + d->width = elemText.toUInt(); +} + +Image::~Image() +{ + if (d->deref()) + { + delete d->pixmapBuffer; + d->pixmapBuffer=0L; + delete d; + } +} + +QString Image::title() const +{ + return d->title; +} + +const KURL &Image::url() const +{ + return d->url; +} + +const KURL &Image::link() const +{ + return d->link; +} + +QString Image::description() const +{ + return d->description; +} + +unsigned int Image::height() const +{ + return d->height; +} + +unsigned int Image::width() const +{ + return d->width; +} + +void Image::getPixmap() +{ + // Ignore subsequent calls if we didn't finish the previous download. + if (d->pixmapBuffer) + return; + + d->pixmapBuffer = new QBuffer; + d->pixmapBuffer->open(IO_WriteOnly); + + d->job = KIO::get(d->url, false, false); + connect(d->job, SIGNAL(data(KIO::Job *, const QByteArray &)), + this, SLOT(slotData(KIO::Job *, const QByteArray &))); + connect(d->job, SIGNAL(result(KIO::Job *)), this, SLOT(slotResult(KIO::Job *))); +} + +void Image::slotData(KIO::Job *, const QByteArray &data) +{ + d->pixmapBuffer->writeBlock(data.data(), data.size()); +} + +void Image::slotResult(KIO::Job *job) +{ + QPixmap pixmap; + if (!job->error()) + pixmap = QPixmap(d->pixmapBuffer->buffer()); + emit gotPixmap(pixmap); + + delete d->pixmapBuffer; + d->pixmapBuffer = NULL; +} + +void Image::abort() +{ + if (d->job) + { + d->job->kill(true); + d->job = NULL; + } +} + +Image &Image::operator=(const Image &other) +{ + if (this != &other) { + other.d->ref(); + if (d && d->deref()) + delete d; + d = other.d; + } + return *this; +} + +bool Image::operator==(const Image &other) const +{ + return d->title == other.title() && + d->url == other.url() && + d->description == other.description() && + d->height == other.height() && + d->width == other.width() && + d->link == other.link(); +} + +#include "image.moc" +// vim:noet:ts=4 diff --git a/plugins/rssfeed/rss/image.h b/plugins/rssfeed/rss/image.h new file mode 100644 index 0000000..e9e65b1 --- /dev/null +++ b/plugins/rssfeed/rss/image.h @@ -0,0 +1,173 @@ +/* + * image.h + * + * Copyright (c) 2001, 2002, 2003 Frerich Raabe <raabe@kde.org> + * + * 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. For licensing and distribution details, check the + * accompanying file 'COPYING'. + */ +#ifndef LIBRSS_IMAGE_H +#define LIBRSS_IMAGE_H + +#include "global.h" + +#include <qobject.h> + +class QDomNode; + +namespace KIO +{ + class Job; +} +class KURL; + +namespace RSS +{ + /** + * Represents an image as stored in a RSS file. You don't have to + * instantiate one of these yourself, the common way to access instances + * is via Document::image(). + * @see Document::image() + */ + class Image : public QObject + { + Q_OBJECT + public: + /** + * Default constructor. + */ + Image(); + + /** + * Copy constructor. + * @param other The Image object to copy. + */ + Image(const Image &other); + + /** + * Constructs an Image from a piece of RSS markup. + * @param node A QDomNode which references the DOM leaf to be used + * for constructing the Image. + */ + Image(const QDomNode &node); + + /** + * Assignment operator. + * @param other The Image object to clone. + * @return A reference to the cloned Image object. + */ + Image &operator=(const Image &other); + + /** + * Compares two images. Two images are considered identical if + * their properties (title, description, link etc.) are identical. + * Note that this does not include the actual pixmap data! + * @param other The image to compare with. + * @return Whether the two images are equal. + */ + bool operator==(const Image &other) const; + + /** + * Convenience method. Simply calls !operator==(). + * @param other The image to compared with. + * @return Whether the two images are unequal. + */ + bool operator!=(const Image &other) const { return !operator==(other); } + + /** + * Destructor. + */ + virtual ~Image(); + + /** + * RSS 0.90 and upwards + * @return The 'caption' of this image, or QString::null if no + * caption is available. + */ + QString title() const; + + /** + * RSS 0.90 and upwards + * @return The URL pointing to the file containing the graphic + * data (GIF, JPEG or PNG format), or an empty KURL if no URL + * is available. You can use getPixmap() and gotPixmap() to have + * the Image download the pixmap data itself. + * Note that the RSS 0.91 Specification dictates that URLs not + * starting with "http://" or "ftp://" are considered invalid. + */ + const KURL &url() const; + + /** + * RSS 0.90 and upwards + * @return A link to some resource, or an empty KURL of no link is + * available. Clicking on the image should lead the user to the + * resource referenced by this URL. + * Note that the RSS 0.91 Specification dictates that URLs not + * starting with "http://" or "ftp://" are considered invalid. + */ + const KURL &link() const; + + /** + * RSS 0.91 and upwards + * @return A description of what this picture shows, or + * QString::null if no description is available. Useful for + * people who deactivated images but want or need to know what is + * shown. + */ + QString description() const; + + /** + * RSS 0.91 and upwards + * @return The height in pixels as reported by the news site, the + * default value is 31 pixels. The RSS 0.91 Specification requires + * this value to be between 1 and 400. + * '0' if this information isn't available. This is merely provided + * for completeness, you should not rely on this value but rather + * check what height the QPixmap as returned by gotPixmap() + * reports. + */ + unsigned int height() const; + + /** + * RSS 0.91 and upwards + * @return The width in pixels as reported by the news site, the + * default value is 88 pixels. The RSS 0.91 Specification requires + * this value to be between 1 and 144. + * This is merely provided for completeness, you should not rely + * on this value but rather check what width the QPixmap as + * returned by gotPixmap() reports. + */ + unsigned int width() const; + + /** + * Makes the image download the image data as referenced by the + * URL returned by url(). You have to connect to the signal + * gotPixmap() first and then call getPixmap(). + */ + void getPixmap(); + void abort(); + + signals: + /** + * Emitted when this Image is done downloading the actual graphics + * data as referenced by the URL returned by url(). You can trigger + * this download by calling getPixmap(). + * @param pixmap The pixmap as constructed from the data referenced + * by the URL returned by link(). + */ + void gotPixmap(const QPixmap &pixmap); + + private slots: + void slotData(KIO::Job *job, const QByteArray &data); + void slotResult(KIO::Job *job); + + private: + struct Private; + Private *d; + }; +} + +#endif // LIBRSS_IMAGE_H +// vim: noet:ts=4 diff --git a/plugins/rssfeed/rss/librss.doxyfile b/plugins/rssfeed/rss/librss.doxyfile new file mode 100644 index 0000000..c81ac16 --- /dev/null +++ b/plugins/rssfeed/rss/librss.doxyfile @@ -0,0 +1,921 @@ +# Doxyfile 1.2.14 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project +# +# All text after a hash (#) is considered a comment and will be ignored +# The format is: +# TAG = value [value, ...] +# For lists items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (" ") + +#--------------------------------------------------------------------------- +# General configuration options +#--------------------------------------------------------------------------- + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded +# by quotes) that should identify the project. + +PROJECT_NAME = librss + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. +# This could be handy for archiving the generated documentation or +# if some version control system is used. + +PROJECT_NUMBER = 0.1 + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) +# base path where the generated documentation will be put. +# If a relative path is entered, it will be relative to the location +# where doxygen was started. If left blank the current directory will be used. + +OUTPUT_DIRECTORY = doc/ + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# The default language is English, other supported languages are: +# Brazilian, Chinese, Croatian, Czech, Danish, Dutch, Finnish, French, +# German, Greek, Hungarian, Italian, Japanese, Korean, Norwegian, Polish, +# Portuguese, Romanian, Russian, Slovak, Slovene, Spanish and Swedish. + +OUTPUT_LANGUAGE = English + +# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in +# documentation are documented, even if no documentation was available. +# Private class members and static file members will be hidden unless +# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES + +EXTRACT_ALL = NO + +# If the EXTRACT_PRIVATE tag is set to YES all private members of a class +# will be included in the documentation. + +EXTRACT_PRIVATE = NO + +# If the EXTRACT_STATIC tag is set to YES all static members of a file +# will be included in the documentation. + +EXTRACT_STATIC = YES + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) +# defined locally in source files will be included in the documentation. +# If set to NO only classes defined in header files are included. + +EXTRACT_LOCAL_CLASSES = NO + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all +# undocumented members of documented classes, files or namespaces. +# If set to NO (the default) these members will be included in the +# various overviews, but no documentation section is generated. +# This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_MEMBERS = YES + +# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. +# If set to NO (the default) these class will be included in the various +# overviews. This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_CLASSES = NO + +# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will +# include brief member descriptions after the members that are listed in +# the file and class documentation (similar to JavaDoc). +# Set to NO to disable this. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend +# the brief description of a member or function before the detailed description. +# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. + +REPEAT_BRIEF = YES + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# Doxygen will generate a detailed section even if there is only a brief +# description. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all inherited +# members of a class in the documentation of that class as if those members were +# ordinary class members. Constructors, destructors and assignment operators of +# the base classes will not be shown. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full +# path before files name in the file list and in the header files. If set +# to NO the shortest path that makes the file name unique will be used. + +FULL_PATH_NAMES = NO + +# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag +# can be used to strip a user defined part of the path. Stripping is +# only done if one of the specified strings matches the left-hand part of +# the path. It is allowed to use relative paths in the argument list. + +STRIP_FROM_PATH = + +# The INTERNAL_DOCS tag determines if documentation +# that is typed after a \internal command is included. If the tag is set +# to NO (the default) then the documentation will be excluded. +# Set it to YES to include the internal documentation. + +INTERNAL_DOCS = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct +# doxygen to hide any special comment blocks from generated source code +# fragments. Normal C and C++ comments will always remain visible. + +STRIP_CODE_COMMENTS = YES + +# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate +# file names in lower case letters. If set to YES upper case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# users are adviced to set this option to NO. + +CASE_SENSE_NAMES = YES + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter +# (but less readable) file names. This can be useful is your file systems +# doesn't support long names like on DOS, Mac, or CD-ROM. + +SHORT_NAMES = NO + +# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen +# will show members with their full class and namespace scopes in the +# documentation. If set to YES the scope will be hidden. + +HIDE_SCOPE_NAMES = NO + +# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen +# will generate a verbatim copy of the header file for each class for +# which an include is specified. Set to NO to disable this. + +VERBATIM_HEADERS = YES + +# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen +# will put list of the files that are included by a file in the documentation +# of that file. + +SHOW_INCLUDE_FILES = YES + +# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen +# will interpret the first line (until the first dot) of a JavaDoc-style +# comment as the brief description. If set to NO, the JavaDoc +# comments will behave just like the Qt-style comments (thus requiring an +# explict @brief command for a brief description. + +JAVADOC_AUTOBRIEF = NO + +# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented +# member inherits the documentation from any documented member that it +# reimplements. + +INHERIT_DOCS = YES + +# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] +# is inserted in the documentation for inline members. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen +# will sort the (detailed) documentation of file and class members +# alphabetically by member name. If set to NO the members will appear in +# declaration order. + +SORT_MEMBER_DOCS = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES, then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. + +DISTRIBUTE_GROUP_DOC = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. +# Doxygen uses this value to replace tabs by spaces in code fragments. + +TAB_SIZE = 4 + +# The GENERATE_TODOLIST tag can be used to enable (YES) or +# disable (NO) the todo list. This list is created by putting \todo +# commands in the documentation. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or +# disable (NO) the test list. This list is created by putting \test +# commands in the documentation. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or +# disable (NO) the bug list. This list is created by putting \bug +# commands in the documentation. + +GENERATE_BUGLIST = YES + +# This tag can be used to specify a number of aliases that acts +# as commands in the documentation. An alias has the form "name=value". +# For example adding "sideeffect=\par Side Effects:\n" will allow you to +# put the command \sideeffect (or @sideeffect) in the documentation, which +# will result in a user defined paragraph with heading "Side Effects:". +# You can put \n's in the value part of an alias to insert newlines. + +ALIASES = + +# The ENABLED_SECTIONS tag can be used to enable conditional +# documentation sections, marked by \if sectionname ... \endif. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines +# the initial value of a variable or define consist of for it to appear in +# the documentation. If the initializer consists of more lines than specified +# here it will be hidden. Use a value of 0 to hide initializers completely. +# The appearance of the initializer of individual variables and defines in the +# documentation can be controlled using \showinitializer or \hideinitializer +# command in the documentation regardless of this setting. + +MAX_INITIALIZER_LINES = 30 + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources +# only. Doxygen will then generate output that is more tailored for C. +# For instance some of the names that are used will be different. The list +# of all members will be omitted, etc. + +OPTIMIZE_OUTPUT_FOR_C = NO + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated +# at the bottom of the documentation of classes and structs. If set to YES the +# list will mention the files that were used to generate the documentation. + +SHOW_USED_FILES = YES + +#--------------------------------------------------------------------------- +# configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated +# by doxygen. Possible values are YES and NO. If left blank NO is used. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated by doxygen. Possible values are YES and NO. If left blank +# NO is used. + +WARNINGS = YES + +# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings +# for undocumented members. If EXTRACT_ALL is set to YES then this flag will +# automatically be disabled. + +WARN_IF_UNDOCUMENTED = YES + +# The WARN_FORMAT tag determines the format of the warning messages that +# doxygen can produce. The string should contain the $file, $line, and $text +# tags, which will be replaced by the file and line number from which the +# warning originated and the warning text. + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning +# and error messages should be written. If left blank the output is written +# to stderr. + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag can be used to specify the files and/or directories that contain +# documented source files. You may enter file names like "myfile.cpp" or +# directories like "/usr/src/myproject". Separate the files or directories +# with spaces. + +INPUT = . + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank the following patterns are tested: +# *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx *.hpp +# *.h++ *.idl *.odl + +FILE_PATTERNS = + +# The RECURSIVE tag can be used to turn specify whether or not subdirectories +# should be searched for input files as well. Possible values are YES and NO. +# If left blank NO is used. + +RECURSIVE = NO + +# The EXCLUDE tag can be used to specify files and/or directories that should +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used select whether or not files or directories +# that are symbolic links (a Unix filesystem feature) are excluded from the input. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. + +EXCLUDE_PATTERNS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or +# directories that contain example code fragments that are included (see +# the \include command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank all files are included. + +EXAMPLE_PATTERNS = + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude +# commands irrespective of the value of the RECURSIVE tag. +# Possible values are YES and NO. If left blank NO is used. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or +# directories that contain image that are included in the documentation (see +# the \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command <filter> <input-file>, where <filter> +# is the value of the INPUT_FILTER tag, and <input-file> is the name of an +# input file. Doxygen will then use the output that the filter program writes +# to standard output. + +INPUT_FILTER = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will be used to filter the input files when producing source +# files to browse. + +FILTER_SOURCE_FILES = NO + +#--------------------------------------------------------------------------- +# configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will +# be generated. Documented entities will be cross-referenced with these sources. + +SOURCE_BROWSER = YES + +# Setting the INLINE_SOURCES tag to YES will include the body +# of functions and classes directly in the documentation. + +INLINE_SOURCES = NO + +# If the REFERENCED_BY_RELATION tag is set to YES (the default) +# then for each documented function all documented +# functions referencing it will be listed. + +REFERENCED_BY_RELATION = YES + +# If the REFERENCES_RELATION tag is set to YES (the default) +# then for each documented function all documented entities +# called/used by that function will be listed. + +REFERENCES_RELATION = YES + +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index +# of all compounds will be generated. Enable this if the project +# contains a lot of classes, structs, unions or interfaces. + +ALPHABETICAL_INDEX = YES + +# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then +# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns +# in which this list will be split (can be a number in the range [1..20]) + +COLS_IN_ALPHA_INDEX = 5 + +# In case all classes in a project start with a common prefix, all +# classes will be put under the same header in the alphabetical index. +# The IGNORE_PREFIX tag can be used to specify one or more prefixes that +# should be ignored while generating the index headers. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES (the default) Doxygen will +# generate HTML output. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `html' will be used as the default path. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for +# each generated HTML page (for example: .htm,.php,.asp). If it is left blank +# doxygen will generate files with .html extension. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a personal HTML header for +# each generated HTML page. If it is left blank doxygen will generate a +# standard header. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a personal HTML footer for +# each generated HTML page. If it is left blank doxygen will generate a +# standard footer. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user defined cascading +# style sheet that is used by each HTML page. It can be used to +# fine-tune the look of the HTML output. If the tag is left blank doxygen +# will generate a default style sheet + +HTML_STYLESHEET = + +# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, +# files or namespaces will be aligned in HTML using tables. If set to +# NO a bullet list will be used. + +HTML_ALIGN_MEMBERS = YES + +# If the GENERATE_HTMLHELP tag is set to YES, additional index files +# will be generated that can be used as input for tools like the +# Microsoft HTML help workshop to generate a compressed HTML help file (.chm) +# of the generated HTML documentation. + +GENERATE_HTMLHELP = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag +# controls if a separate .chi index file is generated (YES) or that +# it should be included in the master .chm file (NO). + +GENERATE_CHI = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag +# controls whether a binary table of contents is generated (YES) or a +# normal table of contents (NO) in the .chm file. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members +# to the contents of the Html help documentation and to the tree view. + +TOC_EXPAND = NO + +# The DISABLE_INDEX tag can be used to turn on/off the condensed index at +# top of each HTML page. The value NO (the default) enables the index and +# the value YES disables it. + +DISABLE_INDEX = NO + +# This tag can be used to set the number of enum values (range [1..20]) +# that doxygen will group on one line in the generated HTML documentation. + +ENUM_VALUES_PER_LINE = 4 + +# If the GENERATE_TREEVIEW tag is set to YES, a side panel will be +# generated containing a tree-like index structure (just like the one that +# is generated for HTML Help). For this to work a browser that supports +# JavaScript and frames is required (for instance Mozilla, Netscape 4.0+, +# or Internet explorer 4.0+). Note that for large projects the tree generation +# can take a very long time. In such cases it is better to disable this feature. +# Windows users are probably better off using the HTML help feature. + +GENERATE_TREEVIEW = YES + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be +# used to set the initial width (in pixels) of the frame in which the tree +# is shown. + +TREEVIEW_WIDTH = 250 + +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- + +# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will +# generate Latex output. + +GENERATE_LATEX = NO + +# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `latex' will be used as the default path. + +LATEX_OUTPUT = latex + +# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact +# LaTeX documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_LATEX = NO + +# The PAPER_TYPE tag can be used to set the paper type that is used +# by the printer. Possible values are: a4, a4wide, letter, legal and +# executive. If left blank a4wide will be used. + +PAPER_TYPE = a4wide + +# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX +# packages that should be included in the LaTeX output. + +EXTRA_PACKAGES = + +# The LATEX_HEADER tag can be used to specify a personal LaTeX header for +# the generated latex document. The header should contain everything until +# the first chapter. If it is left blank doxygen will generate a +# standard header. Notice: only use this tag if you know what you are doing! + +LATEX_HEADER = + +# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated +# is prepared for conversion to pdf (using ps2pdf). The pdf file will +# contain links (just like the HTML output) instead of page references +# This makes the output suitable for online browsing using a pdf viewer. + +PDF_HYPERLINKS = NO + +# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of +# plain latex in the generated Makefile. Set this option to YES to get a +# higher quality PDF documentation. + +USE_PDFLATEX = NO + +# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. +# command to the generated LaTeX files. This will instruct LaTeX to keep +# running if errors occur, instead of asking the user for help. +# This option is also used when generating formulas in HTML. + +LATEX_BATCHMODE = NO + +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- + +# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output +# The RTF output is optimised for Word 97 and may not look very pretty with +# other RTF readers or editors. + +GENERATE_RTF = NO + +# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `rtf' will be used as the default path. + +RTF_OUTPUT = rtf + +# If the COMPACT_RTF tag is set to YES Doxygen generates more compact +# RTF documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_RTF = NO + +# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated +# will contain hyperlink fields. The RTF file will +# contain links (just like the HTML output) instead of page references. +# This makes the output suitable for online browsing using WORD or other +# programs which support those fields. +# Note: wordpad (write) and others do not support links. + +RTF_HYPERLINKS = NO + +# Load stylesheet definitions from file. Syntax is similar to doxygen's +# config file, i.e. a series of assigments. You only have to provide +# replacements, missing definitions are set to their default value. + +RTF_STYLESHEET_FILE = + +# Set optional variables used in the generation of an rtf document. +# Syntax is similar to doxygen's config file. + +RTF_EXTENSIONS_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- + +# If the GENERATE_MAN tag is set to YES (the default) Doxygen will +# generate man pages + +GENERATE_MAN = NO + +# The MAN_OUTPUT tag is used to specify where the man pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `man' will be used as the default path. + +MAN_OUTPUT = man + +# The MAN_EXTENSION tag determines the extension that is added to +# the generated man pages (default is the subroutine's section .3) + +MAN_EXTENSION = .3 + +# If the MAN_LINKS tag is set to YES and Doxygen generates man output, +# then it will generate one additional man file for each entity +# documented in the real man page(s). These additional files +# only source the real man page, but without them the man command +# would be unable to find the correct page. The default is NO. + +MAN_LINKS = NO + +#--------------------------------------------------------------------------- +# configuration options related to the XML output +#--------------------------------------------------------------------------- + +# If the GENERATE_XML tag is set to YES Doxygen will +# generate an XML file that captures the structure of +# the code including all documentation. Note that this +# feature is still experimental and incomplete at the +# moment. + +GENERATE_XML = NO + +#--------------------------------------------------------------------------- +# configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- + +# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will +# generate an AutoGen Definitions (see autogen.sf.net) file +# that captures the structure of the code including all +# documentation. Note that this feature is still experimental +# and incomplete at the moment. + +GENERATE_AUTOGEN_DEF = NO + +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- + +# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will +# evaluate all C-preprocessor directives found in the sources and include +# files. + +ENABLE_PREPROCESSING = YES + +# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro +# names in the source code. If set to NO (the default) only conditional +# compilation will be performed. Macro expansion can be done in a controlled +# way by setting EXPAND_ONLY_PREDEF to YES. + +MACRO_EXPANSION = NO + +# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES +# then the macro expansion is limited to the macros specified with the +# PREDEFINED and EXPAND_AS_PREDEFINED tags. + +EXPAND_ONLY_PREDEF = NO + +# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files +# in the INCLUDE_PATH (see below) will be search if a #include is found. + +SEARCH_INCLUDES = YES + +# The INCLUDE_PATH tag can be used to specify one or more directories that +# contain include files that are not input files but should be processed by +# the preprocessor. + +INCLUDE_PATH = + +# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard +# patterns (like *.h and *.hpp) to filter out the header-files in the +# directories. If left blank, the patterns specified with FILE_PATTERNS will +# be used. + +INCLUDE_FILE_PATTERNS = + +# The PREDEFINED tag can be used to specify one or more macro names that +# are defined before the preprocessor is started (similar to the -D option of +# gcc). The argument of the tag is a list of macros of the form: name +# or name=definition (no spaces). If the definition and the = are +# omitted =1 is assumed. + +PREDEFINED = + +# If the MACRO_EXPANSION and EXPAND_PREDEF_ONLY tags are set to YES then +# this tag can be used to specify a list of macro names that should be expanded. +# The macro definition that is found in the sources will be used. +# Use the PREDEFINED tag if you want to use a different macro definition. + +EXPAND_AS_DEFINED = + +# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then +# doxygen's preprocessor will remove all function-like macros that are alone +# on a line and do not end with a semicolon. Such function macros are typically +# used for boiler-plate code, and will confuse the parser if not removed. + +SKIP_FUNCTION_MACROS = YES + +#--------------------------------------------------------------------------- +# Configuration::addtions related to external references +#--------------------------------------------------------------------------- + +# The TAGFILES tag can be used to specify one or more tagfiles. + +TAGFILES = + +# When a file name is specified after GENERATE_TAGFILE, doxygen will create +# a tag file that is based on the input files it reads. + +GENERATE_TAGFILE = + +# If the ALLEXTERNALS tag is set to YES all external classes will be listed +# in the class index. If set to NO only the inherited external classes +# will be listed. + +ALLEXTERNALS = NO + +# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed +# in the modules index. If set to NO, only the current project's groups will +# be listed. + +EXTERNAL_GROUPS = YES + +# The PERL_PATH should be the absolute path and name of the perl script +# interpreter (i.e. the result of `which perl'). + +PERL_PATH = /usr/bin/perl + +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- + +# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will +# generate a inheritance diagram (in Html, RTF and LaTeX) for classes with base or +# super classes. Setting the tag to NO turns the diagrams off. Note that this +# option is superceded by the HAVE_DOT option below. This is only a fallback. It is +# recommended to install and use dot, since it yield more powerful graphs. + +CLASS_DIAGRAMS = YES + +# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is +# available from the path. This tool is part of Graphviz, a graph visualization +# toolkit from AT&T and Lucent Bell Labs. The other options in this section +# have no effect if this option is set to NO (the default) + +HAVE_DOT = YES + +# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect inheritance relations. Setting this tag to YES will force the +# the CLASS_DIAGRAMS tag to NO. + +CLASS_GRAPH = YES + +# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect implementation dependencies (inheritance, containment, and +# class references variables) of the class with other documented classes. + +COLLABORATION_GRAPH = YES + +# If set to YES, the inheritance and collaboration graphs will show the +# relations between templates and their instances. + +TEMPLATE_RELATIONS = YES + +# If set to YES, the inheritance and collaboration graphs will hide +# inheritance and usage relations if the target is undocumented +# or is not a class. + +HIDE_UNDOC_RELATIONS = YES + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT +# tags are set to YES then doxygen will generate a graph for each documented +# file showing the direct and indirect include dependencies of the file with +# other documented files. + +INCLUDE_GRAPH = YES + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and +# HAVE_DOT tags are set to YES then doxygen will generate a graph for each +# documented header file showing the documented files that directly or +# indirectly include this file. + +INCLUDED_BY_GRAPH = YES + +# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen +# will graphical hierarchy of all classes instead of a textual one. + +GRAPHICAL_HIERARCHY = YES + +# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images +# generated by dot. Possible values are gif, jpg, and png +# If left blank gif will be used. + +DOT_IMAGE_FORMAT = png + +# The tag DOT_PATH can be used to specify the path where the dot tool can be +# found. If left blank, it is assumed the dot tool can be found on the path. + +DOT_PATH = + +# The DOTFILE_DIRS tag can be used to specify one or more directories that +# contain dot files that are included in the documentation (see the +# \dotfile command). + +DOTFILE_DIRS = + +# The MAX_DOT_GRAPH_WIDTH tag can be used to set the maximum allowed width +# (in pixels) of the graphs generated by dot. If a graph becomes larger than +# this value, doxygen will try to truncate the graph, so that it fits within +# the specified constraint. Beware that most browsers cannot cope with very +# large images. + +MAX_DOT_GRAPH_WIDTH = 1024 + +# The MAX_DOT_GRAPH_HEIGHT tag can be used to set the maximum allows height +# (in pixels) of the graphs generated by dot. If a graph becomes larger than +# this value, doxygen will try to truncate the graph, so that it fits within +# the specified constraint. Beware that most browsers cannot cope with very +# large images. + +MAX_DOT_GRAPH_HEIGHT = 1024 + +# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will +# generate a legend page explaining the meaning of the various boxes and +# arrows in the dot generated graphs. + +GENERATE_LEGEND = YES + +# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will +# remove the intermedate dot files that are used to generate +# the various graphs. + +DOT_CLEANUP = YES + +#--------------------------------------------------------------------------- +# Configuration::addtions related to the search engine +#--------------------------------------------------------------------------- + +# The SEARCHENGINE tag specifies whether or not a search engine should be +# used. If set to NO the values of all tags below this one will be ignored. + +SEARCHENGINE = NO + +# The CGI_NAME tag should be the name of the CGI script that +# starts the search engine (doxysearch) with the correct parameters. +# A script with this name will be generated by doxygen. + +CGI_NAME = search.cgi + +# The CGI_URL tag should be the absolute URL to the directory where the +# cgi binaries are located. See the documentation of your http daemon for +# details. + +CGI_URL = + +# The DOC_URL tag should be the absolute URL to the directory where the +# documentation is located. If left blank the absolute path to the +# documentation, with file:// prepended to it, will be used. + +DOC_URL = + +# The DOC_ABSPATH tag should be the absolute path to the directory where the +# documentation is located. If left blank the directory on the local machine +# will be used. + +DOC_ABSPATH = + +# The BIN_ABSPATH tag must point to the directory where the doxysearch binary +# is installed. + +BIN_ABSPATH = /usr/local/bin/ + +# The EXT_DOC_PATHS tag can be used to specify one or more paths to +# documentation generated for other projects. This allows doxysearch to search +# the documentation for these projects as well. + +EXT_DOC_PATHS = diff --git a/plugins/rssfeed/rss/librss.h b/plugins/rssfeed/rss/librss.h new file mode 100644 index 0000000..c9637d0 --- /dev/null +++ b/plugins/rssfeed/rss/librss.h @@ -0,0 +1,22 @@ +/* + * librss.h + * + * Copyright (c) 2003 Frerich Raabe <raabe@kde.org> + * + * 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. For licensing and distribution details, check the + * accompanying file 'COPYING'. + */ +#ifndef LIBRSS_LIBRSS_H +#define LIBRSS_LIBRSS_H + +#include "article.h" +#include "document.h" +#include "global.h" +#include "image.h" +#include "loader.h" +#include "textinput.h" + +#endif // LIBRSS_LIBRSS_H +// vim: noet:ts=4 diff --git a/plugins/rssfeed/rss/loader.cpp b/plugins/rssfeed/rss/loader.cpp new file mode 100644 index 0000000..9dfb50a --- /dev/null +++ b/plugins/rssfeed/rss/loader.cpp @@ -0,0 +1,425 @@ +/* + * loader.cpp + * + * Copyright (c) 2001, 2002, 2003 Frerich Raabe <raabe@kde.org> + * + * 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. For licensing and distribution details, check the + * accompanying file 'COPYING'. + */ +#include "loader.h" +#include "document.h" + +#include <kio/job.h> +#include <kprocess.h> +#include <kurl.h> +#include <kdebug.h> + +#include <qdom.h> +#include <qbuffer.h> +#include <qregexp.h> +#include <qstringlist.h> +#include <qtimer.h> + +using namespace RSS; + +DataRetriever::DataRetriever() +{ +} + +DataRetriever::~DataRetriever() +{ +} + +struct FileRetriever::Private +{ + Private() + : buffer(NULL), + lastError(0), job(NULL) + { + } + + ~Private() + { + delete buffer; + } + + QBuffer *buffer; + int lastError; + KIO::Job *job; +}; + +FileRetriever::FileRetriever() + : d(new Private) +{ +} + +FileRetriever::~FileRetriever() +{ + delete d; +} + +bool FileRetriever::m_useCache = true; + +void FileRetriever::setUseCache(bool enabled) +{ + m_useCache = enabled; +} + +void FileRetriever::retrieveData(const KURL &url) +{ + if (d->buffer) + return; + + d->buffer = new QBuffer; + d->buffer->open(IO_WriteOnly); + + KURL u=url; + + if (u.protocol()=="feed") + u.setProtocol("http"); + + d->job = KIO::get(u, !m_useCache, false); + + + QTimer::singleShot(1000*90, this, SLOT(slotTimeout())); + + connect(d->job, SIGNAL(data(KIO::Job *, const QByteArray &)), + SLOT(slotData(KIO::Job *, const QByteArray &))); + connect(d->job, SIGNAL(result(KIO::Job *)), SLOT(slotResult(KIO::Job *))); + connect(d->job, SIGNAL(permanentRedirection(KIO::Job *, const KURL &, const KURL &)), + SLOT(slotPermanentRedirection(KIO::Job *, const KURL &, const KURL &))); +} + +void FileRetriever::slotTimeout() +{ + abort(); + + delete d->buffer; + d->buffer = NULL; + + d->lastError = KIO::ERR_SERVER_TIMEOUT; + + emit dataRetrieved(QByteArray(), false); +} + +int FileRetriever::errorCode() const +{ + return d->lastError; +} + +void FileRetriever::slotData(KIO::Job *, const QByteArray &data) +{ + d->buffer->writeBlock(data.data(), data.size()); +} + +void FileRetriever::slotResult(KIO::Job *job) +{ + QByteArray data = d->buffer->buffer(); + data.detach(); + + delete d->buffer; + d->buffer = NULL; + + d->lastError = job->error(); + emit dataRetrieved(data, d->lastError == 0); +} + +void FileRetriever::slotPermanentRedirection(KIO::Job *, const KURL &, const KURL &newUrl) +{ + emit permanentRedirection(newUrl); +} + +void FileRetriever::abort() +{ + if (d->job) + { + d->job->kill(true); + d->job = NULL; + } +} + +struct OutputRetriever::Private +{ + Private() : process(NULL), + buffer(NULL), + lastError(0) + { + } + + ~Private() + { + delete process; + delete buffer; + } + + KShellProcess *process; + QBuffer *buffer; + int lastError; +}; + +OutputRetriever::OutputRetriever() : + d(new Private) +{ +} + +OutputRetriever::~OutputRetriever() +{ + delete d; +} + +void OutputRetriever::retrieveData(const KURL &url) +{ + // Ignore subsequent calls if we didn't finish the previous job yet. + if (d->buffer || d->process) + return; + + d->buffer = new QBuffer; + d->buffer->open(IO_WriteOnly); + + d->process = new KShellProcess(); + connect(d->process, SIGNAL(processExited(KProcess *)), + SLOT(slotExited(KProcess *))); + connect(d->process, SIGNAL(receivedStdout(KProcess *, char *, int)), + SLOT(slotOutput(KProcess *, char *, int))); + *d->process << url.path(); + d->process->start(KProcess::NotifyOnExit, KProcess::Stdout); +} + +int OutputRetriever::errorCode() const +{ + return d->lastError; +} + +void OutputRetriever::slotOutput(KProcess *, char *data, int length) +{ + d->buffer->writeBlock(data, length); +} + +void OutputRetriever::slotExited(KProcess *p) +{ + if (!p->normalExit()) + d->lastError = p->exitStatus(); + + QByteArray data = d->buffer->buffer(); + data.detach(); + + delete d->buffer; + d->buffer = NULL; + + delete d->process; + d->process = NULL; + + emit dataRetrieved(data, p->normalExit() && p->exitStatus() == 0); +} + +struct Loader::Private +{ + Private() : retriever(NULL), + lastError(0) + { + } + + ~Private() + { + delete retriever; + } + + DataRetriever *retriever; + int lastError; + KURL discoveredFeedURL; + KURL url; +}; + +Loader *Loader::create() +{ + return new Loader; +} + +Loader *Loader::create(QObject *object, const char *slot) +{ + Loader *loader = create(); + connect(loader, SIGNAL(loadingComplete(Loader *, Document, Status)), + object, slot); + return loader; +} + +Loader::Loader() : d(new Private) +{ +} + +Loader::~Loader() +{ + delete d; +} + +void Loader::loadFrom(const KURL &url, DataRetriever *retriever) +{ + if (d->retriever != NULL) + return; + + d->url=url; + d->retriever = retriever; + + connect(d->retriever, SIGNAL(dataRetrieved(const QByteArray &, bool)), + this, SLOT(slotRetrieverDone(const QByteArray &, bool))); + + d->retriever->retrieveData(url); +} + +int Loader::errorCode() const +{ + return d->lastError; +} + +void Loader::abort() +{ + if (d && d->retriever) + { + d->retriever->abort(); + delete d->retriever; + d->retriever=NULL; + } + emit loadingComplete(this, QDomDocument(), Aborted); + delete this; +} + +const KURL &Loader::discoveredFeedURL() const +{ + return d->discoveredFeedURL; +} + +#include <kdebug.h> + +void Loader::slotRetrieverDone(const QByteArray &data, bool success) +{ + d->lastError = d->retriever->errorCode(); + + delete d->retriever; + d->retriever = NULL; + + Document rssDoc; + Status status = Success; + + if (success) { + QDomDocument doc; + + /* Some servers insert whitespace before the <?xml...?> declaration. + * QDom doesn't tolerate that (and it's right, that's invalid XML), + * so we strip that. + */ + + const char *charData = data.data(); + int len = data.count(); + + while (len && QChar(*charData).isSpace()) { + --len; + ++charData; + } + + if ( len > 3 && QChar(*charData) == QChar(0357) ) { // 0357 0273 0277 + len -= 3; + charData += 3; + } + QByteArray tmpData; + tmpData.setRawData(charData, len); + + if (doc.setContent(tmpData)) + { + rssDoc = Document(doc); + if (!rssDoc.isValid()) + { + discoverFeeds(tmpData); + status = ParseError; + } + } + else + { + discoverFeeds(tmpData); + status = ParseError; + } + + tmpData.resetRawData(charData, len); + } else + status = RetrieveError; + + emit loadingComplete(this, rssDoc, status); + + delete this; +} + +void Loader::discoverFeeds(const QByteArray &data) +{ + QString str = QString(data).simplifyWhiteSpace(); + QString s2; + //QTextStream ts( &str, IO_WriteOnly ); + //ts << data.data(); + + // "<[\\s]link[^>]*rel[\\s]=[\\s]\\\"[\\s]alternate[\\s]\\\"[^>]*>" + // "type[\\s]=[\\s]\\\"application/rss+xml\\\"" + // "href[\\s]=[\\s]\\\"application/rss+xml\\\"" + QRegExp rx( "(?:REL)[^=]*=[^sAa]*(?:service.feed|ALTERNATE)[\\s]*[^s][^s](?:[^>]*)(?:HREF)[^=]*=[^A-Z0-9-_~,./$]*([^'\">\\s]*)", false); + if (rx.search(str)!=-1) + s2=rx.cap(1); + else{ + // does not support Atom/RSS autodiscovery.. try finding feeds by brute force.... + int pos=0; + QStringList feeds; + QString host=d->url.host(); + rx.setPattern("(?:<A )[^H]*(?:HREF)[^=]*=[^A-Z0-9-_~,./]*([^'\">\\s]*)"); + while ( pos >= 0 ) { + pos = rx.search( str, pos ); + s2=rx.cap(1); + if (s2.endsWith(".rdf")|s2.endsWith(".rss")|s2.endsWith(".xml")) + feeds.append(s2); + if ( pos >= 0 ) { + pos += rx.matchedLength(); + } + } + + s2=feeds.first(); + KURL testURL; + // loop through, prefer feeds on same host + for ( QStringList::Iterator it = feeds.begin(); it != feeds.end(); ++it ) { + testURL=*it; + if (testURL.host()==host) + { + s2=*it; + break; + } + } + } + + if (s2.isNull()) { + kdDebug() << "No feed found for a site" << endl; + return; + } + + if (KURL::isRelativeURL(s2)) + { + if (s2.startsWith("//")) + { + s2=s2.prepend(d->url.protocol()+":"); + d->discoveredFeedURL=s2; + } + else if (s2.startsWith("/")) + { + d->discoveredFeedURL=d->url; + d->discoveredFeedURL.setPath(s2); + } + else + { + d->discoveredFeedURL=d->url; + d->discoveredFeedURL.addPath(s2); + } + d->discoveredFeedURL.cleanPath(); + } + else + d->discoveredFeedURL=s2; + + d->discoveredFeedURL.cleanPath(); +} + +#include "loader.moc" +// vim:noet:ts=4 diff --git a/plugins/rssfeed/rss/loader.h b/plugins/rssfeed/rss/loader.h new file mode 100644 index 0000000..fb06634 --- /dev/null +++ b/plugins/rssfeed/rss/loader.h @@ -0,0 +1,339 @@ +/* + * loader.h + * + * Copyright (c) 2001, 2002, 2003 Frerich Raabe <raabe@kde.org> + * + * 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. For licensing and distribution details, check the + * accompanying file 'COPYING'. + */ +#ifndef LIBRSS_LOADER_H +#define LIBRSS_LOADER_H + +#include "global.h" + +class KURL; + +#include <qobject.h> + +namespace KIO +{ + class Job; +} +class KProcess; + +namespace RSS +{ + class Document; + + /** + * Abstract baseclass for all data retriever classes. Subclass this to add + * a new retrieval algorithm which can then be plugged into the RSS loader. + * @see Loader, FileRetriever, OutputRetriever + */ + class DataRetriever : public QObject + { + Q_OBJECT + public: + /** + * Default constructor. + */ + DataRetriever(); + + /** + * Destructor. + */ + virtual ~DataRetriever(); + + /** + * Retrieve data from the given URL. This method is supposed to get + * reimplemented by subclasses. It will be called by the Loader + * class in case it needs to retrieve the data. + * @see Loader::loadFrom() + */ + virtual void retrieveData(const KURL &url) = 0; + + /** + * @return An error code which might give a more precise information + * about what went wrong in case the 'success' flag returned with + * the dataRetrieved() signal was 'false'. Note that the meaning of + * the returned integer depends on the actual data retriever. + */ + virtual int errorCode() const = 0; + + virtual void abort() = 0; + signals: + /** + * Emit this signal to tell the Loader class that the retrieval + * process was finished. + * @param data Should contain the retrieved data and will get + * parsed by the Loader class. + * @param success Indicates whether there were any problems during + * the retrieval process. Pass 'true' to indicate that everything + * went seamlessy, 'false' to tell the Loader that something went + * wrong and that the data parameter might contain no or invalid + * data. + */ + void dataRetrieved(const QByteArray &data, bool success); + + private: + DataRetriever(const DataRetriever &other); + DataRetriever &operator=(const DataRetriever &other); + }; + + /** + * Implements a file retriever, to be used with Loader::loadFrom(). + * @see DataRetriever, Loader::loadFrom() + */ + class FileRetriever : public DataRetriever + { + Q_OBJECT + public: + /** + * Default constructor. + */ + FileRetriever(); + + /** + * Destructor. + */ + virtual ~FileRetriever(); + + /** + * Downloads the file referenced by the given URL and passes it's + * contents on to the Loader. + * @param url An URL referencing a file which is assumed to + * reference valid XML. + * @see Loader::loadFrom() + */ + virtual void retrieveData(const KURL &url); + + /** + * @return The error code for the last process of retrieving data. + * The returned numbers correspond directly to the error codes + * <a href="http://developer.kde.org/documentation/library/cvs-api/classref/kio/KIO.html#Error">as + * defined by KIO</a>. + */ + virtual int errorCode() const; + + virtual void abort(); + + static void setUseCache(bool enabled); + + signals: + /** + * Signals a permanent redirection. The redirection itself is + * handled internally, so you don't need to call Loader::loadFrom() + * with the new URL. This signal is useful in case you want to + * notify the user, or adjust a database entry. + * @see Loader::loadFrom() + */ + void permanentRedirection(const KURL &url); + + protected slots: + void slotTimeout(); + + private slots: + void slotData(KIO::Job *job, const QByteArray &data); + void slotResult(KIO::Job *job); + void slotPermanentRedirection(KIO::Job *job, const KURL &fromUrl, + const KURL &toUrl); + + private: + static bool m_useCache; + + FileRetriever(const FileRetriever &other); + FileRetriever &operator=(const FileRetriever &other); + + struct Private; + Private *d; + }; + + /** + * Implements a data retriever which executes a program and stores returned + * by the program on stdout. To be used with Loader::loadFrom(). + * @see DataRetriever, Loader::loadFrom() + */ + class OutputRetriever : public DataRetriever + { + Q_OBJECT + public: + /** + * Default constructor. + */ + OutputRetriever(); + + /** + * Destructor. + */ + virtual ~OutputRetriever(); + + /** + * Executes the program referenced by the given URL and retrieves + * the data which the program prints to stdout. + * @param url An URL which is supposed to reference an executable + * file. + * @see Loader::loadFrom() + */ + virtual void retrieveData(const KURL &url); + + /** + * @return The error code for the last process of retrieving data. + * 0 is returned in case there was no error, otherwise an error + * code which depends on the particular program which was run is + * returned. + */ + virtual int errorCode() const; + + virtual void abort() {} + + private slots: + void slotOutput(KProcess *process, char *data, int length); + void slotExited(KProcess *process); + + private: + OutputRetriever(const OutputRetriever &other); + OutputRetriever &operator=(const OutputRetriever &other); + + struct Private; + Private *d; + }; + + /** + * This class is the preferred way of loading RSS files. Usage is very + * straightforward: + * + * \code + * Loader *loader = Loader::create(); + * connect(loader, SIGNAL(loadingComplete(Loader *, Document, Status)), + * this, SLOT(slotLoadingComplete(Loader *, Document, Status))); + * loader->loadFrom("http://www.blah.org/foobar.rdf", new FileRetriever); + * \endcode + * + * This creates a Loader object, connects it's loadingComplete() signal to + * your custom slot and then makes it load the file + * 'http://www.blah.org/foobar.rdf' using the FileRetriever. You could've + * done something like this as well: + * + * \code + * // create the Loader, connect it's signal... + * loader->loadFrom("/home/myself/some-script.py", new OutputRetriever); + * \endcode + * + * That'd make the Loader use another algorithm for retrieving the RSS data; + * 'OutputRetriever' will make it execute the script + * '/home/myself/some-script.py' and assume whatever that script prints to + * stdout is RSS markup. This is e.g. handy for conversion scripts, which + * download a HTML file and convert it's contents into RSS markup. + * + * No matter what kind of retrieval algorithm you employ, your + * 'slotLoadingComplete' method might look like this: + * + * \code + * void MyClass::slotLoadingComplete(Loader *loader, Document doc, Status status) + * { + * // Note that Loader::~Loader() is private, so you cannot delete Loader instances. + * // You don't need to do that anyway since Loader instances delete themselves. + * + * if (status != RSS::Success) + * return; + * + * QString title = doc.title(); + * // do whatever you want with the information. + * } + * \endcode + * + * \note You have to create a copy of the passed Document instance in + * case you want/need to use it after the slot attached to the + * loadingComplete signal goes out of scope. This is e.g. the case if you + * intend to call getPixmap() on Document::image()! + */ + class Loader : public QObject + { + Q_OBJECT + friend class someClassWhichDoesNotExist; + public: + /** + * Constructs a Loader instance. This is pretty much what the + * default constructor would do, except that it ensures that all + * Loader instances have been allocated on the heap (this is + * required so that Loader's can delete themselves safely after they + * emitted the loadingComplete() signal.). + * @return A pointer to a new Loader instance. + */ + static Loader *create(); + + /** + * Convenience method. Does the same as the above method except that + * it also does the job of connecting the loadingComplete() signal + * to the given slot for you. + * @param object A QObject which features the specified slot + * @param slot Which slot to connect to. + */ + static Loader *create(QObject *object, const char *slot); + + /** + * Loads the RSS file referenced by the given URL using the + * specified retrieval algorithm. Make sure that you connected + * to the loadingComplete() signal before calling this method so + * that you're guaranteed to get notified when the loading finished. + * \note A Loader object cannot load from multiple URLs simultaneously; + * consequently, subsequent calls to loadFrom will be discarded + * silently, only the first loadFrom request will be executed. + * @param url An URL referencing the input file. + * @param retriever A subclass of DataRetriever which implements a + * specialized retrieval behaviour. Note that the ownership of the + * retriever is transferred to the Loader, i.e. the Loader will + * delete it when it doesn't need it anymore. + * @see DataRetriever, Loader::loadingComplete() + */ + void loadFrom(const KURL &url, DataRetriever *retriever); + + /** + * Retrieves the error code of the last loading process (if any), + * as reported by the employed data retrever. + */ + int errorCode() const; + + const KURL &discoveredFeedURL() const; + + void abort(); + + signals: + /** + * This signal gets emitted when the loading process triggered by + * calling loadFrom() finished. + * @param loader A pointer pointing to the loader object which + * emitted this signal; this is handy in case you connect multiple + * loaders to a single slot. + * @param doc In case status is Success, this parameter holds the + * parsed RSS file. In case it's RetrieveError, you should query + * loader->errorCode() for the actual error code. + * Note that you have to create a copy of the passed Document + * instance in case you want/need to use it after the slot attached + * to the loadingComplete signal goes out of scope. This is e.g. + * the case if you intend to call getPixmap() on Document::image()! + * @param status A status byte telling whether there were any problems + * while retrieving or parsing the data. + * @see Document, Status + */ + void loadingComplete(Loader *loader, Document doc, Status status); + + private slots: + void slotRetrieverDone(const QByteArray &data, bool success); + + private: + Loader(); + Loader(const Loader &other); + Loader &operator=(const Loader &other); + ~Loader(); + void discoverFeeds(const QByteArray &data); + + struct Private; + Private *d; + }; +} + +#endif // LIBRSS_LOADER_H +// vim: noet:ts=4 diff --git a/plugins/rssfeed/rss/rss-faq.html b/plugins/rssfeed/rss/rss-faq.html new file mode 100644 index 0000000..480b19f --- /dev/null +++ b/plugins/rssfeed/rss/rss-faq.html @@ -0,0 +1,396 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html> +<head> +<title>RSS Headline Syndication - Frequently Asked Questions for Content Providers</title> +<meta http-equiv="Content-Type" content="text/html; charset=windows-1252" /> +<meta name="description" content="RSS Headline Syndication - Frequently Asked Questions for Content Providers" /> +<meta name="keywords" content="rss faq, rss, faq, rich site summary, rdf site summary, really simple syndication, headline syndication, syndication" /> +<meta name="robots" content="index,follow" /> +<style type="text/css"> +<!-- +td.content +{ +font-family: Verdana, Arial, Helvetica, sans-serif; +color: #000000; +font-size: 10pt; +font-weight: normal; +} +td.contentbold +{ +font-family: Verdana, Arial, Helvetica, sans-serif; +color: #000000; +font-size: 10pt; +font-weight: bold; +} +td.contentsmall +{ +font-family: Verdana, Arial, Helvetica, sans-serif; +color: #000000; +font-size: 8pt; +font-weight: normal; +} +--> +</style> +</head> +<body bgcolor="#ffffff"> +<p align="right"> +<i> + This document was taken from <a href="http://www.purplepages.ie/rss/">http://www.purplepages.ie/rss/</a>.<br/> + <tt><a href="mailto:raabe@kde.org">Frerich Raabe</a></tt> +</i> +</p> +<table border="0" width="620" cellpadding="1" cellspacing="0"> + <tr> + <td width="620" class="contentbold" colspan="2">RSS Headline Syndication<br /><br /> + <a name="top">Frequently Asked Questions for Content Providers</a> + </td> + </tr> + <tr> + <td width="620" class="contentbold" colspan="2"> + <br /><a href="#whatishs">1. What is headline syndication?</a><br /> + <a href="#rss">2. What is RSS?</a><br /> + <a href="#whyrss">3. Why syndicate your headlines with RSS?</a><br /> + <a href="#howrss">4. How can I create an RSS file?</a><br /> + <a href="#promoterss">5. How can I promote my RSS file?</a><br /> + <a href="#morerss">6. Where can I find more information about RSS?</a><br /> + </td> + </tr> + <tr> + <td width="620" class="contentbold" colspan="2"><br /> + <a name="whatishs">1. What is headline syndication?</a> + </td> + </tr> + <tr> + <td width="620" class="content" colspan="2"><br /> + Websites that publish new content regularly usually provide a list of news headline style links to their latest content. In addition to displaying these headlines on their own websites, it is very common for publishers to make them available for syndication, so that other websites or applications can also include their headlines.<br /><br /> + Headline syndication does not deal with the full text of articles, it is simply about syndicating an automatically updating list of headlines, with each headline being a link to the item that it refers to on the publishers website. + </td> + </tr> + <tr> + <td width="620" class="contentbold" colspan="2"><br /> + <a name="rss">2. What is RSS?</a> + </td> + </tr> + <tr> + <td width="620" class="content" colspan="2"><br /> + <a href="#top">top</a> + </td> + </tr> + <tr> + <td width="620" class="content" colspan="2"><br /> + RSS is the name given to a simple and well-established XML format used to syndicate headlines. Once a website creates an RSS file they have created a means to allow others to syndicate their headlines.<br /><br /> + The first version of RSS (RSS 0.9) was released by <a href="http://www.netscape.com/">Netscape</a> in March 1999 as a format for adding news channels to their <a href="http://my.netscape.com/">My.Netscape.Com</a> portal. Then in July 1999 Netscape released RSS 0.91, incorporating most of the features of a format called <scriptingNews>, which was created by <a href="http://www.userland.com/">UserLand</a>. Shortly thereafter Netscape discontinued developing the RSS format, however UserLand persisted and RSS continued to grow in strength. In December 2000, the separate RSS-DEV Working Group released RSS 1.0 and Userland announced RSS 0.92. As of April 2001, Userland is now planning RSS 0.93. Although RSS is not clearly an acronym of anything, different people have called it Rich Site Summary, RDF Site Summary and Really Simple Syndication at different times.<br /><br /> + The lack of clarity in what RSS stands for or which version is the correct one to use can seem confusing to beginners. However these issues don't need to addressed by a website wanting to create an RSS file. RSS is a very well recognised format, in fact it is often referred to as the most successful XML format to date. Some websites have a preference for one version, others create more than one RSS file and support multiple versions and a recent survey suggests that the first two versions of RSS (0.9 and 0.91) are still by far the most popular. + </td> + </tr> + <tr> + <td width="620" class="content" colspan="2"><br /> + <a href="#top">top</a> + </td> + </tr> + <tr> + <td width="620" class="contentbold" colspan="2"><br /> + <a name="whyrss">3. Why syndicate your headlines with RSS?</a> + </td> + </tr> + <tr> + <td width="620" class="content" colspan="2"><br /> + Syndicating headlines is an excellent and cost-effective way of driving traffic to, and increasing brand awareness of, any website that publishes new content regularly.<br /><br /> + Once a website produces an RSS file they are enabling others to syndicate their headlines, without any further work on their part.<br /><br /> + The main benefits of creating an RSS file:<br /> + </td> + </tr> + <tr> + <td width="15" class="content" align="center" valign="top">•</td> + <td width="605" class="content">RSS content can be included in customisable online news portals that aggregate RSS headlines like <a href="http://my.userland.com/">My.Userland.Com</a>. + </td> + </tr> + <tr> + <td width="15" class="content" align="center" valign="top">•</td> + <td width="605" class="content">Websites that display news headlines can use an RSS file to incorporate another websites headlines into their own. + </td> + </tr> + <tr> + <td width="15" class="content" align="center" valign="top">•</td> + <td width="605" class="content">RSS content can be added to personal desktop news reading applications like <a href="http://www.headlineviewer.com/">Headline Viewer</a> or <a href="http://radio.userland.com/">Radio Userland</a>. + </td> + </tr> + <tr> + <td width="15" class="content" align="center" valign="top">•</td> + <td width="605" class="content">Email newsletter providers could allow users to subscribe to RSS channels. <a href="http://www.xml.com/">XML.com</a> and <a href="http://www.xmltree.com/">XMLTree.com</a> previously offered such a service called Newsboy. + </td> + </tr> + <tr> + <td width="620" class="content" colspan="2"><br /> + One positive side effect of producing an RSS file is that it can also be used by headline aggregation services like <a href="http://www.moreover.com/">Moreover.com</a>, who power news portals, specialist news search engines, business intelligence services or provide newsfeeds to websites. Most such companies use crawler-based technologies to aggregate and do not insist upon content being available in RSS, however they do have some requirements which having an RSS file addresses, sparing the need for any work on the part of a website that already publishes its headlines in RSS. + </td> + </tr> + <tr> + <td width="620" class="content" colspan="2"><br /> + <a href="#top">top</a> + </td> + </tr> + <tr> + <td width="620" class="contentbold" colspan="2"><br /> + <a name="howrss">4. How can I create an RSS file?</a> + </td> + </tr> + <tr> + <td width="620" class="content" colspan="2"><br /> + RSS is a simple XML format and anyone who has experience in a mark-up language like HTML or XML should find it very easy to create and maintain an RSS file by hand. + <br /><br />Many websites prefer to generate their RSS file using a programming language, which involves a little more work to begin with but means that maintenance is no longer an issue. + </td> + </tr> + <tr> + <td width="620" class="contentbold" colspan="2"><br /> + In this section: + </td> + </tr> + <tr> + <td width="620" class="content" colspan="2"> + <a href="#specifications">RSS Specifications</a><br /> + <a href="#validators">RSS Validators</a><br /> + <a href="#tutorials">RSS Tutorials - The Basics</a><br /> + <a href="#tutorialsgen">RSS Tutorials - Generating RSS</a><br /> + <a href="#examples">RSS Examples</a><br /> + <a href="#tools">RSS Tools & Utilities</a><br /> + </td> + </tr> + <tr> + <td width="620" class="contentbold" colspan="2"><br /> + <a name="specifications">RSS Specifications:</a><br /> + </td> + </tr> + <tr> + <td width="15" class="content" align="center" valign="top">•</td> + <td width="605" class="content" valign="top"><strong>RSS 0.93</strong> (Planning stage, April 2001)<br /><a href="http://backend.userland.com/rss093">http://backend.userland.com/rss093</a> (Userland) + </td> + </tr> + <tr> + <td width="15" class="content" align="center" valign="top">•</td> + <td width="605" class="content" valign="top"><strong>RSS 0.92</strong> (December 2000)<br /><a href="http://backend.userland.com/rss092">http://backend.userland.com/rss092</a> (Userland)<br /> + </td> + </tr> + <tr> + <td width="15" class="content" align="center" valign="top">•</td> + <td width="605" class="content" valign="top"><strong>RSS 1.0</strong> (December 2000)<br /><a href="http://groups.yahoo.com/group/rss-dev/files/specification.html">http://groups.yahoo.com/group/rss-dev/files/specification.html</a> (RSS-DEV Working Group)<br /> + </td> + </tr> + <tr> + <td width="15" class="content" align="center" valign="top">•</td> + <td width="605" class="content" valign="top"><strong>RSS 0.91</strong> (July 1999)<br /><a href="http://backend.userland.com/rss091">http://backend.userland.com/rss091</a> (Userland)<br /> + <a href="http://www.purplepages.ie/RSS/netscape/rss0.91.html">http://www.purplepages.ie/RSS/netscape/rss0.91.html</a> (Netscape)<br /> + <a href="http://my.netscape.com/publish/formats/rss-spec-0.91.html">http://my.netscape.com/publish/formats/rss-spec-0.91.html</a> (Netscape, Revision 3)<br /> + </td> + </tr> + <tr> + <td width="15" class="content" align="center" valign="top">•</td> + <td width="605" class="content" valign="top"><strong>RSS 0.90</strong> (March 1999)<br /><a href="http://www.purplepages.ie/RSS/netscape/rss0.90.html">http://www.purplepages.ie/RSS/netscape/rss0.90.html</a> (Netscape)<br /> + </td> + </tr> + <tr> + <td width="620" class="contentbold" colspan="2"><br /> + <a name="validators">RSS Validators</a>:<br /> + </td> + </tr> + <tr> + <td width="15" class="content" align="center" valign="top">•</td> + <td width="605" class="content"><a href="http://aggregator.userland.com/validator">http://aggregator.userland.com/validator</a> (RSS 0.91, RSS 0.92)</td> + </tr> + <tr> + <td width="15" class="content" align="center" valign="top">•</td> + <td width="605" class="content"><a href="http://www.bath.ac.uk/~ccslrd/rss_validator/1.0/">http://www.bath.ac.uk/~ccslrd/rss_validator/1.0/</a> (RSS 1.0)</td> + </tr> + <tr> + <td width="15" class="content" align="center" valign="top">•</td> + <td width="605" class="content"><a href="http://www.bath.ac.uk/~ccslrd/rss_validator/">http://www.bath.ac.uk/~ccslrd/rss_validator/</a> (RSS 0.9)</td> + </tr> + <tr> + <td width="620" class="content" colspan="2"><br /> + <strong><a name="tutorials">RSS Tutorials - The Basics:</a></strong> (See also <a href="#specifications">RSS Specifications</a>, <a href="#websites">Websites</a>)<br /> + </td> + </tr> + <tr> + <td width="15" class="content" align="center" valign="top">•</td> + <td width="605" class="content" colspan="2"> - <a href="http://www.oreillynet.com/pub/a/network/4000/08/25/magazine/rss_tut.html">A step-by-step guide to building an RSS 1.0 document from the O'Reilly Network.</a></td> + </tr> + <tr> + <td width="15" class="content" align="center" valign="top">•</td> + <td width="605" class="content" colspan="2"> - <a href="http://publishing.about.com/arts/publishing/library/blrss.htm">An easy to understand introduction to RSS 0.91 from About.com.</a></td> + </tr> + <tr> + <td width="15" class="content" align="center" valign="top">•</td> + <td width="605" class="content" colspan="2"> - <a href="http://webreference.com/xml/column13/index.html">A comprehensive guide to creating RSS 0.91 files from Webreference.</a></td> + </tr> + <tr> + <td width="620" class="contentbold" colspan="2"><br /> + <a name="tutorialsgen">RSS Tutorials - Generating RSS</a>:<br /> + </td> + </tr> + <tr> + <td width="15" class="content" align="center" valign="top">•</td> + <td width="605" class="content">Active Server Pages (ASP)<br /> + <a href="http://www.purplepages.ie/site/articles/article.asp?faq=6&fldAuto=76">An article explaining how RSS files can be generated using ASP.</a> + </td> + </tr> + <tr> + <td width="15" class="content" align="center" valign="top">•</td> + <td width="605" class="content">Perl<br /> + <a href="http://www.webtechniques.com/archives/2000/02/eisenzopf/">Jonathan Eisenzopf explains how his XML::RSS module can be used to create an RSS file.</a> + </td> + </tr> + <tr> + <td width="15" class="content" align="center" valign="top">•</td> + <td width="605" class="content">PHP<br /> + <a href="http://linux.gelrevision.nl/php/">phpChannel, a set of two PHP class files to write rss files.</a> + </td> + </tr> + <tr> + <td width="620" class="contentbold" colspan="2"><br /> + <a name="tools">RSS Tools & Utilities</a>: + </td> + </tr> + <tr> + <td width="620" class="content" colspan="2"><br /> + Aaron Swartz provides a useful online utility called <a href="http://logicerror.com/blogifyYourPage">BlogifyYourPage</a>, that makes it easy to produce an RSS 1.0 file for any page.<br /><br /> + The <a href="http://www.webreference.com/perl/tools/">RSS Channel Editor</a> is a simple Perl CGI script that makes it easy to maintain an RSS channel. It can be used online at Webreference and you can also download the source. + </td> + </tr> + <tr> + <td width="620" class="content" colspan="2"><br /> + <a href="#top">top</a> + </td> + </tr> + <tr> + <td width="620" class="contentbold" colspan="2"><br /> + <a name="examples">RSS Examples</a>: + </td> + </tr> + <tr> + <td width="620" class="content" colspan="2"><br /> + <a href="http://newsfeeds.manilasites.com/">Newsfeeds</a> reviews sources of RSS files, good examples and ideas you can use in putting together your own feed.<br /><br /> + <a href="http://www.ourfavoritesongs.com/">OurFavoriteSongs.Com</a> is a source of popular syndicated files, the top picks of <a href="http://radio.userland.com/">Radio Userland</a> users. + </td> + </tr> + <tr> + <td width="620" class="content" colspan="2"><br /> + <a name="promoterss"><strong>5. How can I promote my RSS file?</strong></a> + <br /><br />There are a couple of important places to register RSS files, firstly <a href="http://www.xmltree.com/">XMLTree.com</a>, a specialist directory of XML content, and secondly <a href="http://my.userland.com/">My.Userland.Com</a>. Once an RSS file has been included in these sources it is likely to be found by websites, online news portals or news reading applications seeking RSS content.<br /><br /> + Websites should also create an information page, about syndicating their headlines. This will make existing users aware that the website has an RSS file so they can add it to their news reading applications or even include it on their own websites.<br /><br /> + This information page will be indexed by regular search engines and can also be submitted to various niche directories: + </td> + </tr> + <tr> + <td width="15" class="content" align="center" valign="top">•</td> + <td width="605" class="content" colspan="2"><a href="http://www.4freecontent.com/">4FreeContent</a></td> + </tr> + <tr> + <td width="15" class="content" align="center" valign="top">•</td> + <td width="605" class="content" colspan="2"><a href="http://www.findsticky.com/">FindSticky</a></td> + </tr> + <tr> + <td width="15" class="content" align="center" valign="top">•</td> + <td width="605" class="content" colspan="2"><a href="http://www.freesticky.com/">FreeSticky</a></td> + </tr> + <tr> + <td width="15" class="content" align="center" valign="top">•</td> + <td width="605" class="content" colspan="2"><a href="http://newsfeeds.manilasites.com/">Newsfeeds</a></td> + </tr> + <tr> + <td width="15" class="content" align="center" valign="top">•</td> + <td width="605" class="content" colspan="2"><a href="http://www.purplepages.ie/site/content/">Purple Pages</a></td> + </tr> + <tr> + <td width="15" class="content" align="center" valign="top">•</td> + <td width="605" class="content" colspan="2"><a href="http://www.woodoggy.com/">WooDoggy</a></td> + </tr> + <tr> + <td width="620" class="content" colspan="2"><br /> + Websites that are interested in having their headlines picked up by organisations that aggregate headline content may also wish to visit: + </td> + </tr> + <tr> + <td width="15" class="content" align="center" valign="top">•</td> + <td width="605" class="content" colspan="2"><a href="http://www.linkyournews.com/">LinkYourNews.com</a></td> + </tr> + <tr> + <td width="15" class="content" align="center" valign="top">•</td> + <td width="605" class="content" colspan="2"><a href="http://www.magportal.com/">MagPortal.com</a></td> + </tr> + <tr> + <td width="15" class="content" align="center" valign="top">•</td> + <td width="605" class="content" colspan="2"><a href="http://www.moreover.com/">Moreover.com</a></td> + </tr> + <tr> + <td width="15" class="content" align="center" valign="top">•</td> + <td width="605" class="content" colspan="2"><a href="http://www.newsnow.co.uk/">NewsNow.co.uk</a></td> + </tr> + <tr> + <td width="15" class="content" align="center" valign="top">•</td> + <td width="605" class="content" colspan="2"><a href="http://www.newsisfree.com/">NewsIsFree.com</a></td> + </tr> + <tr> + <td width="620" class="contentbold" colspan="2"><br /> + <a name="morerss">6. Where can I find more information about RSS?</a><br /><br /> + <a name="websites">Websites</a> + </td> + </tr> + <tr> + <td width="15" class="content" align="center" valign="top">•</td> + <td width="605" class="content" colspan="2"><a href="http://www.oreillynet.com/rss/">O'Reilly DevCenter RSS</a> - Articles about RSS from the O'Reilly Network.</td> + </tr> + <tr> + <td width="15" class="content" align="center" valign="top">•</td> + <td width="605" class="content" colspan="2"><a href="http://blogspace.com/rss/">RSS Info</a> - News and information on the RSS format</td> + </tr> + <tr> + <td width="15" class="content" align="center" valign="top">•</td> + <td width="605" class="content" colspan="2"><a href="http://rsswhys.weblogger.com/">RSS Why?s</a> - A site that aims to objectively and concisely explore all the points surrounding the creation, maintenance, and history of RSS.</td> + </tr> + <tr> + <td width="15" class="content" align="center" valign="top">•</td> + <td width="605" class="content" colspan="2"><a href="http://www.webreference.com/authoring/languages/xml/rss/">WebReference RSS Articles</a> - A collection of RSS articles and resources from Webreference.</td> + </tr> + <tr> + <td width="620" class="contentbold" colspan="2"><br /> + Discussion Lists + </td> + </tr> + <tr> + <td width="15" class="content" align="center" valign="top">•</td> + <td width="605" class="content" colspan="2"><a href="http://groups.yahoo.com/group/reallySimpleSyndication">ReallySimpleSyndication</a> - RSS 0.93.</td> + </tr> + <tr> + <td width="15" class="content" align="center" valign="top">•</td> + <td width="605" class="content" colspan="2"><a href="http://groups.yahoo.com/group/rss-dev">RSS-DEV</a> - RSS 1.0.</td> + </tr> + <tr> + <td width="15" class="content" align="center" valign="top">•</td> + <td width="605" class="content" colspan="2"><a href="http://groups.yahoo.com/group/syndication">Syndication</a> - XML syndication, mainly RSS 0.91.</td> + </tr> + <tr> + <td width="620" class="contentbold" colspan="2"><br /> + More RSS FAQs + </td> + </tr> + <tr> + <td width="15" class="content" align="center" valign="top">•</td> + <td width="605" class="content" colspan="2"><a href="http://www.voidstar.com/rssfaq">RSS FAQ</a> - A detailed RSS FAQ from Julian Bond, readers can also contribute.</td> + </tr> + <tr> + <td width="620" class="content" colspan="2"><br /> + <a href="#top">top</a> + </td> + </tr> + <tr> + <td width="620" class="contentsmall" colspan="2"><br /><br /><a href="http://www.rssfaq.com/">RSSFAQ</a> Copyright © 2001 Members of the Syndication, RSS-DEV and ReallySimpleSyndication Groups.</td> + </tr> + <tr> + <td width="620" class="contentsmall" colspan="2">You may freely copy and distribute this document. Please give acknowledgements if you do.</td> + </tr> + <tr> + <td width="620" class="contentsmall" colspan="2">Last Updated: 24-August-2001 <a href="mailto:alis@purplepages.ie">Alis Marsden</a>.</td> + </tr> +</table> +</body> +</html> diff --git a/plugins/rssfeed/rss/testlibrss.cpp b/plugins/rssfeed/rss/testlibrss.cpp new file mode 100644 index 0000000..5d98bba --- /dev/null +++ b/plugins/rssfeed/rss/testlibrss.cpp @@ -0,0 +1,75 @@ +#include "testlibrss.h" + +#include "image.h" + +#include <kaboutdata.h> +#include <kcmdlineargs.h> +#include <kapplication.h> +#include <kdebug.h> + +using namespace RSS; + +static const KCmdLineOptions options[] = +{ + { "+url", I18N_NOOP("URL of feed"), 0 }, + KCmdLineLastOption +}; + + +void Tester::test( const QString &url ) +{ + Loader *loader = Loader::create(); + connect( loader, SIGNAL( loadingComplete( Loader *, Document, Status ) ), + this, SLOT( slotLoadingComplete( Loader *, Document, Status ) ) ); + loader->loadFrom( url, new FileRetriever ); +} + +void Tester::slotLoadingComplete( Loader *loader, Document doc, Status status ) +{ + if ( status == Success ) + { + kdDebug() << "Successfully retrieved '" << doc.title() << "'" << endl; + kdDebug() << doc.description() << endl; + + if ( doc.image() ) { + kdDebug() << "Image: "; + kdDebug() << " Title: " << doc.image()->title() << endl; + kdDebug() << " URL: " << doc.image()->url() << endl; + kdDebug() << " Link: " << doc.image()->link() << endl; + } + + kdDebug() << "Articles:" << endl; + + Article::List list = doc.articles(); + Article::List::ConstIterator it; + Article::List::ConstIterator en=list.end(); + for (it = list.begin(); it != en; ++it) + { + kdDebug() << "\tTitle: " << (*it).title() << endl; + kdDebug() << "\tText: " << (*it).description() << endl; + } + } + + if ( status != Success ) + kdDebug() << "ERROR " << loader->errorCode() << endl; + + kapp->quit(); +} + +int main( int argc, char **argv ) +{ + KAboutData aboutData( "testlibrss", "testlibrss", "0.1" ); + KCmdLineArgs::init( argc, argv, &aboutData ); + KCmdLineArgs::addCmdLineOptions( options ); + KApplication app; + + KCmdLineArgs *args = KCmdLineArgs::parsedArgs(); + if ( args->count() != 1 ) args->usage(); + + Tester tester; + tester.test( args->arg( 0 ) ); + + return app.exec(); +} + +#include "testlibrss.moc" diff --git a/plugins/rssfeed/rss/testlibrss.h b/plugins/rssfeed/rss/testlibrss.h new file mode 100644 index 0000000..c65fa3b --- /dev/null +++ b/plugins/rssfeed/rss/testlibrss.h @@ -0,0 +1,25 @@ +#ifndef TESTLIBRSS_H +#define TESTLIBRSS_H + +#include <qobject.h> + +#include "loader.h" +#include "document.h" +#include "article.h" +#include "global.h" + +using RSS::Loader; +using RSS::Document; +using RSS::Status; + +class Tester : public QObject +{ + Q_OBJECT + public: + void test( const QString &url ); + + private slots: + void slotLoadingComplete( Loader *loader, Document doc, Status status ); +}; + +#endif diff --git a/plugins/rssfeed/rss/textinput.cpp b/plugins/rssfeed/rss/textinput.cpp new file mode 100644 index 0000000..432b773 --- /dev/null +++ b/plugins/rssfeed/rss/textinput.cpp @@ -0,0 +1,96 @@ +/* + * textinput.cpp + * + * Copyright (c) 2001, 2002, 2003 Frerich Raabe <raabe@kde.org> + * + * 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. For licensing and distribution details, check the + * accompanying file 'COPYING'. + */ +#include "textinput.h" +#include "tools_p.h" + +#include <kurl.h> + +#include <qdom.h> + +using namespace RSS; + +struct TextInput::Private : public Shared +{ + QString title; + QString description; + QString name; + KURL link; +}; + +TextInput::TextInput() : d(new Private) +{ +} + +TextInput::TextInput(const TextInput &other) : d(0) +{ + *this = other; +} + +TextInput::TextInput(const QDomNode &node) : d(new Private) +{ + QString elemText; + + if (!(elemText = extractNode(node, QString::fromLatin1("title"))).isNull()) + d->title = elemText; + if (!(elemText = extractNode(node, QString::fromLatin1("description"))).isNull()) + d->description = elemText; + if (!(elemText = extractNode(node, QString::fromLatin1("name")))) + d->name = elemText; + if (!(elemText = extractNode(node, QString::fromLatin1("link"))).isNull()) + d->link = elemText; +} + +TextInput::~TextInput() +{ + if (d->deref()) + delete d; +} + +QString TextInput::title() const +{ + return d->title; +} + +QString TextInput::description() const +{ + return d->description; +} + +QString TextInput::name() const +{ + return d->name; +} + +const KURL &TextInput::link() const +{ + return d->link; +} + +TextInput &TextInput::operator=(const TextInput &other) +{ + if (this != &other) { + other.d->ref(); + if (d && d->deref()) + delete d; + d = other.d; + } + return *this; +} + +bool TextInput::operator==(const TextInput &other) const +{ + return d->title == other.title() && + d->description == other.description() && + d->name == other.name() && + d->link == other.link(); +} + +// vim:noet:ts=4 diff --git a/plugins/rssfeed/rss/textinput.h b/plugins/rssfeed/rss/textinput.h new file mode 100644 index 0000000..dd13c42 --- /dev/null +++ b/plugins/rssfeed/rss/textinput.h @@ -0,0 +1,121 @@ +/* + * textinput.h + * + * Copyright (c) 2001, 2002, 2003 Frerich Raabe <raabe@kde.org> + * + * 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. For licensing and distribution details, check the + * accompanying file 'COPYING'. + */ +#ifndef LIBRSS_TEXTINPUT_H +#define LIBRSS_TEXTINPUT_H + +#include "global.h" + +class KURL; + +class QDomNode; +class QString; + +namespace RSS +{ + /** + * Represents a text input facility as stored in a RSS file for the purpose + * of allowing users to submit queries back to the publisher's site. You + * don't have to instantiate one of these yourself, the common way to access + * instances is via Document::textInput(). + * @see Document::textInput() + */ + class TextInput + { + public: + /** + * Default constructor. + */ + TextInput(); + + /** + * Copy constructor. + * @param other The TextInput object to copy. + */ + TextInput(const TextInput &other); + + /** + * Constructs a TextInput from a piece of RSS markup. + * @param node A QDomNode which references the DOM leaf to be used + * for constructing the TextInput. + */ + TextInput(const QDomNode &node); + + /** + * Assignment operator. + * @param other The TextInput object to clone. + * @return A reference to the cloned TextInput object. + */ + TextInput &operator=(const TextInput &other); + + /** + * Compares two text inputs. Two text inputs are considered + * identical if their properties (title, description, link etc.) + * are identical. + * @param other The text input to compare with. + * @return Whether the two text inputs are equal. + */ + bool operator==(const TextInput &other) const; + + /** + * Convenience method. Simply calls !operator==(). + * @param other The text input to compared with. + * @return Whether the two text inputs are unequal. + */ + bool operator!=(const TextInput &other) const { return !operator==(other); } + + /** + * Destructor. + */ + virtual ~TextInput(); + + /** + * RSS 0.90 and upwards + * @return The title (often a label to be used for the input field) + * of the text input, or QString::null if no title is available. + */ + QString title() const; + + /** + * RSS 0.90 and upwards + * @return The description (usually used as a tooltip which appears + * if the mouse hovers above the input field for a short time) of + * the text input, or QString::null if no description is + * available. + */ + QString description() const; + + /** + * RSS 0.90 and upwards + * @return The name of the text input (what's this for?) of the + * text input, or QString::null, if no name is available. + */ + QString name() const; + + /** + * RSS 0.90 and upwards + * @return A link to which the contents of the input field should + * be sent after the user specified them. This is often a CGI + * program on a remote server which evaluates the entered + * information. An empty KURL is returned in case no link is + * available. + * Note that the RSS 0.91 Specification dictates that URLs not + * starting with "http://" or "ftp://" are considered invalid. + */ + const KURL &link() const; + + private: + struct Private; + Private *d; + }; +} + +#endif // LIBRSS_TEXTINPUT_H +// vim: noet:ts=4 diff --git a/plugins/rssfeed/rss/tools_p.cpp b/plugins/rssfeed/rss/tools_p.cpp new file mode 100644 index 0000000..c1ebbc9 --- /dev/null +++ b/plugins/rssfeed/rss/tools_p.cpp @@ -0,0 +1,51 @@ +/* + * tools_p.cpp + * + * Copyright (c) 2001, 2002, 2003 Frerich Raabe <raabe@kde.org> + * + * 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. For licensing and distribution details, check the + * accompanying file 'COPYING'. + */ +#include "tools_p.h" + +#include <krfcdate.h> +#include <qdom.h> + +time_t RSS::parseISO8601Date(const QString &s) +{ + // do some sanity check: 26-12-2004T00:00+00:00 is parsed to epoch+1 in the KRFCDate, which is wrong. So let's check if the date begins with YYYY -fo + if (s.stripWhiteSpace().left(4).toInt() < 1000) + return 0; // error + + // FIXME: imho this is done in KRFCDate::parseDateISO8601() automatically, so we could omit it? -fo + if (s.find('T') != -1) + return KRFCDate::parseDateISO8601(s); + else + return KRFCDate::parseDateISO8601(s + "T12:00:00"); +} + + +QString RSS::extractNode(const QDomNode &parent, const QString &elemName, bool isInlined) +{ + QDomNode node = parent.namedItem(elemName); + if (node.isNull()) + return QString::null; + + QString result = node.toElement().text(); + + bool hasPre = result.contains("<pre>",false); + bool hasHtml = hasPre || result.contains("<"); // FIXME: test if we have html, should be more clever -> regexp + if(!isInlined && !hasHtml) // perform nl2br if not a inline elt and it has no html elts + result = result = result.replace(QChar('\n'), "<br />"); + if(!hasPre) // strip white spaces if no <pre> + result = result.simplifyWhiteSpace(); + + if (result.isEmpty()) + return QString::null; + + return result; +} + +// vim:noet:ts=4 diff --git a/plugins/rssfeed/rss/tools_p.h b/plugins/rssfeed/rss/tools_p.h new file mode 100644 index 0000000..5076004 --- /dev/null +++ b/plugins/rssfeed/rss/tools_p.h @@ -0,0 +1,34 @@ +/* + * tools_p.h + * + * Copyright (c) 2001, 2002, 2003 Frerich Raabe <raabe@kde.org> + * + * 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. For licensing and distribution details, check the + * accompanying file 'COPYING'. + */ +#ifndef LIBRSS_TOOLS_P_H +#define LIBRSS_TOOLS_P_H + +#include <time.h> + +class QDomNode; +class QString; + +namespace RSS +{ + struct Shared + { + Shared() : count(1) { } + void ref() { count++; } + bool deref() { return !--count; } + unsigned int count; + }; + + QString extractNode(const QDomNode &parent, const QString &elemName, bool isInlined=true); + time_t parseISO8601Date(const QString &s); +} + +#endif // LIBRSS_TOOLS_P_H +// vim:noet:ts=4 diff --git a/plugins/rssfeed/rssarticle.cpp b/plugins/rssfeed/rssarticle.cpp new file mode 100644 index 0000000..afc932d --- /dev/null +++ b/plugins/rssfeed/rssarticle.cpp @@ -0,0 +1,103 @@ +/*************************************************************************** + * Copyright (C) 2006 by Alan Jones * + * skyphyr@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ +#include "rssarticle.h" + +namespace kt +{ + + RssArticle::RssArticle( ) + { + + } + + RssArticle::RssArticle(RSS::Article article) + { + //these lines generate errors when compiling + m_title = article.title(); + m_link = article.link(); + m_description = article.description(); + m_pubDate = article.pubDate(); + m_guid = article.guid(); + m_downloaded = 0; + } + + RssArticle::RssArticle(const RssArticle &other) + { + *this = other; + } + + RssArticle::RssArticle(QString title, KURL link, QString description, QDateTime pubDate, QString guid, int downloaded) + { + m_title = title; + m_link = link; + m_description = description; + m_pubDate = pubDate; + m_guid = guid; + m_downloaded = downloaded; + } + + RssArticle &RssArticle::operator=(const RssArticle &other) + { + if (&other != this) + { + m_title = other.title(); + m_link = other.link(); + m_description = other.description(); + m_pubDate = other.pubDate(); + m_guid = other.guid(); + m_downloaded = other.downloaded(); + } + return *this; + } + + bool RssArticle::operator==(const RssArticle &other) const + { + //let's try just using the guid for now as it should be sufficient + //return m_title==other.title() && m_link==other.link() && m_description==other.description() && m_pubDate==other.pubDate(); + return m_guid==other.guid(); + } + + QDataStream &operator<<( QDataStream &out, const RssArticle &article ) + { + out << article.title() << article.link() << article.description() << article.pubDate() << article.guid() << article.downloaded(); + + return out; + } + + QDataStream &operator>>( QDataStream &in, RssArticle &article ) + { + KURL link; + QString title; + QString description; + QDateTime pubDate; + QString guid; + int downloaded; + in >> title >> link >> description >> pubDate >> guid >> downloaded; + article = RssArticle(title, link, description, pubDate, guid, downloaded); + + return in; + } + + RssArticle::~RssArticle() + { + + } + +} diff --git a/plugins/rssfeed/rssarticle.h b/plugins/rssfeed/rssarticle.h new file mode 100644 index 0000000..4d8c536 --- /dev/null +++ b/plugins/rssfeed/rssarticle.h @@ -0,0 +1,82 @@ +/*************************************************************************** + * Copyright (C) 2006 by Alan Jones * + * skyphyr@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ +#ifndef RSSARTICLE_H +#define RSSARTICLE_H + +#include <qstring.h> +#include <qdatetime.h> +#include <qdatastream.h> + +#include <kurl.h> + +#include "rss/article.h" + +namespace kt +{ + /** + * @brief RssFeed Manager Class + * @author Alan Jones <skyphyr@gmail.com> + * + * + */ + + class RssArticle + { + public: + + typedef QValueList<RssArticle> List; + + /** + * Default constructor. + */ + RssArticle(); + RssArticle(RSS::Article article); + RssArticle(const RssArticle &other); + RssArticle &operator=(const RssArticle &other); + bool operator==(const RssArticle &other) const; + RssArticle(QString title, KURL link, QString description, QDateTime pubDate, QString guid, int downloaded = 0); + + void setTitle(const QString& title) { m_title=title; } + void setDownloaded(const int downloaded) { m_downloaded=downloaded; } + + QString title() const { return m_title; } + KURL link() const { return m_link; } + QString description() const { return m_description; } + QDateTime pubDate() const { return m_pubDate; } + QString guid() const { return m_guid; } + int downloaded() const { return m_downloaded; } + + ~RssArticle(); + + private: + KURL m_link; + QString m_title; + QString m_description; + QDateTime m_pubDate; + QString m_guid; + int m_downloaded; + }; + + QDataStream &operator<<( QDataStream &out, const RssArticle &article ); + QDataStream &operator>>( QDataStream &in, RssArticle &article ); + +} + +#endif diff --git a/plugins/rssfeed/rssfeed.cpp b/plugins/rssfeed/rssfeed.cpp new file mode 100644 index 0000000..0d1244b --- /dev/null +++ b/plugins/rssfeed/rssfeed.cpp @@ -0,0 +1,359 @@ +/*************************************************************************** + * Copyright (C) 2006 by Alan Jones * + * skyphyr@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ +#include "rssfeed.h" + +#include <kglobal.h> +#include <kstandarddirs.h> +#include <krfcdate.h> +#include <kio/netaccess.h> +#include <qfile.h> +#include <qapplication.h> +#include <qdir.h> + +namespace kt +{ + + void RssFeed::startFeed() + { + if (m_active) + { + refreshFeed(); + refreshTimer.start(QTime().msecsTo(m_autoRefresh)); + } + else + { + refreshTimer.stop(); + } + } + + void RssFeed::initialize() + { + feedLoading = false; + loadArticles(); + + connect(&refreshTimer, SIGNAL(timeout()), this, SLOT( refreshFeed() ) ); + connect(this, SIGNAL(articlesChanged(const RssArticle::List&)), this, SLOT( saveArticles() ) ); + + startFeed(); + } + + RssFeed::RssFeed(QObject * parent) : QObject(parent) + { + m_active = false; + m_articleAge = 365; + m_ignoreTTL = false; + m_title = "New"; + + initialize(); + } + + RssFeed::RssFeed(KURL feedUrl, QString title, bool active, int articleAge, bool ignoreTTL, QTime autoRefresh ) + { + m_feedUrl = feedUrl; + m_title = title; + m_active = active; + m_articleAge = articleAge; + m_ignoreTTL = ignoreTTL; + m_autoRefresh = autoRefresh; + + initialize(); + } + + RssFeed::RssFeed(const RssFeed &other) : QObject() + { + *this = other; + } + + RssFeed &RssFeed::operator=(const RssFeed &other) + { + if (&other != this) + { + m_feedUrl = other.feedUrl(); + m_title = other.title(); + m_active = other.active(); + m_articleAge = other.articleAge(); + m_ignoreTTL = other.ignoreTTL(); + m_autoRefresh = other.autoRefresh(); + } + + initialize(); + + return *this; + } + + void RssFeed::setFeedUrl( const KURL& url ) + { + if (m_feedUrl != url) + { + m_feedUrl = url; + loadArticles(); + startFeed(); + emit feedUrlChanged(url); + } + } + + void RssFeed::setFeedUrl( const QString& url ) + { + if (m_feedUrl != url) + { + m_feedUrl = url; + loadArticles(); + startFeed(); + emit feedUrlChanged(url); + } + } + + void RssFeed::setActive( bool active ) + { + if (m_active != active) + { + m_active = active; + + startFeed(); + + emit activeChanged(active); + } + } + + void RssFeed::setArticleAge( int articleAge ) + { + if (m_articleAge != articleAge) + { + if (articleAge < m_articleAge) + { + cleanArticles(); + } + + m_articleAge = articleAge; + emit articleAgeChanged(articleAge); + } + } + + void RssFeed::setTitle( const QString& title ) + { + if (m_title != title) + { + m_title = title; + emit titleChanged(title); + } + } + + void RssFeed::setAutoRefresh( const QTime& autoRefresh ) + { + if (m_autoRefresh != autoRefresh) + { + m_autoRefresh = autoRefresh; + if (m_active) + { + refreshTimer.changeInterval(QTime().msecsTo(m_autoRefresh)); + } + + emit autoRefreshChanged(autoRefresh); + } + } + + void RssFeed::setIgnoreTTL( bool ignoreTTL ) + { + if (m_ignoreTTL != ignoreTTL) + { + m_ignoreTTL = ignoreTTL; + emit ignoreTTLChanged(ignoreTTL); + } + } + + QString RssFeed::getFilename() + { + QDir directory; + directory.mkdir(KGlobal::dirs()->saveLocation("data","ktorrent") + "rssfeeds"); + return KGlobal::dirs()->saveLocation("data","ktorrent") + "rssfeeds/" + m_feedUrl.prettyURL(-1).replace("/", "_").replace(":", "_") + ".ktr"; + + } + + void RssFeed::loadArticles() + { + QString filename = getFilename(); + + //load articles from disk + QFile file(filename); + + if (file.exists()) + { + file.open( IO_ReadOnly ); + QDataStream in(&file); + + in >> m_articles; + emit articlesChanged( m_articles ); + } + } + + void RssFeed::saveArticles() + { + QString filename = getFilename(); + + //load articles from disk + QFile file(filename); + + file.open( IO_WriteOnly ); + QDataStream out(&file); + + out << m_articles; + } + + void RssFeed::cleanArticles() + { + bool removed = false; + + RssArticle::List::iterator it; + for ( it = m_articles.begin(); it != m_articles.end(); ) + { + if ((*it).pubDate().daysTo(QDateTime::currentDateTime()) > m_articleAge) + { + it = m_articles.erase(it); + removed = true; + } + else + { + it++; + } + } + + if (removed) + { + emit articlesChanged(m_articles); + } + + } + + void RssFeed::clearArticles() + { + m_articles.clear(); + } + + void RssFeed::refreshFeed() + { + if (feedLoading) + return; + + feedLoading = true; + cleanArticles(); + Loader * feedLoader = Loader::create(); + connect( feedLoader, SIGNAL( loadingComplete( Loader *, Document, Status ) ), + this, SLOT( feedLoaded( Loader *, Document, Status ) ) ); + feedLoader->loadFrom( m_feedUrl, new FileRetriever ); + } + + void RssFeed::feedLoaded(Loader *feedLoader, Document doc, Status status) + { + feedLoading = false; + + if ( status == Success ) + { + bool added = false; + + if (m_title.isEmpty() || m_title == QString("New")) + { + setTitle(doc.title()); + emit updateTitle(doc.title()); + } + + if (!m_ignoreTTL) + { + if (doc.ttl() < 0) + { + setAutoRefresh(QTime().addSecs(3600)); + } + else + { + setAutoRefresh(QTime().addSecs(doc.ttl() * 60)); + } + } + + RssArticle curArticle; + + for (int i=doc.articles().count()-1; i>=0; i--) + { + curArticle = doc.articles()[i]; + if (curArticle.pubDate().daysTo(QDateTime::currentDateTime()) < m_articleAge && !m_articles.contains(curArticle)) + { + m_articles.prepend(curArticle); + emit scanRssArticle(curArticle); + added = true; + } + } + + if (added) + { + emit articlesChanged(m_articles); + } + } else { + qDebug( "There was and error loading the feed\n"); + } + + disconnect( feedLoader, SIGNAL( loadingComplete( Loader *, Document, Status ) ), + this, SLOT( feedLoaded( Loader *, Document, Status ) ) ); + feedLoader->deleteLater(); + + } + + void RssFeed::setDownloaded(QString link, int downloaded) + { + bool changed = false; + + RssArticle::List::iterator it; + for ( it = m_articles.begin(); it != m_articles.end(); it++ ) + { + if ((*it).link().prettyURL() == link) + { + (*it).setDownloaded( downloaded ); + changed = true; + } + } + + if (changed) + { + emit articlesChanged(m_articles); + } + } + + QDataStream &operator<<( QDataStream &out, const RssFeed &feed ) + { + out << feed.feedUrl() << feed.title() << int(feed.active()) << feed.articleAge() << int(feed.ignoreTTL()) << feed.autoRefresh(); + + return out; + } + + QDataStream &operator>>( QDataStream &in, RssFeed &feed ) + { + KURL feedUrl; + QString title; + int active; + int articleAge; + int ignoreTTL; + QTime autoRefresh; + in >> feedUrl >> title >> active >> articleAge >> ignoreTTL >> autoRefresh; + feed = RssFeed(feedUrl, title, active, articleAge, ignoreTTL, autoRefresh); + + return in; + } + + RssFeed::~RssFeed() + { + } +} diff --git a/plugins/rssfeed/rssfeed.h b/plugins/rssfeed/rssfeed.h new file mode 100644 index 0000000..9bcd2f7 --- /dev/null +++ b/plugins/rssfeed/rssfeed.h @@ -0,0 +1,127 @@ +/*************************************************************************** + * Copyright (C) 2006 by Alan Jones * + * skyphyr@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ +#ifndef RSSFEED_H +#define RSSFEED_H + +#include <qobject.h> +#include <qstring.h> +#include <qdatetime.h> +#include <qvaluelist.h> +#include <qtimer.h> +#include <qdatastream.h> + +#include <kurl.h> + +#include "rss/loader.h" +#include "rss/document.h" + +#include "rssarticle.h" + +using namespace RSS; + +namespace kt +{ + /** + * @brief RssFeed Class + * @author Alan Jones <skyphyr@gmail.com> + * + * + */ + + class RssFeed : public QObject + { + Q_OBJECT + public: + + /** + * Default constructor. + */ + RssFeed(QObject * parent = 0); + RssFeed(KURL feedUrl, QString title = "", bool active = false, int articleAge = 3, bool ignoreTTL = false, QTime autoRefresh = QTime()); + RssFeed(const RssFeed &other); + RssFeed &operator=(const RssFeed &other); + ~RssFeed(); + + KURL feedUrl() const { return m_feedUrl; } + bool active() const { return m_active; } + int articleAge() const { return m_articleAge; } + QString title() const { return m_title; } + QTime autoRefresh() const { return m_autoRefresh; } + bool ignoreTTL() const { return m_ignoreTTL; } + + + RssArticle::List articles() const { return m_articles; } + + + public slots: + void refreshFeed(); + void feedLoaded(Loader *feedLoader, Document doc, Status status); + + void clearArticles(); + + void setFeedUrl( const KURL& url ); + void setFeedUrl( const QString& url ); + void setActive( bool active ); + void setArticleAge( int articleAge ); + void setTitle( const QString& title ); + void setAutoRefresh( const QTime& autoRefresh ); + void setIgnoreTTL( bool ignoreTTL ); + void saveArticles(); + + void setDownloaded(QString link, int downloaded); + + signals: + void feedUrlChanged( const KURL& url ); + void activeChanged( bool active ); + void articleAgeChanged( int articleAge ); + void titleChanged( const QString& title ); + void updateTitle( const QString& title ); + void autoRefreshChanged( const QTime& autoRefresh ); + void ignoreTTLChanged( bool ignoreTTL ); + + void articlesChanged( const RssArticle::List& articles ); + + void scanRssArticle( RssArticle article ); + + private: + KURL m_feedUrl; + bool m_active; + int m_articleAge; + QString m_title; + QTime m_autoRefresh; + bool m_ignoreTTL; + RssArticle::List m_articles; + QTimer refreshTimer; + + bool feedLoading; + + QString getFilename(); + void initialize(); + void startFeed(); + void cleanArticles(); + void loadArticles(); + }; + + QDataStream &operator<<( QDataStream &out, const RssFeed &feed ); + QDataStream &operator>>( QDataStream &in, RssFeed &feed ); + +} + +#endif diff --git a/plugins/rssfeed/rssfeedmanager.cpp b/plugins/rssfeed/rssfeedmanager.cpp new file mode 100644 index 0000000..106ca0f --- /dev/null +++ b/plugins/rssfeed/rssfeedmanager.cpp @@ -0,0 +1,1318 @@ +/*************************************************************************** + * Copyright (C) 2006 by Alan Jones * + * skyphyr@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ +#include "rssfeedmanager.h" + +#include <kdirlister.h> +#include <kfileitem.h> +#include <klocale.h> +#include <kio/netaccess.h> +#include <kstandarddirs.h> +#include <keditlistbox.h> +// #include <kmimetype.h> +#include <kmessagebox.h> + +#include <qstring.h> +#include <qobject.h> +#include <qfile.h> +#include <qdatetime.h> + +#include <qlineedit.h> +#include <kurlrequester.h> +#include <qspinbox.h> +#include <qcheckbox.h> +#include <qdatetimeedit.h> +#include <qtable.h> +#include <qregexp.h> +#include <qlayout.h> + +#include <torrent/globals.h> +#include <util/log.h> +#include <util/constants.h> + +#include <interfaces/coreinterface.h> + +#include <qapplication.h> + +#include "../../libktorrent/torrent/bdecoder.h" +#include "../../libktorrent/torrent/bnode.h" + +#include "rsslinkdownloader.h" + +using namespace bt; + +namespace kt +{ + + RssFeedManager::RssFeedManager(CoreInterface* core, QWidget * parent) : RssFeedWidget(parent) + { + //Construct the manager + m_core = core; + currentFeed = -1; + currentAcceptFilter = -1; + currentRejectFilter = -1; + + feedListSaving = false; + filterListSaving = false; + + //get the articles list setup + feedArticles->setLeftMargin(0); + feedArticles->verticalHeader()->hide(); + feedArticles->setNumCols(3); + feedArticles->setColumnLabels(QStringList() << i18n("Title") << i18n("Description") << i18n("Link")); + feedArticles->horizontalHeader()->setStretchEnabled(true, 0); + feedArticles->hideColumn(1); + feedArticles->hideColumn(2); + + //get the matches list setup + filterMatches->setLeftMargin(0); + filterMatches->verticalHeader()->hide(); + filterMatches->setNumCols(4); + filterMatches->setColumnLabels(QStringList() << i18n("Season") << i18n("Episode") << i18n("Time") << i18n("Link")); + filterMatches->setColumnWidth(0, 60); + filterMatches->setColumnWidth(1, 60); + filterMatches->setColumnWidth(2, 180); + filterMatches->horizontalHeader()->setStretchEnabled(true, 3); + + loadFeedList(); + loadFilterList(); + + //connect the buttons + connect(newFeed, SIGNAL(clicked()), this, SLOT(addNewFeed() ) ); + connect(deleteFeed, SIGNAL(clicked()), this, SLOT(deleteSelectedFeed() ) ); + + connect(newAcceptFilter, SIGNAL(clicked()), this, SLOT(addNewAcceptFilter() ) ); + connect(deleteAcceptFilter, SIGNAL(clicked()), this, SLOT(deleteSelectedAcceptFilter() ) ); + + connect(newRejectFilter, SIGNAL(clicked()), this, SLOT(addNewRejectFilter() ) ); + connect(deleteRejectFilter, SIGNAL(clicked()), this, SLOT(deleteSelectedRejectFilter() ) ); + + //connect the changing of the active feed + connect(feedlist, SIGNAL(selectionChanged()), this, SLOT(changedActiveFeed()) ); + + //connect the changing of the url to enable the refresh button + connect(feedUrl, SIGNAL(textChanged(const QString &)), this, SLOT(changedFeedUrl()) ); + + //connect the changing of the filters + connect(acceptFilterList, SIGNAL(selectionChanged()), this, SLOT(changedActiveAcceptFilter()) ); + connect(rejectFilterList, SIGNAL(selectionChanged()), this, SLOT(changedActiveRejectFilter()) ); + + //connect the selection and downloading of articles + connect(feedArticles, SIGNAL(selectionChanged()), this, SLOT(changedArticleSelection()) ); + connect(downloadArticle, SIGNAL(clicked()), this, SLOT(downloadSelectedArticles()) ); + + //connect the selection, downloading and deletion of matches + connect(filterMatches, SIGNAL(selectionChanged()), this, SLOT(changedMatchSelection()) ); + connect(downloadFilterMatch, SIGNAL(clicked()), this, SLOT(downloadSelectedMatches()) ); + connect(deleteFilterMatch, SIGNAL(clicked()), this, SLOT(deleteSelectedMatches()) ); + + //connect the test text update to the slot + connect(testText, SIGNAL(textChanged(const QString &)), this, SLOT(testTextChanged()) ); + connect(testTestText, SIGNAL(clicked()), this, SLOT(testFilter()) ); + + changedActiveFeed(); + changedActiveAcceptFilter(); + + } + + RssFeedManager::~RssFeedManager() + { + //Destruct the manager + } + + void RssFeedManager::clearArticles() + { + int pos = feeds.find((RssFeed *)sender()); + + if (pos >= 0) + { + feeds.at(pos)->clearArticles(); + if (feedlist->isSelected(pos)) + { + //this feed is active so we should update the display + feedArticles->setNumRows(0); + } + } + } + + void RssFeedManager::changedFeedUrl() + { + refreshFeed->setEnabled(!feedUrl->url().isEmpty()); + } + + void RssFeedManager::connectFeed(int index) + { + + connect(feedTitle, SIGNAL(textChanged(const QString &)), feeds.at(index), SLOT(setTitle(const QString &) ) ); + connect(feeds.at(index), SIGNAL(titleChanged(const QString &)), this, SLOT(setFeedTitle(const QString &) ) ); + + //url + connect(feedUrl, SIGNAL(textChanged(const QString &)), feeds.at(index), SLOT(setFeedUrl(const QString&) ) ); + connect(feeds.at(index), SIGNAL(feedUrlChanged(const KURL&)), feedUrl, SLOT(setKURL(const KURL&) ) ); + + //articleAge + connect(feedArticleAge, SIGNAL(valueChanged(int)), feeds.at(index), SLOT(setArticleAge(int) ) ); + connect(feeds.at(index), SIGNAL(articleAgeChanged(int)), feedArticleAge, SLOT(setValue(int) ) ); + + //active + connect(feedActive, SIGNAL(toggled(bool)), feeds.at(index), SLOT(setActive(bool) ) ); + connect(feeds.at(index), SIGNAL(activeChanged(bool)), feedActive, SLOT(setChecked(bool) ) ); + + //autoRefresh + connect(feedAutoRefresh, SIGNAL(valueChanged(const QTime&)), feeds.at(index), SLOT(setAutoRefresh(const QTime&) ) ); + connect(feeds.at(index), SIGNAL(autoRefreshChanged(const QTime&)), feedAutoRefresh, SLOT(setTime(const QTime&) ) ); + + //ignoreTTL + connect(feedIgnoreTTL, SIGNAL(toggled(bool)), feeds.at(index), SLOT(setIgnoreTTL(bool) ) ); + connect(feeds.at(index), SIGNAL(ignoreTTLChanged(bool)), feedIgnoreTTL, SLOT(setChecked(bool) ) ); + + //articles + connect(feeds.at(index), SIGNAL(articlesChanged(const RssArticle::List&)), this, SLOT(updateArticles(const RssArticle::List&) ) ); + + //connect the refresh button + connect(refreshFeed, SIGNAL(clicked()), feeds.at(index), SLOT(refreshFeed()) ); + } + + void RssFeedManager::disconnectFeed(int index) + { + disconnect(feedTitle, SIGNAL(textChanged(const QString &)), feeds.at(index), SLOT(setTitle(const QString &) ) ); + disconnect(feeds.at(index), SIGNAL(titleChanged(const QString &)), this, SLOT(setFeedTitle(const QString &) ) ); + + //url + disconnect(feedUrl, SIGNAL(textChanged(const QString &)), feeds.at(index), SLOT(setFeedUrl(const QString&) ) ); + disconnect(feeds.at(index), SIGNAL(feedUrlChanged(const KURL&)), feedUrl, SLOT(setKURL(const KURL&) ) ); + + //articleAge + disconnect(feedArticleAge, SIGNAL(valueChanged(int)), feeds.at(index), SLOT(setArticleAge(int) ) ); + disconnect(feeds.at(index), SIGNAL(articleAgeChanged(int)), feedArticleAge, SLOT(setValue(int) ) ); + + //active + disconnect(feedActive, SIGNAL(toggled(bool)), feeds.at(index), SLOT(setActive(bool) ) ); + disconnect(feeds.at(index), SIGNAL(activeChanged(bool)), feedActive, SLOT(setChecked(bool) ) ); + + //autoRefresh + disconnect(feedAutoRefresh, SIGNAL(valueChanged(const QTime&)), feeds.at(index), SLOT(setAutoRefresh(const QTime&) ) ); + disconnect(feeds.at(index), SIGNAL(autoRefreshChanged(const QTime&)), feedAutoRefresh, SLOT(setTime(const QTime&) ) ); + + //ignoreTTL + disconnect(feedIgnoreTTL, SIGNAL(toggled(bool)), feeds.at(index), SLOT(setIgnoreTTL(bool) ) ); + disconnect(feeds.at(index), SIGNAL(ignoreTTLChanged(bool)), feedIgnoreTTL, SLOT(setChecked(bool) ) ); + + //articles + disconnect(feeds.at(index), SIGNAL(articlesChanged(const RssArticle::List&)), this, SLOT(updateArticles(const RssArticle::List&) ) ); + + disconnect(refreshFeed, SIGNAL(clicked()), feeds.at(index), SLOT(refreshFeed()) ); + } + + void RssFeedManager::connectFilter(int index, bool acceptFilter) + { + if (acceptFilter) + { + //title + connect(filterTitle, SIGNAL(textChanged(const QString &)), acceptFilters.at(index), SLOT(setTitle(const QString &) ) ); + connect(acceptFilters.at(index), SIGNAL(titleChanged(const QString &)), this, SLOT(setFilterTitle(const QString &) ) ); + //active + connect(filterActive, SIGNAL(toggled(bool)), acceptFilters.at(index), SLOT(setActive(bool) ) ); + connect(acceptFilters.at(index), SIGNAL(activeChanged(bool)), filterActive, SLOT(setChecked(bool) ) ); + //regExps + connect(filterRegExps, SIGNAL(changed()), this, SLOT(updateRegExps()) ); + //series + connect(filterSeries, SIGNAL(toggled(bool)), acceptFilters.at(index), SLOT(setSeries(bool) ) ); + connect(acceptFilters.at(index), SIGNAL(seriesChanged(bool)), filterSeries, SLOT(setChecked(bool) ) ); + //sansEpisode + connect(filterSansEpisode, SIGNAL(toggled(bool)), acceptFilters.at(index), SLOT(setSansEpisode(bool) ) ); + connect(acceptFilters.at(index), SIGNAL(sansEpisodeChanged(bool)), filterSansEpisode, SLOT(setChecked(bool) ) ); + //minSeason + connect(filterMinSeason, SIGNAL(valueChanged(int)), acceptFilters.at(index), SLOT(setMinSeason(int) ) ); + connect(acceptFilters.at(index), SIGNAL(minSeasonChanged(int)), filterMinSeason, SLOT(setValue(int) ) ); + //minEpisode + connect(filterMinEpisode, SIGNAL(valueChanged(int)), acceptFilters.at(index), SLOT(setMinEpisode(int) ) ); + connect(acceptFilters.at(index), SIGNAL(minEpisodeChanged(int)), filterMinEpisode, SLOT(setValue(int) ) ); + //maxSeason + connect(filterMaxSeason, SIGNAL(valueChanged(int)), acceptFilters.at(index), SLOT(setMaxSeason(int) ) ); + connect(acceptFilters.at(index), SIGNAL(maxSeasonChanged(int)), filterMaxSeason, SLOT(setValue(int) ) ); + //maxEpisode + connect(filterMaxEpisode, SIGNAL(valueChanged(int)), acceptFilters.at(index), SLOT(setMaxEpisode(int) ) ); + connect(acceptFilters.at(index), SIGNAL(maxEpisodeChanged(int)), filterMaxEpisode, SLOT(setValue(int) ) ); + //matches + connect(acceptFilters.at(index), SIGNAL(matchesChanged(const QValueList<FilterMatch>&)), this, SLOT(updateMatches(const QValueList<FilterMatch>&) ) ); + + connect(processFilter, SIGNAL(clicked()), acceptFilters.at(index), SIGNAL(rescanFilter()) ); + + } + else + { + //title + connect(filterTitle, SIGNAL(textChanged(const QString &)), rejectFilters.at(index), SLOT(setTitle(const QString &) ) ); + connect(rejectFilters.at(index), SIGNAL(titleChanged(const QString &)), this, SLOT(setFilterTitle(const QString &) ) ); + //active + connect(filterActive, SIGNAL(toggled(bool)), rejectFilters.at(index), SLOT(setActive(bool) ) ); + connect(rejectFilters.at(index), SIGNAL(activeChanged(bool)), filterActive, SLOT(setChecked(bool) ) ); + //regExps + connect(filterRegExps, SIGNAL(changed()), this, SLOT(updateRegExps()) ); + //series + connect(filterSeries, SIGNAL(toggled(bool)), rejectFilters.at(index), SLOT(setSeries(bool) ) ); + connect(rejectFilters.at(index), SIGNAL(seriesChanged(bool)), filterSeries, SLOT(setChecked(bool) ) ); + //sansEpisode + connect(filterSansEpisode, SIGNAL(toggled(bool)), rejectFilters.at(index), SLOT(setSansEpisode(bool) ) ); + connect(rejectFilters.at(index), SIGNAL(sansEpisodeChanged(bool)), filterSansEpisode, SLOT(setChecked(bool) ) ); + //minSeason + connect(filterMinSeason, SIGNAL(valueChanged(int)), rejectFilters.at(index), SLOT(setMinSeason(int) ) ); + connect(rejectFilters.at(index), SIGNAL(minSeasonChanged(int)), filterMinSeason, SLOT(setValue(int) ) ); + //minEpisode + connect(filterMinEpisode, SIGNAL(valueChanged(int)), rejectFilters.at(index), SLOT(setMinEpisode(int) ) ); + connect(rejectFilters.at(index), SIGNAL(minEpisodeChanged(int)), filterMinEpisode, SLOT(setValue(int) ) ); + //maxSeason + connect(filterMaxSeason, SIGNAL(valueChanged(int)), rejectFilters.at(index), SLOT(setMaxSeason(int) ) ); + connect(rejectFilters.at(index), SIGNAL(maxSeasonChanged(int)), filterMaxSeason, SLOT(setValue(int) ) ); + //maxEpisode + connect(filterMaxEpisode, SIGNAL(valueChanged(int)), rejectFilters.at(index), SLOT(setMaxEpisode(int) ) ); + connect(rejectFilters.at(index), SIGNAL(maxEpisodeChanged(int)), filterMaxEpisode, SLOT(setValue(int) ) ); + //matches + connect(rejectFilters.at(index), SIGNAL(matchesChanged(const QValueList<FilterMatch>&)), this, SLOT(updateMatches(const QValueList<FilterMatch>&) ) ); + + connect(processFilter, SIGNAL(clicked()), rejectFilters.at(index), SIGNAL(rescanFilter()) ); + + } + } + + void RssFeedManager::disconnectFilter(int index, bool acceptFilter) + { + if (acceptFilter) + { + //title + disconnect(filterTitle, SIGNAL(textChanged(const QString &)), acceptFilters.at(index), SLOT(setTitle(const QString &) ) ); + disconnect(acceptFilters.at(index), SIGNAL(titleChanged(const QString &)), this, SLOT(setFilterTitle(const QString &) ) ); + //active + disconnect(filterActive, SIGNAL(toggled(bool)), acceptFilters.at(index), SLOT(setActive(bool) ) ); + disconnect(acceptFilters.at(index), SIGNAL(activeChanged(bool)), filterActive, SLOT(setChecked(bool) ) ); + //regExps + disconnect(filterRegExps, SIGNAL(changed()), this, SLOT(updateRegExps()) ); + //series + disconnect(filterSeries, SIGNAL(toggled(bool)), acceptFilters.at(index), SLOT(setSeries(bool) ) ); + disconnect(acceptFilters.at(index), SIGNAL(seriesChanged(bool)), filterSeries, SLOT(setChecked(bool) ) ); + //sansEpisode + disconnect(filterSansEpisode, SIGNAL(toggled(bool)), acceptFilters.at(index), SLOT(setSansEpisode(bool) ) ); + disconnect(acceptFilters.at(index), SIGNAL(sansEpisodeChanged(bool)), filterSansEpisode, SLOT(setChecked(bool) ) ); + //minSeason + disconnect(filterMinSeason, SIGNAL(valueChanged(int)), acceptFilters.at(index), SLOT(setMinSeason(int) ) ); + disconnect(acceptFilters.at(index), SIGNAL(minSeasonChanged(int)), filterMinSeason, SLOT(setValue(int) ) ); + //minEpisode + disconnect(filterMinEpisode, SIGNAL(valueChanged(int)), acceptFilters.at(index), SLOT(setMinEpisode(int) ) ); + disconnect(acceptFilters.at(index), SIGNAL(minEpisodeChanged(int)), filterMinEpisode, SLOT(setValue(int) ) ); + //maxSeason + disconnect(filterMaxSeason, SIGNAL(valueChanged(int)), acceptFilters.at(index), SLOT(setMaxSeason(int) ) ); + disconnect(acceptFilters.at(index), SIGNAL(maxSeasonChanged(int)), filterMaxSeason, SLOT(setValue(int) ) ); + //maxEpisode + disconnect(filterMaxEpisode, SIGNAL(valueChanged(int)), acceptFilters.at(index), SLOT(setMaxEpisode(int) ) ); + disconnect(acceptFilters.at(index), SIGNAL(maxEpisodeChanged(int)), filterMaxEpisode, SLOT(setValue(int) ) ); + //matches + disconnect(acceptFilters.at(index), SIGNAL(matchesChanged(const QValueList<FilterMatch>&)), this, SLOT(updateMatches(const QValueList<FilterMatch>&) ) ); + + disconnect(processFilter, SIGNAL(clicked()), acceptFilters.at(index), SIGNAL(rescanFilter()) ); + } + else + { + //title + disconnect(filterTitle, SIGNAL(textChanged(const QString &)), rejectFilters.at(index), SLOT(setTitle(const QString &) ) ); + disconnect(rejectFilters.at(index), SIGNAL(titleChanged(const QString &)), this, SLOT(setFilterTitle(const QString &) ) ); + //active + disconnect(filterActive, SIGNAL(toggled(bool)), rejectFilters.at(index), SLOT(setActive(bool) ) ); + disconnect(rejectFilters.at(index), SIGNAL(activeChanged(bool)), filterActive, SLOT(setChecked(bool) ) ); + //regExps + disconnect(filterRegExps, SIGNAL(changed()), this, SLOT(updateRegExps()) ); + //series + disconnect(filterSeries, SIGNAL(toggled(bool)), rejectFilters.at(index), SLOT(setSeries(bool) ) ); + disconnect(rejectFilters.at(index), SIGNAL(seriesChanged(bool)), filterSeries, SLOT(setChecked(bool) ) ); + //sansEpisode + disconnect(filterSansEpisode, SIGNAL(toggled(bool)), rejectFilters.at(index), SLOT(setSansEpisode(bool) ) ); + disconnect(rejectFilters.at(index), SIGNAL(sansEpisodeChanged(bool)), filterSansEpisode, SLOT(setChecked(bool) ) ); + //minSeason + disconnect(filterMinSeason, SIGNAL(valueChanged(int)), rejectFilters.at(index), SLOT(setMinSeason(int) ) ); + disconnect(rejectFilters.at(index), SIGNAL(minSeasonChanged(int)), filterMinSeason, SLOT(setValue(int) ) ); + //minEpisode + disconnect(filterMinEpisode, SIGNAL(valueChanged(int)), rejectFilters.at(index), SLOT(setMinEpisode(int) ) ); + disconnect(rejectFilters.at(index), SIGNAL(minEpisodeChanged(int)), filterMinEpisode, SLOT(setValue(int) ) ); + //maxSeason + disconnect(filterMaxSeason, SIGNAL(valueChanged(int)), rejectFilters.at(index), SLOT(setMaxSeason(int) ) ); + disconnect(rejectFilters.at(index), SIGNAL(maxSeasonChanged(int)), filterMaxSeason, SLOT(setValue(int) ) ); + //maxEpisode + disconnect(filterMaxEpisode, SIGNAL(valueChanged(int)), rejectFilters.at(index), SLOT(setMaxEpisode(int) ) ); + disconnect(rejectFilters.at(index), SIGNAL(maxEpisodeChanged(int)), filterMaxEpisode, SLOT(setValue(int) ) ); + //matches + disconnect(rejectFilters.at(index), SIGNAL(matchesChanged(const QValueList<FilterMatch>&)), this, SLOT(updateMatches(const QValueList<FilterMatch>&) ) ); + + disconnect(processFilter, SIGNAL(clicked()), rejectFilters.at(index), SIGNAL(rescanFilter()) ); + + } + } + + void RssFeedManager::addNewFeed(RssFeed feed) + { + if (feeds.isEmpty()) + deleteFeed->setEnabled(true); + + feeds.append(new RssFeed(feed)); + + int index = feeds.count()-1; + feedlist->insertItem(feeds.at(index)->title()); + feedlist->setCurrentItem(index); + + //update the feed list + connect(feeds.at(index), SIGNAL(titleChanged(const QString&)), this, SLOT(updateFeedList()) ); + + //clear the articles list when the url is changed + connect(feeds.at(index), SIGNAL(feedUrlChanged(const KURL&)), this, SLOT(clearArticles() ) ); + + //connect the scanArticle signal to the scanArticle slot + connect(feeds.at(index), SIGNAL(scanRssArticle(RssArticle)), this, SLOT(scanArticle(RssArticle) ) ); + + //connect all the fields to the save slot + //title + connect(feeds.at(index), SIGNAL(titleChanged(const QString &)), this, SLOT(saveFeedList() ) ); + //url + connect(feeds.at(index), SIGNAL(feedUrlChanged(const KURL&)), this, SLOT(saveFeedList() ) ); + //articleAge + connect(feeds.at(index), SIGNAL(articleAgeChanged(int)), this, SLOT(saveFeedList() ) ); + //active + connect(feeds.at(index), SIGNAL(activeChanged(bool)), this, SLOT(saveFeedList() ) ); + //autoRefresh + connect(feeds.at(index), SIGNAL(autoRefreshChanged(const QTime&)), this, SLOT(saveFeedList() ) ); + //ignoreTTL + connect(feeds.at(index), SIGNAL(ignoreTTLChanged(bool)), this, SLOT(saveFeedList() ) ); + + } + + void RssFeedManager::addNewAcceptFilter(RssFilter filter) + { + if (acceptFilters.isEmpty()) + deleteAcceptFilter->setEnabled(true); + + acceptFilters.append(new RssFilter(filter)); + + int index = acceptFilters.count()-1; + acceptFilterList->insertItem(acceptFilters.at(index)->title()); + acceptFilterList->setCurrentItem(index); + + connect(acceptFilters.at(index), SIGNAL(titleChanged(const QString&)), this, SLOT(updateAcceptFilterList()) ); + + //connect all the fields to the save slot + //title + connect(acceptFilters.at(index), SIGNAL(titleChanged(const QString &)), this, SLOT(saveFilterList() ) ); + //active + connect(acceptFilters.at(index), SIGNAL(activeChanged( bool )), this, SLOT(saveFilterList() ) ); + //regexps + connect(acceptFilters.at(index), SIGNAL(regExpsChanged( const QStringList& )), this, SLOT(saveFilterList() ) ); + //series + connect(acceptFilters.at(index), SIGNAL(seriesChanged( bool )), this, SLOT(saveFilterList() ) ); + //sansEpisode + connect(acceptFilters.at(index), SIGNAL(sansEpisodeChanged( bool )), this, SLOT(saveFilterList() ) ); + //minSeason + connect(acceptFilters.at(index), SIGNAL(minSeasonChanged (int )), this, SLOT(saveFilterList() ) ); + //minEpisode + connect(acceptFilters.at(index), SIGNAL(minEpisodeChanged (int )), this, SLOT(saveFilterList() ) ); + //maxSeason + connect(acceptFilters.at(index), SIGNAL(maxSeasonChanged (int )), this, SLOT(saveFilterList() ) ); + //maxEpiosde + connect(acceptFilters.at(index), SIGNAL(maxEpisodeChanged (int )), this, SLOT(saveFilterList() ) ); + //matches + connect(acceptFilters.at(index), SIGNAL(matchesChanged( const QValueList<FilterMatch>& )), this, SLOT(saveFilterList() ) ); + + //connect the rescan signal to the rescan slot + connect(acceptFilters.at(index), SIGNAL(rescanFilter()), this, SLOT(rescanFilter()) ); + +// //connect all except the matchesChanged to the rescanFilter slot +// //title +// connect(acceptFilters.at(index), SIGNAL(titleChanged(const QString &)), this, SLOT(rescanFilter() ) ); +// //active +// connect(acceptFilters.at(index), SIGNAL(activeChanged( bool )), this, SLOT(rescanFilter() ) ); +// //regexps +// connect(acceptFilters.at(index), SIGNAL(regExpsChanged( const QStringList& )), this, SLOT(rescanFilter() ) ); +// //series +// connect(acceptFilters.at(index), SIGNAL(seriesChanged( bool )), this, SLOT(rescanFilter() ) ); +// //sansEpisode +// connect(acceptFilters.at(index), SIGNAL(sansEpisodeChanged( bool )), this, SLOT(rescanFilter() ) ); +// //minSeason +// connect(acceptFilters.at(index), SIGNAL(minSeasonChanged (int )), this, SLOT(rescanFilter() ) ); +// //minEpisode +// connect(acceptFilters.at(index), SIGNAL(minEpisodeChanged (int )), this, SLOT(rescanFilter() ) ); +// //maxSeason +// connect(acceptFilters.at(index), SIGNAL(maxSeasonChanged (int )), this, SLOT(rescanFilter() ) ); +// //maxEpiosde +// connect(acceptFilters.at(index), SIGNAL(maxEpisodeChanged (int )), this, SLOT(rescanFilter() ) ); + + } + + void RssFeedManager::addNewRejectFilter(RssFilter filter) + { + if (rejectFilters.isEmpty()) + deleteRejectFilter->setEnabled(true); + + rejectFilters.append(new RssFilter(filter)); + + int index = rejectFilters.count()-1; + rejectFilterList->insertItem(rejectFilters.at(index)->title()); + rejectFilterList->setCurrentItem(index); + + connect(rejectFilters.at(index), SIGNAL(titleChanged(const QString&)), this, SLOT(updateRejectFilterList()) ); + + //connect all the fields to the save slot + //title + connect(rejectFilters.at(index), SIGNAL(titleChanged(const QString &)), this, SLOT(saveFilterList() ) ); + //active + connect(rejectFilters.at(index), SIGNAL(activeChanged( bool )), this, SLOT(saveFilterList() ) ); + //regexps + connect(rejectFilters.at(index), SIGNAL(regExpsChanged( const QStringList& )), this, SLOT(saveFilterList() ) ); + //series + connect(rejectFilters.at(index), SIGNAL(seriesChanged( bool )), this, SLOT(saveFilterList() ) ); + //sansEpisode + connect(rejectFilters.at(index), SIGNAL(sansEpisodeChanged( bool )), this, SLOT(saveFilterList() ) ); + //minSeason + connect(rejectFilters.at(index), SIGNAL(minSeasonChanged (int )), this, SLOT(saveFilterList() ) ); + //minEpisode + connect(rejectFilters.at(index), SIGNAL(minEpisodeChanged (int )), this, SLOT(saveFilterList() ) ); + //maxSeason + connect(rejectFilters.at(index), SIGNAL(maxSeasonChanged (int )), this, SLOT(saveFilterList() ) ); + //maxEpiosde + connect(rejectFilters.at(index), SIGNAL(maxEpisodeChanged (int )), this, SLOT(saveFilterList() ) ); + //matches + connect(rejectFilters.at(index), SIGNAL(matchesChanged( const QValueList<FilterMatch>& )), this, SLOT(saveFilterList() ) ); + + } + + void RssFeedManager::deleteSelectedFeed() + { + int currentItem = feedlist->currentItem(); + + if (currentItem < 0) + return; + + int newItem=currentItem-1; + + if (currentItem == -1 && feeds.count()) + newItem = 0; + + disconnectFeed(currentItem); + currentFeed = -1; + + delete feeds.at(currentItem); + feeds.remove(currentItem); + feedlist->removeItem(currentItem); + + if (feeds.isEmpty()) + deleteFeed->setEnabled(false); + + if (newItem >= 0) + { + feedlist->setSelected(newItem, true); + } + + saveFeedList(); + } + + void RssFeedManager::deleteSelectedAcceptFilter() + { + int currentItem = acceptFilterList->currentItem(); + + if (currentItem < 0) + return; + + int newItem=currentItem-1; + + if (currentItem == -1 && acceptFilters.count()) + newItem = 0; + + disconnectFilter(currentItem, true); + currentAcceptFilter = -1; + + delete acceptFilters.at(currentItem); + acceptFilters.remove(currentItem); + acceptFilterList->removeItem(currentItem); + + if (acceptFilters.isEmpty()) + deleteAcceptFilter->setEnabled(false); + + if (newItem >= 0) + { + acceptFilterList->setSelected(newItem, true); + } + + saveFilterList(); + } + + void RssFeedManager::deleteSelectedRejectFilter() + { + int currentItem = rejectFilterList->currentItem(); + + if (currentItem < 0) + return; + + int newItem=currentItem-1; + + if (currentItem == -1 && rejectFilters.count()) + newItem = 0; + + disconnectFilter(currentItem, false); + currentRejectFilter = -1; + + delete rejectFilters.at(currentItem); + rejectFilters.remove(currentItem); + rejectFilterList->removeItem(currentItem); + + if (rejectFilters.isEmpty()) + deleteRejectFilter->setEnabled(false); + + if (newItem >= 0) + { + rejectFilterList->setSelected(newItem, true); + } + + saveFilterList(); + } + + void RssFeedManager::updateRegExps() + { + if (currentRejectFilter < 0) + { + //accept filter is active + acceptFilters.at(currentAcceptFilter)->setRegExps(filterRegExps->items()); + } + else + { + //reject filter is active + rejectFilters.at(currentRejectFilter)->setRegExps(filterRegExps->items()); + } + } + + void RssFeedManager::updateFeedList(int item) + { + int cursorPos = feedTitle->cursorPosition(); + if (item < 0) + { + //let's check which one sent the signal - if we can't figure it all then update them all + int pos = feeds.find((RssFeed *)sender()); + + if (pos < 0) + { + for (int i=0; i<feedlist->count(); i++) + { + feedlist->changeItem(feeds.at(i)->title(), i); + } + } + else + { + //just change the feed sending the signal + feedlist->changeItem(feeds.at(pos)->title(), pos); + if (feedlist->isSelected(pos)) + { + feedTitle->setFocus(); + } + } + } + else + { + //just update item + feedlist->changeItem(feeds.at(item)->title(), item); + } + feedTitle->setCursorPosition(cursorPos); + } + + void RssFeedManager::updateAcceptFilterList(int item) + { + int cursorPos = filterTitle->cursorPosition(); + if (item < 0) + { + //let's check which one sent the signal - if we can't figure it all then update them all + int pos = acceptFilters.find((RssFilter *)sender()); + + if (pos < 0) + { + for (int i=0; i<feedlist->count(); i++) + { + acceptFilterList->changeItem(acceptFilters.at(i)->title(), i); + } + } + else + { + //just change the feed sending the signal + acceptFilterList->changeItem(acceptFilters.at(pos)->title(), pos); + if (acceptFilterList->isSelected(pos)) + { + filterTitle->setFocus(); + } + } + } + else + { + //just update item + acceptFilterList->changeItem(acceptFilters.at(item)->title(), item); + } + filterTitle->setCursorPosition(cursorPos); + } + + void RssFeedManager::updateRejectFilterList(int item) + { + int cursorPos = filterTitle->cursorPosition(); + if (item < 0) + { + //let's check which one sent the signal - if we can't figure it all then update them all + int pos = rejectFilters.find((RssFilter *)sender()); + + if (pos < 0) + { + for (int i=0; i<feedlist->count(); i++) + { + rejectFilterList->changeItem(rejectFilters.at(i)->title(), i); + } + } + else + { + //just change the feed sending the signal + rejectFilterList->changeItem(rejectFilters.at(pos)->title(), pos); + if (rejectFilterList->isSelected(pos)) + { + filterTitle->setFocus(); + } + } + } + else + { + //just update item + rejectFilterList->changeItem(rejectFilters.at(item)->title(), item); + } + filterTitle->setCursorPosition(cursorPos); + } + + void RssFeedManager::updateArticles(const RssArticle::List& articles) + { + feedArticles->setNumRows(articles.count()); + for (int i=0; i<articles.count(); i++) + { + QString info; + if (articles[i].downloaded()==1) + { + info = ": Manually downloaded"; + } + else if (articles[i].downloaded()==3) + { + info = ": Automatically downloaded"; + } + feedArticles->setText(i, 0, articles[i].title() + info); + feedArticles->setText(i, 1, articles[i].description()); + feedArticles->setText(i, 2, articles[i].link().prettyURL()); + } + } + + void RssFeedManager::updateMatches(const QValueList<FilterMatch>& matches) + { + filterMatches->setNumRows(matches.count()); + for (int i=0; i<matches.count(); i++) + { + filterMatches->setText(i, 0, QString::number(matches[i].season())); + filterMatches->setText(i, 1, QString::number(matches[i].episode())); + filterMatches->setText(i, 2, matches[i].time()); + filterMatches->setText(i, 3, matches[i].link()); + } + + changedMatchSelection(); + } + + void RssFeedManager::changedArticleSelection() + { + bool downloadEnabled = false; + for (int i=0; i<feedArticles->numSelections(); i++) + { + if (feedArticles->selection(i).numRows()) + { + downloadEnabled = true; + break; + } + } + downloadArticle->setEnabled(downloadEnabled); + } + + void RssFeedManager::changedMatchSelection() + { + bool downloadEnabled = false; + for (int i=0; i<filterMatches->numSelections(); i++) + { + if (filterMatches->selection(i).numRows()) + { + downloadEnabled = true; + break; + } + } + downloadFilterMatch->setEnabled(downloadEnabled); + deleteFilterMatch->setEnabled(downloadEnabled); + } + + void RssFeedManager::downloadSelectedArticles() + { + for (int i=0; i<feedArticles->numSelections(); i++) + { + int endRow = feedArticles->selection(i).topRow() + feedArticles->selection(i).numRows(); + RssLinkDownloader * curDownload; + for (int j=feedArticles->selection(i).topRow(); j<endRow; j++) + { + curDownload = new RssLinkDownloader(m_core, feedArticles->text(j, 2)); + for (int i=0; i<feeds.count(); i++) + { + connect(curDownload, SIGNAL(linkDownloaded( QString, int )), feeds.at(i), SLOT(setDownloaded(QString, int)) ); + } + } + } + } + + void RssFeedManager::downloadSelectedMatches() + { + for (int i=0; i<filterMatches->numSelections(); i++) + { + int endRow = filterMatches->selection(i).topRow() + filterMatches->selection(i).numRows(); + for (int j=filterMatches->selection(i).topRow(); j<endRow; j++) + { + new RssLinkDownloader(m_core, filterMatches->text(j, 3)); + } + } + } + + void RssFeedManager::deleteSelectedMatches() + { + QStringList selectedLinks; + for (int i=0; i<filterMatches->numSelections(); i++) + { + int endRow = filterMatches->selection(i).topRow() + filterMatches->selection(i).numRows(); + for (int j=filterMatches->selection(i).topRow(); j<endRow; j++) + { + //add a match to the list of selected matches + selectedLinks.append(filterMatches->text(j, 3)); + } + } + + RssFilter * curFilter; + if (currentRejectFilter<0) + { + //we're currently testing an acceptFilter + curFilter = acceptFilters.at(currentAcceptFilter); + } + else + { + //it's a reject filter + curFilter = rejectFilters.at(currentRejectFilter); + } + + for (int i=0; i<selectedLinks.count(); i++) + { + curFilter->deleteMatch( selectedLinks[i] ); + } + + updateMatches(curFilter->matches()); + } + + void RssFeedManager::changedActiveFeed() + { + if (currentFeed != feedlist->currentItem() || currentFeed < 0) + { + //the selection has indeed changed + if (currentFeed >= 0) + { + //disconnect the gui signals from the old feed + disconnectFeed(currentFeed); + } + + //update the currentFeed + currentFeed = feedlist->currentItem(); + if (currentFeed >= 0) + { + //set the values + //title + feedTitle->setText(feeds.at(currentFeed)->title()); + //url + feedUrl->setKURL(feeds.at(currentFeed)->feedUrl()); + refreshFeed->setEnabled(!feeds.at(currentFeed)->feedUrl().url().isEmpty()); + //articleAge + feedArticleAge->setValue(feeds.at(currentFeed)->articleAge()); + //active + feedActive->setChecked(feeds.at(currentFeed)->active()); + //autoRefresh + feedAutoRefresh->setTime(feeds.at(currentFeed)->autoRefresh()); + //ignoreTTL + feedIgnoreTTL->setChecked(feeds.at(currentFeed)->ignoreTTL()); + feedAutoRefresh->setEnabled(feeds.at(currentFeed)->ignoreTTL()); + //articles + updateArticles(feeds.at(currentFeed)->articles()); + + //title + feedTitle->setEnabled(true); + //url + feedUrl->setEnabled(true); + //articleAge + feedArticleAge->setEnabled(true); + //active + feedActive->setEnabled(true); + //ignoreTTL + feedIgnoreTTL->setEnabled(true); + + //connect all the signals + connectFeed(currentFeed); + } + else + { + //clear the items + //title + feedTitle->clear(); + //url + feedUrl->clear(); + //articleAge + feedArticleAge->setValue(0); + //active + feedActive->setChecked(false); + //autoRefresh + feedAutoRefresh->setTime(QTime()); + //ignoreTTL + feedIgnoreTTL->setChecked(false); + //articles + feedArticles->setNumRows(0); + + //title + feedTitle->setEnabled(false); + //url + feedUrl->setEnabled(false); + //articleAge + feedArticleAge->setEnabled(false); + //active + feedActive->setEnabled(false); + //autoRefresh + feedAutoRefresh->setEnabled(false); + //ignoreTTL + feedIgnoreTTL->setEnabled(false); + } + } + } + + void RssFeedManager::changedActiveAcceptFilter() + { + if (currentRejectFilter >= 0) + { + rejectFilterList->setSelected(currentRejectFilter, false); + disconnectFilter(currentRejectFilter, false); + currentRejectFilter = -1; + } + + if (currentAcceptFilter != acceptFilterList->currentItem() || currentAcceptFilter < 0) + { + //the selection has indeed changed + + if (currentAcceptFilter >= 0) + { + //disconnect the gui signals from the old feed + disconnectFilter(currentAcceptFilter, true); + + } + + //update the currentFeed + currentAcceptFilter = acceptFilterList->currentItem(); + + if (currentAcceptFilter >= 0) + { + //set the values + filterTitle->setText(acceptFilters.at(currentAcceptFilter)->title()); + filterActive->setChecked(acceptFilters.at(currentAcceptFilter)->active()); + filterRegExps->setItems(acceptFilters.at(currentAcceptFilter)->regExps()); + filterSeries->setChecked(acceptFilters.at(currentAcceptFilter)->series()); + filterSansEpisode->setChecked(acceptFilters.at(currentAcceptFilter)->sansEpisode()); + filterMinSeason->setValue(acceptFilters.at(currentAcceptFilter)->minSeason()); + filterMinEpisode->setValue(acceptFilters.at(currentAcceptFilter)->minEpisode()); + filterMaxSeason->setValue(acceptFilters.at(currentAcceptFilter)->maxSeason()); + filterMaxEpisode->setValue(acceptFilters.at(currentAcceptFilter)->maxEpisode()); + + updateMatches(acceptFilters.at(currentAcceptFilter)->matches()); + + filterTitle->setEnabled(true); + filterActive->setEnabled(true); + filterRegExps->setEnabled(true); + filterSeries->setEnabled(true); + filterSansEpisode->setEnabled(true); + filterMinSeason->setEnabled(true); + filterMinEpisode->setEnabled(true); + filterMaxSeason->setEnabled(true); + filterMaxEpisode->setEnabled(true); + + processFilter->setEnabled(true); + testText->setEnabled(true); + + //connect all the signals + connectFilter(currentAcceptFilter, true); + } + else + { + if (currentRejectFilter < 0) + { + //clear the items + filterTitle->clear(); + filterActive->setChecked(false); + filterRegExps->clear(); + filterSeries->setChecked(false); + filterSansEpisode->setChecked(false); + filterMinSeason->setValue(0); + filterMinEpisode->setValue(0); + filterMaxSeason->setValue(0); + filterMaxEpisode->setValue(0); + filterMatches->setNumRows(0); + + filterTitle->setEnabled(false); + filterActive->setEnabled(false); + filterRegExps->setEnabled(false); + filterSeries->setEnabled(false); + filterSansEpisode->setEnabled(false); + filterMinSeason->setEnabled(false); + filterMinEpisode->setEnabled(false); + filterMaxSeason->setEnabled(false); + filterMaxEpisode->setEnabled(false); + + processFilter->setEnabled(false); + testText->setEnabled(false); + + } + } + } + } + + void RssFeedManager::changedActiveRejectFilter() + { + if (currentAcceptFilter >= 0) + { + acceptFilterList->setSelected(currentAcceptFilter, false); + disconnectFilter(currentAcceptFilter, true); + currentAcceptFilter = -1; + } + + if (currentRejectFilter != rejectFilterList->currentItem() || currentRejectFilter < 0) + { + //the selection has indeed changed + + if (currentRejectFilter >= 0) + { + //disconnect the gui signals from the old feed + disconnectFilter(currentRejectFilter, false); + } + + //update the currentFeed + currentRejectFilter = rejectFilterList->currentItem(); + + if (currentRejectFilter >= 0) + { + //set the values + //title + filterTitle->setText(rejectFilters.at(currentRejectFilter)->title()); + filterActive->setChecked(rejectFilters.at(currentRejectFilter)->active()); + filterRegExps->setItems(rejectFilters.at(currentRejectFilter)->regExps()); + filterSeries->setChecked(rejectFilters.at(currentRejectFilter)->series()); + filterSansEpisode->setChecked(rejectFilters.at(currentRejectFilter)->sansEpisode()); + filterMinSeason->setValue(rejectFilters.at(currentRejectFilter)->minSeason()); + filterMinEpisode->setValue(rejectFilters.at(currentRejectFilter)->minEpisode()); + filterMaxSeason->setValue(rejectFilters.at(currentRejectFilter)->maxSeason()); + filterMaxEpisode->setValue(rejectFilters.at(currentRejectFilter)->maxEpisode()); + + updateMatches(rejectFilters.at(currentRejectFilter)->matches()); + + filterTitle->setEnabled(true); + filterActive->setEnabled(true); + filterRegExps->setEnabled(true); + filterSeries->setEnabled(true); + filterSansEpisode->setEnabled(true); + filterMinSeason->setEnabled(true); + filterMinEpisode->setEnabled(true); + filterMaxSeason->setEnabled(true); + filterMaxEpisode->setEnabled(true); + + processFilter->setEnabled(true); + testText->setEnabled(true); + + //connect all the signals + connectFilter(currentRejectFilter, false); + } + else + { + if (currentRejectFilter < 0) + { + //clear the items + filterTitle->clear(); + filterActive->setChecked(false); + filterRegExps->clear(); + filterSeries->setChecked(false); + filterSansEpisode->setChecked(false); + filterMinSeason->setValue(0); + filterMinEpisode->setValue(0); + filterMaxSeason->setValue(0); + filterMaxEpisode->setValue(0); + filterMatches->setNumRows(0); + + filterTitle->setEnabled(false); + filterActive->setEnabled(false); + filterRegExps->setEnabled(false); + filterSeries->setEnabled(false); + filterSansEpisode->setEnabled(false); + filterMinSeason->setEnabled(false); + filterMinEpisode->setEnabled(false); + filterMaxSeason->setEnabled(false); + filterMaxEpisode->setEnabled(false); + + processFilter->setEnabled(false); + testText->setEnabled(false); + } + } + } + } + + QString RssFeedManager::getFeedListFilename() + { + return KGlobal::dirs()->saveLocation("data","ktorrent") + "rssfeeds.ktr"; + } + + QString RssFeedManager::getFilterListFilename() + { + return KGlobal::dirs()->saveLocation("data","ktorrent") + "rssfilters.ktr"; + } + + void RssFeedManager::saveFeedList() + { + if (feedListSaving) + return; + + feedListSaving = true; + + QString filename = getFeedListFilename(); + + //save feeds to disk + QFile file(filename); + + file.open( IO_WriteOnly ); + QDataStream out(&file); + + out << feeds.count(); + + for (int i=0; i<feeds.count(); i++) + { + out << *(feeds.at(i)); + } + + feedListSaving = false; + } + + void RssFeedManager::loadFeedList() + { + QString filename = getFeedListFilename(); + + //load feeds from disk + QFile file(filename); + + if (file.exists()) + { + file.open( IO_ReadOnly ); + QDataStream in(&file); + + int numFeeds; + + in >> numFeeds; + + RssFeed curFeed; + + for (int i=0; i<numFeeds; i++) + { + in >> curFeed; + addNewFeed(curFeed); + } + + changedActiveFeed(); + } + } + + void RssFeedManager::saveFilterList() + { + if (filterListSaving) + return; + + filterListSaving = true; + + QString filename = getFilterListFilename(); + + //save feeds to disk + QFile file(filename); + + file.open( IO_WriteOnly ); + QDataStream out(&file); + + out << acceptFilters.count(); + + for (int i=0; i<acceptFilters.count(); i++) + { + out << *(acceptFilters.at(i)); + } + + out << rejectFilters.count(); + + for (int i=0; i<rejectFilters.count(); i++) + { + out << *(rejectFilters.at(i)); + } + + filterListSaving = false; + } + + void RssFeedManager::loadFilterList() + { + QString filename = getFilterListFilename(); + + //load feeds from disk + QFile file(filename); + + if (file.exists()) + { + file.open( IO_ReadOnly ); + QDataStream in(&file); + + int numFilters; + + in >> numFilters; + + RssFilter curFilter; + + for (int i=0; i<numFilters; i++) + { + in >> curFilter; + addNewAcceptFilter(curFilter); + } + + in >> numFilters; + + for (int i=0; i<numFilters; i++) + { + in >> curFilter; + addNewRejectFilter(curFilter); + } + + //go through and grab the reject filters + changedActiveRejectFilter(); + changedActiveAcceptFilter(); + } + } + + void RssFeedManager::scanArticle(RssArticle article, RssFilter * filter) + { + //first run it through the reject filters - if any match go no further + for (int i=0; i<rejectFilters.count(); i++) + { + if (rejectFilters.at(i)->scanArticle(article, false)) + { + return; + } + } + + if (filter) + { + //we were passed a filter - so just scan it with that one + if (filter->scanArticle(article)) + { + RssLinkDownloader * curDownload = new RssLinkDownloader(m_core, article.link().prettyURL(), filter); + for (int i=0; i<feeds.count(); i++) + { + connect(curDownload, SIGNAL(linkDownloaded( QString, int )), feeds.at(i), SLOT(setDownloaded(QString, int)) ); + } + } + } + else + { + for (int i=0; i<acceptFilters.count(); i++) + { + if (acceptFilters.at(i)->scanArticle(article)) + { + RssLinkDownloader * curDownload = new RssLinkDownloader(m_core, article.link().prettyURL(), acceptFilters.at(i)); + for (int i=0; i<feeds.count(); i++) + { + connect(curDownload, SIGNAL(linkDownloaded( QString, int )), feeds.at(i), SLOT(setDownloaded(QString, int)) ); + } + } + } + } + } + + void RssFeedManager::rescanFilter() + { + int pos = acceptFilters.find((RssFilter *)sender()); + + if (pos >= 0) + { + for (int i=0; i<feeds.count(); i++) + { + for (int j=0; j<feeds.at(i)->articles().count(); j++) + { + scanArticle(feeds.at(i)->articles()[j], (RssFilter *)sender()); + } + } + } + } + + void RssFeedManager::testTextChanged() + { + testText->setPaletteBackgroundColor(QColor(255, 255, 255)); + testTestText->setEnabled(!testText->text().isEmpty()); + } + + void RssFeedManager::testFilter() + { + RssFilter * curFilter; + if (currentRejectFilter<0) + { + //we're currently testing an acceptFilter + curFilter = acceptFilters.at(currentAcceptFilter); + } + else + { + //it's a reject filter + curFilter = rejectFilters.at(currentRejectFilter); + } + + RssArticle testArticle; + testArticle.setTitle(testText->text()); + + if (curFilter->scanArticle(testArticle, false, false)) + { + testText->setPaletteBackgroundColor(QColor(0, 255, 0)); + } + else + { + testText->setPaletteBackgroundColor(QColor(255, 0, 0)); + } + } + + void RssFeedManager::setFilterTitle(const QString& title) + { + int cursorPos = filterTitle->cursorPosition(); + filterTitle->setText(title); + filterTitle->setCursorPosition(cursorPos); + } + + void RssFeedManager::setFeedTitle(const QString& title) + { + int cursorPos = feedTitle->cursorPosition(); + feedTitle->setText(title); + feedTitle->setCursorPosition(cursorPos); + } + +} diff --git a/plugins/rssfeed/rssfeedmanager.h b/plugins/rssfeed/rssfeedmanager.h new file mode 100644 index 0000000..1afc766 --- /dev/null +++ b/plugins/rssfeed/rssfeedmanager.h @@ -0,0 +1,130 @@ +/*************************************************************************** + * Copyright (C) 2006 by Alan Jones * + * skyphyr@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ +#ifndef RSSFEEDMANAGER_H +#define RSSFEEDMANAGER_H + +#include <kdirlister.h> +#include <kfileitem.h> +#include <qstring.h> +#include <qobject.h> +#include <qdir.h> + +#include <qptrlist.h> +#include <qwidget.h> +#include "rssfeedwidget.h" + +#include "rssfeed.h" +#include "rssfilter.h" + +namespace kt +{ + + class CoreInterface; + + /** + * @brief RssFeed Manager Class + * @author Alan Jones <skyphyr@gmail.com> + * + * + */ + class RssFeedManager : public RssFeedWidget + { + Q_OBJECT + public: + + /** + * Default constructor. + * @param core Pointer to core interface + * @param openSilently Wheather to open torrent silently or nor. + */ + RssFeedManager(CoreInterface* core, QWidget * parent = 0); + ~RssFeedManager(); + + public slots: + void changedActiveFeed(); + void changedArticleSelection(); + void changedFeedUrl(); + void changedMatchSelection(); + void updateArticles(const RssArticle::List& articles); + void downloadSelectedArticles(); + void downloadSelectedMatches(); + void deleteSelectedMatches(); + + void changedActiveAcceptFilter(); + void changedActiveRejectFilter(); + + void clearArticles(); + + void updateFeedList(int item=-1); + void addNewFeed(RssFeed feed = RssFeed()); + void deleteSelectedFeed(); + + void updateAcceptFilterList(int item=-1); + void addNewAcceptFilter(RssFilter filter = RssFilter()); + void deleteSelectedAcceptFilter(); + + void updateRejectFilterList(int item=-1); + void addNewRejectFilter(RssFilter filter = RssFilter()); + void deleteSelectedRejectFilter(); + + void updateRegExps(); + void updateMatches(const QValueList<FilterMatch>& matches); + + void saveFeedList(); + void saveFilterList(); + + void disconnectFeed(int index); + void connectFeed(int index); + + void disconnectFilter(int index, bool acceptFilter); + void connectFilter(int index, bool acceptFilter); + + void scanArticle(RssArticle article, RssFilter * filter = NULL); + void rescanFilter(); + + void testTextChanged(); + void testFilter(); + + void setFilterTitle(const QString& title); + void setFeedTitle(const QString& title); + + private: + CoreInterface* m_core; + + QPtrList<RssFeed> feeds; + int currentFeed; + + QPtrList<RssFilter> acceptFilters; + int currentAcceptFilter; + QPtrList<RssFilter> rejectFilters; + int currentRejectFilter; + + QString getFeedListFilename(); + void loadFeedList(); + + QString getFilterListFilename(); + void loadFilterList(); + + bool feedListSaving; + bool filterListSaving; + + }; +} +#endif diff --git a/plugins/rssfeed/rssfeedplugin.cpp b/plugins/rssfeed/rssfeedplugin.cpp new file mode 100644 index 0000000..0d845b7 --- /dev/null +++ b/plugins/rssfeed/rssfeedplugin.cpp @@ -0,0 +1,86 @@ +/*************************************************************************** + * Copyright (C) 2006 by Alan Jones * + * skyphyr@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ +#include <kgenericfactory.h> +#include <kiconloader.h> + +#include <interfaces/coreinterface.h> +#include <interfaces/guiinterface.h> +#include <interfaces/plugin.h> +#include <util/constants.h> +#include <util/log.h> + +#include <qstring.h> +#include <qfile.h> + +#include <kmessagebox.h> +#include <klocale.h> +#include <kglobal.h> + +#include "rssfeedmanager.h" +#include "rssfeedplugin.h" + +using namespace bt; + +K_EXPORT_COMPONENT_FACTORY(ktrssfeedplugin,KGenericFactory<kt::RssFeedPlugin>("rssfeedplugin")) + +namespace kt +{ + const QString NAME = "RSS Feeds"; + const QString AUTHOR = "Alan Jones"; + const QString EMAIL = "skyphyr@gmail.com"; + const QString DESCRIPTION = i18n("Automatically scans RSS feeds for torrent matching regular expressions and loads them."); + + RssFeedPlugin::RssFeedPlugin(QObject* parent, const char* name, const QStringList& args) + : Plugin(parent, name, args,NAME,i18n("RSS Feeds"),AUTHOR,EMAIL,DESCRIPTION,"player_playlist") + { + m_rssFeedManager = 0; + } + + + RssFeedPlugin::~RssFeedPlugin() + { + } + + void RssFeedPlugin::load() + { + //add the new tab to the gui + KIconLoader* iload = KGlobal::iconLoader(); + m_rssFeedManager = new RssFeedManager(getCore()); + getGUI()->addTabPage( + m_rssFeedManager,iload->loadIconSet("player_playlist", KIcon::Small), + i18n("RSS Feeds")); + + } + + void RssFeedPlugin::unload() + { + // be sure to remove the page's tab before deleting the widget + getGUI()->removeTabPage(m_rssFeedManager); + delete m_rssFeedManager; + m_rssFeedManager = 0; + } + + bool RssFeedPlugin::versionCheck(const QString & version) const + { + return version == KT_VERSION_MACRO; + } + +} + diff --git a/plugins/rssfeed/rssfeedplugin.h b/plugins/rssfeed/rssfeedplugin.h new file mode 100644 index 0000000..4ecc6af --- /dev/null +++ b/plugins/rssfeed/rssfeedplugin.h @@ -0,0 +1,55 @@ +/*************************************************************************** + * Copyright (C) 2006 by Alan Jones * + * skyphyr@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ +#ifndef KTRSSFEEDPLUGIN_H +#define KTRSSFEEDPLUGIN_H + +#include <interfaces/plugin.h> + +class QString; + + +namespace kt +{ + class RssFeedManager; + + /** + * @author Alan Jones <skyphyr@gmail.com> + * @brief KTorrent RssFeed plugin + * Automatically scans rssfeeds for torrent matching regular expressions and loads them. + */ + class RssFeedPlugin : public Plugin + { + Q_OBJECT + public: + RssFeedPlugin(QObject* parent, const char* name, const QStringList& args); + virtual ~RssFeedPlugin(); + + virtual void load(); + virtual void unload(); + virtual bool versionCheck(const QString& version) const; + + private: + RssFeedManager * m_rssFeedManager; + + }; + +} + +#endif diff --git a/plugins/rssfeed/rssfeedwidget.ui b/plugins/rssfeed/rssfeedwidget.ui new file mode 100644 index 0000000..55bd27b --- /dev/null +++ b/plugins/rssfeed/rssfeedwidget.ui @@ -0,0 +1,969 @@ +<!DOCTYPE UI><UI version="3.3" stdsetdef="1"> +<class>RssFeedWidget</class> +<comment>The display widget for the rssfeed tab</comment> +<author>Alan Jones</author> +<widget class="QWidget"> + <property name="name"> + <cstring>RssFeedWidget</cstring> + </property> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>798</width> + <height>530</height> + </rect> + </property> + <property name="caption"> + <string>Rss Feeds</string> + </property> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QTabWidget" row="0" column="0"> + <property name="name"> + <cstring>tabs</cstring> + </property> + <property name="margin"> + <number>2</number> + </property> + <widget class="QWidget"> + <property name="name"> + <cstring>feeds</cstring> + </property> + <attribute name="title"> + <string>Feeds</string> + </attribute> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QSplitter"> + <property name="name"> + <cstring>splitter16</cstring> + </property> + <property name="orientation"> + <enum>Horizontal</enum> + </property> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout2</cstring> + </property> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QListBox" row="0" column="0" rowspan="1" colspan="2"> + <property name="name"> + <cstring>feedlist</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>7</hsizetype> + <vsizetype>7</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + <widget class="QPushButton" row="1" column="1"> + <property name="name"> + <cstring>deleteFeed</cstring> + </property> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>De&lete</string> + </property> + </widget> + <widget class="QPushButton" row="1" column="0"> + <property name="name"> + <cstring>newFeed</cstring> + </property> + <property name="text"> + <string>&New</string> + </property> + </widget> + </grid> + </widget> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>feedLayout</cstring> + </property> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="KURLRequester" row="1" column="1"> + <property name="name"> + <cstring>feedUrl</cstring> + </property> + </widget> + <widget class="QLabel" row="1" column="0"> + <property name="name"> + <cstring>feedUrlLabel</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>0</hsizetype> + <vsizetype>5</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>&URL</string> + </property> + <property name="buddy" stdset="0"> + <cstring>feedUrl</cstring> + </property> + </widget> + <widget class="QGroupBox" row="3" column="0" rowspan="1" colspan="2"> + <property name="name"> + <cstring>feedBox</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>5</hsizetype> + <vsizetype>5</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="margin"> + <number>2</number> + </property> + <property name="title"> + <string>Articles</string> + </property> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <spacer row="1" column="1"> + <property name="name"> + <cstring>spacer1</cstring> + </property> + <property name="orientation"> + <enum>Horizontal</enum> + </property> + <property name="sizeType"> + <enum>Expanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>90</width> + <height>21</height> + </size> + </property> + </spacer> + <widget class="QPushButton" row="1" column="2"> + <property name="name"> + <cstring>downloadArticle</cstring> + </property> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>Download</string> + </property> + <property name="accel"> + <string></string> + </property> + </widget> + <widget class="QTable" row="0" column="0" rowspan="1" colspan="3"> + <property name="name"> + <cstring>feedArticles</cstring> + </property> + <property name="numRows"> + <number>0</number> + </property> + <property name="numCols"> + <number>0</number> + </property> + <property name="readOnly"> + <bool>true</bool> + </property> + <property name="selectionMode"> + <enum>MultiRow</enum> + </property> + </widget> + </grid> + </widget> + <widget class="QLayoutWidget" row="2" column="0" rowspan="1" colspan="2"> + <property name="name"> + <cstring>layout13</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLabel"> + <property name="name"> + <cstring>feedRefreshTimeLabel</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>0</hsizetype> + <vsizetype>5</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Auto&refresh</string> + </property> + <property name="buddy" stdset="0"> + <cstring>feedAutoRefresh</cstring> + </property> + </widget> + <widget class="QTimeEdit"> + <property name="name"> + <cstring>feedAutoRefresh</cstring> + </property> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="time"> + <time> + <hour>0</hour> + <minute>30</minute> + <second>0</second> + </time> + </property> + </widget> + <widget class="QCheckBox"> + <property name="name"> + <cstring>feedIgnoreTTL</cstring> + </property> + <property name="text"> + <string>I&gnore TTL</string> + </property> + </widget> + <spacer> + <property name="name"> + <cstring>spacer3</cstring> + </property> + <property name="orientation"> + <enum>Horizontal</enum> + </property> + <property name="sizeType"> + <enum>Expanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>60</width> + <height>21</height> + </size> + </property> + </spacer> + <widget class="QPushButton"> + <property name="name"> + <cstring>refreshFeed</cstring> + </property> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>Refresh</string> + </property> + <property name="accel"> + <string></string> + </property> + </widget> + </hbox> + </widget> + <widget class="QLayoutWidget" row="0" column="0" rowspan="1" colspan="2"> + <property name="name"> + <cstring>layout47</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLabel"> + <property name="name"> + <cstring>feedTitleLabel</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>0</hsizetype> + <vsizetype>5</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>&Title</string> + </property> + <property name="buddy" stdset="0"> + <cstring>feedTitle</cstring> + </property> + </widget> + <widget class="QLineEdit"> + <property name="name"> + <cstring>feedTitle</cstring> + </property> + </widget> + <widget class="QLabel"> + <property name="name"> + <cstring>feedArticleAgeLabel</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>0</hsizetype> + <vsizetype>5</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Keep Articles (days)</string> + </property> + </widget> + <widget class="QSpinBox"> + <property name="name"> + <cstring>feedArticleAge</cstring> + </property> + <property name="maxValue"> + <number>3650</number> + </property> + </widget> + <widget class="QCheckBox"> + <property name="name"> + <cstring>feedActive</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>0</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>&Active</string> + </property> + </widget> + </hbox> + </widget> + </grid> + </widget> + </widget> + </vbox> + </widget> + <widget class="QWidget"> + <property name="name"> + <cstring>filters</cstring> + </property> + <attribute name="title"> + <string>Filters</string> + </attribute> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout28</cstring> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QGroupBox"> + <property name="name"> + <cstring>groupBox12</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>5</hsizetype> + <vsizetype>5</vsizetype> + <horstretch>0</horstretch> + <verstretch>3</verstretch> + </sizepolicy> + </property> + <property name="title"> + <string>Accept Filters</string> + </property> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QListBox" row="0" column="0" rowspan="1" colspan="2"> + <property name="name"> + <cstring>acceptFilterList</cstring> + </property> + </widget> + <widget class="QPushButton" row="1" column="1"> + <property name="name"> + <cstring>deleteAcceptFilter</cstring> + </property> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>Delete</string> + </property> + <property name="accel"> + <string></string> + </property> + </widget> + <widget class="QPushButton" row="1" column="0"> + <property name="name"> + <cstring>newAcceptFilter</cstring> + </property> + <property name="text"> + <string>&New</string> + </property> + </widget> + </grid> + </widget> + <widget class="QGroupBox"> + <property name="name"> + <cstring>groupBox13</cstring> + </property> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>5</hsizetype> + <vsizetype>5</vsizetype> + <horstretch>0</horstretch> + <verstretch>1</verstretch> + </sizepolicy> + </property> + <property name="title"> + <string>Reject Filters</string> + </property> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QListBox" row="0" column="0" rowspan="1" colspan="2"> + <property name="name"> + <cstring>rejectFilterList</cstring> + </property> + </widget> + <widget class="QPushButton" row="1" column="1"> + <property name="name"> + <cstring>deleteRejectFilter</cstring> + </property> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>Delete</string> + </property> + <property name="accel"> + <string></string> + </property> + </widget> + <widget class="QPushButton" row="1" column="0"> + <property name="name"> + <cstring>newRejectFilter</cstring> + </property> + <property name="text"> + <string>&New</string> + </property> + </widget> + </grid> + </widget> + </vbox> + </widget> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout14</cstring> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout9</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLabel"> + <property name="name"> + <cstring>filterTitleLabel</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>0</hsizetype> + <vsizetype>5</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>&Title</string> + </property> + <property name="buddy" stdset="0"> + <cstring>filterTitle</cstring> + </property> + </widget> + <widget class="QLineEdit"> + <property name="name"> + <cstring>filterTitle</cstring> + </property> + </widget> + <widget class="QCheckBox"> + <property name="name"> + <cstring>filterActive</cstring> + </property> + <property name="text"> + <string>&Active</string> + </property> + </widget> + </hbox> + </widget> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout12</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="KEditListBox"> + <property name="name"> + <cstring>filterRegExps</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>7</hsizetype> + <vsizetype>7</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="frameShape"> + <enum>GroupBoxPanel</enum> + </property> + <property name="frameShadow"> + <enum>Sunken</enum> + </property> + <property name="margin"> + <number>0</number> + </property> + <property name="title"> + <string>Regular Expressions</string> + </property> + <property name="flat"> + <bool>false</bool> + </property> + <property name="buttons"> + <set>Remove|Add</set> + </property> + </widget> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout11</cstring> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout28</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QCheckBox"> + <property name="name"> + <cstring>filterSeries</cstring> + </property> + <property name="text"> + <string>Treat as &Series</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + <spacer> + <property name="name"> + <cstring>spacer20</cstring> + </property> + <property name="orientation"> + <enum>Horizontal</enum> + </property> + <property name="sizeType"> + <enum>Preferred</enum> + </property> + <property name="sizeHint"> + <size> + <width>50</width> + <height>21</height> + </size> + </property> + </spacer> + <widget class="QPushButton"> + <property name="name"> + <cstring>processFilter</cstring> + </property> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="maximumSize"> + <size> + <width>60</width> + <height>32767</height> + </size> + </property> + <property name="text"> + <string>Process</string> + </property> + <property name="accel"> + <string></string> + </property> + </widget> + </hbox> + </widget> + <widget class="QGroupBox"> + <property name="name"> + <cstring>seriesBox</cstring> + </property> + <property name="maximumSize"> + <size> + <width>32000</width> + <height>32000</height> + </size> + </property> + <property name="title"> + <string>Series Criteria</string> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout9</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <spacer> + <property name="name"> + <cstring>spacer7</cstring> + </property> + <property name="orientation"> + <enum>Horizontal</enum> + </property> + <property name="sizeType"> + <enum>Expanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>20</width> + <height>21</height> + </size> + </property> + </spacer> + <widget class="QCheckBox"> + <property name="name"> + <cstring>filterSansEpisode</cstring> + </property> + <property name="text"> + <string>Match Without Episode</string> + </property> + <property name="accel"> + <string></string> + </property> + </widget> + </hbox> + </widget> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout10</cstring> + </property> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QSpinBox" row="0" column="1"> + <property name="name"> + <cstring>filterMinSeason</cstring> + </property> + </widget> + <widget class="QLabel" row="1" column="2"> + <property name="name"> + <cstring>filterMaxEpisodeLabel</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>0</hsizetype> + <vsizetype>5</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Max Episode</string> + </property> + </widget> + <widget class="QSpinBox" row="0" column="3"> + <property name="name"> + <cstring>filterMinEpisode</cstring> + </property> + </widget> + <widget class="QSpinBox" row="1" column="1"> + <property name="name"> + <cstring>filterMaxSeason</cstring> + </property> + </widget> + <widget class="QLabel" row="1" column="0"> + <property name="name"> + <cstring>filterMaxSeasonLabel</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>0</hsizetype> + <vsizetype>5</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Max Season</string> + </property> + <property name="buddy" stdset="0"> + <cstring>filterMinSeason</cstring> + </property> + </widget> + <widget class="QLabel" row="0" column="0"> + <property name="name"> + <cstring>filterMinSeasonLabel</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>0</hsizetype> + <vsizetype>5</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Min Season</string> + </property> + <property name="buddy" stdset="0"> + <cstring>filterMinSeason</cstring> + </property> + </widget> + <widget class="QSpinBox" row="1" column="3"> + <property name="name"> + <cstring>filterMaxEpisode</cstring> + </property> + </widget> + <widget class="QLabel" row="0" column="2"> + <property name="name"> + <cstring>filterMinEpisodeLabel</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>0</hsizetype> + <vsizetype>5</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Min Episode</string> + </property> + </widget> + </grid> + </widget> + </vbox> + </widget> + </vbox> + </widget> + </hbox> + </widget> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout33</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLabel"> + <property name="name"> + <cstring>testTextLabel</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>0</hsizetype> + <vsizetype>5</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Test te&xt</string> + </property> + <property name="buddy" stdset="0"> + <cstring>testText</cstring> + </property> + </widget> + <widget class="QLineEdit"> + <property name="name"> + <cstring>testText</cstring> + </property> + <property name="paletteBackgroundColor"> + <color> + <red>255</red> + <green>255</green> + <blue>255</blue> + </color> + </property> + </widget> + <widget class="QPushButton"> + <property name="name"> + <cstring>testTestText</cstring> + </property> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>Te&st</string> + </property> + </widget> + </hbox> + </widget> + <widget class="QGroupBox"> + <property name="name"> + <cstring>filterMatchesBox</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>5</hsizetype> + <vsizetype>5</vsizetype> + <horstretch>0</horstretch> + <verstretch>1</verstretch> + </sizepolicy> + </property> + <property name="title"> + <string>Filter Matches</string> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QTable"> + <property name="name"> + <cstring>filterMatches</cstring> + </property> + <property name="numRows"> + <number>0</number> + </property> + <property name="numCols"> + <number>0</number> + </property> + <property name="readOnly"> + <bool>true</bool> + </property> + <property name="selectionMode"> + <enum>MultiRow</enum> + </property> + </widget> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout13</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QPushButton"> + <property name="name"> + <cstring>downloadFilterMatch</cstring> + </property> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>Download</string> + </property> + <property name="accel"> + <string></string> + </property> + </widget> + <spacer> + <property name="name"> + <cstring>spacer6</cstring> + </property> + <property name="orientation"> + <enum>Horizontal</enum> + </property> + <property name="sizeType"> + <enum>Expanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>170</width> + <height>21</height> + </size> + </property> + </spacer> + <widget class="QPushButton"> + <property name="name"> + <cstring>deleteFilterMatch</cstring> + </property> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>Delete</string> + </property> + <property name="accel"> + <string></string> + </property> + </widget> + </hbox> + </widget> + </vbox> + </widget> + </vbox> + </widget> + </hbox> + </widget> + </widget> + </grid> +</widget> +<customwidgets> +</customwidgets> +<connections> + <connection> + <sender>filterSeries</sender> + <signal>toggled(bool)</signal> + <receiver>seriesBox</receiver> + <slot>setEnabled(bool)</slot> + </connection> + <connection> + <sender>feedIgnoreTTL</sender> + <signal>toggled(bool)</signal> + <receiver>feedAutoRefresh</receiver> + <slot>setEnabled(bool)</slot> + </connection> +</connections> +<layoutdefaults spacing="2" margin="2"/> +<includehints> + <includehint>kurlrequester.h</includehint> + <includehint>klineedit.h</includehint> + <includehint>kpushbutton.h</includehint> + <includehint>keditlistbox.h</includehint> + <includehint>klineedit.h</includehint> +</includehints> +</UI> diff --git a/plugins/rssfeed/rssfilter.cpp b/plugins/rssfeed/rssfilter.cpp new file mode 100644 index 0000000..6cf1f1f --- /dev/null +++ b/plugins/rssfeed/rssfilter.cpp @@ -0,0 +1,423 @@ +/*************************************************************************** + * Copyright (C) 2006 by Alan Jones * + * skyphyr@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ +#include "rssfilter.h" + +namespace kt +{ + + FilterMatch::FilterMatch(int season, int episode, QString link, QString time) + { + m_season = season; + m_episode = episode; + m_link = link; + m_time = time; + } + + FilterMatch::FilterMatch(const FilterMatch &other) + { + *this = other; + } + + FilterMatch &FilterMatch::operator=(const FilterMatch &other) + { + if (&other != this) + { + m_season = other.season(); + m_episode = other.episode(); + m_link = other.link(); + m_time = other.time(); + } + + return *this; + } + + bool FilterMatch::operator==(const FilterMatch &other) const + { + return m_link==other.link() && m_season==other.season() && m_episode==other.episode(); + } + + RssFilter::RssFilter(QObject * parent) : QObject(parent) + { + m_title = "New"; + m_active = false; + m_series = false; + m_sansEpisode = false; + m_minSeason = m_minEpisode = m_maxSeason = m_maxEpisode = 0; + } + + RssFilter::RssFilter(QString title, bool active, QStringList regExps, bool series, bool sansEpisode, + int minSeason, int minEpisode, int maxSeason, int maxEpisode, + QValueList<FilterMatch> matches) + { + m_title = title; + m_active = active; + m_regExps = regExps; + m_series = series; + m_sansEpisode = sansEpisode; + m_minSeason = minSeason; + m_minEpisode = minEpisode; + m_maxSeason = maxSeason; + m_maxEpisode = maxEpisode; + m_matches = matches; + } + + RssFilter::RssFilter(const RssFilter &other) : QObject() + { + *this = other; + } + + RssFilter &RssFilter::operator=(const RssFilter &other) + { + if (&other != this) + { + m_title = other.title(); + m_active = other.active(); + m_regExps = other.regExps(); + m_series = other.series(); + m_sansEpisode = other.sansEpisode(); + m_minSeason = other.minSeason(); + m_minEpisode = other.minEpisode(); + m_maxSeason = other.maxSeason(); + m_maxEpisode = other.maxEpisode(); + m_matches = other.matches(); + } + + return *this; + } + + void RssFilter::setTitle( const QString& title ) + { + if (m_title != title) + { + m_title = title; + emit titleChanged(title); + } + } + + void RssFilter::setActive( bool active ) + { + if (m_active != active) + { + m_active = active; + + emit activeChanged(active); + } + } + + void RssFilter::setRegExps( const QStringList& regExps ) + { + if (regExps != m_regExps) + { + m_regExps = regExps; + + emit regExpsChanged(regExps); + } + } + + void RssFilter::setSeries( bool series ) + { + if (m_series != series) + { + m_series = series; + + emit seriesChanged(series); + } + } + + void RssFilter::setSansEpisode( bool sansEpisode ) + { + if (m_sansEpisode != sansEpisode) + { + m_sansEpisode = sansEpisode; + + emit sansEpisodeChanged(sansEpisode); + } + } + + void RssFilter::setMinSeason( int minSeason ) + { + if (m_minSeason != minSeason) + { + m_minSeason = minSeason; + + emit minSeasonChanged(minSeason); + } + } + + void RssFilter::setMinEpisode( int minEpisode ) + { + if (m_minEpisode != minEpisode) + { + m_minEpisode = minEpisode; + + emit minEpisodeChanged(minEpisode); + } + } + + void RssFilter::setMaxSeason( int maxSeason ) + { + if (m_maxSeason != maxSeason) + { + m_maxSeason = maxSeason; + + emit maxSeasonChanged(maxSeason); + } + } + + void RssFilter::setMaxEpisode( int maxEpisode ) + { + if (m_maxEpisode != maxEpisode) + { + m_maxEpisode = maxEpisode; + + emit maxEpisodeChanged(maxEpisode); + } + } + + void RssFilter::setMatches( const QValueList<FilterMatch>& matches ) + { + if (matches != m_matches) + { + m_matches = matches; + + emit matchesChanged(matches); + } + } + + bool RssFilter::episodeInRange(int season, int episode, bool ignoreMatches, bool& alreadyDownloaded) + { + if (m_minSeason > 0) + { + if (season < m_minSeason) + { + return false; + } + if (season == m_minSeason && m_minEpisode > 0) + { + if (episode < m_minEpisode) + { + return false; + } + } + } + + if (m_maxSeason > 0) + { + if (season > m_maxSeason) + { + return false; + } + if (season == m_maxSeason && m_maxEpisode > 0) + { + if (episode > m_maxEpisode) + { + return false; + } + } + } + + for (int i=0; i<m_matches.count(); i++) + { + //if the episode is already in the matches - don't download it + if ((*m_matches.at(i)).season() == season &&(*m_matches.at(i)).episode() == episode) + { + alreadyDownloaded = true; + return !ignoreMatches; + } + + } + + return true; + } + + bool RssFilter::scanArticle( RssArticle article, bool ignoreMatches, bool saveMatch) + { + if (!m_active && saveMatch) + return false; + + QRegExp regEx; + regEx.setCaseSensitive(false); + + if (!m_regExps.count()) + return false; + + for (int i=0; i<m_regExps.count(); i++) + { + if (m_regExps[i].isEmpty()) + continue; + + QString curExp = m_regExps[i]; + bool invert=false; + + if (curExp.startsWith( "!" )) + { + invert=true; + curExp = curExp.remove( 0, 1); + } + + regEx.setPattern(curExp); + + if (!invert) + { + if (!article.title().contains(regEx) && !article.link().prettyURL().contains(regEx) && !article.description().contains(regEx) ) + { + return false; + } + } + else + { + if (article.title().contains(regEx) || article.link().prettyURL().contains(regEx) || article.description().contains(regEx) ) + { + return false; + } + } + } + + int season = 0, episode = 0; + bool alreadyDownloaded = false; + + if (m_series) + { + QStringList episodeFormats; + episodeFormats << "s([0-9]{1,2})[de]([0-9]{1,2})[^0-9]" << "[^0-9]([0-9]{1,2})x([0-9]{1,2})[^0-9]" << "[^0-9]([0-9]{1,2})([0-9]{2})[^0-9]"; + for (int i=0; i<episodeFormats.count(); i++) + { + regEx.setPattern(*episodeFormats.at(i)); + if (regEx.search(article.title()) >= 0) + { + season = (*regEx.capturedTexts().at(1)).toInt(); + episode = (*regEx.capturedTexts().at(2)).toInt(); + if (!episodeInRange(season,episode,ignoreMatches,alreadyDownloaded)) + { + return false; + } + break; + } + + if (regEx.search(article.link().prettyURL()) >= 0) + { + season = (*regEx.capturedTexts().at(1)).toInt(); + episode = (*regEx.capturedTexts().at(2)).toInt(); + if (!episodeInRange(season,episode,ignoreMatches,alreadyDownloaded)) + { + return false; + } + break; + } + + if (regEx.search(article.description()) >= 0) + { + season = (*regEx.capturedTexts().at(1)).toInt(); + episode = (*regEx.capturedTexts().at(2)).toInt(); + if (!episodeInRange(season,episode,ignoreMatches,alreadyDownloaded)) + { + return false; + } + break; + } + } + + if (!m_sansEpisode) + { + if (!season && !episode) + { + //no episode number was found and we're not downloading matches without episode numbers + return false; + } + } + } + + if (!alreadyDownloaded && saveMatch) + { + FilterMatch newMatch(season, episode, article.link().prettyURL()); + m_matches.append(newMatch); + emit matchesChanged(m_matches); + } + + return true; + } + + void RssFilter::deleteMatch(const QString& link) + { + + QValueList<FilterMatch>::iterator it = m_matches.begin(); + while (it != m_matches.end()) + { + if ((*it).link() == link) + { + it = m_matches.remove(it); + } + else + { + it++; + } + } + + } + + QDataStream &operator<<( QDataStream &out, const FilterMatch &filterMatch ) + { + out << filterMatch.season() << filterMatch.episode() << filterMatch.time() << filterMatch.link(); + + return out; + } + + QDataStream &operator>>( QDataStream &in, FilterMatch &filterMatch ) + { + int season, episode; + QString time; + QString link; + in >> season >> episode >> time >> link; + filterMatch = FilterMatch(season, episode, link, time); + + return in; + } + + QDataStream &operator<<( QDataStream &out, const RssFilter &filter ) + { + out << filter.title() << int(filter.active()) << filter.regExps() << int(filter.series()) << int(filter.sansEpisode()) << filter.minSeason() << filter.minEpisode() << filter.maxSeason() << filter.maxEpisode() << filter.matches(); + + return out; + } + + QDataStream &operator>>( QDataStream &in, RssFilter &filter ) + { + QString title; + int active; + QStringList regExps; + int series; + int sansEpisode; + int minSeason; + int minEpisode; + int maxSeason; + int maxEpisode; + QValueList<FilterMatch> matches; + in >> title >> active >> regExps >> series >> sansEpisode >> minSeason >> minEpisode >> maxSeason >> maxEpisode >> matches; + + filter = RssFilter(title, active, regExps, series, sansEpisode, minSeason, minEpisode, maxSeason, maxEpisode, matches); + + return in; + } + + RssFilter::~RssFilter() + { + } + +} diff --git a/plugins/rssfeed/rssfilter.h b/plugins/rssfeed/rssfilter.h new file mode 100644 index 0000000..be31d18 --- /dev/null +++ b/plugins/rssfeed/rssfilter.h @@ -0,0 +1,151 @@ +/*************************************************************************** + * Copyright (C) 2006 by Alan Jones * + * skyphyr@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ +#ifndef RSSFILTER_H +#define RSSFILTER_H + +#include <qobject.h> +#include <qstring.h> +#include <qtimer.h> +#include <qdatastream.h> +#include <qregexp.h> + +#include "rssarticle.h" + +using namespace RSS; + +namespace kt +{ + /** + * @brief RssFilter Class + * @author Alan Jones <skyphyr@gmail.com> + * + * + */ + class FilterMatch + { + public: + + FilterMatch() { m_season = 0; m_episode = 0; m_time = QDateTime::currentDateTime().toString(); m_link=QString(); }; + FilterMatch(int season, int episode, QString link, QString time = QDateTime::currentDateTime().toString()); + FilterMatch(const FilterMatch &other); + FilterMatch &operator=(const FilterMatch &other); + bool operator==(const FilterMatch &other) const; + ~FilterMatch() {}; + + QString link() const { return m_link; } + int season() const { return m_season; } + int episode() const { return m_episode; } + QString time() const { return m_time; } + + void setLink(const QString& link) { m_link = link; } + void setSeason(int season) { m_season = season; } + void setEpisode(int episode) { m_episode = episode; } + void setTime(QString time) { m_time = time; } + + private: + int m_season; + int m_episode; + QString m_link; + QString m_time; + }; + + class RssFilter : public QObject + { + Q_OBJECT + public: + + /** + * Default constructor. + */ + RssFilter(QObject * parent = 0); + RssFilter(const RssFilter &other); + RssFilter(QString title, bool active, QStringList regexps, bool series, bool sansEpisode, + int minSeason, int minEpisode, int maxSeason, int maxEpisode, + QValueList<FilterMatch> matches); + RssFilter &operator=(const RssFilter &other); + ~RssFilter(); + + QString title() const { return m_title; } + bool active() const { return m_active; } + QStringList regExps() const { return m_regExps; } + bool series() const { return m_series; } + bool sansEpisode() const { return m_sansEpisode; } + int minSeason() const { return m_minSeason; } + int minEpisode() const { return m_minEpisode; } + int maxSeason() const { return m_maxSeason; } + int maxEpisode() const { return m_maxEpisode; } + QValueList<FilterMatch> matches() const { return m_matches; } + + bool scanArticle(RssArticle article, bool ignoreMatches = true, bool saveMatch = true); + void deleteMatch(const QString& link); + + public slots: + void setTitle( const QString& title ); + void setActive( bool active ); + void setRegExps ( const QStringList& regexps ); + void setSeries ( bool series ); + void setSansEpisode ( bool sansEpisode ); + void setMinSeason( int minSeason ); + void setMinEpisode( int minEpisode ); + void setMaxSeason( int maxSeason ); + void setMaxEpisode( int maxEpisode ); + void setMatches( const QValueList<FilterMatch>& matches ); + + //void scanFilter(); + + signals: + void titleChanged( const QString& title ); + void activeChanged( bool active ); + void regExpsChanged( const QStringList& regexps ); + void seriesChanged( bool series ); + void sansEpisodeChanged( bool sansEpisode ); + void minSeasonChanged (int minSeason); + void minEpisodeChanged (int minEpisode); + void maxSeasonChanged (int maxSeason); + void maxEpisodeChanged (int maxEpisode); + void matchesChanged( const QValueList<FilterMatch>& matches ); + + void rescanFilter(); + + private: + QString m_title; + bool m_active; + QStringList m_regExps; + bool m_series; + bool m_sansEpisode; + int m_minSeason; + int m_minEpisode; + int m_maxSeason; + int m_maxEpisode; + QValueList<FilterMatch> m_matches; + + bool episodeInRange(int season, int episode, bool ignoreMatches, bool& alreadyDownloaded); + + }; + + QDataStream &operator<<( QDataStream &out, const FilterMatch &filterMatch ); + QDataStream &operator>>( QDataStream &in, FilterMatch &filterMatch ); + + QDataStream &operator<<( QDataStream &out, const RssFilter &filter ); + QDataStream &operator>>( QDataStream &in, RssFilter &filter ); + +} + +#endif diff --git a/plugins/rssfeed/rsslinkdownloader.cpp b/plugins/rssfeed/rsslinkdownloader.cpp new file mode 100644 index 0000000..4b0f390 --- /dev/null +++ b/plugins/rssfeed/rsslinkdownloader.cpp @@ -0,0 +1,202 @@ +/*************************************************************************** + * Copyright (C) 2006 by Alan Jones * + * skyphyr@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ +#include "rsslinkdownloader.h" + +#include <klocale.h> +#include <kmimetype.h> +#include <kmessagebox.h> + +#include <qfile.h> + +#include "../../libktorrent/torrent/bdecoder.h" +#include "../../libktorrent/torrent/bnode.h" + +using namespace bt; + +namespace kt +{ + + RssLinkDownloader::RssLinkDownloader(CoreInterface* core, QString link, RssFilter * filter, QObject * parent) : QObject (parent) + { + //tempFile.setAutoDelete(true); + m_core = core; + firstLink = true; + curFilter = filter; + if (!KURL(link).isValid()) + { + // no valid URL, so just display an error message + KMessageBox::error(0,i18n("Failed to find and download a valid torrent for %1").arg(curLink)); + QTimer::singleShot(50,this,SLOT(suicide())); + } + else + { + //first let's download the link so we can process it to check for the actual torrent + curLink = curSubLink = link; + curFile = KIO::storedGet(link,false,false); + connect(curFile, SIGNAL(result(KIO::Job*)),this,SLOT(processLink( KIO::Job* ))); + } + } + + RssLinkDownloader::~RssLinkDownloader() + { + + } + + void RssLinkDownloader::processLink(KIO::Job* jobStatus) + { + + if (!jobStatus->error()) + { + //the file downloaded ok - so let's check if it's a torrent + KMimeType linkType = *KMimeType::findByContent(curFile->data()); + if (linkType.is("text/html")) + { + if (firstLink) + { + KURL url = curLink; + //let's go through the data and populate our sublink array + QTextStream html(curFile->data(), IO_ReadOnly); + + //go through a line at a time checking for a torrent + QString htmlline = html.readLine(); + while (!htmlline.isNull()) + { + QRegExp hrefTags = QString("<A.*HREF.*</A"); + hrefTags.setCaseSensitive(false); + hrefTags.setMinimal(true); + + int matchPos = 0; + while (htmlline.find(hrefTags, matchPos) >= 0) + { + matchPos += hrefTags.matchedLength(); + //we're found an <a href tag - let's check it if contains download + QRegExp hrefText = QString("d(own)?load"); + hrefText.setCaseSensitive(false); + + if (!hrefTags.capturedTexts()[0].contains(hrefText)) + { + //link text doesn't contain dload/download + continue; + } + + //we're found an <a href tag - now let's the the url out of it + hrefText = QString("HREF=\"?([^\">< ]*)[\" ]"); + hrefText.setCaseSensitive(false); + + hrefTags.capturedTexts()[0].find(hrefText); + //lets get the captured + QString hrefLink = hrefText.capturedTexts()[1]; + + if (hrefLink.startsWith("/")) + { + hrefLink = url.protocol() + "://" + url.host() + hrefLink; + } + else if (!hrefLink.startsWith("http://", false)) + { + hrefLink = url.url().left(url.url().findRev("/")+1) + hrefLink; + } + + subLinks.append(hrefLink); + + } + + //run the query again + htmlline = html.readLine(); + } + + + firstLink = false; + } + } + else + { + + //I know this may check a file which we've already been told is html, but sometimes it lies + try + { + //last ditched brute force attempt to check if it's a torrent file + BNode* node = 0; + BDecoder decoder(curFile->data(),false); + node = decoder.decode(); + BDictNode* dict = dynamic_cast<BDictNode*>(node); + + if (dict) + { + delete node; + node = dict = 0; + + if (curFilter) + { + m_core->loadSilently( curSubLink ); + emit linkDownloaded( curLink, 3); + } + else + { + m_core->load( curSubLink ); + emit linkDownloaded( curLink, 1); + } + + //delete ourself and finish + deleteLater(); + return; + } + + + } + catch (...) + { + //we can just ignore any errors here + } + } + + } + //curFile->deleteLater(); + + //check for the next item + if (subLinks.isEmpty()) + { + if (curFilter) + { + //we've failed to download a torrent for this match + curFilter->deleteMatch( curLink ); + } + else + { + //failed to download a selected article from a feed + KMessageBox::error(0,i18n("Failed to find and download a valid torrent for %1").arg(curLink)); + } + deleteLater(); + } + else + { + curSubLink = subLinks.first(); + subLinks.pop_front(); + curFile = KIO::storedGet(curSubLink,false,false); + connect(curFile, SIGNAL(result(KIO::Job*)),this,SLOT(processLink( KIO::Job* ))); + } + } + + + void RssLinkDownloader::suicide() + { + deleteLater(); + } + +}
\ No newline at end of file diff --git a/plugins/rssfeed/rsslinkdownloader.h b/plugins/rssfeed/rsslinkdownloader.h new file mode 100644 index 0000000..2266f29 --- /dev/null +++ b/plugins/rssfeed/rsslinkdownloader.h @@ -0,0 +1,82 @@ +/*************************************************************************** + * Copyright (C) 2006 by Alan Jones * + * skyphyr@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ +#ifndef RSSLINKDOWNLOADER_H +#define RSSLINKDOWNLOADER_H + +#include <kio/job.h> +#include <ktempfile.h> + +#include <interfaces/coreinterface.h> +#include <torrent/globals.h> +#include <util/log.h> +#include <util/constants.h> + +#include <qstring.h> + +#include "rssfilter.h" +#include "rssarticle.h" + +using namespace RSS; + +namespace kt +{ + /** + * @brief RssLinkDownloader Class + * @author Alan Jones <skyphyr@gmail.com> + * + * + */ + + class RssLinkDownloader : public QObject + { + Q_OBJECT + public: + + /** + * Default constructor. + */ + RssLinkDownloader(CoreInterface* core, QString link, RssFilter * filter = 0, QObject * parent = 0); + + ~RssLinkDownloader(); + + + public slots: + void processLink(KIO::Job* jobStatus); + void suicide(); + + signals: + void linkDownloaded( QString link, int downloaded ); + + private: + KIO::StoredTransferJob * curFile; + QString curLink, curSubLink; + QStringList subLinks; + RssFilter * curFilter; + bool firstLink; + + //KTempFile tempFile; + + CoreInterface* m_core; + }; + + +} + +#endif |