/* ********************************************************************** **
** Copyright notice **
** **
** (c) 2005-2009 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.ui.internal.dialogs.importer;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.IMessageProvider;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.viewers.CheckboxTreeViewer;
import org.eclipse.jface.viewers.DoubleClickEvent;
import org.eclipse.jface.viewers.IDoubleClickListener;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerFilter;
import org.eclipse.jface.window.Window;
import org.eclipse.jface.wizard.WizardPage;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
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.HttpConnectionInputStream;
import org.rssowl.core.connection.IAbortable;
import org.rssowl.core.connection.IConnectionPropertyConstants;
import org.rssowl.core.connection.ICredentials;
import org.rssowl.core.connection.IProtocolHandler;
import org.rssowl.core.interpreter.ITypeImporter;
import org.rssowl.core.interpreter.InterpreterException;
import org.rssowl.core.interpreter.ParserException;
import org.rssowl.core.persist.IBookMark;
import org.rssowl.core.persist.IEntity;
import org.rssowl.core.persist.IFeed;
import org.rssowl.core.persist.IFolder;
import org.rssowl.core.persist.IFolderChild;
import org.rssowl.core.persist.ILabel;
import org.rssowl.core.persist.INewsBin;
import org.rssowl.core.persist.IPreference;
import org.rssowl.core.persist.ISearchFilter;
import org.rssowl.core.persist.ISearchMark;
import org.rssowl.core.persist.dao.DynamicDAO;
import org.rssowl.core.persist.dao.IBookMarkDAO;
import org.rssowl.core.persist.reference.FeedLinkReference;
import org.rssowl.core.util.CoreUtils;
import org.rssowl.core.util.Pair;
import org.rssowl.core.util.RegExUtils;
import org.rssowl.core.util.StringUtils;
import org.rssowl.core.util.SyncUtils;
import org.rssowl.core.util.URIUtils;
import org.rssowl.ui.internal.Activator;
import org.rssowl.ui.internal.Application;
import org.rssowl.ui.internal.Controller;
import org.rssowl.ui.internal.OwlUI;
import org.rssowl.ui.internal.dialogs.CustomWizardDialog;
import org.rssowl.ui.internal.dialogs.LoginDialog;
import org.rssowl.ui.internal.dialogs.PreviewFeedDialog;
import org.rssowl.ui.internal.dialogs.importer.ImportSourcePage.Source;
import org.rssowl.ui.internal.dialogs.welcome.WelcomeWizard;
import org.rssowl.ui.internal.util.FolderChildCheckboxTree;
import org.rssowl.ui.internal.util.JobRunner;
import org.rssowl.ui.internal.util.LayoutUtils;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringReader;
import java.lang.reflect.InvocationTargetException;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
*A {@link WizardPage} to select the elements to import.
*
* @author bpasero
*/
public class ImportElementsPage extends WizardPage {
/* Initial Connection Timeout when looking for Feeds remotely */
private static final int INITIAL_CON_TIMEOUT = 30000;
/* Connection Timeout when testing for Feeds remotely */
private static final int FEED_CON_TIMEOUT = 7000;
/* Default Feed Search Language */
private static final String DEFAULT_LANGUAGE = "en"; //$NON-NLS-1$
/* URL to export from Google Reader */
private static final String GOOGLE_READER_OPML_URI = "https://www.google.com/reader/subscriptions/export"; //$NON-NLS-1$
private CheckboxTreeViewer fViewer;
private FolderChildCheckboxTree fFolderChildTree;
private Button fDeselectAll;
private Button fSelectAll;
private Button fPreviewButton;
private Button fFlattenCheck;
private Button fHideExistingCheck;
private ExistingBookmarkFilter fExistingFilter = new ExistingBookmarkFilter();
private Map<URI, IFeed> fLoadedFeedCache = new ConcurrentHashMap<URI, IFeed>();
private IProgressMonitor fCurrentProgressMonitor;
/* Remember Current Import Values */
private Source fCurrentSourceKind;
private String fCurrentSourceResource;
private String fCurrentSourceKeywords;
private boolean fCurrentSourceLocalizedFeedSearch;
private long fCurrentSourceFileModified;
/* Imported Entities */
private List<ILabel> fLabels = Collections.synchronizedList(new ArrayList<ILabel>());
private List<ISearchFilter> fFilters = Collections.synchronizedList(new ArrayList<ISearchFilter>());
private List<IPreference> fPreferences = Collections.synchronizedList(new ArrayList<IPreference>());
/* Filter to Exclude Existing Bookmarks (empty folders are excluded as well) */
private static class ExistingBookmarkFilter extends ViewerFilter {
private IBookMarkDAO dao = DynamicDAO.getDAO(IBookMarkDAO.class);
private Map<IFolderChild, Boolean> cache = new IdentityHashMap<IFolderChild, Boolean>();
@Override
public boolean select(Viewer viewer, Object parentElement, Object element) {
if (element instanceof IFolderChild)
return select((IFolderChild) element);
return true;
}
void clear() {
cache.clear();
}
private boolean select(IFolderChild element) {
/* Bookmark (exclude if another Bookmark with same Link exists) */
if (element instanceof IBookMark) {
IBookMark bm = (IBookMark) element;
Boolean select = cache.get(bm);
if (select == null) {
select = !dao.exists(bm.getFeedLinkReference());
cache.put(bm, select);
}
return select;
}
/* Bin (exclude if another Bin with same name Exists at same Location) */
else if (element instanceof INewsBin) {
INewsBin bin = (INewsBin) element;
Boolean select = cache.get(bin);
if (select == null) {
select = !CoreUtils.existsNewsBin(bin);
cache.put(bin, select);
}
return select;
}
/* Search (exclude if another Search with same name Exists at same Location and same Conditions) */
else if (element instanceof ISearchMark) {
ISearchMark searchmark = (ISearchMark) element;
Boolean select = cache.get(searchmark);
if (select == null) {
select = !CoreUtils.existsSearchMark(searchmark);
cache.put(searchmark, select);
}
return select;
}
/* Folder */
else if (element instanceof IFolder) {
IFolder folder = (IFolder) element;
Boolean select = cache.get(folder);
if (select == null) {
List<IFolderChild> children = folder.getChildren();
for (IFolderChild child : children) {
select = select(child);
if (select)
break;
}
cache.put(folder, select);
}
return select != null ? select : false;
}
return true;
}
}
ImportElementsPage() {
super(Messages.ImportElementsPage_CHOOSE_ELEMENTS, Messages.ImportElementsPage_CHOOSE_ELEMENTS, null);
setMessage(Messages.ImportElementsPage_CHOOSE_ELEMENTS_MESSAGE);
}
/* Get Elements to Import */
List<IFolderChild> getFolderChildsToImport() {
doImportSource(); //Ensure to be in sync with Source
return fFolderChildTree.getCheckedElements();
}
/* Returns Labels available for Import */
List<ILabel> getLabelsToImport() {
doImportSource(); //Ensure to be in sync with Source
return fLabels;
}
/* Returns Filters available for Import */
List<ISearchFilter> getFiltersToImport() {
doImportSource(); //Ensure to be in sync with Source
return fFilters;
}
/* Returns the Preferences available for Import */
List<IPreference> getPreferencesToImport() {
doImportSource(); //Ensure to be in sync with Source
return fPreferences;
}
/* Returns whether existing bookmarks should be ignored for the Import */
boolean excludeExisting() {
return fHideExistingCheck.getSelection();
}
/* Check if the Options Page should be shown from the Wizard */
boolean showOptionsPage() {
return !fLabels.isEmpty() || !fFilters.isEmpty() || !fPreferences.isEmpty();
}
/*
* @see org.eclipse.jface.dialogs.IDialogPage#createControl(org.eclipse.swt.widgets.Composite)
*/
public void createControl(Composite parent) {
boolean isWelcome = (getWizard() instanceof WelcomeWizard);
/* Title Image */
setImageDescriptor(OwlUI.getImageDescriptor(getWizard() instanceof WelcomeWizard ? "icons/wizban/welcome_wiz.gif" : "icons/wizban/import_wiz.png")); //$NON-NLS-1$ //$NON-NLS-2$
/* Container */
Composite container = new Composite(parent, SWT.NONE);
container.setLayout(new GridLayout(1, false));
/* Viewer for Folder Child Selection */
fFolderChildTree = new FolderChildCheckboxTree(container);
if (!isWelcome)
((GridData) fFolderChildTree.getViewer().getTree().getLayoutData()).heightHint = 140;
fViewer = fFolderChildTree.getViewer();
/* Open Preview on Doubleclick */
fViewer.addDoubleClickListener(new IDoubleClickListener() {
public void doubleClick(DoubleClickEvent event) {
openPreview(event.getSelection());
}
});
/* Control Preview Button Enablement */
fViewer.addSelectionChangedListener(new ISelectionChangedListener() {
public void selectionChanged(SelectionChangedEvent event) {
ISelection selection = event.getSelection();
fPreviewButton.setEnabled(!selection.isEmpty() && ((IStructuredSelection) selection).getFirstElement() instanceof IBookMark);
}
});
/* Filter (exclude existing) */
fViewer.addFilter(fExistingFilter);
/* Update Page Complete on Selection */
fViewer.getTree().addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
updatePageComplete();
}
});
/* Select All / Deselect All */
Composite buttonContainer = new Composite(container, SWT.NONE);
buttonContainer.setLayout(LayoutUtils.createGridLayout(isWelcome ? 5 : 6, 0, 0));
buttonContainer.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false));
fSelectAll = new Button(buttonContainer, SWT.PUSH);
fSelectAll.setText(Messages.ImportElementsPage_SELECT_ALL);
Dialog.applyDialogFont(fSelectAll);
setButtonLayoutData(fSelectAll);
fSelectAll.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
fFolderChildTree.setAllChecked(true);
updatePageComplete();
}
});
fDeselectAll = new Button(buttonContainer, SWT.PUSH);
fDeselectAll.setText(Messages.ImportElementsPage_DESELECT_ALL);
Dialog.applyDialogFont(fDeselectAll);
setButtonLayoutData(fDeselectAll);
fDeselectAll.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
fFolderChildTree.setAllChecked(false);
updatePageComplete();
}
});
if (!Application.IS_MAC) {
Label sep = new Label(buttonContainer, SWT.SEPARATOR | SWT.VERTICAL);
sep.setLayoutData(new GridData(SWT.BEGINNING, SWT.FILL, false, false));
((GridData) sep.getLayoutData()).heightHint = 20;
}
fPreviewButton = new Button(buttonContainer, SWT.PUSH);
fPreviewButton.setText(Messages.ImportElementsPage_PREVIEW);
fPreviewButton.setEnabled(false);
fPreviewButton.setToolTipText(Messages.ImportElementsPage_SHOW_PREVIEW);
Dialog.applyDialogFont(fPreviewButton);
setButtonLayoutData(fPreviewButton);
fPreviewButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
openPreview(fViewer.getSelection());
}
});
/* Show as Flat List of News Marks */
fFlattenCheck = new Button(buttonContainer, SWT.CHECK);
fFlattenCheck.setText(Messages.ImportElementsPage_FLATTEN);
Dialog.applyDialogFont(fFlattenCheck);
setButtonLayoutData(fFlattenCheck);
((GridData) fFlattenCheck.getLayoutData()).horizontalAlignment = SWT.END;
((GridData) fFlattenCheck.getLayoutData()).horizontalIndent = 30;
((GridData) fFlattenCheck.getLayoutData()).grabExcessHorizontalSpace = true;
fFlattenCheck.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
fFolderChildTree.setFlat(fFlattenCheck.getSelection());
fViewer.expandToLevel(2);
fFolderChildTree.setAllChecked(true);
}
});
/* Hide Existing News Marks */
fHideExistingCheck = new Button(buttonContainer, SWT.CHECK);
fHideExistingCheck.setText(Messages.ImportElementsPage_HIDE_EXISTING);
fHideExistingCheck.setSelection(true);
Dialog.applyDialogFont(fHideExistingCheck);
setButtonLayoutData(fHideExistingCheck);
((GridData) fHideExistingCheck.getLayoutData()).exclude = isWelcome;
fHideExistingCheck.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
if (fHideExistingCheck.getSelection())
fViewer.addFilter(fExistingFilter);
else
fViewer.removeFilter(fExistingFilter);
fViewer.expandToLevel(2);
updateMessage(false);
}
});
/* React on user clicking Cancel if a progress is showing */
if (getContainer() instanceof CustomWizardDialog) {
Button cancelButton = ((CustomWizardDialog) getContainer()).getButton(IDialogConstants.CANCEL_ID);
if (cancelButton != null) {
cancelButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
onCancel();
}
});
}
}
Dialog.applyDialogFont(container);
setControl(container);
}
private void onCancel() {
if (fCurrentProgressMonitor != null && getShell() != null && !getShell().isDisposed()) {
IProgressMonitor monitor = fCurrentProgressMonitor;
monitor.setTaskName(Messages.ImportElementsPage_CANCEL_SEARCH);
monitor.subTask(""); //$NON-NLS-1$
}
}
private void openPreview(ISelection selection) {
IStructuredSelection sel = (IStructuredSelection) selection;
if (!sel.isEmpty()) {
Object[] elements = sel.toArray();
int offset = 0;
for (Object element : elements) {
if (element instanceof IBookMark) {
IBookMark bookmark = (IBookMark) element;
IFeed loadedFeed = fLoadedFeedCache.get(bookmark.getFeedLinkReference().getLink());
PreviewFeedDialog dialog = new PreviewFeedDialog(getShell(), bookmark, loadedFeed);
dialog.setBlockOnOpen(false);
dialog.open();
if (offset != 0) {
Point location = dialog.getShell().getLocation();
dialog.getShell().setLocation(location.x + offset, location.y + offset);
}
offset += 20;
}
}
}
}
private void updateMessage(boolean clearErrors) {
List<?> input = (List<?>) fViewer.getInput();
if (!input.isEmpty() && fViewer.getTree().getItemCount() == 0 && fViewer.getFilters().length > 0)
setMessage(Messages.ImportElementsPage_HIDDEN_ELEMENTS_INFO, IMessageProvider.WARNING);
else
setMessage(Messages.ImportElementsPage_CHOOSE_ELEMENTS_MESSAGE);
if (clearErrors)
setErrorMessage(null);
updatePageComplete();
}
private void updatePageComplete() {
boolean complete = (showOptionsPage() || fViewer.getCheckedElements().length > 0);
setPageComplete(complete);
}
/*
* @see org.eclipse.jface.dialogs.DialogPage#setVisible(boolean)
*/
@Override
public void setVisible(boolean visible) {
super.setVisible(visible);
if (!visible)
return;
fViewer.getControl().setFocus();
/* Load Elements to Import from Source on first time */
doImportSource();
}
@SuppressWarnings("unchecked")
private void doImportSource() {
ImportSourcePage importSourcePage = (ImportSourcePage) getPreviousPage();
final Source source = importSourcePage.getSource();
/* Return if the Source did not Change */
if (source == Source.RECOMMENDED && fCurrentSourceKind == Source.RECOMMENDED)
return;
else if (source == Source.GOOGLE && fCurrentSourceKind == Source.GOOGLE)
return;
else if (source == Source.KEYWORD && fCurrentSourceKind == Source.KEYWORD && importSourcePage.getImportKeywords().equals(fCurrentSourceKeywords) && fCurrentSourceLocalizedFeedSearch == importSourcePage.isLocalizedFeedSearch())
return;
else if (source == Source.RESOURCE && fCurrentSourceKind == Source.RESOURCE) {
String importResource = importSourcePage.getImportResource();
/* Same URL */
if (importSourcePage.isRemoteSource() && importResource.equals(fCurrentSourceResource))
return;
/* Same Unmodified File */
else if (importResource.equals(fCurrentSourceResource)) {
File file = new File(importResource);
if (file.exists() && file.lastModified() == fCurrentSourceFileModified)
return;
}
}
/* Remember Source */
fCurrentSourceKind = source;
fCurrentSourceResource = importSourcePage.getImportResource();
final File sourceFile = (source == Source.RESOURCE) ? new File(importSourcePage.getImportResource()) : null;
fCurrentSourceFileModified = (sourceFile != null && sourceFile.exists()) ? sourceFile.lastModified() : 0;
fCurrentSourceKeywords = importSourcePage.getImportKeywords();
fCurrentSourceLocalizedFeedSearch = importSourcePage.isLocalizedFeedSearch();
/* Reset Fields */
fLabels.clear();
fFilters.clear();
fPreferences.clear();
/* Reset Messages */
setErrorMessage(null);
setMessage(Messages.ImportElementsPage_CHOOSE_ELEMENTS_MESSAGE);
/* Clear Viewer before loading */
setImportedElements(Collections.EMPTY_LIST);
/* Ask for Username and Password if importing from Google */
if (source == Source.GOOGLE) {
URI googleLoginUri = URI.create(SyncUtils.GOOGLE_LOGIN);
LoginDialog dialog = new LoginDialog(getShell(), googleLoginUri, null);
dialog.setHeader(Messages.ImportElementsPage_LOGIN_GOOGLE_READER);
dialog.setSubline(Messages.ImportElementsPage_ENTER_GOOGLE_ACCOUNT);
dialog.setTitleImageDescriptor(OwlUI.getImageDescriptor("icons/wizban/reader_wiz.png")); //$NON-NLS-1$
if (dialog.open() != IDialogConstants.OK_ID) {
setErrorMessage(Messages.ImportElementsPage_MISSING_ACCOUNT);
setPageComplete(false);
fCurrentSourceKind = null;
return;
}
}
/* Import Runnable */
Runnable runnable = new Runnable() {
public void run() {
try {
/* Import from Supplied File */
if (source == Source.RESOURCE && sourceFile != null && sourceFile.exists())
importFromLocalResource(sourceFile);
/* Import from Supplied Online Resource */
else if (source == Source.RESOURCE && URIUtils.looksLikeLink(fCurrentSourceResource))
importFromOnlineResource(new URI(URIUtils.ensureProtocol(fCurrentSourceResource)));
/* Import from Google */
else if (source == Source.GOOGLE)
importFromGoogleReader();
/* Import by Keyword Search */
else if (source == Source.KEYWORD)
importFromKeywordSearch(fCurrentSourceKeywords, fCurrentSourceLocalizedFeedSearch);
/* Import from Default OPML File */
else if (source == Source.RECOMMENDED)
importFromLocalResource(getClass().getResourceAsStream("/default_feeds.xml")); //$NON-NLS-1$;
}
/* Log and Show any Exception during Import */
catch (Exception e) {
/* Log Message */
String logMessage = e.getMessage();
if (e instanceof InvocationTargetException && e.getCause() != null && StringUtils.isSet(e.getCause().getMessage()))
logMessage = e.getCause().getMessage();
if (StringUtils.isSet(logMessage))
logMessage = NLS.bind(Messages.ImportElementsPage_UNABLE_TO_IMPORT_REASON, logMessage);
else
logMessage = Messages.ImportElementsPage_UNABLE_TO_IMPORT;
/* User Message */
String userMessage = CoreUtils.toMessage(e);
if (StringUtils.isSet(userMessage))
userMessage = NLS.bind(Messages.ImportElementsPage_UNABLE_TO_IMPORT_REASON, userMessage);
else
userMessage = Messages.ImportElementsPage_UNABLE_TO_IMPORT;
Activator.getDefault().logError(logMessage, e);
setErrorMessage(userMessage);
setPageComplete(false);
/* Give a chance to try again */
fCurrentSourceKind = null;
}
}
};
/* Perform delayed if potential remote import to give Viewer a chance to show */
if (importSourcePage.isRemoteSource())
JobRunner.runInUIThread(50, getShell(), runnable);
else
runnable.run();
}
/* Import from a Local Input Stream (no progress required) */
private void importFromLocalResource(InputStream in) throws InterpreterException, ParserException {
/* Show Folder Childs in Viewer */
List<? extends IEntity> types = Owl.getInterpreter().importFrom(in);
setImportedElements(types);
updateMessage(true);
}
/* Import from a Local File */
private void importFromLocalResource(final File file) throws FileNotFoundException, InvocationTargetException, InterruptedException {
boolean bruteForce = false;
/* First Try to Import as OPML */
try {
importFromLocalResource(new FileInputStream(file));
} catch (ParserException e) {
bruteForce = true;
} catch (InterpreterException e) {
bruteForce = true;
}
/* Then try to parse links found in the file as Feeds */
if (bruteForce) {
IRunnableWithProgress runnable = new IRunnableWithProgress() {
public void run(final IProgressMonitor monitor) throws InvocationTargetException {
monitor.beginTask(Messages.ImportElementsPage_SEARCHING_FOR_FEEDS, IProgressMonitor.UNKNOWN);
fCurrentProgressMonitor = monitor;
/* Return on Cancellation */
if (monitor.isCanceled() || Controller.getDefault().isShuttingDown())
return;
/* Read Content */
Reader reader = null;
try {
reader = new FileReader(file);
String content = StringUtils.readString(reader);
/* Extract Links from Content */
List<String> links = new ArrayList<String>();
if (StringUtils.isSet(content))
links.addAll(RegExUtils.extractLinksFromText(content, false));
/* Check Links for valid Feeds */
importFromLinksBruteforce(links, monitor);
} catch (Exception e) {
throw new InvocationTargetException(e);
} finally {
fCurrentProgressMonitor = null;
monitor.done();
/* Close Reader */
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
throw new InvocationTargetException(e);
}
}
}
}
};
/* Run Operation in Background and allow for Cancellation */
getContainer().run(true, true, runnable);
}
}
private void importFromOnlineResource(final URI link) throws InvocationTargetException, InterruptedException {
IRunnableWithProgress runnable = new IRunnableWithProgress() {
public void run(final IProgressMonitor monitor) throws InvocationTargetException {
InputStream in = null;
boolean canceled = false;
Exception error = null;
boolean bruteForce = false;
try {
monitor.beginTask(Messages.ImportElementsPage_SEARCHING_FOR_FEEDS, IProgressMonitor.UNKNOWN);
monitor.subTask(Messages.ImportElementsPage_CONNECTING);
fCurrentProgressMonitor = monitor;
/* Return on Cancellation */
if (monitor.isCanceled() || Controller.getDefault().isShuttingDown()) {
canceled = true;
return;
}
/* Open Stream */
in = openStream(link, monitor, INITIAL_CON_TIMEOUT, false, false, null);
/* Return on Cancellation */
if (monitor.isCanceled() || Controller.getDefault().isShuttingDown()) {
canceled = true;
return;
}
/* Try to Import */
try {
final List<? extends IEntity> types = Owl.getInterpreter().importFrom(in);
/* Return on Cancellation */
if (monitor.isCanceled() || Controller.getDefault().isShuttingDown()) {
canceled = true;
return;
}
/* Show in UI */
JobRunner.runInUIThread(getShell(), new Runnable() {
public void run() {
setImportedElements(types);
updateMessage(true);
}
});
}
/* Error Importing from File - Try Bruteforce then */
catch (Exception e) {
error = e;
bruteForce = true;
}
}
/* Error finding a Handler for the Link - Rethrow */
catch (Exception e) {
final boolean showError[] = new boolean[] { true };
/* Give user a chance to log in */
if (e instanceof AuthenticationRequiredException && !monitor.isCanceled() && !Controller.getDefault().isShuttingDown()) {
final Shell shell = getShell();
if (shell != null && !shell.isDisposed()) {
boolean locked = Controller.getDefault().getLoginDialogLock().tryLock();
if (locked) {
try {
final AuthenticationRequiredException authEx = (AuthenticationRequiredException) e;
JobRunner.runSyncedInUIThread(shell, new Runnable() {
public void run() {
try {
/* Return on Cancelation or shutdown or deletion */
if (monitor.isCanceled() || Controller.getDefault().isShuttingDown())
return;
/* Credentials might have been provided meanwhile in another dialog */
try {
URI normalizedUri = URIUtils.normalizeUri(link, true);
if (Owl.getConnectionService().getAuthCredentials(normalizedUri, authEx.getRealm()) != null) {
importFromOnlineResource(link);
showError[0] = false;
return;
}
} catch (CredentialsException exe) {
Activator.getDefault().getLog().log(exe.getStatus());
}
/* Show Login Dialog */
LoginDialog login = new LoginDialog(shell, link, authEx.getRealm());
if (login.open() == Window.OK && !monitor.isCanceled() && !Controller.getDefault().isShuttingDown()) {
importFromOnlineResource(link);
showError[0] = false;
}
} catch (InvocationTargetException e) {
/* Ignore - Error will be handled outside already */
} catch (InterruptedException e) {
/* Ignore - Error will be handled outside already */
}
}
});
} finally {
Controller.getDefault().getLoginDialogLock().unlock();
}
}
}
}
/* Rethrow Exception */
if (showError[0])
throw new InvocationTargetException(e);
} finally {
/* Reset Field in case of error or cancellation */
if (canceled || error != null)
fCurrentProgressMonitor = null;
/* Close Input Stream */
if (in != null) {
try {
if ((canceled || error != null) && in instanceof IAbortable)
((IAbortable) in).abort();
else
in.close();
} catch (IOException e) {
throw new InvocationTargetException(e);
}
}
}
/* Scan remote Resource for Links and valid Feeds */
if (bruteForce && !monitor.isCanceled() && !Controller.getDefault().isShuttingDown()) {
try {
importFromOnlineResourceBruteforce(link, monitor, false, false);
} catch (Exception e) {
throw new InvocationTargetException(e);
} finally {
fCurrentProgressMonitor = null;
}
}
/* Done */
monitor.done();
fCurrentProgressMonitor = null;
}
};
/* Run Operation in Background and allow for Cancellation */
getContainer().run(true, true, runnable);
}
private void importFromGoogleReader() throws InvocationTargetException, InterruptedException {
IRunnableWithProgress runnable = new IRunnableWithProgress() {
public void run(final IProgressMonitor monitor) throws InvocationTargetException {
InputStream in = null;
boolean canceled = false;
Exception error = null;
try {
monitor.beginTask(Messages.ImportElementsPage_IMPORT_GOOGLE_READER, IProgressMonitor.UNKNOWN);
monitor.subTask(Messages.ImportElementsPage_CONNECTING);
fCurrentProgressMonitor = monitor;
/* Return on Cancellation */
if (monitor.isCanceled() || Controller.getDefault().isShuttingDown()) {
canceled = true;
return;
}
/* Obtain Google Account Credentials */
ICredentials credentials = Owl.getConnectionService().getAuthCredentials(URI.create(SyncUtils.GOOGLE_LOGIN), null);
if (credentials == null) {
canceled = true;
return;
}
/* Obtain Auth Token */
String googleAuthToken = SyncUtils.getGoogleAuthToken(credentials.getUsername(), credentials.getPassword(), monitor);
/* Open Stream */
in = openStream(URI.create(GOOGLE_READER_OPML_URI), monitor, INITIAL_CON_TIMEOUT, false, false, googleAuthToken);
/* Return on Cancellation */
if (monitor.isCanceled() || Controller.getDefault().isShuttingDown()) {
canceled = true;
return;
}
/* Try to Import */
try {
final List<? extends IEntity> types = Owl.getInterpreter().importFrom(in);
/* Return on Cancellation */
if (monitor.isCanceled() || Controller.getDefault().isShuttingDown()) {
canceled = true;
return;
}
/* Show in UI */
JobRunner.runInUIThread(getShell(), new Runnable() {
public void run() {
setImportedElements(types);
updateMessage(true);
}
});
}
/* Error Importing from File - Try Bruteforce then */
catch (Exception e) {
error = e;
}
}
/* Error finding a Handler for the Link - Rethrow */
catch (Exception e) {
throw new InvocationTargetException(e);
} finally {
/* Reset Field in case of error or cancellation */
if (canceled || error != null)
fCurrentProgressMonitor = null;
/* Close Input Stream */
if (in != null) {
try {
if ((canceled || error != null) && in instanceof IAbortable)
((IAbortable) in).abort();
else
in.close();
} catch (IOException e) {
throw new InvocationTargetException(e);
}
}
}
/* Done */
monitor.done();
fCurrentProgressMonitor = null;
}
};
/* Run Operation in Background and allow for Cancellation */
getContainer().run(true, true, runnable);
}
private void importFromKeywordSearch(final String keywords, final boolean isLocalizedSearch) throws Exception {
IRunnableWithProgress runnable = new IRunnableWithProgress() {
public void run(IProgressMonitor monitor) throws InvocationTargetException {
try {
monitor.beginTask(Messages.ImportElementsPage_SEARCHING_FOR_FEEDS, IProgressMonitor.UNKNOWN);
monitor.subTask(Messages.ImportElementsPage_CONNECTING);
fCurrentProgressMonitor = monitor;
/* Build Link for Keyword-Feed Search */
String linkVal = Controller.getDefault().toFeedSearchLink(keywords);
/* Return on Cancellation */
if (monitor.isCanceled() || Controller.getDefault().isShuttingDown())
return;
/* Scan remote Resource for Links and valid Feeds */
importFromOnlineResourceBruteforce(new URI(linkVal), monitor, true, isLocalizedSearch);
} catch (Exception e) {
throw new InvocationTargetException(e);
} finally {
monitor.done();
fCurrentProgressMonitor = null;
}
}
};
/* Run Operation in Background and allow for Cancellation */
getContainer().run(true, true, runnable);
}
private void importFromOnlineResourceBruteforce(URI resourceLink, IProgressMonitor monitor, final boolean isKeywordSearch, boolean isLocalizedSearch) throws ConnectionException, IOException {
/* Read Content */
Pair<String, URI> result = readContent(resourceLink, isLocalizedSearch, monitor);
if (result == null)
return;
String content = result.getFirst();
resourceLink = result.getSecond();
/* Add the used Link at first if the user has typed in a valid feed address */
List<String> links = new ArrayList<String>();
if (!isKeywordSearch)
links.add(resourceLink.toString());
/* Extract Links from Content */
if (StringUtils.isSet(content))
links.addAll(RegExUtils.extractLinksFromText(content, false));
/* Sort List: First process likely feeds, then others */
final String resourceLinkValue = resourceLink.toString();
Collections.sort(links, new Comparator<String>() {
public int compare(String o1, String o2) {
/* Check common feed patterns in URL */
if (URIUtils.looksLikeFeedLink(o1, false))
return -1;
else if (URIUtils.looksLikeFeedLink(o2, false))
return 1;
/* Check Origin from same Domain */
if (!isKeywordSearch) {
if (o1.contains(resourceLinkValue))
return -1;
else if (o2.contains(resourceLinkValue))
return 1;
}
return -1;
}
});
/* If this is not a keyword search, add the top most feed entry first */
if (!isKeywordSearch) {
URI feed = CoreUtils.findFeed(new BufferedReader(new StringReader(content)), resourceLink);
if (feed != null) {
String feedStr = feed.toString();
if (links.contains(feedStr))
links.remove(feedStr);
links.add(0, feedStr);
}
}
/* Look for Links */
importFromLinksBruteforce(links, monitor);
}
@SuppressWarnings("null")
private void importFromLinksBruteforce(List<String> links, IProgressMonitor monitor) {
/* Return on Cancellation */
if (monitor.isCanceled() || Controller.getDefault().isShuttingDown())
return;
/* Update Task Information */
monitor.beginTask(Messages.ImportElementsPage_SEARCHING_FOR_FEEDS, links.size());
monitor.subTask(Messages.ImportElementsPage_FETCHING_RESULTS);
/* A Root to add Found Bookmarks into */
final IFolder defaultRootFolder = Owl.getModelFactory().createFolder(null, null, Messages.ImportElementsPage_BOOKMARKS);
defaultRootFolder.setProperty(ITypeImporter.TEMPORARY_FOLDER, true);
/* For Each Link of the Queue - try to interpret as Feed */
int counter = 0;
final List<String> foundBookMarkNames = new ArrayList<String>();
IBookMarkDAO dao = DynamicDAO.getDAO(IBookMarkDAO.class);
for (String feedLinkVal : links) {
monitor.worked(1);
InputStream in = null;
boolean canceled = false;
Exception error = null;
try {
URI feedLink = new URI(feedLinkVal);
/* Report Progress Back To User */
if (counter == 1)
monitor.subTask(Messages.ImportElementsPage_SINGLE_RESULT);
else if (counter > 1)
monitor.subTask(NLS.bind(Messages.ImportElementsPage_N_RESULTS, counter));
/* Ignore if already present in Subscriptions List (ignoring trailing slashes) */
if (dao.exists(new FeedLinkReference(feedLink)))
continue;
else if (feedLinkVal.endsWith("/") && dao.exists(new FeedLinkReference(new URI(feedLinkVal.substring(0, feedLinkVal.length() - 1))))) //$NON-NLS-1$
continue;
else if (!feedLinkVal.endsWith("/") && dao.exists(new FeedLinkReference(new URI(feedLinkVal + "/")))) //$NON-NLS-1$ //$NON-NLS-2$
continue;
/* Return on Cancellation */
if (monitor.isCanceled() || Controller.getDefault().isShuttingDown())
break;
/* Open Stream to potential Feed */
in = openStream(feedLink, monitor, FEED_CON_TIMEOUT, false, false, null);
/* Return on Cancellation */
if (monitor.isCanceled() || Controller.getDefault().isShuttingDown()) {
canceled = true;
break;
}
/* Try to interpret as Feed */
IFeed feed = Owl.getModelFactory().createFeed(null, feedLink);
Owl.getInterpreter().interpret(in, feed, null);
fLoadedFeedCache.put(feedLink, feed);
/* Return on Cancellation */
if (monitor.isCanceled() || Controller.getDefault().isShuttingDown()) {
canceled = true;
break;
}
/* Add as Result if Feed contains News */
if (!feed.getNews().isEmpty() && StringUtils.isSet(feed.getTitle())) {
String title = feed.getTitle();
boolean sameTitleExists = foundBookMarkNames.contains(title);
if (sameTitleExists && StringUtils.isSet(feed.getFormat()))
title = NLS.bind(Messages.ImportElementsPage_FEED_TITLE, title, feed.getFormat());
final IBookMark bookmark = Owl.getModelFactory().createBookMark(null, defaultRootFolder, new FeedLinkReference(feedLink), title);
foundBookMarkNames.add(bookmark.getName());
counter++;
if (StringUtils.isSet(feed.getDescription()))
bookmark.setProperty(ITypeImporter.DESCRIPTION_KEY, feed.getDescription());
if (feed.getHomepage() != null)
bookmark.setProperty(ITypeImporter.HOMEPAGE_KEY, feed.getHomepage());
/* Directly show in Viewer */
JobRunner.runInUIThread(getShell(), new Runnable() {
public void run() {
addImportedElement(bookmark);
}
});
}
}
/* Ignore Errors (likely not a Feed then) */
catch (Exception e) {
error = e;
}
/* Close Stream */
finally {
/* Close Input Stream */
if (in != null) {
try {
if ((canceled || error != null) && in instanceof IAbortable)
((IAbortable) in).abort();
else
in.close();
} catch (IOException e) {
/* Ignore Silently */
}
}
}
}
/* Inform if no feeds have been found */
if (counter == 0) {
JobRunner.runInUIThread(getShell(), new Runnable() {
public void run() {
setMessage(Messages.ImportElementsPage_NO_FEEDS_FOUND, IMessageProvider.INFORMATION);
}
});
}
}
private Pair<String, URI> readContent(URI link, boolean isLocalizedSearch, IProgressMonitor monitor) throws ConnectionException, IOException {
InputStream in = null;
try {
/* Return on Cancellation */
if (monitor.isCanceled() || Controller.getDefault().isShuttingDown())
return null;
/* Open Stream */
in = openStream(link, monitor, INITIAL_CON_TIMEOUT, true, isLocalizedSearch, null);
/* Return on Cancellation */
if (monitor.isCanceled() || Controller.getDefault().isShuttingDown())
return null;
/* Read Content */
String content = StringUtils.readString(new InputStreamReader(in));
/* Return actual URI that was connected to (supporting redirects) */
if (in instanceof HttpConnectionInputStream)
return Pair.create(content, ((HttpConnectionInputStream) in).getLink());
/* Otherwise just use input URI */
return Pair.create(content, link);
} finally {
if (in instanceof IAbortable)
((IAbortable) in).abort();
else if (in != null)
in.close();
}
}
private InputStream openStream(URI link, IProgressMonitor monitor, int timeout, boolean setAcceptLanguage, boolean isLocalized, String authToken) throws ConnectionException {
IProtocolHandler handler = Owl.getConnectionService().getHandler(link);
Map<Object, Object> properties = new HashMap<Object, Object>();
properties.put(IConnectionPropertyConstants.CON_TIMEOUT, timeout);
/* Set Authorization Header if required */
if (StringUtils.isSet(authToken)) {
Map<String, String> headers = new HashMap<String, String>();
headers.put("Authorization", SyncUtils.getGoogleAuthorizationHeader(authToken)); //$NON-NLS-1$
properties.put(IConnectionPropertyConstants.HEADERS, headers);
}
/* Set the Accept-Language Header */
if (setAcceptLanguage) {
StringBuilder languageHeader = new StringBuilder();
String clientLanguage = Locale.getDefault().getLanguage();
/* Set Both English and Client Locale */
if (!isLocalized) {
languageHeader.append(DEFAULT_LANGUAGE);
if (StringUtils.isSet(clientLanguage) && !DEFAULT_LANGUAGE.equals(clientLanguage))
languageHeader.append(",").append(clientLanguage); //$NON-NLS-1$
}
/* Only set Client Locale */
else {
if (StringUtils.isSet(clientLanguage))
languageHeader.append(clientLanguage);
else
languageHeader.append(DEFAULT_LANGUAGE);
}
properties.put(IConnectionPropertyConstants.ACCEPT_LANGUAGE, languageHeader.toString());
}
return handler.openStream(link, monitor, properties);
}
/* Updates Caches and Shows Elements */
private void setImportedElements(List<? extends IEntity> types) {
List<IFolderChild> folderChilds = new ArrayList<IFolderChild>();
for (IEntity type : types) {
if (type instanceof IFolderChild)
folderChilds.add((IFolderChild) type);
else if (type instanceof ILabel)
fLabels.add((ILabel) type);
else if (type instanceof ISearchFilter)
fFilters.add((ISearchFilter) type);
else if (type instanceof IPreference)
fPreferences.add((IPreference) type);
}
/* Re-Add Filter if necessary */
if (!fHideExistingCheck.getSelection()) {
fHideExistingCheck.setSelection(true);
fViewer.addFilter(fExistingFilter);
}
/* Apply as Input */
fViewer.setInput(folderChilds);
OwlUI.setAllChecked(fViewer.getTree(), true);
fExistingFilter.clear();
}
/* Adds a IFolderChild to the Viewer and updates caches */
@SuppressWarnings("unchecked")
private void addImportedElement(IFolderChild child) {
Object input = fViewer.getInput();
((List) input).add(child);
fViewer.add(input, child);
fViewer.setChecked(child, true);
fViewer.reveal(child);
}
}