Package org.rssowl.ui.internal

Source Code of org.rssowl.ui.internal.Controller

/*   **********************************************************************  **
**   Copyright notice                                                       **
**                                                                          **
**   (c) 2005-2006 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;

import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IExtensionRegistry;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.SafeRunner;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.window.Window;
import org.eclipse.swt.widgets.Shell;
import org.rssowl.core.IApplicationService;
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.NotModifiedException;
import org.rssowl.core.connection.UnknownFeedException;
import org.rssowl.core.internal.InternalOwl;
import org.rssowl.core.internal.persist.pref.DefaultPreferences;
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.IConditionalGet;
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.IMark;
import org.rssowl.core.persist.IModelFactory;
import org.rssowl.core.persist.INews;
import org.rssowl.core.persist.IPreference;
import org.rssowl.core.persist.ISearchField;
import org.rssowl.core.persist.ISearchMark;
import org.rssowl.core.persist.SearchSpecifier;
import org.rssowl.core.persist.dao.DynamicDAO;
import org.rssowl.core.persist.dao.IBookMarkDAO;
import org.rssowl.core.persist.dao.IConditionalGetDAO;
import org.rssowl.core.persist.dao.IFolderDAO;
import org.rssowl.core.persist.dao.ILabelDAO;
import org.rssowl.core.persist.dao.IPreferenceDAO;
import org.rssowl.core.persist.dao.ISearchMarkDAO;
import org.rssowl.core.persist.event.BookMarkAdapter;
import org.rssowl.core.persist.event.BookMarkEvent;
import org.rssowl.core.persist.pref.IPreferenceScope;
import org.rssowl.core.persist.service.PersistenceException;
import org.rssowl.core.util.ITask;
import org.rssowl.core.util.JobQueue;
import org.rssowl.core.util.LoggingSafeRunnable;
import org.rssowl.core.util.Pair;
import org.rssowl.core.util.StringUtils;
import org.rssowl.core.util.TaskAdapter;
import org.rssowl.core.util.URIUtils;
import org.rssowl.ui.internal.actions.ReloadTypesAction;
import org.rssowl.ui.internal.dialogs.LoginDialog;
import org.rssowl.ui.internal.dialogs.properties.EntityPropertyPageWrapper;
import org.rssowl.ui.internal.editors.feed.NewsGrouping;
import org.rssowl.ui.internal.notifier.NotificationService;
import org.rssowl.ui.internal.util.ImportUtils;
import org.rssowl.ui.internal.util.JobRunner;
import org.rssowl.ui.internal.views.explorer.BookMarkExplorer;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
* <p>
* Note: The Controller should not be responsible for handling Exceptions, e.g.
* showing a Dialog when Authentication is required. Its only responsible for
* calling the appropiate methods on a complex operation like loading a Feed.
* </p>
* <p>
* Note: As required by the UI, the controller should be filled with more
* methods.
* </p>
*
* @author bpasero
*/
public class Controller {

  /* Extension-Points */
  private static final String ENTITY_PROPERTY_PAGE_EXTENSION_POINT = "org.rssowl.ui.EntityPropertyPage"; //$NON-NLS-1$

  /* The Singleton Instance */
  private static Controller fInstance;

  /* Token to ask the DB if this is the first start of RSSOwl */
  private static final String FIRST_START_TOKEN = "org.rssowl.ui.FirstStartToken"; //$NON-NLS-1$

  /* Max. number of concurrent running reload Jobs */
  private static final int MAX_CONCURRENT_RELOAD_JOBS = 10;

  /* Max. number of concurrent Jobs for saving a Feed */
  private static final int MAX_CONCURRENT_SAVE_JOBS = 1;

  /* Max number of jobs in the queue used for saving feeds before it blocks */
  private static final int MAX_SAVE_QUEUE_SIZE = 1;

  /* Connection Timeouts in MS */
  private static final int FEED_CON_TIMEOUT = 30000;

  /* Flag indicating if RSSOwl is starting up for the first time */
  static boolean fgFirstStartup;

  /* Queue for reloading Feeds */
  private final JobQueue fReloadFeedQueue;

  /* Queue for saving Feeds */
  private final JobQueue fSaveFeedQueue;

  /* News-Service */
  private NewsService fNewsService;

  /* Notification Service */
  private NotificationService fNotificationService;

