~ubuntu-branches/ubuntu/utopic/kde-workspace/utopic-proposed

« back to all changes in this revision

Viewing changes to plasma/generic/dataengines/rss/rss.cpp

  • Committer: Bazaar Package Importer
  • Author(s): Michał Zając
  • Date: 2011-07-09 08:31:15 UTC
  • Revision ID: james.westby@ubuntu.com-20110709083115-ohyxn6z93mily9fc
Tags: upstream-4.6.90
Import upstream version 4.6.90

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 *   Copyright (C) 2007 Aaron Seigo <aseigo@kde.org>
 
3
 *   Copyright (C) 2007 Petri Damsten <damu@iki.fi>
 
4
 *   Copyright (C) 2008 Rob Scheepmaker <r.scheepmaker@student.utwente.nl>
 
5
 *
 
6
 *   This program is free software; you can redistribute it and/or modify
 
7
 *   it under the terms of the GNU Library General Public License version 2 as
 
8
 *   published by the Free Software Foundation
 
9
 *
 
10
 *   This program is distributed in the hope that it will be useful,
 
11
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 
12
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
13
 *   GNU General Public License for more details
 
14
 *
 
15
 *   You should have received a copy of the GNU Library General Public
 
16
 *   License along with this program; if not, write to the
 
17
 *   Free Software Foundation, Inc.,
 
18
 *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
19
 */
 
20
 
 
21
//Own
 
22
#include "rss.h"
 
23
 
 
24
//KDE
 
25
#include <KDebug>
 
26
#include <KUrl>
 
27
#include <KStandardDirs>
 
28
#include <Solid/Networking>
 
29
#include <syndication/item.h>
 
30
#include <syndication/loader.h>
 
31
#include <syndication/image.h>
 
32
#include <syndication/person.h>
 
33
 
 
34
//Plasma
 
35
#include <Plasma/DataEngine>
 
36
 
 
37
//Qt
 
38
#include <QDateTime>
 
39
#include <QDBusReply>
 
40
#include <QDBusInterface>
 
41
#include <QTimer>
 
42
#include <QSignalMapper>
 
43
 
 
44
#define TIMEOUT 15000    //timeout before updating the source if not all feeds
 
45
                         //are fetched.
 
46
#define CACHE_TIMEOUT 60 //time in seconds before the cached feeds are marked
 
47
                         //as out of date.
 
48
#define MINIMUM_INTERVAL 60000
 
49
#define FAVICONINTERFACE "org.kde.FavIcon"
 
50
 
 
51
RssEngine::RssEngine(QObject* parent, const QVariantList& args)
 
52
    : Plasma::DataEngine(parent, args),
 
53
    m_forceUpdate(false)
 
54
{
 
55
    Q_UNUSED(args)
 
56
    setMinimumPollingInterval(MINIMUM_INTERVAL);
 
57
    m_favIconsModule = new QDBusInterface("org.kde.kded", "/modules/favicons",
 
58
                                          FAVICONINTERFACE);
 
59
    m_signalMapper = new QSignalMapper(this);
 
60
    connect(m_favIconsModule, SIGNAL(iconChanged(bool,QString,QString)),
 
61
            this, SLOT(slotIconChanged(bool,QString,QString)));
 
62
    connect(m_signalMapper, SIGNAL(mapped(const QString &)),
 
63
            this, SLOT(timeout(const QString &)));
 
64
    connect(Solid::Networking::notifier(), SIGNAL(statusChanged(Solid::Networking::Status)),
 
65
            SLOT(networkStatusChanged(Solid::Networking::Status)));
 
66
 
 
67
}
 
68
 
 
69
RssEngine::~RssEngine()
 
70
{
 
71
    delete m_favIconsModule;
 
72
}
 
73
 
 
74
void RssEngine::networkStatusChanged(Solid::Networking::Status status)
 
