2 #include <QApplication>
3 #include <QNetworkRequest>
4 #include <QNetworkReply>
5 #include <QNetworkCookie>
11 #include <qjson/parser.h>
17 #include "googlereader.h"
19 void Feed::updateSubscription(Feed *feed) {
21 sortid = feed->sortid;
22 firstitemmsec = feed->firstitemmsec;
23 cat_id = feed->cat_id;
24 cat_label = feed->cat_label;
25 subscription_updated = true;
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);
34 if(continuation != "" && cont)
35 url.addEncodedQueryItem("c", continuation.toUtf8());
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");
44 request.setRawHeader("Authorization", reader->getAuth());
45 request.setRawHeader("User-Agent", "Mozilla/5.0 gzip");
47 reply = reader->getManager()->get(request);
48 connect(reply, SIGNAL(finished()), SLOT(fetchFinished()));
51 void Feed::fetchFinished() {
53 qDebug() << "Download of" << reply->url() << "failed:" << qPrintable(reply->errorString());
59 QVariantMap result = parser.parse(reply->readAll(), &ok).toMap();
61 continuation = result["continuation"].toString();
62 updated = result["updated"].toUInt();
64 foreach(QVariant l, result["items"].toList()) {
65 QVariantMap e = l.toMap();
66 Entry *entry = new Entry();;
67 QString content, summary;
69 entry->id = e["id"].toString();
70 entry->published = QDateTime::fromTime_t(e["published"].toUInt());
71 entry->author = e["author"].toString();
72 entry->source = (e["origin"].toMap())["streamId"].toString();
73 foreach(QVariant a, e["alternate"].toList()) {
74 QVariantMap alt = a.toMap();
75 if(alt["type"].toString() == "text/html")
76 entry->link = alt["href"].toString();
79 content = (e["content"].toMap())["content"].toString();
80 summary = (e["summary"].toMap())["content"].toString();
81 if(content != "") entry->content = content; else entry->content = summary;
83 if(e["isReadStateLocked"].toBool())
84 entry->flags |= ENTRY_FLAG_LOCKED | ENTRY_FLAG_READ;
87 p.mainFrame()->setHtml(e["title"].toString());
88 entry->title = p.mainFrame()->toPlainText();
90 foreach(QVariant c, e["categories"].toList()) {
91 QString cat = c.toString();
92 if(cat.endsWith("/state/com.google/read"))
93 entry->flags |= ENTRY_FLAG_READ;
94 else if(cat.endsWith("/state/com.google/starred"))
95 entry->flags |= ENTRY_FLAG_STARRED;
96 else if(cat.endsWith("/state/com.google/broadcast"))
97 entry->flags |= ENTRY_FLAG_SHARED;
104 lastUpdated = QDateTime::currentDateTime();
106 emit updateFeedComplete();
108 reply->deleteLater();
111 GoogleReader::GoogleReader() {
112 /* Use the system proxy setting */
113 QNetworkProxyFactory::setUseSystemConfiguration(true);
117 proxy.setType(QNetworkProxy::HttpProxy);
118 proxy.setHostName("proxy");
121 proxy.setPassword("");
122 QNetworkProxy::setApplicationProxy(proxy);
125 connect(&manager, SIGNAL(finished(QNetworkReply*)),
126 SLOT(downloadFinished(QNetworkReply*)));
129 updateSubscriptionsPending = false;
130 updateUnreadPending = false;
133 login_url.setUrl("https://www.google.com/accounts/ClientLogin");
134 subscriptions_url.setUrl("http://www.google.com/reader/api/0/subscription/list?output=json");
135 unread_url.setUrl("http://www.google.com/reader/api/0/unread-count?output=json");
136 edittag_url.setUrl("http://www.google.com/reader/api/0/edit-tag?client=-");
137 token_url.setUrl("http://www.google.com/reader/api/0/token");
138 markallread_url.setUrl("http://www.google.com/reader/api/0/mark-all-as-read?client=-");
140 /* Add the virtual 'All items' feed */
141 Feed *feed = new Feed(this);
142 /* TODO: With the user id, unread counts are not updated automatically... */
143 feed->id = "user/-/state/com.google/reading-list";
144 feed->title = "All items";
146 feeds.insert(feed->id, feed);
147 connect(feed, SIGNAL(allReadChanged()), SIGNAL(allReadChanged()));
149 /* Add the virtual 'Starred items' feed */
150 Feed *feed = new Feed(this);
151 feed->id = "user/-/state/com.google/starred";
152 feed->title = "Starred items";
154 feeds.insert(feed->id, feed);
155 connect(feed, SIGNAL(allReadChanged()), SIGNAL(allReadChanged()));
157 /* Add the virtual 'Shared items' feed */
158 feed = new Feed(this);
159 feed->id = "user/-/state/com.google/broadcast";
160 feed->title = "Shared items";
162 feeds.insert(feed->id, feed);
163 connect(feed, SIGNAL(allReadChanged()), SIGNAL(allReadChanged()));
166 void GoogleReader::downloadFinished(QNetworkReply *reply) {
167 QUrl url = reply->url();
169 /* TODO: Instead of comparing against the url, use the signal from the
170 * QNetworkReply... */
172 if (reply->error()) {
173 qDebug() << "Download of" << url << "failed:" << qPrintable(reply->errorString());
174 if(url == login_url) {
176 emit loginFailed("Incorrect username or password");
178 else if(url == edittag_url)
182 else if(url == login_url) {
183 QByteArray data = reply->readAll();
184 data.remove(0, data.indexOf("Auth=", 0) + 5);
185 data.remove(data.indexOf("\n", 0), 1024);
187 auth.append("GoogleLogin auth=");
190 qDebug() << "Auth:" << auth;
196 /* TODO: Replace this with a proper state machine */
197 if(updateSubscriptionsPending) {
198 updateSubscriptionsPending = false;
199 updateSubscriptions();
202 else if(url == token_url) {
203 token = reply->readAll();
204 qDebug() << "token:" << token;
206 else if(url == subscriptions_url) {
207 parseSubscriptions(reply->readAll());
209 /* TODO: Replace this with a proper state machine */
210 if(updateUnreadPending) {
211 updateUnreadPending = false;
215 else if(url == unread_url) {
216 parseUnread(reply->readAll());
218 else if(url == edittag_url) {
219 QByteArray data = reply->readAll();
220 //qDebug() << "Result:" << data;
222 else if(url == markallread_url) {
223 QByteArray data = reply->readAll();
224 //qDebug() << "Result:" << data;
227 reply->deleteLater();
230 void GoogleReader::parseSubscriptions(QByteArray data) {
231 QJson::Parser parser;
233 QVariantMap result = parser.parse(data, &ok).toMap();
235 /* Clear the subscription updated flag */
236 QHash<QString, Feed *>::iterator i;
237 for(i = feeds.begin(); i != feeds.end(); ++i)
238 i.value()->subscription_updated = false;
240 foreach(QVariant l, result["subscriptions"].toList()) {
241 QVariantMap subscription = l.toMap();
242 Feed *feed = new Feed(this);
245 feed->id = subscription["id"].toString();
246 feed->title = subscription["title"].toString();
247 feed->sortid = subscription["sortid"].toString();
248 feed->firstitemmsec = subscription["firstitemmsec"].toString();
250 foreach(QVariant c, subscription["categories"].toList()) {
251 QVariantMap cat = c.toMap();
252 feed->cat_id = cat["id"].toString();
253 feed->cat_label = cat["label"].toString();
256 existing_feed = feeds.value(feed->id);
258 existing_feed->updateSubscription(feed);
260 feed = existing_feed;
264 feed->subscription_updated = true;
265 feeds.insert(feed->id, feed);
266 connect(feed, SIGNAL(allReadChanged()), SIGNAL(allReadChanged()));
270 /* Delete feeds no longer subscribed to */
271 for(i = feeds.begin(); i != feeds.end(); ++i) {
272 if(i.value()->subscription_updated == false && i.value()->special == 0) {
273 printf("DELETED: %s\n", i.value()->title.toLatin1().data());
278 lastUpdated = QDateTime::currentDateTime();
279 emit updateSubscriptionsComplete();
282 void GoogleReader::parseUnread(QByteArray data) {
283 QJson::Parser parser;
285 QVariantMap result = parser.parse(data, &ok).toMap();
287 foreach(QVariant l, result["unreadcounts"].toList()) {
288 QVariantMap unread = l.toMap();
289 QString id = unread["id"].toString();
290 int count = unread["count"].toInt();
291 ulong newestitem = unread["newestitem"].toUInt();
293 Feed *f = feeds.value(id);
296 f->newestitem = newestitem;
298 /* Not a good idea if it doesn't happen sequentially. */
299 /* Pre-fetch feeds with unread items */
300 /* f->fetch(false); */
304 lastUpdated = QDateTime::currentDateTime();
305 emit updateUnreadComplete();
308 void GoogleReader::clientLogin() {
315 QNetworkRequest request;
316 request.setUrl(login_url);
318 buffer.open(QBuffer::ReadWrite | QBuffer::Truncate);
319 buffer.write("Email=");
320 buffer.write(QUrl::toPercentEncoding(login));
321 buffer.write("&Passwd=");
322 buffer.write(QUrl::toPercentEncoding(passwd));
323 buffer.write("&service=reader&source=grms&continue=http://www.google.com");
326 //qDebug() << buffer.readAll();
329 manager.post(request, &buffer);
332 void GoogleReader::getToken() {
333 QNetworkRequest request;
334 request.setRawHeader("Authorization", auth);
335 request.setRawHeader("User-Agent", "Mozilla/5.0 gzip");
336 request.setUrl(token_url);
337 manager.get(request);
340 void GoogleReader::updateSubscriptions() {
341 QNetworkRequest request;
343 if(updateSubscriptionsPending)
347 updateSubscriptionsPending = true;
352 request.setRawHeader("Authorization", auth);
353 request.setRawHeader("User-Agent", "Mozilla/5.0 gzip");
354 request.setUrl(subscriptions_url);
355 manager.get(request);
358 void GoogleReader::updateUnread() {
359 QNetworkRequest request;
361 if(updateUnreadPending)
365 updateUnreadPending = true;
370 request.setRawHeader("Authorization", auth);
371 request.setRawHeader("User-Agent", "Mozilla/5.0 gzip");
372 request.setUrl(unread_url);
373 manager.get(request);
376 static bool compareFeedItems(const Feed *f1, const Feed *f2) {
377 if(f1->special || f2->special)
378 return f1->special > f2->special;
380 if(f1->cat_label == f2->cat_label)
381 return f1->title.toLower() < f2->title.toLower();
383 if(f1->cat_label.length() == 0)
386 if(f2->cat_label.length() == 0)
389 return f1->cat_label.toLower() < f2->cat_label.toLower();
392 QList<Feed *> GoogleReader::getFeeds() {
393 QList<Feed *> list = feeds.values();
394 qSort(list.begin(), list.end(), compareFeedItems);
398 void Feed::addEntry(Entry *entry) {
399 entries.insert(entry->id, entry);
402 void Feed::delEntry(Entry *entry) {
403 entries.remove(entry->id);
406 void Feed::updateUnread(int i) {
407 bool allRead = (unread == 0);
410 if(unread <= 0) unread = 0;
412 if(allRead != (unread == 0))
413 emit allReadChanged();
416 void Feed::markRead() {
418 /* Mark all the remaining items read */
420 QNetworkRequest request;
421 request.setRawHeader("Authorization", reader->getAuth());
422 request.setRawHeader("User-Agent", "Mozilla/5.0 gzip");
423 request.setUrl(reader->markallread_url);
425 buffer.open(QBuffer::ReadWrite | QBuffer::Truncate);
427 buffer.write(QUrl::toPercentEncoding(id));
428 //buffer.write("&ts=");
429 //buffer.write(QByteArray::number(oldest));
431 buffer.write(QUrl::toPercentEncoding(reader->token));
434 //qDebug() << buffer.readAll();
437 reader->manager.post(request, &buffer);
441 /* Go over all the entries and mark them read */
442 QHash<QString, Entry *>::iterator i;
443 for(i = entries.begin(); i != entries.end(); ++i)
444 i.value()->flags |= ENTRY_FLAG_READ | ENTRY_FLAG_LOCKED;
447 emit allReadChanged();
450 static bool compareEntryItems(const Entry *e1, const Entry *e2) {
451 return e1->published > e2->published;
454 QList<Entry *> Feed::getEntries() {
455 QList<Entry *> list = entries.values();
456 qSort(list.begin(), list.end(), compareEntryItems);
460 /* TODO: Remove the duplicate code in changing stated */
462 void Entry::markRead(bool mark_read) {
463 /* Check if the read flag differs from the requested state */
464 if(((flags & ENTRY_FLAG_READ) != 0) == mark_read)
467 /* Cannot mark an item unread if it's locked */
468 if((flags & ENTRY_FLAG_LOCKED) && !mark_read)
471 QNetworkRequest request;
472 request.setRawHeader("Authorization", feed->reader->getAuth());
473 request.setRawHeader("User-Agent", "Mozilla/5.0 gzip");
474 request.setUrl(feed->reader->edittag_url);
476 postread.open(QBuffer::ReadWrite | QBuffer::Truncate);
477 postread.write("i=");
478 postread.write(QUrl::toPercentEncoding(id));
480 postread.write("&a=");
482 postread.write("&r=");
483 postread.write(QUrl::toPercentEncoding("user/-/state/com.google/read"));
484 postread.write("&ac=edit-tags&T=");
485 postread.write(QUrl::toPercentEncoding(feed->reader->token));
487 feed->reader->manager.post(request, &postread);
489 feed->updateUnread(mark_read ? -1 : 1);
492 flags |= ENTRY_FLAG_READ;
494 flags &= ~ENTRY_FLAG_READ;
497 void Entry::markStar(bool mark_star) {
498 /* Check if the starred flag differs from the requested state */
499 if(((flags & ENTRY_FLAG_STARRED) != 0) == mark_star)
502 QNetworkRequest request;
503 request.setRawHeader("Authorization", feed->reader->getAuth());
504 request.setRawHeader("User-Agent", "Mozilla/5.0 gzip");
505 request.setUrl(feed->reader->edittag_url);
507 poststar.open(QBuffer::ReadWrite | QBuffer::Truncate);
508 poststar.write("i=");
509 poststar.write(QUrl::toPercentEncoding(id));
511 poststar.write("&a=");
513 poststar.write("&r=");
514 poststar.write(QUrl::toPercentEncoding("user/-/state/com.google/starred"));
515 poststar.write("&ac=edit-tags&T=");
516 poststar.write(QUrl::toPercentEncoding(feed->reader->token));
518 feed->reader->manager.post(request, &poststar);
520 Feed *starred = feed->reader->feeds.value("user/-/state/com.google/starred");
523 starred->addEntry(this);
524 flags |= ENTRY_FLAG_STARRED;
527 starred->delEntry(this);
528 flags &= ~ENTRY_FLAG_STARRED;
532 void Entry::markShared(bool mark_shared) {
533 /* Check if the shared flag differs from the requested state */
534 if(((flags & ENTRY_FLAG_SHARED) != 0) == mark_shared)
537 QNetworkRequest request;
538 request.setRawHeader("Authorization", feed->reader->getAuth());
539 request.setRawHeader("User-Agent", "Mozilla/5.0 gzip");
540 request.setUrl(feed->reader->edittag_url);
542 postshared.open(QBuffer::ReadWrite | QBuffer::Truncate);
543 postshared.write("i=");
544 postshared.write(QUrl::toPercentEncoding(id));
546 postshared.write("&a=");
548 postshared.write("&r=");
549 postshared.write(QUrl::toPercentEncoding("user/-/state/com.google/broadcast"));
550 postshared.write("&ac=edit-tags&T=");
551 postshared.write(QUrl::toPercentEncoding(feed->reader->token));
553 feed->reader->manager.post(request, &postshared);
555 Feed *shared = feed->reader->feeds.value("user/-/state/com.google/broadcast");
558 shared->addEntry(this);
559 flags |= ENTRY_FLAG_SHARED;
562 shared->delEntry(this);
563 flags &= ~ENTRY_FLAG_SHARED;