  /* Saved Search Service */
  private SavedSearchService fSavedSearchService;

  /* Feed-Reload Service */
  private FeedReloadService fFeedReloadService;

  /* Contributed Entity-Property-Pages */
  final List<EntityPropertyPageWrapper> fEntityPropertyPages;

  /* Flag is set to TRUE when shutting down the application */
  private boolean fShuttingDown;

  /* Service to access some cached Entities */
  private CacheService fCacheService;

  /* Service to manage Contexts */
  private ContextService fContextService;

  /* Misc. */
  private IApplicationService fAppService;
  private IBookMarkDAO fBookMarkDAO;
  private IFolderDAO fFolderDAO;
  private IConditionalGetDAO fConditionalGetDAO;
  private IPreferenceDAO fPrefsDAO;
  private ILabelDAO fLabelDao;
  private IModelFactory fFactory;
  private Lock fLoginDialogLock = new ReentrantLock();
  private BookMarkAdapter fBookMarkListener;

  /* Task to perform Reload-Operations */
  private class ReloadTask implements ITask {
    private final Long fId;
    private final IBookMark fBookMark;
    private final Shell fShell;
    private final Priority fPriority;

    ReloadTask(IBookMark bookmark, Shell shell, ITask.Priority priority) {
      Assert.isNotNull(bookmark);
      Assert.isNotNull(bookmark.getId());

      fBookMark = bookmark;
      fId = bookmark.getId();
      fShell = shell;
      fPriority = priority;
    }

    public IStatus run(IProgressMonitor monitor) {
      IStatus status = reload(fBookMark, fShell, monitor);
      return status;
    }

    public String getName() {
      return fBookMark.getName();
    }

    public Priority getPriority() {
      return fPriority;
    }

    @Override
    public int hashCode() {
      return fId.hashCode();
    }

    @Override
    public boolean equals(Object obj) {
      if (this == obj)
        return true;

      if (obj == null)
        return false;

      if (getClass() != obj.getClass())
        return false;

      final ReloadTask other = (ReloadTask) obj;
      return fId.equals(other.fId);
    }
  }

  private Controller() {
    fReloadFeedQueue = new JobQueue("Updating Feeds", MAX_CONCURRENT_RELOAD_JOBS, Integer.MAX_VALUE, true, 0);
    fSaveFeedQueue = new JobQueue("Updating Feeds", MAX_CONCURRENT_SAVE_JOBS, MAX_SAVE_QUEUE_SIZE, true, 0);
    fSaveFeedQueue.setUnknownProgress(true);
    fEntityPropertyPages = loadEntityPropertyPages();
    fBookMarkDAO = DynamicDAO.getDAO(IBookMarkDAO.class);
    fConditionalGetDAO = DynamicDAO.getDAO(IConditionalGetDAO.class);
    fFolderDAO = DynamicDAO.getDAO(IFolderDAO.class);
    fLabelDao = DynamicDAO.getDAO(ILabelDAO.class);
    fPrefsDAO = Owl.getPersistenceService().getDAOService().getPreferencesDAO();
    fAppService = Owl.getApplicationService();
    fFactory = Owl.getModelFactory();
  }

  private void registerListeners() {
    fBookMarkListener = new BookMarkAdapter() {
      @Override
      public void entitiesDeleted(Set<BookMarkEvent> events) {
        for (BookMarkEvent event : events) {
          OwlUI.deleteImage(event.getEntity().getId());
        }
      }
    };

    DynamicDAO.addEntityListener(IBookMark.class, fBookMarkListener);
  }

  private void unregisterListeners() {
    DynamicDAO.removeEntityListener(IBookMark.class, fBookMarkListener);
  }

