Package org.rssowl.ui.internal

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

/*   **********************************************************************  **
**   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.IProgressMonitor;
import org.eclipse.jface.dialogs.ProgressMonitorDialog;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.rssowl.core.persist.IFeed;
import org.rssowl.core.persist.INews;
import org.rssowl.core.persist.NewsCounter;
import org.rssowl.core.persist.NewsCounterItem;
import org.rssowl.core.persist.dao.DynamicDAO;
import org.rssowl.core.persist.dao.INewsCounterDAO;
import org.rssowl.core.persist.event.FeedAdapter;
import org.rssowl.core.persist.event.FeedEvent;
import org.rssowl.core.persist.event.NewsEvent;
import org.rssowl.core.persist.event.NewsListener;
import org.rssowl.core.persist.reference.FeedLinkReference;
import org.rssowl.ui.internal.util.ModelUtils;

import java.lang.reflect.InvocationTargetException;
import java.net.URI;
import java.util.Collection;
import java.util.List;
import java.util.Set;

/**
* An internal Service helping Viewers to deal with the presentation of
* <code>INews</code>. Current implemented features:
* <ul>
* <li>Fast access to Unread-Count of News from a Feed</li>
* <li>Fast access to New-Count of News from a Feed</li>
* <li>Fast access to Sticky-Count of News from a Feed</li>
* </ul>
* <p>
* TODO Introduce a Listener that allows others to register on changes to one of
* the fields. This reduces possible errors in case some plugin manages to
* register as News-Listener right before this service.
* </p>
*
* @author bpasero
*/
public class NewsService {

  /* The Counter for various aspects of News, the key is the feed link */
  private NewsCounter fCounter;

  /* Delay before Progress is shown */
  private static final int SHOW_PROGRESS_THRESHOLD = 2000;

  private INewsCounterDAO fNewsCounterDao;

  /* Subclass of a Progress Monitor Dialog to show progress after a Crash */
  private static class NewsServiceProgressMonitorDialog extends ProgressMonitorDialog {
    NewsServiceProgressMonitorDialog(Shell parent) {
      super(parent);
    }

    /*
     * @see org.eclipse.jface.dialogs.Dialog#getInitialLocation(org.eclipse.swt.graphics.Point)
     */
    @Override
    protected Point getInitialLocation(Point initialSize) {
      Rectangle displayBounds = getParentShell().getDisplay().getPrimaryMonitor().getBounds();
      Point shellSize = getInitialSize();
      int x = displayBounds.x + (displayBounds.width - shellSize.x) >> 1;
      int y = displayBounds.y + (displayBounds.height - shellSize.y) >> 1;

      return new Point(x, y);
    }
  }

  NewsService() {
    fNewsCounterDao = DynamicDAO.getDAO(INewsCounterDAO.class);
    fCounter = loadCounter();
    registerListeners();
  }

  /**
   * Returns the number of unread News for the Feed referenced by
   * <code>feedLinkRef</code>.
   *
   * @param feedLinkRef The reference to the link of the Feed.
   * @return the number of unread News for the Feed having the given Id.
   */
  public int getUnreadCount(FeedLinkReference feedLinkRef) {
    synchronized (this) {
      NewsCounterItem counter = getFromCounter(feedLinkRef);

      /* Feed has no news */
      if (counter == null)
        return 0;

      return counter.getUnreadCounter();
    }
  }

  /**
   * Returns the number of new News for the Feed referenced by
   * <code>feedLinkRef</code>.
   *
   * @param feedLinkRef The reference to the link of the Feed.
   * @return the number of unread News for the Feed having the given link.
   */
  public int getNewCount(FeedLinkReference feedLinkRef) {
    synchronized (this) {
      NewsCounterItem counter = getFromCounter(feedLinkRef);

      /* Feed has no news */
      if (counter == null)
        return 0;

      return counter.getNewCounter();
    }
  }

