00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022 #include "rss.h"
00023
00024
00025 #include <KDebug>
00026 #include <KUrl>
00027 #include <kstandarddirs.h>
00028 #include <syndication/item.h>
00029 #include <syndication/loader.h>
00030 #include <syndication/image.h>
00031
00032
00033 #include <Plasma/DataEngine>
00034
00035
00036 #include <QDateTime>
00037 #include <QDBusReply>
00038 #include <QDBusInterface>
00039 #include <QTimer>
00040 #include <QSignalMapper>
00041
00042 #define TIMEOUT 15000 //timeout before updating the source if not all feeds
00043
00044 #define CACHE_TIMEOUT 60 //time in seconds before the cached feeds are marked
00045
00046 #define MINIMUM_INTERVAL 60000
00047 #define FAVICONINTERFACE "org.kde.FavIcon"
00048
00049 RssEngine::RssEngine(QObject* parent, const QVariantList& args)
00050 : Plasma::DataEngine(parent, args)
00051 {
00052 Q_UNUSED(args)
00053 setMinimumPollingInterval(MINIMUM_INTERVAL);
00054 m_favIconsModule = new QDBusInterface("org.kde.kded", "/modules/favicons",
00055 FAVICONINTERFACE);
00056 m_signalMapper = new QSignalMapper(this);
00057 connect(m_favIconsModule, SIGNAL(iconChanged(bool,QString,QString)),
00058 this, SLOT(slotIconChanged(bool,QString,QString)));
00059 connect(m_signalMapper, SIGNAL(mapped(const QString &)),
00060 this, SLOT(timeout(const QString &)));
00061 }
00062
00063 RssEngine::~RssEngine()
00064 {
00065 delete m_favIconsModule;
00066 }
00067
00068 bool RssEngine::updateSourceEvent(const QString &name)
00069 {
00070
00071
00072
00073
00074
00075
00076
00077
00078 const QStringList sources = name.split(" ", QString::SkipEmptyParts);
00079
00080 foreach (const QString& source, sources) {
00081
00082
00083
00084 if (QDateTime::currentDateTime() >
00085 m_feedTimes[source.toLower()].addSecs(CACHE_TIMEOUT)){
00086 kDebug() << "Cache from " << source <<
00087 " older than 60 seconds, refreshing...";
00088
00089 Syndication::Loader * loader = Syndication::Loader::create();
00090 connect(loader, SIGNAL(loadingComplete(Syndication::Loader*,
00091 Syndication::FeedPtr,
00092 Syndication::ErrorCode)),
00093 this, SLOT(processRss(Syndication::Loader*,
00094 Syndication::FeedPtr,
00095 Syndication::ErrorCode)));
00096
00097 m_feedMap.insert(loader, source);
00098 m_sourceMap.insert(loader, name);
00099 loader->loadFrom(source);
00100 } else {
00101 kDebug() << "Recent cached version of " << source <<
00102 " found. Skipping...";
00103
00104
00105 if (cachesUpToDate(name)) {
00106 updateFeeds(name, m_feedTitles[ source ] );
00107 }
00108 }
00109 }
00110
00111 QTimer *timer = new QTimer(this);
00112 m_timerMap[name] = timer;
00113 timer->setSingleShot(true);
00114 m_signalMapper->setMapping(timer, name);
00115
00116 connect(timer, SIGNAL(timeout()), m_signalMapper, SLOT(map()));
00117
00118 timer->start(TIMEOUT);
00119 return true;
00120 }
00121
00122 void RssEngine::slotIconChanged(bool isHost, const QString& hostOrURL,
00123 const QString& iconName)
00124 {
00125 Q_UNUSED(isHost);
00126 QString iconFile = KGlobal::dirs()->findResource("cache",
00127 iconName+".png");
00128 QString url = hostOrURL.toLower();
00129
00130 m_feedIcons[url] = iconFile;
00131 QMap<QString, QVariant> map;
00132
00133 for (int i = 0; i < m_feedItems[url].size(); i++) {
00134 map = m_feedItems[url].at(i).toMap();
00135 map["icon"] = iconFile;
00136 m_feedItems[url].replace(i, map);
00137 }
00138
00139
00140 foreach (const QString& source, m_sourceMap) {
00141 if (source.contains(url, Qt::CaseInsensitive) &&
00142 cachesUpToDate(source)) {
00143 kDebug() << "all caches from source " << source <<
00144 " up to date, updating...";
00145 updateFeeds(source, m_feedTitles[ source ] );
00146 }
00147 }
00148 }
00149
00150 void RssEngine::timeout(const QString & source)
00151 {
00152 kDebug() << "timout fired, updating source";
00153 updateFeeds(source, m_feedTitles[ source ] );
00154 m_signalMapper->removeMappings(m_timerMap[source]);
00155 }
00156
00157 bool RssEngine::sourceRequestEvent(const QString &name)
00158 {
00159 setData(name, DataEngine::Data());
00160 updateSourceEvent(name);
00161 return true;
00162 }
00163
00164 void RssEngine::processRss(Syndication::Loader* loader,
00165 Syndication::FeedPtr feed,
00166 Syndication::ErrorCode error)
00167 {
00168 QString url = m_feedMap.take(loader);
00169 QString source = m_sourceMap.take(loader);
00170 QString title;
00171 bool iconRequested = false;
00172 KUrl u(url);
00173
00174 if (error != Syndication::Success) {
00175 kDebug() << "Syndication did not work out... url = " << url;
00176 title = i18n("Syndication did not work out");
00177 setData(source, "title", i18n("Fetching feed failed."));
00178 setData(source, "link", url);
00179 } else {
00180 title = feed->title();
00181 QVariantList items;
00182 QString location;
00183
00184 foreach (const Syndication::ItemPtr& item, feed->items()) {
00185 QMap<QString, QVariant> dataItem;
00186
00187 dataItem["title"] = item->title();
00188 dataItem["feed_title"] = feed->title();
00189 dataItem["link"] = item->link();
00190 dataItem["feed_url"] = url;
00191 dataItem["description"] = item->description();
00192 dataItem["content"] = item->content();
00193 dataItem["time"] = (uint)item->datePublished();
00194 if (!m_feedIcons.contains(url.toLower()) && !iconRequested) {
00195
00196 location = iconLocation(u);
00197 if (location.isEmpty()) {
00198 m_favIconsModule->call( "downloadHostIcon", u.url() );
00199 } else {
00200
00201 slotIconChanged(false, u.url(), iconLocation(u));
00202 }
00203 iconRequested = true;
00204 }
00205 dataItem["icon"] = m_feedIcons[url.toLower()];
00206
00207 items.append(dataItem);
00208 }
00209 m_feedItems[url.toLower()] = items;
00210 m_feedTimes[url.toLower()] = QDateTime::currentDateTime();
00211 m_feedTitles[url.toLower()] = title;
00212
00213
00214
00215
00216
00217
00218
00219 if (cachesUpToDate(source)) {
00220 kDebug() << "all caches from source " << source
00221 << " up to date, updating...";
00222 updateFeeds(source, title);
00223 } else {
00224 kDebug() << "not all caches from source " << source
00225 << ", delaying update.";
00226 }
00227 }
00228 }
00229
00230 void RssEngine::updateFeeds(const QString & source, const QString & title)
00231 {
00236 const QVariantList list = mergeFeeds(source);
00237 setData(source, "items", list);
00238 QStringList sources = source.split(" ", QString::SkipEmptyParts);
00239 if (sources.size() > 1) {
00240 setData(source, "title", i18np("1 RSS feed fetched",
00241 "%1 RSS feeds fetched", sources.size()));
00242 } else {
00243 setData(source, "title", title);
00244 }
00245 }
00246
00247 bool RssEngine::cachesUpToDate(const QString & source) const
00248 {
00249 QStringList sources = source.split(" ", QString::SkipEmptyParts);
00250 bool outOfDate = false;
00251 foreach (const QString &url, sources) {
00252 if (QDateTime::currentDateTime() >
00253 m_feedTimes[url.toLower()].addSecs(CACHE_TIMEOUT)){
00254 outOfDate = true;
00255 }
00256 if (!m_feedIcons.contains(url.toLower())) {
00257 outOfDate = true;
00258 }
00259 }
00260 return (!outOfDate);
00261 }
00262
00263 bool compare(const QVariant &v1, const QVariant &v2)
00264 {
00265 return v1.toMap()["time"].toUInt() > v2.toMap()["time"].toUInt();
00266 }
00267
00268 QVariantList RssEngine::mergeFeeds(QString source) const
00269 {
00270 QVariantList result;
00271 QStringList sources = source.split(" ", QString::SkipEmptyParts);
00272
00273 foreach (const QString& feed, sources) {
00274 result += m_feedItems[feed.toLower()];
00275 }
00276
00277 qSort(result.begin(), result.end(), compare);
00278 return result;
00279 }
00280
00281 QString RssEngine::iconLocation(const KUrl & url) const
00282 {
00283 QDBusReply<QString> reply = m_favIconsModule->call( "iconForUrl", url.url() );
00284 if (reply.isValid()) {
00285 QString result = reply;
00286 return result;
00287 }
00288 return QString();
00289 }
00290
00291 #include "rss.moc"
00292