  private List<EntityPropertyPageWrapper> loadEntityPropertyPages() {
    List<EntityPropertyPageWrapper> pages = new ArrayList<EntityPropertyPageWrapper>();

    IExtensionRegistry reg = Platform.getExtensionRegistry();
    IConfigurationElement elements[] = reg.getConfigurationElementsFor(ENTITY_PROPERTY_PAGE_EXTENSION_POINT);

    /* For each contributed property Page */
    for (IConfigurationElement element : elements) {
      try {
        String id = element.getAttribute("id");
        String name = element.getAttribute("name");
        int order = Integer.valueOf(element.getAttribute("order"));
        boolean handlesMultipleEntities = Boolean.valueOf(element.getAttribute("handlesMultipleEntities"));

        List<Class<?>> targetEntities = new ArrayList<Class<?>>();
        IConfigurationElement[] entityTargets = element.getChildren("targetEntity");
        for (IConfigurationElement entityTarget : entityTargets)
          targetEntities.add(Class.forName(entityTarget.getAttribute("class")));

        pages.add(new EntityPropertyPageWrapper(id, element, targetEntities, name, order, handlesMultipleEntities));
      } catch (ClassNotFoundException e) {
        Activator.getDefault().logError(e.getMessage(), e);
      }
    }

    return pages;
  }

  /**
   * @return The Singleton Instance.
   */
  public static Controller getDefault() {
    if (fInstance == null)
      fInstance = new Controller();

    return fInstance;
  }

  /**
   * @param entities
   * @return The EntityPropertyPageWrappers for the given Entity.
   */
  public Set<EntityPropertyPageWrapper> getEntityPropertyPagesFor(List<IEntity> entities) {
    Set<EntityPropertyPageWrapper> pages = new HashSet<EntityPropertyPageWrapper>();

    /* Retrieve Class-Objects from Entities */
    Set<Class<? extends IEntity>> entityClasses = new HashSet<Class<? extends IEntity>>();
    for (IEntity entity : entities)
      entityClasses.add(entity.getClass());

    /* For each contributed Entity Property-Page */
    for (EntityPropertyPageWrapper pageWrapper : fEntityPropertyPages) {

      /* Ignore Pages that dont handle Multi-Selection */
      if (!pageWrapper.isHandlingMultipleEntities() && entities.size() > 1)
        continue;

      /* Check if Page is handling all of the given Entity-Classes */
      if (pageWrapper.handles(entityClasses))
        pages.add(pageWrapper);
    }

    return pages;
  }

  /**
   * @return Returns the newsService.
   */
  public NewsService getNewsService() {
    return fNewsService;
  }

  /**
   * @return Returns the savedSearchService.
   */
  public SavedSearchService getSavedSearchService() {
    return fSavedSearchService;
  }

  /**
   * @return Returns the contextService.
   */
  public ContextService getContextService() {

    /* Create the Context Service if not yet done */
    if (fContextService == null)
      fContextService = new ContextService();

    return fContextService;
  }

  /**
   * @return Returns the cacheService.
   */
  public CacheService getCacheService() {
    return fCacheService;
  }

  /**
   * @return Returns the JobFamily all reload-jobs belong to.
   */
  public Object getReloadFamily() {
    return fReloadFeedQueue;
  }

  /**
   * @return Returns the reload-service.
   */
  public FeedReloadService getReloadService() {
    return fFeedReloadService;
  }

  /**
   * Reload the given List of BookMarks. The BookMarks are processed in a queue
   * that stores all Tasks of this kind and guarantees that a certain amount of
   * Jobs process the Task concurrently.
   *
   * @param bookmarks The BookMarks to reload.
   * @param shell The Shell this operation is running in, used to open Dialogs
   * if necessary.
   */
  public void reloadQueued(Set<IBookMark> bookmarks, final Shell shell) {

    /* Decide wether this is a high prio Job */
    boolean highPrio = bookmarks.size() == 1;

    /* Create a Task for each Feed to Reload */
    List<ITask> tasks = new ArrayList<ITask>();
    for (final IBookMark bookmark : bookmarks) {
      ReloadTask task = new ReloadTask(bookmark, shell, highPrio ? ITask.Priority.SHORT : ITask.Priority.DEFAULT);

      /* Check if Task is not yet Queued already */
      if (!fReloadFeedQueue.isQueued(task))
        tasks.add(task);
    }

    fReloadFeedQueue.schedule(tasks);
  }

  /**
   * Reload the given BookMark. The BookMark is processed in a queue that stores
   * all Tasks of this kind and guarantees that a certain amount of Jobs process
   * the Task concurrently.
   *
   * @param bookmark The BookMark to reload.
   * @param shell The Shell this operation is running in, used to open Dialogs
   * if necessary.
   */
  public void reloadQueued(IBookMark bookmark, final Shell shell) {

    /* Create a Task for the Bookmark to Reload */
    ReloadTask task = new ReloadTask(bookmark, shell, ITask.Priority.DEFAULT);

    /* Check if Task is not yet Queued already */
    if (!fReloadFeedQueue.isQueued(task))
      fReloadFeedQueue.schedule(task);
  }