75
{
 
76
    if (status == Solid::Networking::Connected || status == Solid::Networking::Unknown) {
 
77
        kDebug() << "network connected, force refreshing feeds in 3 seconds";
 
78
        // The forced update needs to happen after the new feeds are in,
 
79
        // so remember to force the update in processRss()
 
80
        m_forceUpdate = true;
 
81
        // start updating the feeds
 
82
        foreach(const QString &feedUrl, sources()) {
 
83
            updateSourceEvent(feedUrl);
 
84
        }
 
85
    }
 
86
}
 
87
 
 
88
bool RssEngine::updateSourceEvent(const QString &name)
 
89
{
 
90
    /* Plasmoids using this engine should be able to retrieve
 
91
     * multiple feeds at the same time, so we allow a comma
 
92
     * separated list of url's
 
93
     */
 
94
    // NOTE: A comma separated list of feeds is not url compliant. Urls
 
95
    // may and do contain commas see http://www.spiegel.de/schlagzeilen/rss/0,5291,,00.xml
 
96
    // I have changed it to something more not url compliant " " three dots
 
97
    // Otherwise take a list instead
 
98
    const QStringList sources = name.split(' ', QString::SkipEmptyParts);
 
99
 
 
100
    foreach (const QString& source, sources) {
 
101
        setStorageEnabled(source, true);
 
102
        // Let's first see if we've got a recent cached version of
 
103
        // the feed. This avoids 'large' amounts of unnecessary network
 
104
        // traffic.
 
105
        if (QDateTime::currentDateTime() >
 
106
            m_feedTimes[source.toLower()].addSecs(CACHE_TIMEOUT)){
 
107
            kDebug() << "Cache from " << source <<
 
108
                        " older than 60 seconds, refreshing...";
 
109
 
 
110
            Syndication::Loader * loader = Syndication::Loader::create();
 
111
            connect(loader, SIGNAL(loadingComplete(Syndication::Loader*,
 
112
                                                   Syndication::FeedPtr,
 
113
                                                   Syndication::ErrorCode)),
 
114
                      this, SLOT(processRss(Syndication::Loader*,
 
115
                                            Syndication::FeedPtr,
 
116
                                            Syndication::ErrorCode)));
 
117
 
 
118
            m_feedMap.insert(loader, source);
 
119
            m_sourceMap.insert(loader, name);
 
120
            loader->loadFrom(source);
 
121
        } else {
 
122
            kDebug() << "Recent cached version of " << source <<
 
123
                        " found. Skipping...";
 
124
 
 
125
            // We might want to update the source:
 
126
            if (cachesUpToDate(name)) {
 
127
                updateFeeds(name, m_feedTitles[ source ] );
 
128
            }
 
129
        }
 
130
    }
 
131
 
 
132
    QTimer *timer = new QTimer(this);
 
133
    m_timerMap[name] = timer;
 
134
    timer->setSingleShot(true);
 
135
    m_signalMapper->setMapping(timer, name);
 
136
 
 
137
    connect(timer, SIGNAL(timeout()), m_signalMapper, SLOT(map()));
 
138
 
 
139
    timer->start(TIMEOUT);
 
140
    return true;
 
141
}
 
142
 
 
143
void RssEngine::slotIconChanged(bool isHost, const QString& hostOrURL,
 
144
                                             const QString& iconName)
 
145
{
 
146
    Q_UNUSED(isHost);
 
147
    const QString iconFile = KGlobal::dirs()->findResource("cache",
 
148
                                                     iconName+".png");
 
149
    const QString url = hostOrURL.toLower();
 
150
 
 
151
    m_feedIcons[url] = iconFile;
 
152
    QMap<QString, QVariant> map;
 
153
 
 
154
    for (int i = 0; i < m_feedItems[url].size(); i++) {
 
155
        map = m_feedItems[url].at(i).toMap();
 
156
        map["icon"] = iconFile;
 
157
        m_feedItems[url].replace(i, map);
 
158
    }
 
159
 
 
160
    //Are there sources ready to get updated now?
 
161
    foreach (const QString& source, m_sourceMap) {
 
162
        if (source.contains(url, Qt::CaseInsensitive) &&
 
163
            cachesUpToDate(source)) {
 
164
            kDebug() << "all caches from source " << source <<
 
165
                        " up to date, updating...";
 
166
            updateFeeds(source, m_feedTitles[ source ] );
 
167
        }
 
168
    }
 
169
}
 
