Package org.rssowl.core.internal.connection

Source Code of org.rssowl.core.internal.connection.ReaderProtocolHandler

/*   **********************************************************************  **
**   Copyright notice                                                       **
**                                                                          **
**   (c) 2005-2011 RSSOwl Development Team                                  **
**   http://www.rssowl.org/                                                 **
**                                                                          **
**   All rights reserved                                                    **
**                                                                          **
**   This program and the accompanying materials are made available under   **
**   the terms of the Eclipse Public License v1.0 which accompanies this    **
**   distribution, and is available at:                                     **
**   http://www.rssowl.org/legal/epl-v10.html                               **
**                                                                          **
**   A copy is found in the file epl-v10.html and important notices to the  **
**   license from the team is found in the textfile LICENSE.txt distributed **
**   in this package.                                                       **
**                                                                          **
**   This copyright notice MUST APPEAR in all copies of the file!           **
**                                                                          **
**   Contributors:                                                          **
**     RSSOwl Development Team - initial API and implementation             **
**                                                                          **
**  **********************************************************************  */

package org.rssowl.core.internal.connection;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.Status;
import org.osgi.service.url.URLStreamHandlerService;
import org.rssowl.core.Owl;
import org.rssowl.core.connection.AuthenticationRequiredException;
import org.rssowl.core.connection.ConnectionException;
import org.rssowl.core.connection.IAbortable;
import org.rssowl.core.connection.IConnectionPropertyConstants;
import org.rssowl.core.connection.ICredentials;
import org.rssowl.core.connection.ICredentialsProvider;
import org.rssowl.core.connection.SyncConnectionException;
import org.rssowl.core.internal.Activator;
import org.rssowl.core.internal.interpreter.json.JSONException;
import org.rssowl.core.internal.interpreter.json.JSONObject;
import org.rssowl.core.internal.interpreter.json.JSONTokener;
import org.rssowl.core.interpreter.ParserException;
import org.rssowl.core.persist.IConditionalGet;
import org.rssowl.core.persist.IFeed;
import org.rssowl.core.persist.IModelFactory;
import org.rssowl.core.persist.INews;
import org.rssowl.core.util.SyncItem;
import org.rssowl.core.util.SyncUtils;
import org.rssowl.core.util.Triple;
import org.rssowl.core.util.URIUtils;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;

/**
* Extends the {@link DefaultProtocolHandler} dealing with Google Reader
* synchronization. The result from loading a feed is a JSON Object that is
* passed on to the responsible JSON interpreter service.
*
* @author bpasero
*/
public class ReaderProtocolHandler extends DefaultProtocolHandler {

  /* Some Sync Constants */
  private static final String REQUEST_HEADER_USER_AGENT = "User-Agent"; //$NON-NLS-1$
  private static final String REQUEST_HEADER_ACCEPT_CHARSET = "Accept-Charset"; //$NON-NLS-1$
  private static final String REQUEST_HEADER_AUTHORIZATION = "Authorization"; //$NON-NLS-1$
  private static final String UTF_8 = "UTF-8"; //$NON-NLS-1$
  private static final String BROWSER_USER_AGENT = "Mozilla/5.0 (Windows NT 6.1; rv:2.0.1) Gecko/20100101 Firefox/4.0.1"; //$NON-NLS-1$
  private static final int DEFAULT_ITEM_LIMIT = 200;