  /**
   * Reload the given BookMark.
   *
   * @param bookmark The BookMark to reload.
   * @param shell The Shell this operation is running in, used to open Dialogs
   * if necessary, or <code>NULL</code> if no Shell is available.
   * @param monitor A monitor to report progress and respond to cancelation. Use
   * a <code>NullProgressMonitor</code> if no progress is to be reported.
   * @return Returns the Status of the Operation.
   */
  public IStatus reload(final IBookMark bookmark, final Shell shell, final IProgressMonitor monitor) {
    Assert.isNotNull(bookmark);
    CoreException ex = null;

    /* Keep URL of Feed as final var */
    final URI feedLink = bookmark.getFeedLinkReference().getLink();

    try {

      /* Return on Cancelation or shutdown */
      if (monitor.isCanceled() || fShuttingDown)
        return Status.CANCEL_STATUS;

      /* Load Conditional Get for the URL */
      IConditionalGet conditionalGet = fConditionalGetDAO.load(feedLink);

      /* Define Properties for Connection */
      Map<Object, Object> properties = new HashMap<Object, Object>();
      properties.put(IConnectionPropertyConstants.CON_TIMEOUT, FEED_CON_TIMEOUT);

      /* Add Conditional GET Headers if present */
      if (conditionalGet != null) {
        String ifModifiedSince = conditionalGet.getIfModifiedSince();
        if (ifModifiedSince != null)
          properties.put(IConnectionPropertyConstants.IF_MODIFIED_SINCE, ifModifiedSince);

        String ifNoneMatch = conditionalGet.getIfNoneMatch();
        if (ifNoneMatch != null)
          properties.put(IConnectionPropertyConstants.IF_NONE_MATCH, ifNoneMatch);
      }

      /* Load the Feed */
      final Pair<IFeed, IConditionalGet> pairResult = Owl.getConnectionService().reload(feedLink, monitor, properties);

      /* Return on Cancelation or Shutdown */
      if (monitor.isCanceled() || fShuttingDown)
        return Status.CANCEL_STATUS;

      /* Update ConditionalGet Entity */
      boolean conditionalGetIsNull = (conditionalGet == null);
      conditionalGet = updateConditionalGet(feedLink, conditionalGet, pairResult.getSecond());
      boolean deleteConditionalGet = (!conditionalGetIsNull && conditionalGet == null);

      /* Return on Cancelation or Shutdown */
      if (monitor.isCanceled() || fShuttingDown)
        return Status.CANCEL_STATUS;

      /* Load the Favicon directly afterwards if required */
      if (!InternalOwl.PERF_TESTING && OwlUI.getFavicon(bookmark) == null) {
        try {
          byte[] faviconBytes = null;

          /* First try using the Homepage of the Feed */
          URI homepage = pairResult.getFirst().getHomepage();
          if (homepage != null && StringUtils.isSet(homepage.toString()))
            faviconBytes = Owl.getConnectionService().getFeedIcon(homepage);

          /* Then try with Feed address itself */
          if (faviconBytes == null)
            faviconBytes = Owl.getConnectionService().getFeedIcon(feedLink);

          /* Store locally */
          if (!monitor.isCanceled() && !fShuttingDown)
            OwlUI.storeImage(bookmark.getId(), faviconBytes, OwlUI.BOOKMARK, 16, 16);
        } catch (UnknownFeedException e) {
          Activator.getDefault().getLog().log(e.getStatus());
        }
      }

      /* Return on Cancelation or Shutdown */
      if (monitor.isCanceled() || fShuttingDown)
        return Status.CANCEL_STATUS;

      /* Merge and Save Feed */
      if (!InternalOwl.TESTING) {
        final IConditionalGet finalConditionalGet = conditionalGet;
        final boolean finalDeleteConditionalGet = deleteConditionalGet;
        fSaveFeedQueue.schedule(new TaskAdapter() {
          public IStatus run(IProgressMonitor monitor) {

            /* Return on Cancelation or Shutdown */
            if (monitor.isCanceled() || fShuttingDown)
              return Status.CANCEL_STATUS;

            /* Handle Feed Reload */
            fAppService.handleFeedReload(bookmark, pairResult.getFirst(), finalConditionalGet, finalDeleteConditionalGet);
            return Status.OK_STATUS;
          }

          @Override
          public String getName() {
            return "Updating Feeds";
          }
        });
      } else {
        fAppService.handleFeedReload(bookmark, pairResult.getFirst(), conditionalGet, deleteConditionalGet);
      }
    }

    /* Error while reloading */
    catch (CoreException e) {
      ex = e;

      /* Authentication Required */
      if (e instanceof AuthenticationRequiredException && shell != null && !shell.isDisposed() && !fShuttingDown) {

        /* Only one Login Dialog at the same time */
        fLoginDialogLock.lock();
        try {
          final AuthenticationRequiredException authEx = (AuthenticationRequiredException) e;
          JobRunner.runSyncedInUIThread(shell, new Runnable() {
            public void run() {

              /* Check for shutdown flag and return if required */
              if (fShuttingDown || monitor.isCanceled())
                return;

              /* Credentials might have been provided meanwhile in another dialog */
              try {
                URI normalizedUri = URIUtils.normalizeUri(feedLink, true);
                if (!fShuttingDown && Owl.getConnectionService().getAuthCredentials(normalizedUri, authEx.getRealm()) != null) {
                  reloadQueued(bookmark, shell);
                  return;
                }
              } catch (CredentialsException exe) {
                Activator.getDefault().getLog().log(exe.getStatus());
              }

              /* Show Login Dialog */
              LoginDialog login = new LoginDialog(shell, feedLink, authEx.getRealm());
              if (login.open() == Window.OK && !fShuttingDown)
                reloadQueued(bookmark, shell);

              /* Update Error Flag if user hit Cancel */
              else if (!fShuttingDown && !monitor.isCanceled() && !bookmark.isErrorLoading()) {
                bookmark.setErrorLoading(true);
                fBookMarkDAO.save(bookmark);
              }
            }
          });

          return Status.OK_STATUS;
        } finally {
          fLoginDialogLock.unlock();
        }
      }

      /* Feed's Content has not modified since */
      else if (e instanceof NotModifiedException) {
        return Status.OK_STATUS;
      }

      /* Load the Favicon directly afterwards if required */
      else if ((e instanceof InterpreterException || e instanceof ParserException) && OwlUI.getFavicon(bookmark) == null && !fShuttingDown) {
        try {
          byte[] faviconBytes = Owl.getConnectionService().getFeedIcon(feedLink);
          OwlUI.storeImage(bookmark.getId(), faviconBytes, OwlUI.BOOKMARK, 16, 16);
        } catch (ConnectionException exe) {
          Activator.getDefault().getLog().log(exe.getStatus());
        }
      }

      return createWarningStatus(e.getStatus(), bookmark, feedLink);
    }

    /* Save Error State to the Bookmark if present */
    finally {
      updateErrorIndicator(bookmark, monitor, ex);
    }

    return Status.OK_STATUS;
  }