  /**
   * Returns the number of sticky News for the Feed referenced by
   * <code>feedLinkRef</code>.
   *
   * @param feedLinkRef The reference to the link of the Feed.
   * @return the number of sticky News for the Feed having the given Id.
   */
  public int getStickyCount(FeedLinkReference feedLinkRef) {
    synchronized (this) {
      NewsCounterItem counter = getFromCounter(feedLinkRef);

      /* Feed has no news */
      if (counter == null)
        return 0;

      return counter.getStickyCounter();
    }
  }

  /**
   * Stops the News-Service and saves all data.
   */
  public void stopService() {
    synchronized (this) {
      saveState();
    }
  }

  /**
   * Method only used by Tests!
   */
  public void testDirtyShutdown() {
    synchronized (this) {
      fCounter = loadCounter();
    }
  }

  private synchronized NewsCounter loadCounter() {

    /* Load from DB */
    NewsCounter counter = fNewsCounterDao.load();

    /* Perform initial counting */
    if (counter == null)
      counter = countAll();

    /* Delete it to force recount on dirty shutdown */
    else
      fNewsCounterDao.delete();

    return counter;
  }

  private NewsCounter countAll() {
    final NewsCounter newsCounter = new NewsCounter();
    final long start = System.currentTimeMillis();
    final Collection<IFeed> feeds = DynamicDAO.loadAll(IFeed.class);

    final ProgressMonitorDialog dialog = new NewsServiceProgressMonitorDialog(new Shell(Display.getDefault(), SWT.NONE));
    dialog.setOpenOnRun(false);

    /* Runnable will open the Dialog after SHOW_PROGRESS_THRESHOLD ms */
    IRunnableWithProgress runnable = new IRunnableWithProgress() {
      private boolean fDialogOpened;

      public void run(IProgressMonitor monitor) {
        int worked = 0;
        for (IFeed feed : feeds) {
          newsCounter.put(feed.getLink(), count(feed));

          /* Open the Dialog if exceeded SHOW_PROGRESS_THRESHOLD ms */
          if (System.currentTimeMillis() - start > SHOW_PROGRESS_THRESHOLD && !fDialogOpened) {
            dialog.open();
            monitor.beginTask("RSSOwl was not shutdown properly. Restoring data, please wait...", feeds.size() - worked);
            fDialogOpened = true;
          }

          /* Worked a bit again... */
          if (fDialogOpened)
            monitor.worked(1);

          /* Remember the worked items */
          worked++;
        }

        /* Completed */
        monitor.done();
      }
    };

    /* Execute the Runnable */
    try {
      dialog.run(false, false, runnable);
    } catch (InvocationTargetException e) {
      Activator.getDefault().logError(e.getMessage(), e);
    } catch (InterruptedException e) {
      Activator.getDefault().logError(e.getMessage(), e);
    }

    return newsCounter;
  }

  private void putInCounter(FeedLinkReference feedRef, NewsCounterItem counterItem) {
    fCounter.put(feedRef.getLink(), counterItem);
  }

  private NewsCounterItem getFromCounter(FeedLinkReference feedRef) {
    return fCounter.get(feedRef.getLink());
  }

  private NewsCounterItem count(IFeed feed) {
    NewsCounterItem counterItem = new NewsCounterItem();

    List<INews> newsList = feed.getVisibleNews();
    for (INews news : newsList) {
      if (ModelUtils.isUnread(news.getState()))
        counterItem.incrementUnreadCounter();
      if (INews.State.NEW.equals(news.getState()))
        counterItem.incrementNewCounter();
      if (news.isFlagged())
        counterItem.incrementStickyCounter();
    }

    return counterItem;
  }

  private void saveState() {
    fNewsCounterDao.save(fCounter);
  }

