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