--- /dev/null
+#include <QtWebKit>
+#include <QApplication>
+#include <QNetworkRequest>
+#include <QNetworkReply>
+#include <QNetworkCookie>
+#include <QBuffer>
+#include <QTimer>
+#include <QDateTime>
+#include <QDomDocument>
+#include <QDebug>
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+
+#include "googlereader.h"
+
+void Feed::updateSubscription(Feed *feed) {
+ title = feed->title;
+ sortid = feed->sortid;
+ firstitemmsec = feed->firstitemmsec;
+ cat_id = feed->cat_id;
+ cat_label = feed->cat_label;
+ subscription_updated = true;
+}
+
+void Feed::fetch(bool cont) {
+ QNetworkRequest request;
+ QByteArray ba = "http://www.google.com/reader/atom/";
+
+ ba.append(QUrl::toPercentEncoding(id));
+ QUrl url = QUrl::fromEncoded(ba);
+
+ if(continuation != "" && cont)
+ url.addEncodedQueryItem("c", continuation.toUtf8());
+
+ request.setUrl(url);
+ reader->getManager()->get(request);
+}
+
+GoogleReader::GoogleReader() {
+ connect(&manager, SIGNAL(finished(QNetworkReply*)),
+ SLOT(downloadFinished(QNetworkReply*)));
+
+ SID = NULL;
+ updateSubscriptionsPending = false;
+ updateUnreadPending = false;
+ SIDPending = false;
+
+ login_url.setUrl("https://www.google.com/accounts/ClientLogin");
+ subscriptions_url.setUrl("http://www.google.com/reader/api/0/subscription/list");
+ unread_url.setUrl("http://www.google.com/reader/api/0/unread-count");
+ edittag_url.setUrl("http://www.google.com/reader/api/0/edit-tag?client=-");
+ token_url.setUrl("http://www.google.com/reader/api/0/token");
+ markallread_url.setUrl("http://www.google.com/reader/api/0/mark-all-as-read?client=-");
+
+ /* Add the virtual 'Starred items' feed */
+ Feed *feed = new Feed(this);
+ feed->id = "user/-/state/com.google/starred";
+ feed->title = "Starred items";
+ feed->special = true;
+ feeds.insert(feed->id, feed);
+ connect(feed, SIGNAL(allReadChanged()), SIGNAL(allReadChanged()));
+}
+
+void GoogleReader::downloadFinished(QNetworkReply *reply) {
+ QUrl url = reply->url();
+
+ /* TODO: Instead of comparing against the url, use the signal from the
+ * QNetworkReply... */
+
+ if (reply->error()) {
+ qDebug() << "Download of" << url << "failed:" << qPrintable(reply->errorString());
+ if(url == login_url) {
+ SIDPending = false;
+ emit loginFailed("Incorrect username or password");
+ }
+ else if(url == edittag_url)
+ getToken();
+ return;
+ }
+ else if(url == login_url) {
+ QByteArray data = reply->readAll();
+ data.remove(0, data.indexOf("SID=", 0) + 4);
+ data.remove(data.indexOf("\n", 0), 1024);
+ SID = strdup(data.data());
+
+ qDebug() << "SID:" << SID;
+
+ manager.cookieJar()->setCookiesFromUrl(
+ QList<QNetworkCookie>() << QNetworkCookie("SID", SID),
+ QUrl("http://www.google.com"));
+
+ SIDPending = false;
+
+ getToken();
+
+ /* TODO: Replace this with a proper state machine */
+ if(updateSubscriptionsPending) {
+ updateSubscriptionsPending = false;
+ updateSubscriptions();
+ }
+ }
+ else if(url == token_url) {
+ token = reply->readAll();
+ qDebug() << "token:" << token;
+ }
+ else if(url == subscriptions_url) {
+ QByteArray data = reply->readAll();
+ QDomDocument dom;
+ dom.setContent(data);
+ parseSubscriptions(dom);
+ emit updateSubscriptionsComplete();
+
+ /* TODO: Replace this with a proper state machine */
+ if(updateUnreadPending) {
+ updateUnreadPending = false;
+ updateUnread();
+ }
+ }
+ else if(url == unread_url) {
+ QByteArray data = reply->readAll();
+ QDomDocument dom;
+ dom.setContent(data);
+ parseUnread(dom);
+ emit updateUnreadComplete();
+ }
+ else if(url == edittag_url) {
+ QByteArray data = reply->readAll();
+ //qDebug() << "Result:" << data;
+ }
+ else if(url == markallread_url) {
+ QByteArray data = reply->readAll();
+ //qDebug() << "Result:" << data;
+ }
+ else {
+ QByteArray data = reply->readAll();
+ QDomDocument dom;
+ dom.setContent(data);
+ parseFeed(dom);
+ }
+
+ reply->deleteLater();
+}
+
+void GoogleReader::parseFeed(QDomDocument dom) {
+ QDomElement set, e;
+ QDomNode n, c;
+ QString continuation, feedsource;
+ Feed *feed = NULL;
+
+ set = dom.firstChildElement();
+
+ for(n = set.firstChild(); !n.isNull(); n = n.nextSibling()) {
+ e = n.toElement();
+ QString name = e.tagName();
+ if(name == "entry") {
+ Entry entry;
+ QString content, summary;
+
+ QString locked = e.attribute("gr:is-read-state-locked", "false");
+ if(locked == "true")
+ entry.flags |= ENTRY_FLAG_LOCKED | ENTRY_FLAG_READ;
+
+ entry.crawled = e.attribute("gr:crawl-timestamp-msec", "0").toULongLong();
+
+ for(c = n.firstChild(); !c.isNull(); c = c.nextSibling()) {
+ e = c.toElement();
+ name = e.tagName();
+ if(name == "id")
+ entry.id = e.text();
+ else if(name == "title") {
+ QWebPage p;
+ p.mainFrame()->setHtml(e.text());
+ entry.title = p.mainFrame()->toPlainText();
+ }
+ else if(name == "published")
+ entry.published = QDateTime::fromString(e.text(), "yyyy-MM-dd'T'HH:mm:ss'Z'");
+ else if(name == "link")
+ entry.link = QUrl(e.attribute("href", ""));
+ else if(name == "source")
+ entry.source = e.attribute("gr:stream-id", "");
+ else if(name == "content")
+ content = e.text();
+ else if(name == "summary")
+ summary = e.text();
+ else if(name == "author") {
+ e = c.firstChild().toElement();
+ entry.author = e.text();
+ if(entry.author == "(author unknown)")
+ entry.author = "";
+ }
+ else if(name == "category") {
+ QString label = e.attribute("label", "");
+ if(label == "read")
+ entry.flags |= ENTRY_FLAG_READ;
+ else if(label == "starred")
+ entry.flags |= ENTRY_FLAG_STARRED;
+ }
+ }
+
+ if(content != "")
+ entry.content = content;
+ else if(summary != "")
+ entry.content = summary;
+
+ if(!feed)
+ feed = feeds.value(feedsource == "" ? entry.source : feedsource);
+
+ if(feed) {
+ entry.feed = feed;
+ feed->addEntry(new Entry(entry));
+ }
+ }
+ else if(name == "gr:continuation") {
+ continuation = e.text();
+ }
+ else if(name == "id") {
+ if(e.text().endsWith("/state/com.google/starred"))
+ feedsource = "user/-/state/com.google/starred";
+ }
+ }
+
+ if(feed) {
+ feed->lastUpdated = QDateTime::currentDateTime();
+ feed->continuation = continuation;
+ feed->signalUpdated();
+ }
+}
+
+void GoogleReader::parseSubscriptions(QDomDocument dom) {
+ QDomElement set, e;
+ QDomNode n, c;
+
+ /* Clear the subscription updated flag */
+ QHash<QString, Feed *>::iterator i;
+ for(i = feeds.begin(); i != feeds.end(); ++i)
+ i.value()->subscription_updated = false;
+
+ set = dom.firstChildElement();
+ set = set.firstChildElement("list");
+ set = set.firstChildElement("object");
+
+ for (; !set.isNull(); set = set.nextSiblingElement("object")) {
+ Feed *feed = new Feed(this);
+ Feed *existing_feed;
+
+ for(n = set.firstChild(); !n.isNull(); n = n.nextSibling()) {
+ e = n.toElement();
+ QString name = e.attribute("name");
+ if(name == "id")
+ feed->id = e.text();
+ else if(name == "title")
+ feed->title = e.text();
+ else if(name == "sortid")
+ feed->sortid = e.text();
+ else if(name == "firstitemmsec")
+ feed->firstitemmsec = e.text();
+ else if(name == "categories") {
+ for(c = n.firstChild().firstChild(); !c.isNull(); c = c.nextSibling()) {
+ e = c.toElement();
+ QString name = e.attribute("name");
+ if(name == "id")
+ feed->cat_id = e.text();
+ else if(name == "label")
+ feed->cat_label = e.text();
+ }
+ }
+ }
+
+ existing_feed = feeds.value(feed->id);
+ if(existing_feed) {
+ existing_feed->updateSubscription(feed);
+ delete(feed);
+ feed = existing_feed;
+
+ }
+ else {
+ feed->subscription_updated = true;
+ feeds.insert(feed->id, feed);
+ connect(feed, SIGNAL(allReadChanged()), SIGNAL(allReadChanged()));
+ }
+ }
+
+ /* Delete feeds no longer subscribed to */
+ for(i = feeds.begin(); i != feeds.end(); ++i) {
+ if(i.value()->subscription_updated == false && i.value()->special == false) {
+ printf("DELETED: %s\n", i.value()->title.toLatin1().data());
+ i = feeds.erase(i);
+ }
+ }
+
+ lastUpdated = QDateTime::currentDateTime();
+}
+
+void GoogleReader::parseUnread(QDomDocument dom) {
+ QDomElement set, e;
+ QDomNode n, c;
+
+ set = dom.firstChildElement();
+ set = set.firstChildElement("list");
+ set = set.firstChildElement("object");
+
+ for (; !set.isNull(); set = set.nextSiblingElement("object")) {
+ QString id;
+ int count = 0;
+ ulong newestitem = 0;
+
+ for(n = set.firstChild(); !n.isNull(); n = n.nextSibling()) {
+ e = n.toElement();
+ QString name = e.attribute("name");
+ if(name == "id")
+ id = e.text();
+ else if(name == "count")
+ count = e.text().toInt();
+ else if(name == "newestItemTimestampUsec")
+ newestitem = e.text().toULong();
+ }
+
+ Feed *f = feeds.value(id);
+ if(f) {
+ f->unread = count;
+ f->newestitem = newestitem;
+ //qDebug() << f->title << "->" << count;
+ }
+ else {
+ //printf("%s not found\n", id.toLatin1().data());
+ }
+ }
+
+ lastUpdated = QDateTime::currentDateTime();
+}
+
+void GoogleReader::getSID() {
+
+ if(SIDPending)
+ return;
+
+ SIDPending = true;
+
+ QNetworkRequest request;
+ request.setUrl(login_url);
+
+ buffer.open(QBuffer::ReadWrite | QBuffer::Truncate);
+ buffer.write("Email=");
+ buffer.write(QUrl::toPercentEncoding(login));
+ buffer.write("&Passwd=");
+ buffer.write(QUrl::toPercentEncoding(passwd));
+ buffer.write("&service=reader&source=grms&continue=http://www.google.com");
+
+ //buffer.seek(0);
+ //qDebug() << buffer.readAll();
+
+ buffer.seek(0);
+ manager.post(request, &buffer);
+}
+
+void GoogleReader::getToken() {
+ QNetworkRequest request;
+ request.setUrl(token_url);
+ manager.get(request);
+}
+
+void GoogleReader::updateSubscriptions() {
+ QNetworkRequest request;
+
+ if(updateSubscriptionsPending)
+ return;
+
+ if(!SID) {
+ updateSubscriptionsPending = true;
+ getSID();
+ return;
+ }
+
+ request.setUrl(subscriptions_url);
+ manager.get(request);
+}
+
+void GoogleReader::updateUnread() {
+ QNetworkRequest request;
+
+ if(updateUnreadPending)
+ return;
+
+ if(!SID) {
+ updateUnreadPending = true;
+ getSID();
+ return;
+ }
+
+ request.setUrl(unread_url);
+ manager.get(request);
+}
+
+static bool compareFeedItems(const Feed *f1, const Feed *f2) {
+ if(f1->special && !f2->special)
+ return true;
+
+ if(f2->special && !f1->special)
+ return false;
+
+ if(f1->cat_label == f2->cat_label)
+ return f1->title.toLower() < f2->title.toLower();
+
+ if(f1->cat_label.length() == 0)
+ return false;
+
+ if(f2->cat_label.length() == 0)
+ return true;
+
+ return f1->cat_label.toLower() < f2->cat_label.toLower();
+}
+
+QList<Feed *> GoogleReader::getFeeds() {
+ QList<Feed *> list = feeds.values();
+ qSort(list.begin(), list.end(), compareFeedItems);
+ return list;
+}
+
+void Feed::addEntry(Entry *entry) {
+ entries.insert(entry->id, entry);
+}
+
+void Feed::delEntry(Entry *entry) {
+ entries.remove(entry->id);
+}
+
+void Feed::signalUpdated() {
+ // TODO: Clean this up
+ emit updateFeedComplete();
+}
+
+void Feed::updateUnread(int i) {
+ bool allRead = (unread == 0);
+
+ unread += i;
+ if(unread <= 0) unread = 0;
+
+ if(allRead != (unread == 0))
+ emit allReadChanged();
+}
+
+void Feed::markRead() {
+ if(unread > 0) {
+ /* Mark all the remaining items read */
+
+ QNetworkRequest request;
+ request.setUrl(reader->markallread_url);
+
+ buffer.open(QBuffer::ReadWrite | QBuffer::Truncate);
+ buffer.write("s=");
+ buffer.write(QUrl::toPercentEncoding(id));
+ //buffer.write("&ts=");
+ //buffer.write(QByteArray::number(oldest));
+ buffer.write("&T=");
+ buffer.write(QUrl::toPercentEncoding(reader->token));
+
+ //buffer.seek(0);
+ //qDebug() << buffer.readAll();
+
+ buffer.seek(0);
+ reader->manager.post(request, &buffer);
+
+ unread = 0;
+
+ /* Go over all the entries and mark them read */
+ QHash<QString, Entry *>::iterator i;
+ for(i = entries.begin(); i != entries.end(); ++i)
+ i.value()->flags |= ENTRY_FLAG_READ | ENTRY_FLAG_LOCKED;
+ }
+
+ emit allReadChanged();
+}
+
+static bool compareEntryItems(const Entry *e1, const Entry *e2) {
+ return e1->published > e2->published;
+}
+
+QList<Entry *> Feed::getEntries() {
+ QList<Entry *> list = entries.values();
+ qSort(list.begin(), list.end(), compareEntryItems);
+ return list;
+}
+
+/* TODO: Remove the duplicate code in changing stated */
+
+void Entry::markRead(bool mark_read) {
+ /* Check if the read flag differs from the requested state */
+ if(((flags & ENTRY_FLAG_READ) != 0) == mark_read)
+ return;
+
+ /* Cannot mark an item unread if it's locked */
+ if((flags & ENTRY_FLAG_LOCKED) && !mark_read)
+ return;
+
+ QNetworkRequest request;
+ request.setUrl(feed->reader->edittag_url);
+
+ postread.open(QBuffer::ReadWrite | QBuffer::Truncate);
+ postread.write("i=");
+ postread.write(QUrl::toPercentEncoding(id));
+ if(mark_read)
+ postread.write("&a=");
+ else
+ postread.write("&r=");
+ postread.write(QUrl::toPercentEncoding("user/-/state/com.google/read"));
+ postread.write("&ac=edit-tags&T=");
+ postread.write(QUrl::toPercentEncoding(feed->reader->token));
+ postread.seek(0);
+ feed->reader->manager.post(request, &postread);
+
+ feed->updateUnread(mark_read ? -1 : 1);
+
+ if(mark_read)
+ flags |= ENTRY_FLAG_READ;
+ else
+ flags &= ~ENTRY_FLAG_READ;
+}
+
+void Entry::markStar(bool mark_star) {
+ /* Check if the starred flag differs from the requested state */
+ if(((flags & ENTRY_FLAG_STARRED) != 0) == mark_star)
+ return;
+
+ QNetworkRequest request;
+ request.setUrl(feed->reader->edittag_url);
+
+ poststar.open(QBuffer::ReadWrite | QBuffer::Truncate);
+ poststar.write("i=");
+ poststar.write(QUrl::toPercentEncoding(id));
+ if(mark_star)
+ poststar.write("&a=");
+ else
+ poststar.write("&r=");
+ poststar.write(QUrl::toPercentEncoding("user/-/state/com.google/starred"));
+ poststar.write("&ac=edit-tags&T=");
+ poststar.write(QUrl::toPercentEncoding(feed->reader->token));
+ poststar.seek(0);
+ feed->reader->manager.post(request, &poststar);
+
+ Feed *starred = feed->reader->feeds.value("user/-/state/com.google/starred");
+
+ if(mark_star) {
+ starred->addEntry(this);
+ flags |= ENTRY_FLAG_STARRED;
+ }
+ else {
+ starred->delEntry(this);
+ flags &= ~ENTRY_FLAG_STARRED;
+ }
+}