Parse the feed content in the Feed class.
[grr] / src / googlereader.cpp
1 #include <QtWebKit>
2 #include <QApplication>
3 #include <QNetworkRequest>
4 #include <QNetworkReply>
5 #include <QNetworkCookie>
6 #include <QBuffer>
7 #include <QTimer>
8 #include <QDateTime>
9 #include <QDebug>
10
11 #include <qjson/parser.h>
12
13 #include <stdio.h>
14 #include <unistd.h>
15 #include <stdlib.h>
16
17 #include "googlereader.h"
18
19 void Feed::updateSubscription(Feed *feed) {
20         title = feed->title;
21         sortid = feed->sortid;
22         firstitemmsec = feed->firstitemmsec;
23         cat_id = feed->cat_id;
24         cat_label = feed->cat_label;
25         subscription_updated = true;
26 }
27
28 void Feed::fetch(bool cont) {
29         QNetworkRequest request;
30         //QByteArray ba = "http://www.google.com/reader/atom/";
31         QByteArray ba = "http://www.google.com/reader/api/0/stream/contents/";
32         ba.append(QUrl::toPercentEncoding(id));
33         QUrl url = QUrl::fromEncoded(ba);
34
35         if(continuation != "" && cont)
36                 url.addEncodedQueryItem("c", continuation.toUtf8());
37
38         request.setUrl(url);
39         reply = reader->getManager()->get(request);
40         connect(reply, SIGNAL(finished()), SLOT(fetchFinished()));
41 }
42
43 void Feed::fetchFinished() {
44         if (reply->error()) {
45                 qDebug() << "Download of" << reply->url() << "failed:" << qPrintable(reply->errorString());
46                 return;
47         }
48
49         QJson::Parser parser;
50         bool ok;
51         QVariantMap result = parser.parse(reply->readAll(), &ok).toMap();
52         QString continuation;
53
54         continuation = result["continuation"].toString();
55
56         foreach(QVariant l, result["items"].toList()) {
57                 QVariantMap e = l.toMap();
58                 Entry *entry = new Entry();;
59                 QString content, summary;
60
61                 entry->id = e["id"].toString();
62                 entry->published = QDateTime::fromTime_t(e["published"].toUInt());
63                 entry->author = e["author"].toString();
64                 entry->source = (e["origin"].toMap())["streamId"].toString();
65                 entry->link = (e["alternate"].toMap())["href"].toString();
66
67                 content = (e["content"].toMap())["content"].toString();
68                 summary = (e["summary"].toMap())["content"].toString();
69                 if(content != "") entry->content = content; else entry->content = summary;
70
71                 if(e["isReadStateLocked"].toBool())
72                         entry->flags |= ENTRY_FLAG_LOCKED | ENTRY_FLAG_READ;
73
74                 QWebPage p;
75                 p.mainFrame()->setHtml(e["title"].toString());
76                 entry->title = p.mainFrame()->toPlainText();
77
78                 foreach(QVariant c, e["categories"].toList()) {
79                         QString cat = c.toString();
80                         if(cat.endsWith("/state/com.google/read"))
81                                 entry->flags |= ENTRY_FLAG_READ;
82                         else if(cat.endsWith("/state/com.google/starred"))
83                                 entry->flags |= ENTRY_FLAG_STARRED;
84                         else if(cat.endsWith("/state/com.google/broadcast"))
85                                 entry->flags |= ENTRY_FLAG_SHARED;
86                 }
87
88                 entry->feed = this;
89                 addEntry(entry);
90         }
91
92         lastUpdated = QDateTime::currentDateTime();
93
94         emit updateFeedComplete();
95
96         reply->deleteLater();
97 }
98
99 GoogleReader::GoogleReader() {
100         connect(&manager, SIGNAL(finished(QNetworkReply*)),
101                 SLOT(downloadFinished(QNetworkReply*)));
102
103         SID = NULL;
104         updateSubscriptionsPending = false;
105         updateUnreadPending = false;
106         SIDPending = false;
107
108         login_url.setUrl("https://www.google.com/accounts/ClientLogin");
109         subscriptions_url.setUrl("http://www.google.com/reader/api/0/subscription/list?output=json");
110         unread_url.setUrl("http://www.google.com/reader/api/0/unread-count?output=json");
111         edittag_url.setUrl("http://www.google.com/reader/api/0/edit-tag?client=-");
112         token_url.setUrl("http://www.google.com/reader/api/0/token");
113         markallread_url.setUrl("http://www.google.com/reader/api/0/mark-all-as-read?client=-");
114
115         /* Add the virtual 'Starred items' feed */
116         Feed *feed = new Feed(this);
117         feed->id = "user/-/state/com.google/starred";
118         feed->title = "Starred items";
119         feed->special = 2;
120         feeds.insert(feed->id, feed);
121         connect(feed, SIGNAL(allReadChanged()), SIGNAL(allReadChanged()));
122
123         /* Add the virtual 'Shared items' feed */
124         feed = new Feed(this);
125         feed->id = "user/-/state/com.google/broadcast";
126         feed->title = "Shared items";
127         feed->special = 1;
128         feeds.insert(feed->id, feed);
129         connect(feed, SIGNAL(allReadChanged()), SIGNAL(allReadChanged()));
130 }
131
132 void GoogleReader::downloadFinished(QNetworkReply *reply) {
133         QUrl url = reply->url();
134
135         /* TODO: Instead of comparing against the url, use the signal from the
136          * QNetworkReply... */
137
138         if (reply->error()) {
139                 qDebug() << "Download of" << url << "failed:" << qPrintable(reply->errorString());
140                 if(url == login_url) {
141                         SIDPending = false;
142                         emit loginFailed("Incorrect username or password");
143                 }
144                 else if(url == edittag_url)
145                         getToken();
146                 return;
147         }
148         else if(url == login_url) {
149                 QByteArray data = reply->readAll();
150                 data.remove(0, data.indexOf("SID=", 0) + 4);
151                 data.remove(data.indexOf("\n", 0), 1024);
152                 SID = strdup(data.data());
153
154                 qDebug() << "SID:" << SID;
155
156                 manager.cookieJar()->setCookiesFromUrl(
157                         QList<QNetworkCookie>() << QNetworkCookie("SID", SID),
158                         QUrl("http://www.google.com"));
159
160                 SIDPending = false;
161
162                 getToken();
163
164                 /* TODO: Replace this with a proper state machine */
165                 if(updateSubscriptionsPending) {
166                         updateSubscriptionsPending = false;
167                         updateSubscriptions();
168                 }
169         }
170         else if(url == token_url) {
171                 token = reply->readAll();
172                 qDebug() << "token:" << token;
173         }
174         else if(url == subscriptions_url) {
175                 parseSubscriptions(reply->readAll());
176
177                 /* TODO: Replace this with a proper state machine */
178                 if(updateUnreadPending) {
179                         updateUnreadPending = false;
180                         updateUnread();
181                 }
182         }
183         else if(url == unread_url) {
184                 parseUnread(reply->readAll());
185         }
186         else if(url == edittag_url) {
187                 QByteArray data = reply->readAll();
188                 //qDebug() << "Result:" << data;
189         }
190         else if(url == markallread_url) {
191                 QByteArray data = reply->readAll();
192                 //qDebug() << "Result:" << data;
193         }
194
195         reply->deleteLater();
196 }
197
198 void GoogleReader::parseSubscriptions(QByteArray data) {
199         QJson::Parser parser;
200         bool ok;
201         QVariantMap result = parser.parse(data, &ok).toMap();
202
203         /* Clear the subscription updated flag */
204         QHash<QString, Feed *>::iterator i;
205         for(i = feeds.begin(); i != feeds.end(); ++i)
206                 i.value()->subscription_updated = false;
207
208         foreach(QVariant l, result["subscriptions"].toList()) {
209                 QVariantMap subscription = l.toMap();
210                 Feed *feed = new Feed(this);
211                 Feed *existing_feed;
212
213                 feed->id = subscription["id"].toString();
214                 feed->title = subscription["title"].toString();
215                 feed->sortid = subscription["sortid"].toString();
216                 feed->firstitemmsec = subscription["firstitemmsec"].toString();
217
218                 foreach(QVariant c, subscription["categories"].toList()) {
219                         QVariantMap cat = c.toMap();
220                         feed->cat_id = cat["id"].toString();
221                         feed->cat_label = cat["label"].toString();
222                 }
223
224                 existing_feed = feeds.value(feed->id);
225                 if(existing_feed) {
226                         existing_feed->updateSubscription(feed);
227                         delete(feed);
228                         feed = existing_feed;
229
230                 }
231                 else {
232                         feed->subscription_updated = true;
233                         feeds.insert(feed->id, feed);
234                         connect(feed, SIGNAL(allReadChanged()), SIGNAL(allReadChanged()));
235                 }
236         }
237
238         /* Delete feeds no longer subscribed to  */
239         for(i = feeds.begin(); i != feeds.end(); ++i) {
240                 if(i.value()->subscription_updated == false && i.value()->special == 0) {
241                         printf("DELETED: %s\n", i.value()->title.toLatin1().data());
242                         i = feeds.erase(i);
243                 }
244         }
245
246         lastUpdated = QDateTime::currentDateTime();
247         emit updateSubscriptionsComplete();
248 }
249
250 void GoogleReader::parseUnread(QByteArray data) {
251         QJson::Parser parser;
252         bool ok;
253         QVariantMap result = parser.parse(data, &ok).toMap();
254
255         foreach(QVariant l, result["unreadcounts"].toList()) {
256                 QVariantMap unread = l.toMap();
257                 QString id = unread["id"].toString();
258                 int count = unread["count"].toInt();
259                 ulong newestitem = unread["newestitem"].toUInt();
260
261                 Feed *f = feeds.value(id);
262                 if(f) {
263                         f->unread = count;
264                         f->newestitem = newestitem;
265                 }
266         }
267
268         lastUpdated = QDateTime::currentDateTime();
269         emit updateUnreadComplete();
270 }
271
272 void GoogleReader::getSID() {
273
274         if(SIDPending)
275                 return;
276
277         SIDPending = true;
278
279         QNetworkRequest request;
280         request.setUrl(login_url);
281
282         buffer.open(QBuffer::ReadWrite | QBuffer::Truncate);
283         buffer.write("Email=");
284         buffer.write(QUrl::toPercentEncoding(login));
285         buffer.write("&Passwd=");
286         buffer.write(QUrl::toPercentEncoding(passwd));
287         buffer.write("&service=reader&source=grms&continue=http://www.google.com");
288
289         //buffer.seek(0);
290         //qDebug() << buffer.readAll();
291
292         buffer.seek(0);
293         manager.post(request, &buffer);
294 }
295
296 void GoogleReader::getToken() {
297         QNetworkRequest request;
298         request.setUrl(token_url);
299         manager.get(request);
300 }
301
302 void GoogleReader::updateSubscriptions() {
303         QNetworkRequest request;
304
305         if(updateSubscriptionsPending)
306                 return;
307
308         if(!SID) {
309                 updateSubscriptionsPending = true;
310                 getSID();
311                 return;
312         }
313
314         request.setUrl(subscriptions_url);
315         manager.get(request);
316 }
317
318 void GoogleReader::updateUnread() {
319         QNetworkRequest request;
320
321         if(updateUnreadPending)
322                 return;
323
324         if(!SID) {
325                 updateUnreadPending = true;
326                 getSID();
327                 return;
328         }
329
330         request.setUrl(unread_url);
331         manager.get(request);
332 }
333
334 static bool compareFeedItems(const Feed *f1, const Feed *f2) {
335         if(f1->special || f2->special)
336                 return f1->special > f2->special;
337
338         if(f1->cat_label == f2->cat_label)
339                 return f1->title.toLower() < f2->title.toLower();
340
341         if(f1->cat_label.length() == 0)
342                 return false;
343
344         if(f2->cat_label.length() == 0)
345                 return true;
346
347         return f1->cat_label.toLower() < f2->cat_label.toLower();
348 }
349
350 QList<Feed *> GoogleReader::getFeeds() {
351         QList<Feed *> list = feeds.values();
352         qSort(list.begin(), list.end(), compareFeedItems);
353         return list;
354 }
355
356 void Feed::addEntry(Entry *entry) {
357         entries.insert(entry->id, entry);
358 }
359
360 void Feed::delEntry(Entry *entry) {
361         entries.remove(entry->id);
362 }
363
364 void Feed::updateUnread(int i) {
365         bool allRead = (unread == 0);
366
367         unread += i;
368         if(unread <= 0) unread = 0;
369
370         if(allRead != (unread == 0))
371                 emit allReadChanged();
372 }
373
374 void Feed::markRead() {
375         if(unread > 0) {
376                 /* Mark all the remaining items read */
377
378                 QNetworkRequest request;
379                 request.setUrl(reader->markallread_url);
380
381                 buffer.open(QBuffer::ReadWrite | QBuffer::Truncate);
382                 buffer.write("s=");
383                 buffer.write(QUrl::toPercentEncoding(id));
384                 //buffer.write("&ts=");
385                 //buffer.write(QByteArray::number(oldest));
386                 buffer.write("&T=");
387                 buffer.write(QUrl::toPercentEncoding(reader->token));
388
389                 //buffer.seek(0);
390                 //qDebug() << buffer.readAll();
391
392                 buffer.seek(0);
393                 reader->manager.post(request, &buffer);
394
395                 unread = 0;
396
397                 /* Go over all the entries and mark them read */
398                 QHash<QString, Entry *>::iterator i;
399                 for(i = entries.begin(); i != entries.end(); ++i)
400                         i.value()->flags |= ENTRY_FLAG_READ | ENTRY_FLAG_LOCKED;
401         }
402
403         emit allReadChanged();
404 }
405
406 static bool compareEntryItems(const Entry *e1, const Entry *e2) {
407         return e1->published > e2->published;
408 }
409
410 QList<Entry *> Feed::getEntries() {
411         QList<Entry *> list = entries.values();
412         qSort(list.begin(), list.end(), compareEntryItems);
413         return list;
414 }
415
416 /* TODO: Remove the duplicate code in changing stated */
417
418 void Entry::markRead(bool mark_read) {
419         /* Check if the read flag differs from the requested state */
420         if(((flags & ENTRY_FLAG_READ) != 0) == mark_read)
421                 return;
422
423         /* Cannot mark an item unread if it's locked */
424         if((flags & ENTRY_FLAG_LOCKED) && !mark_read)
425                 return;
426
427         QNetworkRequest request;
428         request.setUrl(feed->reader->edittag_url);
429
430         postread.open(QBuffer::ReadWrite | QBuffer::Truncate);
431         postread.write("i=");
432         postread.write(QUrl::toPercentEncoding(id));
433         if(mark_read)
434                 postread.write("&a=");
435         else
436                 postread.write("&r=");
437         postread.write(QUrl::toPercentEncoding("user/-/state/com.google/read"));
438         postread.write("&ac=edit-tags&T=");
439         postread.write(QUrl::toPercentEncoding(feed->reader->token));
440         postread.seek(0);
441         feed->reader->manager.post(request, &postread);
442
443         feed->updateUnread(mark_read ? -1 : 1);
444
445         if(mark_read)
446                 flags |= ENTRY_FLAG_READ;
447         else
448                 flags &= ~ENTRY_FLAG_READ;
449 }
450
451 void Entry::markStar(bool mark_star) {
452         /* Check if the starred flag differs from the requested state */
453         if(((flags & ENTRY_FLAG_STARRED) != 0) == mark_star)
454                 return;
455
456         QNetworkRequest request;
457         request.setUrl(feed->reader->edittag_url);
458
459         poststar.open(QBuffer::ReadWrite | QBuffer::Truncate);
460         poststar.write("i=");
461         poststar.write(QUrl::toPercentEncoding(id));
462         if(mark_star)
463                 poststar.write("&a=");
464         else
465                 poststar.write("&r=");
466         poststar.write(QUrl::toPercentEncoding("user/-/state/com.google/starred"));
467         poststar.write("&ac=edit-tags&T=");
468         poststar.write(QUrl::toPercentEncoding(feed->reader->token));
469         poststar.seek(0);
470         feed->reader->manager.post(request, &poststar);
471
472         Feed *starred = feed->reader->feeds.value("user/-/state/com.google/starred");
473
474         if(mark_star) {
475                 starred->addEntry(this);
476                 flags |= ENTRY_FLAG_STARRED;
477         }
478         else {
479                 starred->delEntry(this);
480                 flags &= ~ENTRY_FLAG_STARRED;
481         }
482 }
483
484 void Entry::markShared(bool mark_shared) {
485         /* Check if the shared flag differs from the requested state */
486         if(((flags & ENTRY_FLAG_SHARED) != 0) == mark_shared)
487                 return;
488
489         QNetworkRequest request;
490         request.setUrl(feed->reader->edittag_url);
491
492         postshared.open(QBuffer::ReadWrite | QBuffer::Truncate);
493         postshared.write("i=");
494         postshared.write(QUrl::toPercentEncoding(id));
495         if(mark_shared)
496                 postshared.write("&a=");
497         else
498                 postshared.write("&r=");
499         postshared.write(QUrl::toPercentEncoding("user/-/state/com.google/broadcast"));
500         postshared.write("&ac=edit-tags&T=");
501         postshared.write(QUrl::toPercentEncoding(feed->reader->token));
502         postshared.seek(0);
503         feed->reader->manager.post(request, &postshared);
504
505         Feed *shared = feed->reader->feeds.value("user/-/state/com.google/broadcast");
506
507         if(mark_shared) {
508                 shared->addEntry(this);
509                 flags |= ENTRY_FLAG_SHARED;
510         }
511         else {
512                 shared->delEntry(this);
513                 flags &= ~ENTRY_FLAG_SHARED;
514         }
515 }