  private void registerListeners() {
    DynamicDAO.addEntityListener(INews.class, new NewsListener() {
      public void entitiesAdded(Set<NewsEvent> events) {
        onNewsAdded(events);
      }

      public void entitiesUpdated(Set<NewsEvent> events) {
        onNewsUpdated(events);
      }

      public void entitiesDeleted(Set<NewsEvent> events) {
        onNewsDeleted(events);
      }
    });

    DynamicDAO.addEntityListener(IFeed.class, new FeedAdapter() {
      @Override
      public void entitiesDeleted(Set<FeedEvent> events) {
        onFeedDeleted(events);
      }
    });
  }

  private void onNewsAdded(Set<NewsEvent> events) {
    for (NewsEvent event : events) {
      INews news = event.getEntity();
      FeedLinkReference feedRef = news.getFeedReference();

      synchronized (this) {
        NewsCounterItem counter = getFromCounter(feedRef);

        /* Create Counter if not yet done */
        if (counter == null) {
          counter = new NewsCounterItem();
          putInCounter(feedRef, counter);
        }

        /* Update Counter */
        if (news.getState() == INews.State.NEW)
          counter.incrementNewCounter();
        if (ModelUtils.isUnread(news.getState()))
          counter.incrementUnreadCounter();
        if (news.isFlagged())
          counter.incrementStickyCounter();
      }
    }
  }

  private void onNewsDeleted(Set<NewsEvent> events) {
    for (NewsEvent event : events) {
      INews news = event.getEntity();

      synchronized (this) {
        NewsCounterItem counter = getFromCounter(news.getFeedReference());
        if (counter != null) {

          /* Update Counter */
          if (news.getState() == INews.State.NEW)
            counter.decrementNewCounter();
          if (ModelUtils.isUnread(news.getState()))
            counter.decrementUnreadCounter();
          if (news.isFlagged())
            counter.decrementStickyCounter();
        }
      }
    }
  }

  private void onNewsUpdated(Set<NewsEvent> events) {
    for (NewsEvent event : events) {
      INews currentNews = event.getEntity();
      INews oldNews = event.getOldNews();
      Assert.isNotNull(oldNews, "oldNews cannot be null on newsUpdated");
      FeedLinkReference feedRef = currentNews.getFeedReference();

      boolean oldStateUnread = ModelUtils.isUnread(oldNews.getState());
      boolean currentStateUnread = ModelUtils.isUnread(currentNews.getState());

      boolean oldStateNew = INews.State.NEW.equals(oldNews.getState());
      boolean currentStateNew = INews.State.NEW.equals(currentNews.getState());

      boolean oldStateSticky = oldNews.isFlagged();
      boolean newStateSticky = currentNews.isFlagged() && currentNews.isVisible();

      /* No Change - continue */
      if (oldStateUnread == currentStateUnread && oldStateNew == currentStateNew && oldStateSticky == newStateSticky)
        continue;

      synchronized (this) {
        NewsCounterItem counter = getFromCounter(feedRef);

        /* News became read */
        if (oldStateUnread && !currentStateUnread)
          counter.decrementUnreadCounter();

        /* News became unread */
        else if (!oldStateUnread && currentStateUnread)
          counter.incrementUnreadCounter();

        /* News no longer New */
        if (oldStateNew && !currentStateNew)
          counter.decrementNewCounter();

        /* News became New */
        else if (!oldStateNew && currentStateNew)
          counter.incrementNewCounter();

        /* News became unsticky */
        if (oldStateSticky && !newStateSticky)
          counter.decrementStickyCounter();

        /* News became sticky */
        else if (!oldStateSticky && newStateSticky)
          counter.incrementStickyCounter();
      }
    }
  }

  private void onFeedDeleted(Set<FeedEvent> events) {
    for (FeedEvent event : events) {
      URI feedLink = event.getEntity().getLink();
      synchronized (this) {
        removeFromCounter(feedLink);
      }
    }
  }

  private void removeFromCounter(URI feedLink) {
    fCounter.remove(feedLink);
  }
}
TOP

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

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.