  private void updateErrorIndicator(final IBookMark bookmark, final IProgressMonitor monitor, CoreException ex) {

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

    /* Reset Error-Loading flag if necessary */
    if (bookmark.isErrorLoading() && (ex == null || ex instanceof NotModifiedException)) {
      bookmark.setErrorLoading(false);
      fBookMarkDAO.save(bookmark);
    }

    /* Set Error-Loading flag if necessary */
    else if (!bookmark.isErrorLoading() && ex != null && !(ex instanceof NotModifiedException) && !(ex instanceof AuthenticationRequiredException)) {
      bookmark.setErrorLoading(true);
      fBookMarkDAO.save(bookmark);
    }
  }

  /*
   * Note that this does not save the conditional get, it just updates the its
   * values.
   */
  private IConditionalGet updateConditionalGet(final URI feedLink, IConditionalGet oldConditionalGet, IConditionalGet newConditionalGet) {

    /* Conditional Get not provided, return */
    if (newConditionalGet == null)
      return null;

    String ifModifiedSince = newConditionalGet.getIfModifiedSince();
    String ifNoneMatch = newConditionalGet.getIfNoneMatch();
    if (ifModifiedSince != null || ifNoneMatch != null) {

      /* Create new */
      if (oldConditionalGet == null)
        return fFactory.createConditionalGet(ifModifiedSince, feedLink, ifNoneMatch);

      /* Else: Update old */
      oldConditionalGet.setHeaders(ifModifiedSince, ifNoneMatch);
      return oldConditionalGet;
    }

    return null;
  }

