com.bedatadriven.rebar.appcache.client.Html5AppCache.java Source code

Java tutorial

Introduction

Here is the source code for com.bedatadriven.rebar.appcache.client.Html5AppCache.java

Source

/*
 * Copyright 2009-2010 BeDataDriven (alex@bedatadriven.com)
 *
 * 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 com.bedatadriven.rebar.appcache.client;

import com.google.gwt.dom.client.Document;
import com.google.gwt.user.client.Cookies;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.rpc.AsyncCallback;

import java.util.Date;
import java.util.logging.Level;
import java.util.logging.Logger;

public class Html5AppCache extends AbstractAppCache {

    public static final String DISABLE_COOKIE_NAME = "HTML5_NO_APPCACHE";
    public static final String DISABLE_COOKIE_VALUE = "DISABLE";

    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 UPDATE_READY = 4;
    public static final int OBSOLETE = 5;
    public static final Status[] STATUS_MAPPING = new Status[] { Status.UNCACHED, Status.IDLE, Status.CHECKING,
            Status.DOWNLOADING, Status.UPDATE_READY, Status.OBSOLETE };
    private static final Logger LOGGER = Logger.getLogger(Html5AppCache.class.getName());
    private static final int TIMEOUT_MILLISECONDS = 60 * 1000;
    private int errorCount = 0;

    /**
     * @return true if the browser supports the AppCache API
     */
    public static native boolean isSupported() /*-{
                                               return typeof $wnd.applicationCache == 'object';
                                               }-*/;

    public static native int update() /*-{
                                      return $wnd.applicationCache.update();
                                      }-*/;

    public static native int getAppCacheStatus() /*-{
                                                 return $wnd.applicationCache.status;
                                                 }-*/;

    /**
     * @return true if the document has an AppCache manifest attached
     */
    public static boolean hasManifest() {
        return Document.get().getDocumentElement().hasAttribute("manifest");
    }

    private static native void sinkProgressEvent(Html5AppCache appCache) /*-{
                                                                         $wnd.applicationCache.onprogress = function(event) {
                                                                         appCache.@com.bedatadriven.rebar.appcache.client.AbstractAppCache::fireProgress(II)(event.loaded, event.total);
                                                                         }
                                                                         }-*/;

    private static native void sinkUpdateReadyEvent(Html5AppCache appCache) /*-{
                                                                            $wnd.applicationCache.onupdateready = function(event) {
                                                                            appCache.@com.bedatadriven.rebar.appcache.client.AbstractAppCache::fireUpdateReady()();
                                                                            }
                                                                            }-*/;

    private static native void sinkErrorEvent(Html5AppCache appCache) /*-{
                                                                      $wnd.applicationCache.onerror = function(event) {
                                                                      appCache.@com.bedatadriven.rebar.appcache.client.Html5AppCache::onError()();
                                                                      }
                                                                      }-*/;

    @Override
    public String getImplementation() {
        return "HTML5";
    }

    @Override
    public void ensureCached(final AsyncCallback<Void> callback) {

        Cookies.removeCookie(DISABLE_COOKIE_NAME);

        if (callbackIfCached(callback))
            return;

        // otherwise we need to wait until the download is complete.
        // unfortunately there doesn't seem to be a way to determine whether we're busy
        // downloading for the first time, or a new version
        new Timer() {
            @Override
            public void run() {
                if (callbackIfCached(callback)) {
                    this.cancel();
                }
            }
        }.scheduleRepeating(1000);
    }

    private boolean callbackIfCached(AsyncCallback<Void> callback) {
        int status = 0;
        try {
            status = getAppCacheStatus();
        } catch (Exception e) {
            callback.onFailure(new AppCacheException(e));
        }
        switch (status) {
        case IDLE:
        case UPDATE_READY:
            callback.onSuccess(null);
            return true;
        case UNCACHED:
            callback.onFailure(new AppCacheException(AppCacheErrorType.MISSING_MANIFEST));
            return true;
        case OBSOLETE:
            callback.onFailure(new AppCacheException(AppCacheErrorType.OBSOLETE));
            return true;
        }
        return false;
    }

    @Override
    public void ensureUpToDate(final AsyncCallback<Void> callback) {

        LOGGER.fine("ensureUpToDate starting...");

        Cookies.removeCookie(DISABLE_COOKIE_NAME);

        // an error retrieving the cache does not neccessarily
        // alter the status if it is just a network problem.
        // but we want to make sure that we have an answer so we
        // have to trap the errors ourselves
        sinkErrorEvent(this);
        final ErrorListener errors = new ErrorListener();

        LOGGER.fine("Status: " + statusDebugString());

        if (getAppCacheStatus() == IDLE || getAppCacheStatus() == UPDATE_READY) {

            try {
                LOGGER.fine("Calling update()");
                update();
            } catch (Exception e) {
                LOGGER.log(Level.SEVERE,
                        "Html5AppCache: call to update() threw exception. Current state = " + getAppCacheStatus(),
                        e);
                callback.onFailure(e);
                return;
            }
        }

        final long startTime = new Date().getTime();

        new Timer() {
            @Override
            public void run() {

                LOGGER.fine("Status: " + statusDebugString());

                switch (getAppCacheStatus()) {
                case IDLE:
                    if (errors.haveOccurred()) {
                        callback.onFailure(new AppCacheException(AppCacheErrorType.CONNECTION));
                    } else {
                        callback.onSuccess(null);
                    }
                    this.cancel();
                    break;

                case UPDATE_READY:
                    callback.onSuccess(null);
                    this.cancel();
                    break;
                case UNCACHED:
                    callback.onFailure(new AppCacheException(AppCacheErrorType.MISSING_MANIFEST));
                    this.cancel();
                    break;
                case OBSOLETE:
                    callback.onFailure(new AppCacheException(AppCacheErrorType.OBSOLETE));
                    this.cancel();
                    break;

                default:
                case CHECKING:
                case DOWNLOADING:
                    long runningTime = new Date().getTime() - startTime;
                    if (runningTime > TIMEOUT_MILLISECONDS) {
                        callback.onFailure(new AppCacheException(AppCacheErrorType.TIMEOUT));
                        this.cancel();
                    }
                }
            }
        }.scheduleRepeating(500);
    }

    private String statusDebugString() {
        int code;
        try {
            code = getAppCacheStatus();
        } catch (Exception e) {
            LOGGER.log(Level.SEVERE, "getAppCacheStatus() threw exception", e);
            return "EXCEPTION";
        }
        switch (code) {
        case IDLE:
            return "IDLE";
        case CHECKING:
            return "CHECKING";
        case UPDATE_READY:
            return "UPDATE_READY";
        case UNCACHED:
            return "UNCACHED";
        case OBSOLETE:
            return "OBSOLETE";
        }
        return "CODE:" + code;
    }

    @Override
    public void removeCache(final AsyncCallback<Void> callback) {
        int status = 0;
        try {
            status = getAppCacheStatus();

            if (status == UNCACHED || status == OBSOLETE) {
                callback.onSuccess(null);
            } else {
                Cookies.setCookie(DISABLE_COOKIE_NAME, DISABLE_COOKIE_VALUE);
                update();

                new Timer() {
                    @Override
                    public void run() {

                        switch (getAppCacheStatus()) {
                        case IDLE:
                        case UPDATE_READY:
                            // connection problems preventing the app cache as being marked as obsolete
                            callback.onFailure(new AppCacheException(AppCacheErrorType.CONNECTION));
                            this.cancel();
                            Cookies.removeCookie(DISABLE_COOKIE_NAME);
                            break;
                        case UNCACHED:
                        case OBSOLETE:
                            callback.onSuccess(null);
                            this.cancel();
                            Cookies.removeCookie(DISABLE_COOKIE_NAME);
                            break;
                        }
                    }
                }.scheduleRepeating(500);

            }
        } catch (Exception e) {
            callback.onFailure(new AppCacheException(e));
        }
    }

    @Override
    public Status getStatus() {
        return STATUS_MAPPING[getAppCacheStatus()];
    }

    @Override
    protected void sinkProgressEvents() {
        sinkProgressEvent(this);
    }

    @Override
    protected void sinkUpdateReadyEvents() {
        sinkUpdateReadyEvent(this);
    }

    private void onError() {
        errorCount++;
    }

    @Override
    public boolean isCachedOnStartup() {
        return true;
    }

    @Override
    public boolean requiresPermission() {
        return true;
    }

    @Override
    public void checkForUpdate() {
        update();
    }

    private class ErrorListener {
        private int initialCount = errorCount;

        public boolean haveOccurred() {
            return errorCount > initialCount;
        }
    }
}