  /*
   * @see
   * org.rssowl.core.internal.connection.DefaultProtocolHandler#reload(java.
   * net.URI, org.eclipse.core.runtime.IProgressMonitor, java.util.Map)
   */
  @SuppressWarnings("unchecked")
  @Override
  public Triple<IFeed, IConditionalGet, URI> reload(URI link, IProgressMonitor monitor, Map<Object, Object> properties) throws CoreException {
    int itemLimit = DEFAULT_ITEM_LIMIT;
    long dateLimit = 0;

    /* Look for Sync-Connection specific properties */
    if (properties != null) {

      /* Item Limit */
      if (properties.containsKey(IConnectionPropertyConstants.ITEM_LIMIT)) {
        Object itemLimitObj = properties.get(IConnectionPropertyConstants.ITEM_LIMIT);
        if (itemLimitObj instanceof Integer)
          itemLimit = (Integer) itemLimitObj;
      }

      /* Date Limit */
      if (properties.containsKey(IConnectionPropertyConstants.DATE_LIMIT)) {
        Object dateLimitObj = properties.get(IConnectionPropertyConstants.DATE_LIMIT);
        if (dateLimitObj instanceof Long) {
          dateLimit = (Long) dateLimitObj / 1000; //Google only seems to accept seconds here
        }
      }
    } else
      properties = new HashMap<Object, Object>();

    URI googleLink = readerToGoogle(link, itemLimit, dateLimit);
    InputStream inS = null;

    /* First Try: Use shared token */
    try {
      String authToken = handleAuthentication(false, monitor);
      inS = openGoogleConnection(authToken, googleLink, monitor, properties);
    } catch (ConnectionException e) {

      /* Rethrow if this exception is not about Authentication issues */
      if (!(e instanceof AuthenticationRequiredException) && !(e instanceof SyncConnectionException))
        throw e;

      /* Return on Cancelation or Shutdown */
      if (monitor.isCanceled()) {
        closeStream(inS, true);
        return null;
      }

      /* Second Try: Obtain fresh token (could be expired) */
      String authToken = handleAuthentication(true, monitor);
      inS = openGoogleConnection(authToken, googleLink, monitor, properties);
    }

    /* Return on Cancelation or Shutdown */
    if (monitor.isCanceled()) {
      closeStream(inS, true);
      return null;
    }

    /* Retrieve Conditional Get if present */
    IConditionalGet conditionalGet = getConditionalGet(googleLink, inS);

    /* Return on Cancelation or Shutdown */
    if (monitor.isCanceled()) {
      closeStream(inS, true);
      return null;
    }

    /* Read JSON Object from Response and parse */
    InputStreamReader reader = null;
    boolean isError = false;
    IModelFactory typesFactory = Owl.getModelFactory();
    IFeed feed = typesFactory.createFeed(null, link);
    feed.setBase(readerToHTTP(link));
    try {
      reader = new InputStreamReader(inS, UTF_8);
      JSONObject obj = new JSONObject(new JSONTokener(reader));
      Owl.getInterpreter().interpretJSONObject(obj, feed);
    } catch (JSONException e) {
      isError = true;
      throw new ParserException(Activator.getDefault().createErrorStatus(e.getMessage(), e));
    } catch (IOException e) {
      isError = true;
      throw new ParserException(Activator.getDefault().createErrorStatus(e.getMessage(), e));
    } finally {
      try {
        if (isError && inS instanceof IAbortable)
          ((IAbortable) inS).abort();
        else if (reader != null)
          reader.close();
      } catch (IOException e) {
        /* Ignore */
      }
    }

    /* Update News based on uncommitted Items */
    Object uncommittedItemsObj = properties.get(IConnectionPropertyConstants.UNCOMMITTED_ITEMS);
    if (uncommittedItemsObj != null) {
      Map<String, SyncItem> uncommittedItems = (Map<String, SyncItem>) uncommittedItemsObj;
      if (!uncommittedItems.isEmpty()) {
        List<INews> news = feed.getNews();
        for (INews item : news) {
          if (item.getGuid() == null || item.getGuid().getValue() == null)
            continue;

          /* Check for Existing Uncommitted SyncItem */
          SyncItem syncItem = uncommittedItems.get(item.getGuid().getValue());
          if (syncItem == null)
            continue;

          /* Apply State from SyncItem to News */
          syncItem.applyTo(item);
        }
      }
    }

    return Triple.create(feed, conditionalGet, link);
  }

  private InputStream openGoogleConnection(String authToken, URI googleLink, IProgressMonitor monitor, Map<Object, Object> properties) throws ConnectionException {

    /* Return on Cancelation or Shutdown */
    if (monitor.isCanceled())
      return null;

    /* Fill necessary headers to retrieve feed from Google */
    Map<String, String> headers = new HashMap<String, String>();
    headers.put(REQUEST_HEADER_AUTHORIZATION, SyncUtils.getGoogleAuthorizationHeader(authToken));
    headers.put(REQUEST_HEADER_ACCEPT_CHARSET, UTF_8.toLowerCase());
    headers.put(REQUEST_HEADER_USER_AGENT, BROWSER_USER_AGENT); //Necessary as otherwise the content is not sent over as gzip for some unknown reason
    properties.put(IConnectionPropertyConstants.HEADERS, headers);

    /* Add Monitor to support early cancelation */
    properties.put(IConnectionPropertyConstants.PROGRESS_MONITOR, monitor);

    return openStream(googleLink, properties);
  }

