package org.vaadin.touchkit.gwt.client.offlinemode;
import java.util.logging.Logger;
import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Element;
import com.google.gwt.storage.client.Storage;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.RootPanel;
import com.vaadin.client.BrowserInfo;
/**
* This is a simple application cache monitor. It also notifies demo users when
* offline mode is ready to be used.
*/
public class CacheManifestStatusIndicator implements EntryPoint {
public static final String UPDATE_NOW_MSG_KEY = "updateNowMessage";
public static final String UPDATE_CHECK_INTERVAL_KEY = "updateCheckInterval";
private static final int UNCACHED = 0;
private static final int IDLE = 1;
private static final int CHECKING = 2;
private static final int DOWNLOADING = 3;
private static final int UPDATEREADY = 4;
private static final int OBSOLETE = 5;
private Element progressElement;
private String updateNowMessage = "There are updates ready to be installed. Would you like to restart now?";
private static boolean updating;
// Check for updates once every 30 min by default.
// TODO(manolo): should be configurable via offline connector
private int updateCheckInterval = 1800000;
private static final Logger logger = Logger.getLogger(CacheManifestStatusIndicator.class.getName());
private static boolean confirmationRequired = true;
@Override
public void onModuleLoad() {
// Do nothing if document has no manifest.
if (hasManifest()) {
init();
}
}
/**
* Let the indicator ask the user to reload the application
* when a new version of the app has been downloaded.
*/
public static void setConfirmationRequired(boolean b) {
confirmationRequired = b;
}
/**
* return true if we are downloading a new version of the app.
*/
public static boolean isUpdating() {
return updating || getStatus() == CHECKING || getStatus() == DOWNLOADING;
}
/**
* Initializes and starts the monitoring.
*/
public void init() {
loadSettingsFromLocalStorage();
hookAllListeners(this);
scheduleUpdateChecker();
if (getStatus() == CHECKING || getStatus() == DOWNLOADING) {
showProgress();
}
// Sometimes android leaves the status indicator spinning and spinning
// and spinning...
pollForStatusOnAndroid();
}
private void pollForStatusOnAndroid() {
if (BrowserInfo.get().isAndroid()) {
Scheduler.get().scheduleFixedPeriod(
new Scheduler.RepeatingCommand() {
@Override
public boolean execute() {
if (updating) {
// The normal listeners are working correctly
return false;
}
switch (getStatus()) {
case IDLE:
hideProgress();
return false;
case UPDATEREADY:
requestUpdate();
return false;
default:
return true;
}
}
}, 500);
}
}
/**
* Loads the configurable settings from localstorage. The settings are
* "updateNowMessage" for specifying the message to show when a new version
* of the cache is ready, and "updateCheckInterval" for specifying how often
* to check for new versions.
*/
private void loadSettingsFromLocalStorage() {
Storage localStorage = Storage.getLocalStorageIfSupported();
if (localStorage != null) {
String newMessage = localStorage.getItem(UPDATE_NOW_MSG_KEY);
if (newMessage != null && !newMessage.isEmpty()) {
updateNowMessage = newMessage;
}
String updateCheckIntervalStr = localStorage
.getItem(UPDATE_CHECK_INTERVAL_KEY);
if (updateCheckIntervalStr != null
&& !updateCheckIntervalStr.isEmpty()) {
// The value in local storage is in seconds, but we need
// milliseconds.
updateCheckInterval = Integer.valueOf(updateCheckIntervalStr) * 1000;
}
}
}
/**
* Check for updates to the application cache every 30 minutes
*/
private void scheduleUpdateChecker() {
Scheduler.get().scheduleFixedPeriod(new Scheduler.RepeatingCommand() {
@Override
public boolean execute() {
// Don't try to update cache if already updating or app is
// paused
if (!isUpdating()
&& OfflineModeEntrypoint.get().getNetworkStatus()
.isAppRunning()) {
updateCache();
}
return true;
}
}, updateCheckInterval);
}
/**
* Called when a cache event is triggered. All events except for error
* events are handled here.
*
* @param event
* The event.
*/
protected void onCacheEvent(Event event) {
// consoleLog(event.getType());
if ("cached".equals(event.getType())) {
hideProgress();
} else if ("checking".equals(event.getType())) {
showProgress();
} else if ("downloading".equals(event.getType())) {
updating = true;
showProgress();
} else if ("noupdate".equals(event.getType())) {
hideProgress();
} else if ("updateready".equals(event.getType())) {
hideProgress();
requestUpdate();
updating = false;
}
}
/**
* Called when an error event is triggered.
*
* @param event
* The error event.
*/
protected void onError(Event event) {
logger.severe("An error occurred");
}
/**
* Shows the progress element, which, by default, is styled to be a small
* animated spinner in the top right corner of the screen.
*/
protected void showProgress() {
if (progressElement == null) {
progressElement = Document.get().createDivElement();
progressElement.addClassName("v-cache-loading-indicator");
}
RootPanel.getBodyElement().appendChild(progressElement);
}
/**
* Hides the progress element.
*/
protected void hideProgress() {
if (progressElement != null) {
progressElement.removeFromParent();
}
}
/**
* Called when a new version of the application cache (i.e. the widgetset)
* has been detected. The default implementation asks the user if we should
* update now unless forced.
*
* @param force
* true to force reloading the site without asking the user.
*/
private void requestUpdate() {
logger.info("Application cache updated, confirmationRequired=" + confirmationRequired);
if (!confirmationRequired || Window.confirm(updateNowMessage)) {
Window.Location.reload();
}
}
/**
* Hooks all listeners to the specified instance.
*
* @param instance
* the instance to hook the listeners to.
*/
protected final native void hookAllListeners(
CacheManifestStatusIndicator instance)
/*-{
$wnd.applicationCache.addEventListener('cached',
function(event) {
instance.@org.vaadin.touchkit.gwt.client.offlinemode.CacheManifestStatusIndicator::onCacheEvent(Lcom/google/gwt/user/client/Event;)(event);
}, false);
$wnd.applicationCache.addEventListener('checking',
function(event) {
instance.@org.vaadin.touchkit.gwt.client.offlinemode.CacheManifestStatusIndicator::onCacheEvent(Lcom/google/gwt/user/client/Event;)(event);
}, false);
$wnd.applicationCache.addEventListener('downloading',
function(event) {
instance.@org.vaadin.touchkit.gwt.client.offlinemode.CacheManifestStatusIndicator::onCacheEvent(Lcom/google/gwt/user/client/Event;)(event);
}, false);
$wnd.applicationCache.addEventListener('noupdate',
function(event) {
instance.@org.vaadin.touchkit.gwt.client.offlinemode.CacheManifestStatusIndicator::onCacheEvent(Lcom/google/gwt/user/client/Event;)(event);
}, false);
$wnd.applicationCache.addEventListener('updateready',
function(event) {
instance.@org.vaadin.touchkit.gwt.client.offlinemode.CacheManifestStatusIndicator::onCacheEvent(Lcom/google/gwt/user/client/Event;)(event);
}, false);
$wnd.applicationCache.addEventListener('error',
function(event) {
instance.@org.vaadin.touchkit.gwt.client.offlinemode.CacheManifestStatusIndicator::onError(Lcom/google/gwt/user/client/Event;)(event);
}, false);
}-*/;
/**
* @return The status of the application cache. See the constants in this
* class for possible values.
*/
private static native int getStatus()
/*-{
return $wnd.applicationCache.status;
}-*/;
/**
* Asks the application cache to update itself, i.e. visit the server and
* check if there's an update available.
*/
protected static native void updateCache()
/*-{
return $wnd.applicationCache.update();
}-*/;
// Return true if the document has the manifest attribute
private static native boolean hasManifest()
/*-{
return $doc.documentElement.hasAttribute('manifest');
}-*/;
}