  /**
   * Tells the Controller to start. This method is called automatically from
   * osgi as soon as the org.rssowl.ui bundle gets activated.
   */
  public void startup() {

    /* Create Relations and Import Default Feeds if required */
    if (!InternalOwl.TESTING) {
      SafeRunner.run(new LoggingSafeRunnable() {
        public void run() throws Exception {

          /* First check wether this action is required */
          IPreference firstStartToken = fPrefsDAO.load(FIRST_START_TOKEN);
          if (firstStartToken != null)
            return;

          onFirstStartup();

          /* Mark this as the first start */
          fPrefsDAO.save(fFactory.createPreference(FIRST_START_TOKEN));
        }
      });
    }

    /* Create the Cache-Service */
    if (!InternalOwl.TESTING) {
      fCacheService = new CacheService();
      fCacheService.cacheRootFolders();
    }

    /* Create the News-Service */
    fNewsService = new NewsService();

    /* Create the Notification Service */
    if (!InternalOwl.TESTING)
      fNotificationService = new NotificationService();

    /* Create the Saved Search Service */
    if (!InternalOwl.TESTING)
      fSavedSearchService = new SavedSearchService();

    /* Register Listeners */
    registerListeners();
  }

  /**
   * Tells the Controller to stop. This method is called automatically from osgi
   * as soon as the org.rssowl.ui bundle gets stopped.
   *
   * @param emergency If set to <code>TRUE</code>, this method is called from
   * a shutdown hook that got triggered from a non-normal shutdown (e.g. System
   * Shutdown).
   */
  public void shutdown(boolean emergency) {
    fShuttingDown = true;

    /* Unregister Listeners */
    unregisterListeners();

    /* Stop the Feed Reload Service */
    if (!InternalOwl.TESTING && !emergency)
      fFeedReloadService.stopService();

    /* Cancel the reload queue */
    if (!emergency)
      fReloadFeedQueue.cancel(false);

    /* Stop the Cache-Service */
    if (!InternalOwl.TESTING && !emergency)
      fCacheService.stopService();

    /* Cancel the feed-save queue (join) */
    if (!emergency)
      fSaveFeedQueue.cancel(true);

    /* Stop the News-Service */
    fNewsService.stopService();

    /* Stop the Notification Service */
    if (!InternalOwl.TESTING && !emergency)
      fNotificationService.stopService();

    /* Stop the Saved Search Service */
    if (!InternalOwl.TESTING && !emergency)
      fSavedSearchService.stopService();

    /* Shutdown ApplicationServer */
    if (!emergency)
      ApplicationServer.getDefault().shutdown();
  }

  /**
   * This method is called just after the windows have been opened.
   */
  public void postUIStartup() {

    /* Create the Feed-Reload Service */
    if (!InternalOwl.TESTING)
      fFeedReloadService = new FeedReloadService();
  }

  /**
   * Returns wether the application is in process of shutting down.
   *
   * @return <code>TRUE</code> if the application has been closed, and
   * <code>FALSE</code> otherwise.
   */
  public boolean isShuttingDown() {
    return fShuttingDown;
  }

  /**
   * This method is called immediately prior to workbench shutdown before any
   * windows have been closed.
   *
   * @return <code>true</code> to allow the workbench to proceed with
   * shutdown, <code>false</code> to veto a non-forced shutdown
   */
  public boolean preUIShutdown() {
    fShuttingDown = true;

    return true;
  }

  private void onFirstStartup() throws PersistenceException, InterpreterException, ParserException {

    /* Add Default Labels */
    List<ILabel> labels = addDefaultLabels();

    /* Import Default Marks */
    importDefaults(labels);

    /* Remember this */
    fgFirstStartup = true;
  }