  private String handleAuthentication(boolean refresh, IProgressMonitor monitor) throws ConnectionException {

    /* Obtain Google Credentials */
    URI googleLoginUri = URI.create(SyncUtils.GOOGLE_LOGIN_URL);
    ICredentialsProvider provider = Owl.getConnectionService().getCredentialsProvider(googleLoginUri);
    ICredentials credentials = provider.getAuthCredentials(googleLoginUri, null);
    if (credentials == null)
      throw new AuthenticationRequiredException(null, Status.CANCEL_STATUS);

    /* Obtain Google Authentication Token */
    String token = SyncUtils.getGoogleAuthToken(credentials.getUsername(), credentials.getPassword(), refresh, monitor);
    if (token == null)
      throw new AuthenticationRequiredException(null, Status.CANCEL_STATUS);

    return token;
  }

  /**
   * Parameters:
   * <ul>
   * <li>ot=[unix timestamp] : The time from which you want to retrieve items.</li>
   * <li>r=[d|n|o] : Sort order of item results.</li>
   * <li>xt=[exclude target] : Used to exclude certain items from the feed.</li>
   * <li>n=[integer] : The maximum number of results to return.</li>
   * <li>ck=[unix timestamp] : Use the current Unix time here, helps Google with
   * caching.</li>
   * <li>client=[your client] : You can use the default Google client (scroll).</li>
   * </ul>
   */
  private URI readerToGoogle(URI uri, int itemLimit, long dateLimit) throws ConnectionException {

    /* Handle Special Feeds */
    String linkVal = uri.toString();
    try {

      /* All Items */
      if (SyncUtils.GOOGLE_READER_ALL_ITEMS_FEED.equals(linkVal))
        return new URI(appendCommonParams(SyncUtils.GOOGLE_STREAM_CONTENTS_URL + "user/-/state/com.google/reading-list", itemLimit, dateLimit, false)); //$NON-NLS-1$

      /* Starred Items */
      else if (SyncUtils.GOOGLE_READER_STARRED_FEED.equals(linkVal))
        return new URI(appendCommonParams(SyncUtils.GOOGLE_STREAM_CONTENTS_URL + "user/-/state/com.google/starred", itemLimit, dateLimit, false)); //$NON-NLS-1$

      /* Shared Items */
      else if (SyncUtils.GOOGLE_READER_SHARED_ITEMS_FEED.equals(linkVal))
        return new URI(appendCommonParams(SyncUtils.GOOGLE_STREAM_CONTENTS_URL + "user/-/state/com.google/broadcast", itemLimit, dateLimit, false)); //$NON-NLS-1$

      /* Recommended Items */
      else if (SyncUtils.GOOGLE_READER_RECOMMENDED_ITEMS_FEED.equals(linkVal)) {
        String language = Locale.getDefault().getLanguage();
        return new URI(appendCommonParams(SyncUtils.GOOGLE_STREAM_CONTENTS_URL + "user/-/state/com.google/itemrecs/" + language, itemLimit, dateLimit, true)); //$NON-NLS-1$
      }

      /* Notes */
      else if (SyncUtils.GOOGLE_READER_NOTES_FEED.equals(linkVal))
        return new URI(appendCommonParams(SyncUtils.GOOGLE_STREAM_CONTENTS_URL + "user/-/state/com.google/created", itemLimit, dateLimit, false)); //$NON-NLS-1$
    } catch (URISyntaxException e) {
      throw new ConnectionException(Activator.getDefault().createErrorStatus(e.getMessage(), e));
    }

    /* Normal Synchronized Feed */
    URI httpUri = readerToHTTP(uri);
    try {
      return new URI(appendCommonParams(SyncUtils.GOOGLE_FEED_URL + URIUtils.urlEncode(httpUri.toString()), itemLimit, dateLimit, false));
    } catch (URISyntaxException e) {
      throw new ConnectionException(Activator.getDefault().createErrorStatus(e.getMessage(), e));
    }
  }

  private String appendCommonParams(String link, int itemLimit, long dateLimit, boolean onlyRecommended) {
    StringBuilder str = new StringBuilder(link);

    /* Item Limit */
    str.append("?n=").append(itemLimit); //$NON-NLS-1$

    /* Client */
    str.append("&client=scroll"); //$NON-NLS-1$

    /* Date Limit */
    if (dateLimit > 0)
      str.append("&ot=").append(dateLimit); //$NON-NLS-1$

    /* No comments or likes */
    str.append("&likes=false&comments=false"); //$NON-NLS-1$

    /* Only Recommended */
    if (onlyRecommended)
      str.append("&xt=user/-/state/com.google/read&xt=user/-/state/com.google/dislike"); //$NON-NLS-1$

    /* Caching Time */
    str.append("&ck=").append(System.currentTimeMillis()); //$NON-NLS-1$

    return str.toString();
  }

