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());
46 reply = reader->getManager()->get(request);
47 connect(reply, SIGNAL(finished()), SLOT(fetchFinished()));
50 void Feed::fetchFinished() {
52 qDebug() << "Download of" << reply->url() << "failed:" << qPrintable(reply->errorString());
58 QVariantMap result = parser.parse(reply->readAll(), &ok).toMap();
60 continuation = result["continuation"].toString();
61 updated = result["updated"].toUInt();
63 foreach(QVariant l, result["items"].toList()) {
64 QVariantMap e = l.toMap();
65 Entry *entry = new Entry();;
66 QString content, summary;
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();
78 content = (e["content"].toMap())["content"].toString();
79 summary = (e["summary"].toMap())["content"].toString();
80 if(content != "") entry->content = content; else entry->content = summary;
82 if(e["isReadStateLocked"].toBool())
83 entry->flags |= ENTRY_FLAG_LOCKED | ENTRY_FLAG_READ;
86 p.mainFrame()->setHtml(e["title"].toString());
87 entry->title = p.mainFrame()->toPlainText();
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;
103 lastUpdated = QDateTime::currentDateTime();
105 emit updateFeedComplete();
107 reply->deleteLater();
110 GoogleReader::GoogleReader() {
111 /* Use the system proxy setting */
112 QNetworkProxyFactory::setUseSystemConfiguration(true);
114 connect(&manager, SIGNAL(finished(QNetworkReply*)),
115 SLOT(downloadFinished(QNetworkReply*)));
118 updateSubscriptionsPending = false;
119 updateUnreadPending = false;
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=-");
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";
135 feeds.insert(feed->id, feed);
136 connect(feed, SIGNAL(allReadChanged()), SIGNAL(allReadChanged()));
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";
143 feeds.insert(feed->id, feed);
144 connect(feed, SIGNAL(allReadChanged()), SIGNAL(allReadChanged()));
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";
151 feeds.insert(feed->id, feed);
152 connect(feed, SIGNAL(allReadChanged()), SIGNAL(allReadChanged()));
155 void GoogleReader::downloadFinished(QNetworkReply *reply) {
156 QUrl url = reply->url();
158 /* TODO: Instead of comparing against the url, use the signal from the
159 * QNetworkReply... */
161 if (reply->error()) {
162 qDebug() << "Download of" << url << "failed:" << qPrintable(reply->errorString());
163 if(url == login_url) {
165 emit loginFailed("Incorrect username or password");
167 else if(url == edittag_url)
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);
176 auth.append("GoogleLogin auth=");
179 qDebug() << "Auth:" << auth;
185 /* TODO: Replace this with a proper state machine */
186 if(updateSubscriptionsPending) {
187 updateSubscriptionsPending = false;
188 updateSubscriptions();
191 else if(url == token_url) {
192 token = reply->readAll();
193 qDebug() << "token:" << token;
195 else if(url == subscriptions_url) {
196 parseSubscriptions(reply->readAll());
198 /* TODO: Replace this with a proper state machine */
199 if(updateUnreadPending) {
200 updateUnreadPending = false;
204 else if(url == unread_url) {
205 parseUnread(reply->readAll());
207 else if(url == edittag_url) {
208 QByteArray data = reply->readAll();
209 //qDebug() << "Result:" << data;
211 else if(url == markallread_url) {
212 QByteArray data = reply->readAll();
213 //qDebug() << "Result:" << data;
216 reply->deleteLater();
219 void GoogleReader::parseSubscriptions(QByteArray data) {
220 QJson::Parser parser;
222 QVariantMap result = parser.parse(data, &ok).toMap();
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;
229 foreach(QVariant l, result["subscriptions"].toList()) {
230 QVariantMap subscription = l.toMap();
231 Feed *feed = new Feed(this);
234 feed->id = subscription["id"].toString();
235 feed->title = subscription["title"].toString();
236 feed->sortid = subscription["sortid"].toString();
237 feed->firstitemmsec = subscription["firstitemmsec"].toString();
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();
245 existing_feed = feeds.value(feed->id);
247 existing_feed->updateSubscription(feed);
249 feed = existing_feed;
253 feed->subscription_updated = true;
254 feeds.insert(feed->id, feed);
255 connect(feed, SIGNAL(allReadChanged()), SIGNAL(allReadChanged()));
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());
267 lastUpdated = QDateTime::currentDateTime();
268 emit updateSubscriptionsComplete();
271 void GoogleReader::parseUnread(QByteArray data) {
272 QJson::Parser parser;
274 QVariantMap result = parser.parse(data, &ok).toMap();
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();
282 Feed *f = feeds.value(id);
285 f->newestitem = newestitem;
287 /* Not a good idea if it doesn't happen sequentially. */
288 /* Pre-fetch feeds with unread items */
289 /* f->fetch(false); */
293 lastUpdated = QDateTime::currentDateTime();
294 emit updateUnreadComplete();
297 void GoogleReader::clientLogin() {
304 QNetworkRequest request;
305 request.setUrl(login_url);
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");
315 //qDebug() << buffer.readAll();
318 manager.post(request, &buffer);
321 void GoogleReader::getToken() {
322 QNetworkRequest request;
323 request.setRawHeader("Authorization", auth);
324 request.setUrl(token_url);
325 manager.get(request);
328 void GoogleReader::updateSubscriptions() {
329 QNetworkRequest request;
331 if(updateSubscriptionsPending)
335 updateSubscriptionsPending = true;
340 request.setRawHeader("Authorization", auth);
341 request.setUrl(subscriptions_url);
342 manager.get(request);
345 void GoogleReader::updateUnread() {
346 QNetworkRequest request;
348 if(updateUnreadPending)
352 updateUnreadPending = true;
357 request.setRawHeader("Authorization", auth);
358 request.setUrl(unread_url);
359 manager.get(request);
362 static bool compareFeedItems(const Feed *f1, const Feed *f2) {
363 if(f1->special || f2->special)
364 return f1->special > f2->special;
366 if(f1->cat_label == f2->cat_label)
367 return f1->title.toLower() < f2->title.toLower();
369 if(f1->cat_label.length() == 0)
372 if(f2->cat_label.length() == 0)
375 return f1->cat_label.toLower() < f2->cat_label.toLower();
378 QList<Feed *> GoogleReader::getFeeds() {
379 QList<Feed *> list = feeds.values();
380 qSort(list.begin(), list.end(), compareFeedItems);
384 void Feed::addEntry(Entry *entry) {
385 entries.insert(entry->id, entry);
388 void Feed::delEntry(Entry *entry) {
389 entries.remove(entry->id);
392 void Feed::updateUnread(int i) {
393 bool allRead = (unread == 0);
396 if(unread <= 0) unread = 0;
398 if(allRead != (unread == 0))
399 emit allReadChanged();
402 void Feed::markRead() {
404 /* Mark all the remaining items read */
406 QNetworkRequest request;
407 request.setRawHeader("Authorization", reader->getAuth());
408 request.setUrl(reader->markallread_url);
410 buffer.open(QBuffer::ReadWrite | QBuffer::Truncate);
412 buffer.write(QUrl::toPercentEncoding(id));
413 //buffer.write("&ts=");
414 //buffer.write(QByteArray::number(oldest));
416 buffer.write(QUrl::toPercentEncoding(reader->token));
419 //qDebug() << buffer.readAll();
422 reader->manager.post(request, &buffer);
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;
432 emit allReadChanged();
435 static bool compareEntryItems(const Entry *e1, const Entry *e2) {
436 return e1->published > e2->published;
439 QList<Entry *> Feed::getEntries() {
440 QList<Entry *> list = entries.values();
441 qSort(list.begin(), list.end(), compareEntryItems);
445 /* TODO: Remove the duplicate code in changing stated */
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)
452 /* Cannot mark an item unread if it's locked */
453 if((flags & ENTRY_FLAG_LOCKED) && !mark_read)
456 QNetworkRequest request;
457 request.setRawHeader("Authorization", feed->reader->getAuth());
458 request.setUrl(feed->reader->edittag_url);
460 postread.open(QBuffer::ReadWrite | QBuffer::Truncate);
461 postread.write("i=");
462 postread.write(QUrl::toPercentEncoding(id));
464 postread.write("&a=");
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));
471 feed->reader->manager.post(request, &postread);
473 feed->updateUnread(mark_read ? -1 : 1);
476 flags |= ENTRY_FLAG_READ;
478 flags &= ~ENTRY_FLAG_READ;
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)
486 QNetworkRequest request;
487 request.setRawHeader("Authorization", feed->reader->getAuth());
488 request.setUrl(feed->reader->edittag_url);
490 poststar.open(QBuffer::ReadWrite | QBuffer::Truncate);
491 poststar.write("i=");
492 poststar.write(QUrl::toPercentEncoding(id));
494 poststar.write("&a=");
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));
501 feed->reader->manager.post(request, &poststar);
503 Feed *starred = feed->reader->feeds.value("user/-/state/com.google/starred");
506 starred->addEntry(this);
507 flags |= ENTRY_FLAG_STARRED;
510 starred->delEntry(this);
511 flags &= ~ENTRY_FLAG_STARRED;
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)
520 QNetworkRequest request;
521 request.setRawHeader("Authorization", feed->reader->getAuth());
522 request.setUrl(feed->reader->edittag_url);
524 postshared.open(QBuffer::ReadWrite | QBuffer::Truncate);
525 postshared.write("i=");
526 postshared.write(QUrl::toPercentEncoding(id));
528 postshared.write("&a=");
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));
535 feed->reader->manager.post(request, &postshared);
537 Feed *shared = feed->reader->feeds.value("user/-/state/com.google/broadcast");
540 shared->addEntry(this);
541 flags |= ENTRY_FLAG_SHARED;
544 shared->delEntry(this);
545 flags &= ~ENTRY_FLAG_SHARED;