  private List<ILabel> addDefaultLabels() throws PersistenceException {
    List<ILabel> labels = new ArrayList<ILabel>();

    ILabel label = fFactory.createLabel(null, "Important");
    label.setColor("159,63,63");
    labels.add(label);
    fLabelDao.save(label);

    label = fFactory.createLabel(null, "Work");
    label.setColor("255,153,0");
    labels.add(label);
    fLabelDao.save(label);

    label = fFactory.createLabel(null, "Personal");
    label.setColor("0,153,0");
    labels.add(label);
    fLabelDao.save(label);

    label = fFactory.createLabel(null, "To Do");
    label.setColor("51,51,255");
    labels.add(label);
    fLabelDao.save(label);

    label = fFactory.createLabel(null, "Later");
    label.setColor("151,53,151");
    labels.add(label);
    fLabelDao.save(label);

    return labels;
  }

  private void importDefaults(List<ILabel> labels) throws PersistenceException, InterpreterException, ParserException {

    /* Import Default Feeds */
    InputStream inS = getClass().getResourceAsStream("/default_feeds.xml"); //$NON-NLS-1$;
    List<? extends IEntity> types = Owl.getInterpreter().importFrom(inS);
    IFolder imported = (IFolder) types.get(0);
    imported.setName("Default"); //$NON-NLS-1$

    /* Create Default SearchMarks */
    String newsEntityName = INews.class.getName();

    /* SearchCondition: New and Updated News */
    {
      ISearchMark mark = fFactory.createSearchMark(null, imported, "New and Updated News");
      mark.setMatchAllConditions(true);

      ISearchField field1 = fFactory.createSearchField(INews.STATE, newsEntityName);
      fFactory.createSearchCondition(null, mark, field1, SearchSpecifier.IS, EnumSet.of(INews.State.NEW, INews.State.UPDATED));
    }

    /* SearchCondition: Recent News */
    {
      ISearchMark mark = fFactory.createSearchMark(null, imported, "Recent News");
      mark.setMatchAllConditions(true);

      ISearchField field1 = fFactory.createSearchField(INews.AGE_IN_DAYS, newsEntityName);
      fFactory.createSearchCondition(null, mark, field1, SearchSpecifier.IS_LESS_THAN, 2);
    }

    /* SearchCondition: News with Attachments */
    {
      ISearchMark mark = fFactory.createSearchMark(null, imported, "News with Attachments");
      mark.setMatchAllConditions(true);

      ISearchField field = fFactory.createSearchField(INews.HAS_ATTACHMENTS, newsEntityName);
      fFactory.createSearchCondition(null, mark, field, SearchSpecifier.IS, true);
    }

    /* SearchCondition: Sticky News */
    {
      ISearchMark mark = fFactory.createSearchMark(null, imported, "Sticky News");
      mark.setMatchAllConditions(true);

      ISearchField field = fFactory.createSearchField(INews.IS_FLAGGED, newsEntityName);
      fFactory.createSearchCondition(null, mark, field, SearchSpecifier.IS, true);
    }

    /* SearchCondition: News is Labeld */
    {
      ISearchMark mark = fFactory.createSearchMark(null, imported, "Labeled News");
      IPreferenceScope preferences = Owl.getPreferenceService().getEntityScope(mark);
      preferences.putInteger(DefaultPreferences.BM_NEWS_GROUPING, NewsGrouping.Type.GROUP_BY_LABEL.ordinal());

      for (ILabel label : labels) {
        ISearchField field = fFactory.createSearchField(INews.LABEL, newsEntityName);
        fFactory.createSearchCondition(null, mark, field, SearchSpecifier.IS, label.getName());
      }
    }

    fFolderDAO.save(imported);
  }