  /*
   * @see
   * org.rssowl.core.internal.connection.DefaultProtocolHandler#openStream(java
   * .net.URI, org.eclipse.core.runtime.IProgressMonitor, java.util.Map)
   */
  @Override
  public InputStream openStream(URI link, IProgressMonitor monitor, Map<Object, Object> properties) throws ConnectionException {
    return super.openStream(readerToHTTP(link), monitor, properties);
  }

  /*
   * @see
   * org.rssowl.core.internal.connection.DefaultProtocolHandler#getFeedIcon(
   * java.net.URI, org.eclipse.core.runtime.IProgressMonitor)
   */
  @Override
  public byte[] getFeedIcon(URI link, IProgressMonitor monitor) {
    try {
      String linkVal = link.toString();

      /* Do not try to resolve special Google Reader feed icons */
      if (SyncUtils.GOOGLE_READER_ALL_ITEMS_FEED.equals(linkVal))
        return null;
      else if (SyncUtils.GOOGLE_READER_STARRED_FEED.equals(linkVal))
        return null;
      else if (SyncUtils.GOOGLE_READER_SHARED_ITEMS_FEED.equals(linkVal))
        return null;
      else if (SyncUtils.GOOGLE_READER_RECOMMENDED_ITEMS_FEED.equals(linkVal))
        return null;
      else if (SyncUtils.GOOGLE_READER_NOTES_FEED.equals(linkVal))
        return null;

      /* Otherwise proceed loading feed icon through HTTP */
      return super.getFeedIcon(readerToHTTP(link), monitor);
    } catch (ConnectionException e) {
      return null;
    }
  }

  /*
   * @see
   * org.rssowl.core.internal.connection.DefaultProtocolHandler#getLabel(java
   * .net.URI, org.eclipse.core.runtime.IProgressMonitor)
   */
  @Override
  public String getLabel(URI link, IProgressMonitor monitor) throws ConnectionException {
    String linkVal = link.toString();

    /* Do not try to resolve special Google Reader feed labels */
    if (SyncUtils.GOOGLE_READER_ALL_ITEMS_FEED.equals(linkVal))
      return Messages.ReaderProtocolHandler_GR_ALL_ITEMS;
    else if (SyncUtils.GOOGLE_READER_STARRED_FEED.equals(linkVal))
      return Messages.ReaderProtocolHandler_GR_STARRED_ITEMS;
    else if (SyncUtils.GOOGLE_READER_SHARED_ITEMS_FEED.equals(linkVal))
      return Messages.ReaderProtocolHandler_GR_SHARED_ITEMS;
    else if (SyncUtils.GOOGLE_READER_RECOMMENDED_ITEMS_FEED.equals(linkVal))
      return Messages.ReaderProtocolHandler_GR_RECOMMENDED_ITEMS;
    else if (SyncUtils.GOOGLE_READER_NOTES_FEED.equals(linkVal))
      return Messages.ReaderProtocolHandler_GR_NOTES;

    /* Otherwise proceed loading feed label through HTTP */
    return super.getLabel(readerToHTTP(link), monitor);
  }

  /*
   * @see
   * org.rssowl.core.internal.connection.DefaultProtocolHandler#getFeed(java
   * .net.URI, org.eclipse.core.runtime.IProgressMonitor)
   */
  @Override
  public URI getFeed(URI website, IProgressMonitor monitor) throws ConnectionException {
    return super.getFeed(readerToHTTP(website), monitor);
  }

  /**
   * Do not override default URLStreamHandler of HTTP/HTTPS and therefor return
   * NULL.
   *
   * @see org.rssowl.core.connection.IProtocolHandler#getURLStreamHandler()
   */
  @Override
  public URLStreamHandlerService getURLStreamHandler() {
    return null;
  }

  private URI readerToHTTP(URI uri) throws ConnectionException {
    try {
      String scheme = SyncUtils.READER_HTTPS_SCHEME.equals(uri.getScheme()) ? URIUtils.HTTPS_SCHEME : URIUtils.HTTP_SCHEME;
      return new URI(scheme, uri.getUserInfo(), uri.getHost(), uri.getPort(), uri.getPath(), uri.getQuery(), uri.getFragment());
    } catch (URISyntaxException e) {
      throw new ConnectionException(Activator.getDefault().createErrorStatus(e.getMessage(), e));
    }
  }
}
TOP

Related Classes of org.rssowl.core.internal.connection.ReaderProtocolHandler

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.