package org.sigmah.offline.status; /* * #%L * Sigmah * %% * Copyright (C) 2010 - 2016 URD * %% * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program. If not, see * <http://www.gnu.org/licenses/gpl-3.0.html>. * #L% */ import com.allen_sauer.gwt.log.client.Log; import com.google.gwt.core.client.GWT; import com.google.gwt.http.client.RequestBuilder; import com.google.gwt.http.client.RequestCallback; import com.google.gwt.http.client.RequestException; import com.google.gwt.http.client.Response; import com.google.gwt.json.client.JSONBoolean; import com.google.gwt.json.client.JSONException; import com.google.gwt.json.client.JSONObject; import com.google.gwt.json.client.JSONParser; import com.google.gwt.json.client.JSONValue; import com.google.gwt.user.client.Timer; import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.inject.Inject; import com.google.inject.Singleton; import org.sigmah.client.event.EventBus; import org.sigmah.client.event.OfflineEvent; import org.sigmah.client.event.handler.OfflineHandler; import org.sigmah.client.i18n.I18N; import org.sigmah.client.page.RequestParameter; import org.sigmah.client.ui.notif.N10N; import org.sigmah.client.ui.zone.Zone; import org.sigmah.offline.dao.UpdateDiaryAsyncDAO; /** * Poll the network to detect changes in the connection state. * * @author Raphaƫl Calabro (rcalabro@ideia.fr) */ @Singleton public class ApplicationStateManager implements OfflineEvent.Source { public static final int NETWORK_POLLING_INTERVAL = 3000; /** * Event bus to let other classes know of changes in the application state. */ private final EventBus eventBus; /** * DAO to read the content of the UpdateDiary table. Used to know if one * or more changes have been made in offline mode. */ private final UpdateDiaryAsyncDAO updateDiaryAsyncDAO; /** * Current application state. */ private ApplicationState state; /** * Current network state. */ private boolean online; /** * Keep tracks of the first reconnection. */ private Runnable onReconnection; @Inject public ApplicationStateManager(EventBus eventBus, UpdateDiaryAsyncDAO updateDiaryAsyncDAO) { this.eventBus = eventBus; this.updateDiaryAsyncDAO = updateDiaryAsyncDAO; this.state = ApplicationState.UNKNOWN; this.online = getInitialStatus(); registerEventHandlers(); if(GWT.isProdMode()) { startNetworkPolling(); } } // -- // Public API. // -- /** * Fire the current state to every state listener. * * @param onStateFound Called when the state is found. */ public void fireCurrentState(final Runnable onStateFound) { updateApplicationState(onStateFound, onReconnection); } /** * Retrieves the last found state. * * @return Last application state. */ public ApplicationState getLastState() { return state; } // --- // Getters and setters. // --- private void setOnline(boolean status) { if(this.online != status || state == ApplicationState.UNKNOWN) { this.online = status; updateApplicationState(onReconnection); } } public ApplicationState getState() { return state; } private void setState(ApplicationState state, Runnable... runnables) { setState(state, true); if(runnables != null) { for(final Runnable runnable : runnables) { runnable.run(); } } } private void setState(ApplicationState state, boolean fireEvent) { this.state = state; if(fireEvent) { eventBus.fireEvent(new OfflineEvent(this, state)); } } // --- // Initialization. // --- private void startNetworkPolling() { new Timer() { @Override public void run() { updateStatus(); } }.scheduleRepeating(NETWORK_POLLING_INTERVAL); } private void registerEventHandlers() { eventBus.addHandler(OfflineEvent.getType(), new OfflineHandler() { @Override public void handleEvent(OfflineEvent event) { if(ApplicationStateManager.this != event.getEventSource()) { final ApplicationState state = event.getState(); setState(state, false); if(state != ApplicationState.UNKNOWN) { online = state != ApplicationState.OFFLINE; } onReconnection.run(); } } }); onReconnection = new Runnable() { private ApplicationState lastState = ApplicationStateManager.this.state; private boolean firstTime = true; @Override public void run() { if(lastState == ApplicationState.ONLINE && state == ApplicationState.OFFLINE) { // BUGFIX #690: Display a message saying that the connection has been lost. N10N.offlineNotif(I18N.CONSTANTS.sigmahOfflineDeconnectionTitle(), I18N.CONSTANTS.sigmahOfflineDeconnectionMessage()); } else if(lastState == ApplicationState.OFFLINE && state == ApplicationState.READY_TO_SYNCHRONIZE) { // Display a message saying that the network is available. N10N.offlineNotif(I18N.CONSTANTS.sigmahOfflineFirstReconnectionTitle(), I18N.CONSTANTS.sigmahOfflineFirstReconnectionMessage()); if(firstTime) { // Also show the offline menu eventBus.updateZoneRequest(Zone.OFFLINE_BANNER.requestWith(RequestParameter.SHOW_BRIEFLY, true)); firstTime = false; } } lastState = state; if(state == ApplicationState.ONLINE) { firstTime = true; } } }; } // --- // Status change handlers. // -- private void updateStatus() { final RequestBuilder requestBuilder = new RequestBuilder(RequestBuilder.GET, "sigmah/online.nocache.json"); try { requestBuilder.sendRequest(null, new RequestCallback() { @Override public void onResponseReceived(com.google.gwt.http.client.Request request, Response response) { if(response != null && response.getText() != null && !response.getText().isEmpty()) { try { final JSONValue value = JSONParser.parseStrict(response.getText()); final JSONObject object = value.isObject(); if(object != null) { final JSONValue onlineObject = object.get("online"); final JSONBoolean online = onlineObject.isBoolean(); setOnline(online != null && online.booleanValue()); } else { setOnline(false); } } catch(JSONException ex) { setOnline(false); Log.error("An error occured while parsing the JSON string: '" + response.getText() + "'.", ex); } } else { setOnline(false); } } @Override public void onError(com.google.gwt.http.client.Request request, Throwable exception) { setOnline(false); } }); } catch (RequestException ex) { setOnline(false); Log.error("An error occured while checking the connection state.", ex); } } private void updateApplicationState(final Runnable... runnables) { if(!GWT.isProdMode()) { setState(ApplicationState.ONLINE, runnables); } else if(online) { isPushNeeded(new AsyncCallback<Boolean>() { @Override public void onFailure(Throwable caught) { setState(ApplicationState.ONLINE, runnables); } @Override public void onSuccess(Boolean pushNeeded) { if(pushNeeded) { setState(ApplicationState.READY_TO_SYNCHRONIZE, runnables); } else { setState(ApplicationState.ONLINE, runnables); } } }); } else { setState(ApplicationState.OFFLINE, runnables); } } private void isPushNeeded(final AsyncCallback<Boolean> callback) { if(updateDiaryAsyncDAO.isAnonymous()) { callback.onSuccess(Boolean.FALSE); } else { updateDiaryAsyncDAO.count(new AsyncCallback<Integer>() { @Override public void onFailure(Throwable caught) { callback.onFailure(caught); } @Override public void onSuccess(Integer result) { callback.onSuccess(result > 0); } }); } } private native boolean getInitialStatus() /*-{ return typeof $wnd.online == 'undefined' || $wnd.online; }-*/; }