  /**
   * TODO Temporary
   *
   * @param fileName
   * @throws FileNotFoundException In case of an error.
   * @throws ParserException In case of an error.
   * @throws InterpreterException In case of an error.
   */
  public void importFeeds(String fileName) throws FileNotFoundException, InterpreterException, ParserException {
    List<IEntity> entitiesToReload = new ArrayList<IEntity>();

    /* Import from File */
    File file = new File(fileName);
    InputStream inS = new FileInputStream(file);
    List<? extends IEntity> types = Owl.getInterpreter().importFrom(inS);
    IFolder defaultContainer = (IFolder) types.get(0);

    /* Map Old Id to IFolderChild */
    Map<Long, IFolderChild> mapOldIdToFolderChild = ImportUtils.createOldIdToEntityMap(types);

    /* Load SearchMarks containing location condition */
    List<ISearchMark> locationConditionSearches = ImportUtils.getLocationConditionSearches(types);

    /* Load the current selected Set */
    IFolder selectedRootFolder;
    if (!InternalOwl.TESTING) {
      String selectedBookMarkSetPref = BookMarkExplorer.getSelectedBookMarkSetPref(OwlUI.getWindow());
      Long selectedFolderID = fPrefsDAO.load(selectedBookMarkSetPref).getLong();
      selectedRootFolder = fFolderDAO.load(selectedFolderID);
    } else {
      Collection<IFolder> rootFolders = DynamicDAO.getDAO(IFolderDAO.class).loadRoots();
      selectedRootFolder = rootFolders.iterator().next();
    }

    /* Load all Root Folders */
    Set<IFolder> rootFolders;
    if (!InternalOwl.TESTING)
      rootFolders = fCacheService.getRootFolders();
    else
      rootFolders = new HashSet<IFolder>(DynamicDAO.getDAO(IFolderDAO.class).loadRoots());

    /* 1.) Handle Folders and Marks from default Container */
    {

      /* Also update Map of Old ID */
      if (defaultContainer.getProperty(ITypeImporter.ID_KEY) != null)
        mapOldIdToFolderChild.put((Long) defaultContainer.getProperty(ITypeImporter.ID_KEY), selectedRootFolder);

      /* Reparent and Save */
      reparentAndSaveChildren(defaultContainer, selectedRootFolder);
      entitiesToReload.addAll(defaultContainer.getChildren());
    }

    /* 2.) Handle other Sets */
    for (int i = 1; i < types.size(); i++) {
      IFolder setFolder = (IFolder) types.get(i);
      IFolder existingSetFolder = null;

      /* Check if set already exists */
      for (IFolder rootFolder : rootFolders) {
        if (rootFolder.getName().equals(setFolder.getName())) {
          existingSetFolder = rootFolder;
          break;
        }
      }

      /* Reparent into Existing Set */
      if (existingSetFolder != null) {

        /* Also update Map of Old ID */
        if (setFolder.getProperty(ITypeImporter.ID_KEY) != null)
          mapOldIdToFolderChild.put((Long) setFolder.getProperty(ITypeImporter.ID_KEY), existingSetFolder);

        /* Reparent and Save */
        reparentAndSaveChildren(setFolder, existingSetFolder);
        entitiesToReload.addAll(existingSetFolder.getChildren());
      }

      /* Otherwise save as new Set */
      else {

        /* Unset ID Property first */
        ImportUtils.unsetIdProperty(setFolder);

        /* Save */
        fFolderDAO.save(setFolder);
        entitiesToReload.add(setFolder);
      }
    }

    /* Fix locations in Search Marks if required and save */
    if (!locationConditionSearches.isEmpty()) {
      ImportUtils.updateLocationConditions(mapOldIdToFolderChild, locationConditionSearches);
      DynamicDAO.getDAO(ISearchMarkDAO.class).saveAll(locationConditionSearches);
    }

    /* Reload imported Feeds */
    if (!InternalOwl.TESTING)
      new ReloadTypesAction(new StructuredSelection(entitiesToReload), OwlUI.getPrimaryShell()).run();
  }

  private void reparentAndSaveChildren(IFolder from, IFolder to) {
    boolean changed = false;

    /* Reparent all imported folders into selected Set */
    List<IFolder> folders = from.getFolders();
    for (IFolder folder : folders) {
      folder.setParent(to);
      to.addFolder(folder, null, null);
      changed = true;
    }

    /* Reparent all imported marks into selected Set */
    List<IMark> marks = from.getMarks();
    for (IMark mark : marks) {
      mark.setParent(to);
      to.addMark(mark, null, null);
      changed = true;
    }

    /* Save Set */
    if (changed)
      fFolderDAO.save(to);
  }

  private IStatus createWarningStatus(IStatus status, IBookMark bookmark, URI feedLink) {
    StringBuilder msg = new StringBuilder();
    msg.append("Error loading '").append(bookmark.getName()).append("'");

    if (StringUtils.isSet(status.getMessage()))
      msg.append("\nProblem: ").append(status.getMessage());

    msg.append("\nLink: ").append(feedLink);

    return new Status(IStatus.WARNING, status.getPlugin(), status.getCode(), msg.toString(), null);
  }
}
TOP

Related Classes of org.rssowl.ui.internal.Controller

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.