170
 
 
171
void RssEngine::timeout(const QString & source)
 
172
{
 
173
    kDebug() << "timout fired, updating source";
 
174
    updateFeeds(source, m_feedTitles[ source ] );
 
175
    m_signalMapper->removeMappings(m_timerMap[source]);
 
176
}
 
177
 
 
178
bool RssEngine::sourceRequestEvent(const QString &name)
 
179
{
 
180
    setData(name, DataEngine::Data());
 
181
    updateSourceEvent(name);
 
182
    return true;
 
183
}
 
184
 
 
185
void RssEngine::processRss(Syndication::Loader* loader,
 
186
                           Syndication::FeedPtr feed,
 
187
                           Syndication::ErrorCode error)
 
188
{
 
189
    const QString url = m_feedMap.take(loader);
 
190
    const QString source = m_sourceMap.take(loader);
 
191
    QString title;
 
192
    bool iconRequested = false;
 
193
    KUrl u(url);
 
194
 
 
195
    if (error != Syndication::Success) {
 
196
        kDebug() << "Syndication did not work out... url = " << url;
 
197
        title = i18n("Syndication did not work out");
 
198
        setData(source, "title", i18n("Fetching feed failed."));
 
199
        setData(source, "link", url);
 
200
    } else {
 
201
        title = feed->title();
 
202
        QVariantList items;
 
203
        QString location;
 
204
 
 
205
        foreach (const Syndication::ItemPtr& item, feed->items()) {
 
206
            QMap<QString, QVariant> dataItem;
 
207
 
 
208
            //some malformed rss feeds can have empty entries
 
209
            if (item->title().isNull() && item->description().isNull() && dataItem["content"].isNull()) {
 
210
                continue;
 
211
            }
 
212
 
 
213
            dataItem["title"]       = item->title();
 
214
            dataItem["feed_title"]  = feed->title();
 
215
            dataItem["link"]        = item->link();
 
216
            dataItem["feed_url"]    = url;
 
217
            dataItem["description"] = item->description();
 
218
            dataItem["content"]     = item->content();
 
219
            dataItem["time"]        = (uint)item->datePublished();
 
220
            if (!m_feedIcons.contains(url.toLower()) && !iconRequested) {
 
221
                //lets request an icon, and only do this once per feed.
 
222
                location = iconLocation(u);
 
223
                if (location.isEmpty()) {
 
224
                    m_favIconsModule->call( "downloadHostIcon", u.url() );
 
225
                } else {
 
226
                    //the icon is already in cache, so call this slot manually.
 
227
                    slotIconChanged(false, u.url(), iconLocation(u));
 
228
                }
 
229
                iconRequested = true;
 
230
            }
 
231
            dataItem["icon"] = m_feedIcons[url.toLower()];
 
232
            QStringList authors;
 
233
            foreach (const boost::shared_ptr<Syndication::Person> a, item->authors()) {
 
234
                authors << a->name();
 
235
            }
 
236
            dataItem["author"] = authors;
 
237
 
 
238
            items.append(dataItem);
 
239
 
 
240
 
 
241
            if (!m_rssSourceNames.contains(url)) {
 
242
                QMap<QString, QVariant> sourceItem;
 
243
 
 
244
                sourceItem["feed_title"] = feed->title();
 
245
                sourceItem["feed_url"] = url;
 
246
                sourceItem["icon"] = m_feedIcons[url.toLower()];
 
247
 
 
248
                m_rssSources.append(sourceItem);
 
249
                m_rssSourceNames.insert(url);
 
250
            }
 
251
        }
 
252
        m_feedItems[url.toLower()] = items;
 
253
        m_feedTimes[url.toLower()] = QDateTime::currentDateTime();
 
254
        m_feedTitles[url.toLower()] = title;
 
255
 
 
256
        // If we update the feeds every time a feed is fetched,
 
257
        // only the first update will actually update a connected
 
258
        // applet, which is actually sane, since plasma updates
 
259
        // only one time each interval. This means, however, that
 
260
        // we maybe want to delay updating the feeds untill either
 
261
        // timeout, or all feeds are up to date.
 
262
        if (cachesUpToDate(source)) {
 
263
            kDebug() << "all caches from source " << source
 
264
                     << " up to date, updating...";
 
265
            updateFeeds(source, title);
 
266
            if (m_forceUpdate) {
 
267
                // Should be used with care ...
 
268
                forceImmediateUpdateOfAllVisualizations();
 
269
                m_forceUpdate = false;
 
270
                // and skip scheduleSourcesUpdated(), since we
 
271
                // force a repaint anyway already
 
272
                return;
 
273
            }
 
274
        } else {
 
275
            kDebug() << "not all caches from source " << source
 
276
                     << ", delaying update.";
 
277
        }
 
278
        scheduleSourcesUpdated();
 
279
    }
 
280
}
 
