/* * Copyright (c) 2010 Lockheed Martin Corporation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.eurekastreams.web.client.ui; import java.util.ArrayList; import java.util.Date; import java.util.Iterator; import java.util.List; import org.eurekastreams.commons.client.ActionProcessor; import com.google.gwt.user.client.Timer; /** * Allows periodic events to be scheduled and run in batches, thus allowing messages to be sent in bundles. */ public class PeriodicEventManager { /** Obvious to everyone but checkstyle. */ private static final long MS_PER_S = 1000; /** Period of the internal timer in ms. */ private static final int POLL_PERIOD = 5 * (int) MS_PER_S; /** Amount of time in ms without user input to consider the app idle. */ private long idleTimeout; /** At what time the system will be considered idle without intervening user input. */ private Date idleTime; /** Internal timer. */ private Timer timer; /** If system is idle. */ private boolean idle; /** User activity detected since last poll. */ private boolean activity; /** Action processor (for batching requests). */ private ActionProcessor actionProcessor; /** Periodic processing. */ private TimerHandler timerHandler = new TimerHandler() { public void run() { Date now = timeNow(); // shut off activity if app is idle if (activity) { idleTime = new Date(now.getTime() + idleTimeout); activity = false; } else if (now.compareTo(idleTime) >= 0) { idle = true; timer.cancel(); } // invoke actions if (!idle) { boolean any = false; for (Registration reg : registrations) { if (now.compareTo(reg.getNextTime()) >= 0) { if (!any) { any = true; // Freeze queue actionProcessor.setQueueRequests(true); } // invoke action reg.getHandler().run(); // update next time reg.computeNextTime(now); } } if (any) { // Flush and unfreeze queue actionProcessor.fireQueuedRequests(); actionProcessor.setQueueRequests(false); } } } }; /** List of registrations. */ private List<Registration> registrations = new ArrayList<Registration>(); /** * Constructor. * * @param inIdleTimeout * Idle timeout in seconds. * @param inTimerFactory * For creating the timer. * @param inActionProcessor * Action processor. */ public PeriodicEventManager(final long inIdleTimeout, final TimerFactory inTimerFactory, final ActionProcessor inActionProcessor) { idleTimeout = inIdleTimeout * MS_PER_S; timer = inTimerFactory.createTimer(timerHandler); actionProcessor = inActionProcessor; } /** * Registers an action to occur on a periodic basis. * * @param handler * Handler that will perform the action. * @param period * Period in seconds. */ public void register(final TimerHandler handler, final int period) { Registration reg = new Registration(handler, period); reg.computeNextTime(timeNow()); registrations.add(reg); } /** * Removes an action from being executed. * * @param handler * Handler that will perform the action. */ public void deregister(final TimerHandler handler) { Iterator<Registration> iter = registrations.iterator(); while (iter.hasNext()) { Registration reg = iter.next(); if (reg.getHandler().equals(handler)) { iter.remove(); } } } /** * Begins processing. */ public void start() { Date now = timeNow(); for (Registration reg : registrations) { reg.computeNextTime(now); } idleTime = new Date(now.getTime() + idleTimeout); startTimer(); } /** * Invoked by the app when user input is detected to indicate the app is not idle. Direct method call for * efficiency, since this could be called very often. */ public void userActivityDetected() { activity = true; if (idle) { startTimer(); } } /** * Starts the timer running. */ private void startTimer() { idle = false; timer.scheduleRepeating(POLL_PERIOD); } /** * Seam for testing. * * @return Current time. */ protected Date timeNow() { return new Date(); } /** * Tracks registered actions. */ private class Registration { /** Action. */ private TimerHandler handler; /** Next time to invoke action. */ private Date nextTime; /** Period (in seconds). */ private int period; /** * Constructor. * * @param inHandler * The action. * @param inPeriod * Period. */ public Registration(final TimerHandler inHandler, final int inPeriod) { handler = inHandler; period = inPeriod; } /** * @return the nextTime */ public Date getNextTime() { return nextTime; } /** * @param inNextTime * the nextTime to set */ public void setNextTime(final Date inNextTime) { nextTime = inNextTime; } /** * Computes the next time the action should be invoked. * * @param now * Current time. */ public void computeNextTime(final Date now) { nextTime = new Date(now.getTime() + MS_PER_S * period); } /** * @return the handler */ public TimerHandler getHandler() { return handler; } /** * @return the period */ public int getPeriod() { return period; } } }