/* * Copyright 2013 cruxframework.org. * * 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.cruxframework.crux.core.clientoffline; import java.util.logging.Level; import java.util.logging.Logger; import org.cruxframework.crux.core.client.collection.FastList; import org.cruxframework.crux.core.client.screen.Screen; import com.google.gwt.core.client.EntryPoint; import com.google.gwt.core.client.GWT; import com.google.gwt.core.client.Scheduler; import com.google.gwt.core.client.Scheduler.ScheduledCommand; import com.google.gwt.dom.client.PartialSupport; import com.google.gwt.event.shared.HandlerRegistration; import com.google.gwt.logging.client.LogConfiguration; /** * @author Thiago da Rosa de Bustamante * */ @PartialSupport public class ApplicationCacheHandler implements EntryPoint { private static Logger logger = Logger.getLogger(ApplicationCacheHandler.class.getName()); public static final int UNCACHED = 0; public static final int IDLE = 1; public static final int CHECKING = 2; public static final int DOWNLOADING = 3; public static final int UPDATEREADY = 4; public static final int OBSOLETE = 5; public static enum CacheEvent {onCached, onChecking, onDownloading, onNoupdate, onUpdateready, onProgress, onObsolete, onError} private OfflineMessages messages = GWT.create(OfflineMessages.class); private OfflineConstants constants = GWT.create(OfflineConstants.class); private ApplicationCacheUIHandler uiHandler = GWT.create(ApplicationCacheUIHandler.class); private static boolean updating = false; private boolean obsolete = false; private static FastList<ApplicationCacheEvent.Handler> cacheEventHandlers = null; private Network network; public static HandlerRegistration addApplicationCacheHandler(final ApplicationCacheEvent.Handler handler) { if (LogConfiguration.loggingIsEnabled()) { logger.log(Level.FINE, "Adding a new applicationCache event handler."); } if (cacheEventHandlers == null) { cacheEventHandlers = new FastList<ApplicationCacheEvent.Handler>(); } cacheEventHandlers.add(handler); return new HandlerRegistration() { @Override public void removeHandler() { int index = cacheEventHandlers.indexOf(handler); if (index >= 0) { cacheEventHandlers.remove(index); } } }; } /** * Initializes and starts the monitoring. */ public void onModuleLoad() { if(!isSupported() || !GWT.isProdMode()) { return; } if (LogConfiguration.loggingIsEnabled()) { logger.log(Level.FINE, "Starting application cache handler."); } hookAllListeners(this); scheduleUpdateChecker(); if (getStatus() == DOWNLOADING) { onDownloading(); } network = Network.get(); // initializes network monitor... // Sometimes android leaves the status indicator spinning and spinning // and spinning... pollForStatusOnAndroid(); } /** * @return The status of the application cache. */ private static native int getStatus()/*-{ return $wnd.applicationCache.status; }-*/; /** * Asks the application cache to update itself. */ public static void updateCache() { try { if (!updating) { updateCacheNative(); } } catch (Exception e) { if (LogConfiguration.loggingIsEnabled()) { logger.log(Level.SEVERE, "Error updating cache.", e); } } } /** * Asks the application cache to update itself. */ public static native void updateCacheNative()/*-{ $wnd.applicationCache.update(); }-*/; public static native void swapCache()/*-{ $wnd.applicationCache.swapCache(); }-*/; private void fireApplicationCacheEvent(CacheEvent eventType) { if (cacheEventHandlers != null) { ApplicationCacheEvent event = new ApplicationCacheEvent(network, eventType); for (int i=0; i< cacheEventHandlers.size(); i++) { cacheEventHandlers.get(i).onCacheEvent(event); } } } private void pollForStatusOnAndroid() { if (Screen.isAndroid()) { Scheduler.get().scheduleFixedDelay(new Scheduler.RepeatingCommand() { @Override public boolean execute() { if (updating) { // The normal listeners are working correctly return false; } switch (getStatus()) { case IDLE: uiHandler.hideMessage(); return false; case UPDATEREADY: requestUpdate(false); return false; default: return true; } } }, 500); } } /** * Check for updates to the application cache every 30 minutes */ private void scheduleUpdateChecker() { Scheduler.get().scheduleFixedDelay(new Scheduler.RepeatingCommand() { @Override public boolean execute() { if (obsolete) { if (LogConfiguration.loggingIsEnabled()) { logger.log(Level.INFO, "Cache was obsolete. Swapping cache."); } swapCache(); } else { if (LogConfiguration.loggingIsEnabled()) { logger.log(Level.INFO, "Trying to update cache..."); } updateCache(); } return true; } }, constants.updateCheckInterval()); } /** * Called when a cached event is triggered * * @param event The event. */ protected void onCached() { if (LogConfiguration.loggingIsEnabled()) { logger.log(Level.INFO, "Resources cached."); } uiHandler.hideMessage(); updating = false; fireApplicationCacheEvent(CacheEvent.onCached); } /** * Called when a checking event is triggered * * @param event The event. */ protected void onChecking() { if (LogConfiguration.loggingIsEnabled()) { logger.log(Level.INFO, messages.checkingResources()); } fireApplicationCacheEvent(CacheEvent.onChecking); } /** * Called when a downloading event is triggered * * @param event The event. */ protected void onDownloading() { if (LogConfiguration.loggingIsEnabled()) { logger.log(Level.INFO, messages.downloadingResources()); } updating = true; fireApplicationCacheEvent(CacheEvent.onDownloading); } /** * Called when a noupdate event is triggered * * @param event The event. */ protected void onNoUpdate() { if (LogConfiguration.loggingIsEnabled()) { logger.log(Level.INFO, "No updates found"); } uiHandler.hideMessage(); updating = false; fireApplicationCacheEvent(CacheEvent.onNoupdate); } /** * Called when a update ready event is triggered * * @param event The event. */ protected void onUpdateReady() { uiHandler.hideMessage(); updating = false; fireApplicationCacheEvent(CacheEvent.onUpdateready); ApplicationCacheHandler.swapCache(); Scheduler.get().scheduleDeferred(new ScheduledCommand() { @Override public void execute() { requestUpdate(false); } }); } /** * Called when a progress event is triggered * * @param event The event. */ protected void onProgress(ProgressEvent event) { if (LogConfiguration.loggingIsEnabled()) { logger.log(Level.INFO, messages.progressStatus(event.getLoaded(), event.getTotal())); } uiHandler.showMessage(messages.progressStatus(event.getLoaded(), event.getTotal())); fireApplicationCacheEvent(CacheEvent.onProgress); } /** * Called when an error event is triggered. * * @param event The error event. */ protected void onError() { if (LogConfiguration.loggingIsEnabled()) { logger.log(Level.INFO, messages.applicationCacheError()); } uiHandler.hideMessage(); updating = false; fireApplicationCacheEvent(CacheEvent.onError); } /** * Called when an error event is triggered. * * @param event The error event. */ protected void onObsolete() { if (LogConfiguration.loggingIsEnabled()) { logger.log(Level.INFO, messages.applicationCacheObsolete()); } this.obsolete = true; updating = false; uiHandler.showMessage(messages.applicationCacheObsolete()); fireApplicationCacheEvent(CacheEvent.onObsolete); } /** * Called when a new version of the application cache * has been detected. Asks the user if we should * update now unless forced. * * @param force true to force reloading the site without asking the user. */ protected void requestUpdate(boolean force) { if (LogConfiguration.loggingIsEnabled()) { logger.log(Level.INFO, "New updates available. Requesting permission to update."); } if (force) { Screen.reload(); } else { uiHandler.confirmReloadPage(); } } /** * * @return */ public static native boolean isSupported()/*-{ if($wnd.applicationCache) { return true; } return false; }-*/; /** * Hooks all listeners to the specified instance. * * @param instance * the instance to hook the listeners to. */ protected final native void hookAllListeners(ApplicationCacheHandler instance)/*-{ $wnd.applicationCache.addEventListener('cached', function(event) { instance.@org.cruxframework.crux.core.clientoffline.ApplicationCacheHandler::onCached()(); }, false); $wnd.applicationCache.addEventListener('checking', function(event) { instance.@org.cruxframework.crux.core.clientoffline.ApplicationCacheHandler::onChecking()(); }, false); $wnd.applicationCache.addEventListener('downloading', function(event) { instance.@org.cruxframework.crux.core.clientoffline.ApplicationCacheHandler::onDownloading()(); }, false); $wnd.applicationCache.addEventListener('noupdate', function(event) { instance.@org.cruxframework.crux.core.clientoffline.ApplicationCacheHandler::onNoUpdate()(); }, false); $wnd.applicationCache.addEventListener('updateready', function(event) { instance.@org.cruxframework.crux.core.clientoffline.ApplicationCacheHandler::onUpdateReady()(); }, false); $wnd.applicationCache.addEventListener('progress', function(event) { instance.@org.cruxframework.crux.core.clientoffline.ApplicationCacheHandler::onProgress(Lorg/cruxframework/crux/core/clientoffline/ProgressEvent;)(event); }, false); $wnd.applicationCache.addEventListener('obsolete', function(event) { instance.@org.cruxframework.crux.core.clientoffline.ApplicationCacheHandler::onObsolete()(); }, false); $wnd.applicationCache.addEventListener('error', function(event) { instance.@org.cruxframework.crux.core.clientoffline.ApplicationCacheHandler::onError()(); }, false); }-*/; }