281
 
 
282
void RssEngine::updateFeeds(const QString & source, const QString & title)
 
283
{
 
284
    /**
 
285
     * TODO: can this be improved? I'm calling mergeFeeds way too
 
286
     * often here...
 
287
     */
 
288
    const QVariantList list = mergeFeeds(source);
 
289
    setData(source, "items", list);
 
290
 
 
291
    setData(source, "sources", m_rssSources);
 
292
    const QStringList sourceNames = source.split(' ', QString::SkipEmptyParts);
 
293
    if (sourceNames.size() >  1) {
 
294
        setData(source, "title", i18np("1 RSS feed fetched",
 
295
                                       "%1 RSS feeds fetched", sourceNames.size()));
 
296
    } else {
 
297
        setData(source, "title", title);
 
298
    }
 
299
}
 
300
 
 
301
bool RssEngine::cachesUpToDate(const QString & source) const
 
302
{
 
303
    const QStringList sources = source.split(' ', QString::SkipEmptyParts);
 
304
    bool outOfDate = false;
 
305
    foreach (const QString &url, sources) {
 
306
        if (QDateTime::currentDateTime() >
 
307
            m_feedTimes[url.toLower()].addSecs(CACHE_TIMEOUT)){
 
308
            outOfDate = true;
 
309
        }
 
310
        if (!m_feedIcons.contains(url.toLower())) {
 
311
            outOfDate = true;
 
312
        }
 
313
    }
 
314
    return (!outOfDate);
 
315
}
 
316
 
 
317
bool compare(const QVariant &v1, const QVariant &v2)
 
318
{
 
319
     return v1.toMap()["time"].toUInt() > v2.toMap()["time"].toUInt();
 
320
}
 
321
 
 
322
QVariantList RssEngine::mergeFeeds(QString source) const
 
323
{
 
324
    QVariantList result;
 
325
    const QStringList sources = source.split(' ', QString::SkipEmptyParts);
 
326
 
 
327
    foreach (const QString& feed, sources) {
 
328
        result += m_feedItems[feed.toLower()];
 
329
    }
 
330
 
 
331
    qSort(result.begin(), result.end(), compare);
 
332
    return result;
 
333
}
 
334
 
 
335
QString RssEngine::iconLocation(const KUrl & url) const
 
336
{
 
337
    QDBusReply<QString> reply = m_favIconsModule->call( "iconForUrl", url.url() );
 
338
    if (reply.isValid()) {
 
339
        QString result = reply;
 
340
        return result;
 
341
    }
 
342
    return QString();
 
343
}
 
344
 
 
345
#include "rss.moc"
 
346