Package org.rssowl.ui.internal.services

Source Code of org.rssowl.ui.internal.services.SavedSearchService

/*   **********************************************************************  **
**   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.services;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.SafeRunner;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.rssowl.core.Owl;
import org.rssowl.core.internal.InternalOwl;
import org.rssowl.core.persist.IBookMark;
import org.rssowl.core.persist.IFolder;
import org.rssowl.core.persist.INews;
import org.rssowl.core.persist.INews.State;
import org.rssowl.core.persist.INewsBin;
import org.rssowl.core.persist.ISearchMark;
import org.rssowl.core.persist.dao.DynamicDAO;
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.event.FolderAdapter;
import org.rssowl.core.persist.event.FolderEvent;
import org.rssowl.core.persist.event.NewsBinAdapter;
import org.rssowl.core.persist.event.NewsBinEvent;
import org.rssowl.core.persist.event.SearchMarkEvent;
import org.rssowl.core.persist.reference.NewsReference;
import org.rssowl.core.persist.service.IModelSearch;
import org.rssowl.core.persist.service.IndexListener;
import org.rssowl.core.util.LoggingSafeRunnable;
import org.rssowl.core.util.Pair;
import org.rssowl.core.util.SearchHit;
import org.rssowl.ui.internal.Controller;

import java.util.ArrayList;
import java.util.Collection;
import java.util.EnumMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;

/**
* The <code>SavedSearchService</code> is responsible to listen for updates to
* the search-index and updating all <code>ISearchMark</code>s as a result to
* that event in order to reflect changing search results in the UI.
*
* @author bpasero
*/
public class SavedSearchService {

  /* Time in millies before updating the saved searches (long) */
  private static final int BATCH_INTERVAL_LONG = 1000;

  /* Time in millies before updating the saved searches (short) */
  private static final int BATCH_INTERVAL_SHORT = 100;

  /* Number of updated documents before using the long batch interval */
  private static final int SHORT_THRESHOLD = 1;

  private final Job fBatchJob;
  private final AtomicBoolean fBatchInProcess = new AtomicBoolean(false);
  private final AtomicBoolean fUpdatedOnce = new AtomicBoolean(false);
  private final AtomicBoolean fForceQuickUpdate = new AtomicBoolean(false);
  private IndexListener fIndexListener;
  private BookMarkAdapter fBookmarkListener;
  private NewsBinAdapter fNewsBinListener;
  private FolderAdapter fFolderListener;

  /** Creates and Starts this Service */
  public SavedSearchService() {
    fBatchJob = createBatchJob();
    registerListeners();
  }

  private Job createBatchJob() {
    Job job = new Job("") { //$NON-NLS-1$
      @Override
      protected IStatus run(IProgressMonitor monitor) {
        fBatchInProcess.set(false);
        fForceQuickUpdate.set(false);

        /* Update all saved searches */
        SafeRunner.run(new LoggingSafeRunnable() {
          public void run() throws Exception {
            if (!Controller.getDefault().isShuttingDown())
              updateSavedSearches(true);
          }
        });

        return Status.OK_STATUS;
      }
    };

    job.setSystem(true);
    job.setUser(false);

    return job;
  }

  private void registerListeners() {

    /* Index Listener */
    fIndexListener = new IndexListener() {
      public void indexUpdated(int entitiesCount) {
        updateSavedSearchesFromEvent(entitiesCount);
      }
    };

    Owl.getPersistenceService().getModelSearch().addIndexListener(fIndexListener);

    /* Bookmark Listener: Update on Reparent */
    fBookmarkListener = new BookMarkAdapter() {
      @Override
      public void entitiesUpdated(Set<BookMarkEvent> events) {
        for (BookMarkEvent event : events) {
          if (event.isRoot()) {
            IFolder oldParent = event.getOldParent();
            IFolder parent = event.getEntity().getParent();

            if (oldParent != null && !oldParent.equals(parent)) {
              updateSavedSearchesFromEvent(1);
              break;
            }
          }
        }
      }
    };

    DynamicDAO.addEntityListener(IBookMark.class, fBookmarkListener);

    /* News Bin Listener: Update on Reparent */
    fNewsBinListener = new NewsBinAdapter() {
      @Override
      public void entitiesUpdated(Set<NewsBinEvent> events) {
        for (NewsBinEvent event : events) {
          if (event.isRoot()) {
            IFolder oldParent = event.getOldParent();
            IFolder parent = event.getEntity().getParent();

            if (oldParent != null && !oldParent.equals(parent)) {
              updateSavedSearchesFromEvent(1);
              break;
            }
          }
        }
      }
    };

    DynamicDAO.addEntityListener(INewsBin.class, fNewsBinListener);

    /* Folder Listener: Update on Reparent */
    fFolderListener = new FolderAdapter() {
      @Override
      public void entitiesUpdated(Set<FolderEvent> events) {
        for (FolderEvent event : events) {
          if (event.isRoot()) {
            IFolder oldParent = event.getOldParent();
            IFolder parent = event.getEntity().getParent();

            if (oldParent != null && !oldParent.equals(parent)) {
              updateSavedSearchesFromEvent(1);
              break;
            }
          }
        }
      }
    };

    DynamicDAO.addEntityListener(IFolder.class, fFolderListener);
  }

