/* ********************************************************************** ** ** 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.util; import org.eclipse.core.runtime.IProgressMonitor; import org.rssowl.core.Owl; import org.rssowl.core.connection.AuthenticationRequiredException; import org.rssowl.core.connection.ConnectionException; import org.rssowl.core.connection.CredentialsException; import org.rssowl.core.connection.IConnectionPropertyConstants; import org.rssowl.core.connection.IProtocolHandler; import org.rssowl.core.connection.SyncConnectionException; import org.rssowl.core.internal.Activator; import org.rssowl.core.persist.IBookMark; import org.rssowl.core.persist.INews; import java.io.BufferedReader; 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.Map; /** * Some tools to synchronize with online services like Google Reader. * * @author bpasero */ public class SyncUtils { /** Flag to control enablement of Google Reader Sync */ public static final boolean ENABLED = false; /** Google Client Login Site */ public static final String GOOGLE_LOGIN_URL = "https://www.google.com/accounts/ClientLogin"; //$NON-NLS-1$ /** Google Stream Service */ public static final String GOOGLE_API_URL = "http://www.google.com/reader/api/0/"; //$NON-NLS-1$ /** Google API Token Service */ public static final String GOOGLE_API_TOKEN_URL = GOOGLE_API_URL + "token"; //$NON-NLS-1$ /** Google Edit-Tag Service */ public static final String GOOGLE_EDIT_TAG_URL = GOOGLE_API_URL + "edit-tag?client=scroll"; //$NON-NLS-1$ /** Google Feed Service */ public static final String GOOGLE_FEED_URL = GOOGLE_API_URL + "stream/contents/feed/"; //$NON-NLS-1$ /** Google Stream Contents Service */ public static final String GOOGLE_STREAM_CONTENTS_URL = GOOGLE_API_URL + "stream/contents/"; //$NON-NLS-1$ /** Google Unread Count Service */ public static final String GOOGLE_UNREAD_COUNT_URL = GOOGLE_API_URL + "unread-count"; //$NON-NLS-1$ /** Google Account Creation URL (follows to Google Reader after signup) */ public static final String GOOGLE_NEW_ACCOUNT_URL = "https://www.google.com/accounts/NewAccount?continue=http%3A%2F%2Fwww.google.com%2Freader%2F&followup=http%3A%2F%2Fwww.google.com%2Freader%2F&service=reader"; //$NON-NLS-1$ /** Google Reader URL */ public static final String GOOGLE_READER_URL = "https://reader.google.com"; //$NON-NLS-1$ /** URL to export from Google Reader */ public static final String GOOGLE_READER_OPML_URI = "https://www.google.com/reader/subscriptions/export"; //$NON-NLS-1$ /** URL to unlock the Google Account using a captcha */ public static final String CAPTCHA_UNLOCK_URL = "https://www.google.com/accounts/DisplayUnlockCaptcha"; //$NON-NLS-1$ /** Schemes to use for synced feeds */ public static final String READER_HTTP_SCHEME = "reader"; //$NON-NLS-1$ public static final String READER_HTTPS_SCHEME = "readers"; //$NON-NLS-1$ /** Special Google Reader Feeds */ public static final String GOOGLE_READER_ALL_ITEMS_FEED = "reader://readinglist"; //$NON-NLS-1$ public static final String GOOGLE_READER_STARRED_FEED = "reader://starred"; //$NON-NLS-1$ public static final String GOOGLE_READER_SHARED_ITEMS_FEED = "reader://shared"; //$NON-NLS-1$ public static final String GOOGLE_READER_RECOMMENDED_ITEMS_FEED = "reader://recommended"; //$NON-NLS-1$ public static final String GOOGLE_READER_NOTES_FEED = "reader://notes"; //$NON-NLS-1$ /** Some special preferences a news can have after parsed from the JSONInterpreter */ public static final String GOOGLE_MARKED_UNREAD = "org.rssowl.pref.GoogleMarkedUnRead"; //$NON-NLS-1$ public static final String GOOGLE_MARKED_READ = "org.rssowl.pref.GoogleMarkedRead"; //$NON-NLS-1$ public static final String GOOGLE_LABELS = "org.rssowl.pref.GoogleLabels"; //$NON-NLS-1$ /** Google Categories */ public static final String CATEGORY_STARRED = "user/-/state/com.google/starred"; //$NON-NLS-1$ public static final String CATEGORY_READ = "user/-/state/com.google/read"; //$NON-NLS-1$ public static final String CATEGORY_UNREAD = "user/-/state/com.google/kept-unread"; //$NON-NLS-1$ public static final String CATEGORY_TRACKING_UNREAD = "user/-/state/com.google/tracking-kept-unread "; //$NON-NLS-1$ public static final String CATEGORY_LABEL_PREFIX = "user/-/label/"; //$NON-NLS-1$ /** Google API Parameters */ public static final String API_PARAM_TOKEN = "T"; //$NON-NLS-1$ public static final String API_PARAM_STREAM = "s"; //$NON-NLS-1$ public static final String API_PARAM_IDENTIFIER = "i"; //$NON-NLS-1$ public static final String API_PARAM_TAG_TO_ADD = "a"; //$NON-NLS-1$ public static final String API_PARAM_TAG_TO_REMOVE = "r"; //$NON-NLS-1$ /** Default Connection Timeouts in MS */ public static final int DEFAULT_CON_TIMEOUT = 30000; /** Short Connection Timeouts in MS */ public static final int SHORT_CON_TIMEOUT = 5000; /* Google Auth Identifier */ private static final String AUTH_IDENTIFIER = "Auth="; //$NON-NLS-1$ /* Google Auth Header */ private static final String GOOGLE_LOGIN_HEADER_VALUE = "GoogleLogin auth="; //$NON-NLS-1$ /* Google Authentication Token can be shared during the session */ private static String fgSharedAuthToken; private static final Object AUTH_TOKEN_LOCK= new Object(); /* Google URL Prefixes */ private static final String GOOGLE_HTTP_URL_PREFIX = "http://www.google.com"; //$NON-NLS-1$ private static final String GOOGLE_HTTPS_URL_PREFIX = "https://www.google.com"; //$NON-NLS-1$ /** * Obtains the Google Auth Token to perform REST operations for Google * Services. * * @param email the user account for google * @param pw the password for the user account * @param refresh if <code>true</code> causes a fresh authentication token to * be obtained and a shared one to be picked up otherwise. * @param monitor an instance of {@link IProgressMonitor} that can be used to * cancel the operation and report progress. * @return the google Auth Token for the given account or <code>null</code> if * none. * @throws ConnectionException Checked Exception to be used in case of any * Exception. */ public static String getGoogleAuthToken(String email, String pw, boolean refresh, IProgressMonitor monitor) throws ConnectionException { /* * Return the shared token if existing or even null if not willing to * refresh. Clients have to force refresh to get the token then. */ if (!refresh) return fgSharedAuthToken; /* Clear Shared Token */ fgSharedAuthToken = null; /* Return on cancellation */ if (monitor.isCanceled()) return null; /* Obtain a new token (only 1 Thread permitted) */ synchronized (AUTH_TOKEN_LOCK) { /* Another thread might have won the race */ if (fgSharedAuthToken != null) return fgSharedAuthToken; /* Return on cancellation */ if (monitor.isCanceled()) return null; /* Now Connect to Google */ try { fgSharedAuthToken = internalGetGoogleAuthToken(email, pw, monitor); } catch (URISyntaxException e) { throw new ConnectionException(Activator.getDefault().createErrorStatus(e.getMessage(), e)); } catch (IOException e) { throw new ConnectionException(Activator.getDefault().createErrorStatus(e.getMessage(), e)); } } return fgSharedAuthToken; } private static String internalGetGoogleAuthToken(String email, String pw, IProgressMonitor monitor) throws ConnectionException, URISyntaxException, IOException { URI uri = new URI(GOOGLE_LOGIN_URL); IProtocolHandler handler = Owl.getConnectionService().getHandler(uri); if (handler != null) { /* Google Specific Parameters */ Map<String, String> parameters = new HashMap<String, String>(); parameters.put("accountType", "GOOGLE"); //$NON-NLS-1$ //$NON-NLS-2$ parameters.put("Email", email); //$NON-NLS-1$ parameters.put("Passwd", pw); //$NON-NLS-1$ parameters.put("service", "reader"); //$NON-NLS-1$ //$NON-NLS-2$ parameters.put("source", "RSSOwl.org-RSSOwl-" + Activator.getDefault().getVersion()); //$NON-NLS-1$ //$NON-NLS-2$ Map<Object, Object> properties = new HashMap<Object, Object>(); properties.put(IConnectionPropertyConstants.PARAMETERS, parameters); properties.put(IConnectionPropertyConstants.POST, Boolean.TRUE); properties.put(IConnectionPropertyConstants.CON_TIMEOUT, getConnectionTimeout()); BufferedReader reader = null; try { InputStream inS = handler.openStream(uri, monitor, properties); reader = new BufferedReader(new InputStreamReader(inS)); String line; while (!monitor.isCanceled() && (line = reader.readLine()) != null) { if (line.startsWith(AUTH_IDENTIFIER)) return line.substring(AUTH_IDENTIFIER.length()); } } finally { try { if (reader != null) reader.close(); } catch (IOException e) { /* Ignore */ } } } return null; } /** * Returns the header value to authenticate against any Google REST services. * * @param authToken the authorization token that can be obtained from * {@link SyncUtils#getGoogleAuthToken(String, String, boolean, IProgressMonitor)} * @return a header value that can be used inside <code>Authorization</code> * to get access to Google Services. */ public static String getGoogleAuthorizationHeader(String authToken) { return GOOGLE_LOGIN_HEADER_VALUE + authToken; } /** * Obtains the Google API Token to perform REST operations for Google * Services. * * @param email the user account for google * @param pw the password for the user account * @param monitor an instance of {@link IProgressMonitor} that can be used to * cancel the operation and report progress. * @return the Google API Token to perform REST operations for Google * Services. * @throws ConnectionException Checked Exception to be used in case of any * Exception. */ public static String getGoogleApiToken(String email, String pw, IProgressMonitor monitor) throws ConnectionException { try { /* First try to use shared authentication token */ try { return internalGetGoogleApiToken(email, pw, false, monitor); } catch (ConnectionException e) { /* Rethrow if this exception is not about Authentication issues */ if (!(e instanceof AuthenticationRequiredException) && !(e instanceof SyncConnectionException)) throw e; /* Second try with up to date authentication token */ return internalGetGoogleApiToken(email, pw, true, monitor); } } catch (URISyntaxException e) { throw new ConnectionException(Activator.getDefault().createErrorStatus(e.getMessage(), e)); } catch (IOException e) { throw new ConnectionException(Activator.getDefault().createErrorStatus(e.getMessage(), e)); } } private static String internalGetGoogleApiToken(String email, String pw, boolean refresh, IProgressMonitor monitor) throws ConnectionException, IOException, URISyntaxException { URI uri = new URI(GOOGLE_API_TOKEN_URL); IProtocolHandler handler = Owl.getConnectionService().getHandler(uri); if (handler != null) { String token = SyncUtils.getGoogleAuthToken(email, pw, refresh, monitor); Map<String, String> headers = new HashMap<String, String>(); headers.put("Authorization", SyncUtils.getGoogleAuthorizationHeader(token)); //$NON-NLS-1$ Map<Object, Object> properties = new HashMap<Object, Object>(); properties.put(IConnectionPropertyConstants.HEADERS, headers); properties.put(IConnectionPropertyConstants.CON_TIMEOUT, getConnectionTimeout()); BufferedReader reader = null; try { InputStream inS = handler.openStream(uri, monitor, properties); reader = new BufferedReader(new InputStreamReader(inS)); String line; while (!monitor.isCanceled() && (line = reader.readLine()) != null) { return line; } } finally { try { if (reader != null) reader.close(); } catch (IOException e) { /* Ignore */ } } } return null; } /** * @param news the {@link INews} to check for synchronization. * @return <code>true</code> if the news is under synchronization control and * <code>false</code> otherwise. */ public static boolean isSynchronized(INews news) { return news != null && news.getParentId() == 0 && isSynchronized(news.getFeedLinkAsText()); } /** * @param bm the {@link IBookMark} to check for synchronization. * @return <code>true</code> if the bookmark is under synchronization control * and <code>false</code> otherwise. */ public static boolean isSynchronized(IBookMark bm) { return isSynchronized(bm.getFeedLinkReference().getLinkAsText()); } /** * @param link the link to check for synchronization. * @return <code>true</code> if the link is under synchronization control and * <code>false</code> otherwise. */ public static boolean isSynchronized(String link) { return link != null && link.startsWith(READER_HTTP_SCHEME); } /** * @param link the link to check for belonging to google sync services. * @return <code>true</code> if the link is from google sync services and * <code>false</code> otherwise. */ public static boolean fromGoogle(String link) { return isSynchronized(link) || link.startsWith(GOOGLE_HTTP_URL_PREFIX) || link.startsWith(GOOGLE_HTTPS_URL_PREFIX); } /** * @return <code>true</code> if the user has stored credentials for Google * Reader synchronization and <code>false</code> otherwise. */ public static boolean hasSyncCredentials() { try { return Owl.getConnectionService().getAuthCredentials(URI.create(SyncUtils.GOOGLE_LOGIN_URL), null) != null; } catch (CredentialsException e) { return false; } } private static int getConnectionTimeout() { return Owl.isShuttingDown() ? SyncUtils.SHORT_CON_TIMEOUT : SyncUtils.DEFAULT_CON_TIMEOUT; } }