  private void updateSavedSearchesFromEvent(int entitiesCount) {
    if (!Controller.getDefault().isShuttingDown()) {
      if (!InternalOwl.TESTING)
        onIndexUpdated(entitiesCount);
      else
        updateSavedSearches(true);
    }
  }

  private void unregisterListeners() {
    Owl.getPersistenceService().getModelSearch().removeIndexListener(fIndexListener);
    DynamicDAO.removeEntityListener(IBookMark.class, fBookmarkListener);
    DynamicDAO.removeEntityListener(INewsBin.class, fNewsBinListener);
    DynamicDAO.removeEntityListener(IFolder.class, fFolderListener);
  }

  private void onIndexUpdated(int entitiesCount) {

    /* Start a new Batch if one is not in progress */
    if (!fBatchInProcess.getAndSet(true)) {
      fBatchJob.schedule((entitiesCount <= SHORT_THRESHOLD || fForceQuickUpdate.get()) ? BATCH_INTERVAL_SHORT : BATCH_INTERVAL_LONG);
      return;
    }
  }

  /**
   * Tells this Service to rapidly update all saved searches when the next
   * indexing is done. This can be called after an atomic operation (e.g.
   * Marking some News as read) to force a quick update on all saved searches.
   */
  public void forceQuickUpdate() {
    fForceQuickUpdate.set(true);
  }

  /**
   * Update the results of all <code>ISearchMark</code>s stored in RSSOwl.
   *
   * @param force If set to <code>TRUE</code>, update saved searches even if
   * done before.
   */
  public void updateSavedSearches(boolean force) {
    if (!force && fUpdatedOnce.get())
      return;

    Collection<ISearchMark> searchMarks = DynamicDAO.loadAll(ISearchMark.class);
    updateSavedSearches(searchMarks);
  }

  /**
   * @param searchMarks The Set of <code>ISearchMark</code> to update the
   * results in.
   */
  public void updateSavedSearches(Collection<ISearchMark> searchMarks) {
    updateSavedSearches(searchMarks, false);
  }

  /**
   * @param searchMarks The Set of <code>ISearchMark</code> to update the
   * results in.
   * @param fromUserEvent Indicates whether to update the saved searches due to
   * a user initiated event or an automatic one.
   */
  public void updateSavedSearches(Collection<ISearchMark> searchMarks, boolean fromUserEvent) {
    boolean firstUpdate = !fUpdatedOnce.get();

    fUpdatedOnce.set(true);
    IModelSearch modelSearch = Owl.getPersistenceService().getModelSearch();
    Set<SearchMarkEvent> events = new HashSet<SearchMarkEvent>(searchMarks.size());

    /* For each Search Mark */
    for (ISearchMark searchMark : searchMarks) {

      /* Return early if shutting down */
      if (Controller.getDefault().isShuttingDown())
        return;

      /* Execute the search */
      List<SearchHit<NewsReference>> results = modelSearch.searchNews(searchMark.getSearchConditions(), searchMark.matchAllConditions());

      /* Fill Result into Map Buckets */
      Map<INews.State, List<NewsReference>> resultsMap = new EnumMap<INews.State, List<NewsReference>>(INews.State.class);

      Set<State> visibleStates = INews.State.getVisible();
      for (SearchHit<NewsReference> searchHit : results) {

        /* Return early if shutting down */
        if (Controller.getDefault().isShuttingDown())
          return;

        INews.State state = (State) searchHit.getData(INews.STATE);
        if (visibleStates.contains(state)) {
          List<NewsReference> newsRefs = resultsMap.get(state);
          if (newsRefs == null) {
            newsRefs = new ArrayList<NewsReference>(results.size() / 3);
            resultsMap.put(state, newsRefs);
          }
          newsRefs.add(searchHit.getResult());
        }
      }

      /* Set Result */
      Pair<Boolean, Boolean> result = searchMark.setNewsRefs(resultsMap);
      boolean changed = result.getFirst();
      boolean newNewsAdded = result.getSecond();

      /* Create Event to indicate changed results if any */
      if (changed)
        events.add(new SearchMarkEvent(searchMark, null, true, !firstUpdate && !fromUserEvent && newNewsAdded));
    }

    /* Notify Listeners */
    if (!events.isEmpty() && !Controller.getDefault().isShuttingDown())
      DynamicDAO.getDAO(ISearchMarkDAO.class).fireNewsChanged(events);
  }

  /** Stops this service and unregisters any listeners added. */
  public void stopService() {
    unregisterListeners();
  }
}
TOP

Related Classes of org.rssowl.ui.internal.services.SavedSearchService

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.