package org.sigmah.client.ui.presenter.zone;
/*
* #%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.google.gwt.dom.client.Style;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.MouseOutEvent;
import com.google.gwt.event.dom.client.MouseOutHandler;
import com.google.gwt.event.dom.client.MouseOverEvent;
import com.google.gwt.event.dom.client.MouseOverHandler;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Anchor;
import org.sigmah.client.inject.Injector;
import org.sigmah.client.ui.presenter.base.AbstractZonePresenter;
import org.sigmah.client.ui.view.base.ViewInterface;
import org.sigmah.client.ui.view.zone.OfflineBannerView;
import org.sigmah.client.ui.zone.Zone;
import org.sigmah.client.ui.zone.ZoneRequest;
import org.sigmah.offline.appcache.ApplicationCache;
import org.sigmah.offline.appcache.ApplicationCacheManager;
import com.google.gwt.user.client.ui.Panel;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.inject.ImplementedBy;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.util.Date;
import java.util.EnumMap;
import java.util.Map;
import org.sigmah.client.event.OfflineEvent;
import org.sigmah.client.event.handler.OfflineHandler;
import org.sigmah.client.i18n.I18N;
import org.sigmah.client.page.Page;
import org.sigmah.client.page.RequestParameter;
import org.sigmah.client.ui.notif.ConfirmCallback;
import org.sigmah.client.ui.notif.N10N;
import org.sigmah.client.ui.view.trace.TraceMenuPanel;
import org.sigmah.client.ui.widget.RatioBar;
import org.sigmah.client.util.MessageType;
import org.sigmah.client.util.profiler.Profiler;
import org.sigmah.offline.appcache.ApplicationCacheEventHandler;
import org.sigmah.offline.indexeddb.IndexedDB;
import org.sigmah.offline.indexeddb.OpenDatabaseRequest;
import org.sigmah.offline.indexeddb.Request;
import org.sigmah.offline.status.ApplicationState;
import org.sigmah.offline.status.ProgressType;
import org.sigmah.offline.sync.SynchroProgressListener;
import org.sigmah.offline.sync.UpdateDates;
import org.sigmah.offline.view.OfflineMenuPanel;
import org.sigmah.offline.view.SynchronizePopup;
import org.sigmah.shared.file.HasProgressListeners;
import org.sigmah.shared.file.ProgressAdapter;
import org.sigmah.shared.file.TransfertManager;
import org.sigmah.shared.file.TransfertType;
import com.allen_sauer.gwt.log.client.Log;
import com.google.gwt.dom.client.Style.Display;
import com.google.gwt.i18n.shared.DateTimeFormat;
import com.google.gwt.user.client.ui.Image;
import java.util.List;
import org.sigmah.client.dispatch.CommandResultHandler;
import org.sigmah.client.ui.res.icon.offline.OfflineIconBundle;
import org.sigmah.client.util.profiler.Execution;
import org.sigmah.client.util.profiler.ExecutionAsyncDAO;
import org.sigmah.client.util.profiler.ProfilerStore;
import org.sigmah.shared.command.SendProbeReport;
import org.sigmah.shared.command.result.Result;
import org.sigmah.shared.dto.profile.ExecutionDTO;
import org.sigmah.shared.dto.referential.GlobalPermissionEnum;
import org.sigmah.shared.util.Collections;
import org.sigmah.shared.util.ProfileUtils;
/**
* Offline banner presenter displaying offline status.
*
* @author Tom Miette (tmiette@ideia.fr)
* @author Raphaƫl Calabro (rcalabro@ideia.fr)
*/
@Singleton
public class OfflineBannerPresenter extends AbstractZonePresenter<OfflineBannerPresenter.View>
implements OfflineEvent.Source {
private static final int AUTOCLOSE_TIME = 3000;
private static final String STYLE_MENU_VISIBLE = "offline-button-active";
private static final double PUSH_VALUE = 0.4;
private static final double UNDEFINED = -1.0;
private Map<ProgressType, Double> progresses;
private boolean linkHover;
private boolean menuHover;
private boolean forceOpen;
private boolean traceLinkHover;
private boolean traceMenuHover;
private boolean traceForceOpen;
private ApplicationState lastState = ApplicationState.UNKNOWN;
private final ExecutionAsyncDAO executionAsyncDAO = new ExecutionAsyncDAO();
/**
* View interface.
*/
@ImplementedBy(OfflineBannerView.class)
public static interface View extends ViewInterface {
Panel getTraceHandle();
Panel getStatusPanel();
OfflineMenuPanel getMenuPanel();
TraceMenuPanel getTraceMenuPanel();
Panel getMenuHandle();
SynchronizePopup getSynchronizePopup();
void setStatus(ApplicationState state);
void setProgress(double progress, boolean undefined);
void setSynchronizeAnchorEnabled(boolean enabled);
void setTransferFilesAnchorEnabled(boolean enabled);
boolean isEnabled(Anchor anchor);
void setWarningIconVisible(boolean visible);
public Image getTraceModeIcon();
}
@Inject
public OfflineBannerPresenter(View view, Injector injector, TransfertManager transfertManager) {
super(view, injector);
}
@Override
public void onBind() {
progresses = new EnumMap<ProgressType, Double>(ProgressType.class);
view.getSynchronizePopup().initialize();
eventBus.addHandler(OfflineEvent.getType(), new OfflineHandler() {
@Override
public void handleEvent(OfflineEvent event) {
onStateChange(event.getState());
}
});
// Application cache progress
ApplicationCacheManager.addHandler(createApplicationCacheEventHandler());
// File transfer progress
final TransfertManager transfertManager = injector.getTransfertManager();
if(transfertManager instanceof HasProgressListeners) {
((HasProgressListeners)transfertManager).setProgressListener(TransfertType.DOWNLOAD, createProgressAdapter(ProgressType.DOWNLOAD));
((HasProgressListeners)transfertManager).setProgressListener(TransfertType.UPLOAD, createProgressAdapter(ProgressType.UPLOAD));
} else {
// Remove file transfer references for unsupported browsers.
view.getMenuPanel().removeFileBaseWidgets();
}
// Toggle visibility of the offline menu
/**/
view.getMenuHandle().addDomHandler(new MouseOverHandler() {
@Override
public void onMouseOver(MouseOverEvent event) {
linkHover = true;
updateMenuVisibility();
}
}, MouseOverEvent.getType());
view.getMenuHandle().addDomHandler(new MouseOutHandler() {
@Override
public void onMouseOut(MouseOutEvent event) {
linkHover = false;
forceOpen = false;
updateMenuVisibility();
}
}, MouseOutEvent.getType());
// Toggle visibility of the offline menu
view.getTraceHandle().addDomHandler(new MouseOverHandler() {
@Override
public void onMouseOver(MouseOverEvent event) {
traceLinkHover = true;
updateTraceMenuVisibility();
}
}, MouseOverEvent.getType());
// Hide menu
view.getTraceHandle().addDomHandler(new MouseOutHandler() {
@Override
public void onMouseOut(MouseOutEvent event) {
traceLinkHover = false;
traceForceOpen = false;
updateTraceMenuVisibility();
}
}, MouseOutEvent.getType());
final OfflineMenuPanel menuPanel = view.getMenuPanel();
menuPanel.addDomHandler(new MouseOverHandler() {
@Override
public void onMouseOver(MouseOverEvent event) {
menuHover = true;
updateMenuVisibility();
}
}, MouseOverEvent.getType());
menuPanel.addDomHandler(new MouseOutHandler() {
@Override
public void onMouseOut(MouseOutEvent event) {
menuHover = false;
forceOpen = false;
updateMenuVisibility();
}
}, MouseOutEvent.getType());
final TraceMenuPanel traceMenuPanel = view.getTraceMenuPanel();
traceMenuPanel.addDomHandler(new MouseOverHandler() {
@Override
public void onMouseOver(MouseOverEvent event) {
traceMenuHover = true;
updateTraceMenuVisibility();
}
}, MouseOverEvent.getType());
traceMenuPanel.addDomHandler(new MouseOutHandler() {
@Override
public void onMouseOut(MouseOutEvent event) {
traceMenuHover = false;
traceForceOpen = false;
updateTraceMenuVisibility();
}
}, MouseOutEvent.getType());
menuPanel.setSigmahUpdateDate(ApplicationCacheManager.getUpdateDate());
menuPanel.setDatabaseUpdateDate(getDatabaseUpdateDate());
// Destroy local database
menuPanel.getRemoveOfflineDataAnchor().addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
confirmUserDatabaseRemoval();
}
});
updateProgressBars();
// Actions
menuPanel.getUpdateDatabaseAnchor().addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
if (!view.isEnabled(menuPanel.getUpdateDatabaseAnchor())) {
return;
}
if (lastState == ApplicationState.READY_TO_SYNCHRONIZE) {
setMenuVisible(false);
pushAndPull();
} else if(lastState == ApplicationState.ONLINE) {
setMenuVisible(false);
pull();
}
}
});
menuPanel.getTransferFilesAnchor().addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
if (!view.isEnabled(menuPanel.getTransferFilesAnchor())) {
return;
}
if (lastState == ApplicationState.ONLINE) {
setMenuVisible(false);
eventBus.navigate(Page.OFFLINE_SELECT_FILES);
}
}
});
traceMenuPanel.getSendReportAnchor().addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
executionAsyncDAO.getAllExecutions(new AsyncCallback<List<Execution>>() {
@Override
public void onFailure(final Throwable caught) {
Log.error("An exception occurred while fetching the executions in the local database.", caught);
}
@Override
public void onSuccess(final List<Execution> executions) {
if (executions != null && !executions.isEmpty()) {
final List<ExecutionDTO> dtos = Collections.map(executions, Execution.DTO_MAPPER);
dispatch.execute(new SendProbeReport(dtos), new CommandResultHandler<Result>() {
@Override
protected void onCommandSuccess(final Result result) {
N10N.infoNotif(I18N.CONSTANTS.infoConfirmation(), I18N.CONSTANTS.probeReportSentSucces());
executionAsyncDAO.removeDataBase(ProfilerStore.EXECUTION);
}
protected void onCommandFailure(final Result result) {
N10N.errorNotif(I18N.CONSTANTS.error(), I18N.CONSTANTS.probeReportSentFailure());
}
});
} else {
N10N.errorNotif(I18N.CONSTANTS.error(), I18N.CONSTANTS.probeReportEmpty());
}
}
});
}
});
traceMenuPanel.getActiveDesactiveModeAnchor().addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
if(Profiler.INSTANCE.isActive()){
Profiler.INSTANCE.setActive(false);
traceMenuPanel.getActiveDesactiveModeAnchor().setText(I18N.CONSTANTS.probesEnableTrace());
traceMenuPanel.getDateActivationModeLabel().setVisible(false);
traceMenuPanel.getDateActivationModeVariable().setVisible(false);
traceMenuPanel.getDateActivationModeVariable().setVisible(false);
traceMenuPanel.getActiveDesactiveModeAnchor().removeStyleName(traceMenuPanel.getDISABLE_ACTION_STYLE());
traceMenuPanel.getActiveDesactiveModeAnchor().addStyleName(traceMenuPanel.getENABLE_ACTION_STYLE());
UpdateDates.setSigmahActivationTraceDate(null);
view.getTraceModeIcon().setResource(OfflineIconBundle.INSTANCE.traceOff());
}else{
Profiler.INSTANCE.setActive(true);
traceMenuPanel.getActiveDesactiveModeAnchor().setText(I18N.CONSTANTS.probesDisableTrace());
UpdateDates.setSigmahActivationTraceDate(new Date());
traceMenuPanel.getDateActivationModeLabel().setVisible(true);
traceMenuPanel.getDateActivationModeVariable().setVisible(true);
traceMenuPanel.getActiveDesactiveModeAnchor().removeStyleName(traceMenuPanel.getENABLE_ACTION_STYLE());
traceMenuPanel.getActiveDesactiveModeAnchor().addStyleName(traceMenuPanel.getDISABLE_ACTION_STYLE());
traceMenuPanel.getDateActivationModeVariable().setText(DateTimeFormat.getFormat(DateTimeFormat.PredefinedFormat.DATE_TIME_SHORT).format(UpdateDates.getSigmahActivationTraceDate()));
view.getTraceModeIcon().setResource(OfflineIconBundle.INSTANCE.traceOn());
}
}
});
}
/**
* {@inheritDoc}
*/
@Override
public void onZoneRequest(ZoneRequest zoneRequest) {
final Boolean showBriefly = zoneRequest.getData(RequestParameter.SHOW_BRIEFLY);
final Boolean pullDatabase = zoneRequest.getData(RequestParameter.PULL_DATABASE);
// Display briefly the menu panel
if(showBriefly != null && showBriefly) {
forceOpen = true;
updateMenuVisibility();
new Timer() {
@Override
public void run() {
forceOpen = false;
updateMenuVisibility();
}
}.schedule(AUTOCLOSE_TIME);
}
// Updating dates
// BUGFIX #714: Dates are refreshed when the current user changes.
view.getMenuPanel().setSigmahUpdateDate(ApplicationCacheManager.getUpdateDate());
view.getMenuPanel().setDatabaseUpdateDate(getDatabaseUpdateDate());
if(ProfileUtils.isGranted(auth(), GlobalPermissionEnum.PROBES_MANAGEMENT)){
view.getTraceHandle().setVisible(true);
view.getTraceHandle().getElement().getStyle().setDisplay(Display.INLINE_BLOCK);
}else{
view.getTraceHandle().setVisible(false);
}
// Users requested to pull data from the server.
if(pullDatabase != null && pullDatabase) {
pull();
}
}
private void updateMenuVisibility() {
setMenuVisible(linkHover || menuHover || forceOpen);
}
private void updateTraceMenuVisibility() {
setTraceMenuVisible(traceLinkHover || traceMenuHover || traceForceOpen);
}
private void setTraceMenuVisible(boolean visible) {
view.getTraceMenuPanel().setVisible(visible);
if(visible) {
view.getTraceHandle().addStyleName(STYLE_MENU_VISIBLE);
} else {
view.getTraceHandle().removeStyleName(STYLE_MENU_VISIBLE);
}
}
private void setMenuVisible(boolean visible) {
view.getMenuPanel().setVisible(visible);
if(visible) {
view.getMenuHandle().addStyleName(STYLE_MENU_VISIBLE);
} else {
view.getMenuHandle().removeStyleName(STYLE_MENU_VISIBLE);
}
}
private void updateProgressBars() {
double total = 0.0;
int undefined = 0;
for(final ProgressType progressType : ProgressType.values()) {
final Double progress = progresses.get(progressType);
final RatioBar bar = view.getMenuPanel().getBar(progressType);
if(progress != null) {
view.getMenuPanel().setBarVisible(progressType, true);
if(progress != UNDEFINED) {
total += progress;
bar.setRatio(progress * 100.0);
} else {
bar.setRatioUndefined();
undefined++;
}
} else {
view.getMenuPanel().setBarVisible(progressType, false);
}
}
final int size = progresses.size();
if(size > 0) {
total /= (double) size;
}
view.setProgress(total, undefined > 0 && undefined == size);
}
private int getPendingTransfers(ProgressType type) {
final HasProgressListeners hasProgressListeners = (HasProgressListeners) injector.getTransfertManager();
switch(type) {
case DOWNLOAD:
return hasProgressListeners.getDownloadQueueSize();
case UPLOAD:
return hasProgressListeners.getUploadQueueSize();
}
throw new IllegalArgumentException("Progress type '" + type + "' is not supported.");
}
private ProgressAdapter createProgressAdapter(final ProgressType type) {
return new ProgressAdapter() {
@Override
public void onProgress(double progress, double speed) {
if(progress < 1.0) {
progresses.put(type, progress);
updateProgressBars();
view.getMenuPanel().setPendingsTransfers(type, getPendingTransfers(type));
} else {
progresses.remove(type);
updateProgressBars();
view.getMenuPanel().setPendingsTransfers(type, 0);
}
}
@Override
public void onLoad(String result) {
progresses.remove(type);
updateProgressBars();
view.getMenuPanel().setPendingsTransfers(type, 0);
}
};
}
private void confirmUserDatabaseRemoval() {
N10N.confirmation(I18N.CONSTANTS.offlineModeHeader(), I18N.CONSTANTS.offlineActionDestroyLocalDataConfirm(), new ConfirmCallback() {
@Override
public void onAction() {
setMenuVisible(false);
N10N.message(I18N.CONSTANTS.offlineActionDestroyLocalDataWorking(), MessageType.OFFLINE);
Profiler.INSTANCE.deleteDatabase();
final OpenDatabaseRequest request = IndexedDB.deleteUserDatabase(auth());
request.addCallback(new AsyncCallback<Request>() {
@Override
public void onFailure(Throwable caught) {
N10N.message(I18N.CONSTANTS.offlineActionDestroyLocalDataFailure(), MessageType.OFFLINE);
}
@Override
public void onSuccess(Request result) {
UpdateDates.setDatabaseUpdateDate(auth(), null);
Window.Location.reload();
}
});
}
}, new ConfirmCallback() {
@Override
public void onAction() {
setMenuVisible(false);
}
});
}
private ApplicationCacheEventHandler createApplicationCacheEventHandler() {
return new ApplicationCacheEventHandler() {
@Override
public void onStatusChange(ApplicationCache.Status status) {
switch (status) {
case DOWNLOADING:
progresses.put(ProgressType.APPLICATION_CACHE, UNDEFINED);
updateProgressBars();
break;
case UPDATEREADY:
progresses.remove(ProgressType.APPLICATION_CACHE);
updateProgressBars();
view.getMenuPanel().setSigmahUpdateDate(new Date());
ApplicationCacheManager.swapCacheAndReload();
break;
default:
break;
}
}
@Override
public void onProgress(int loaded, int total) {
progresses.put(ProgressType.APPLICATION_CACHE, (double)loaded/(double)total);
updateProgressBars();
}
};
}
private Date getDatabaseUpdateDate() {
return UpdateDates.getDatabaseUpdateDate(auth());
}
private void setDatabaseUpdateDate(Date date) {
UpdateDates.setDatabaseUpdateDate(auth(), date);
view.getMenuPanel().setDatabaseUpdateDate(date);
}
/**
* {@inheritDoc}
*/
@Override
public Zone getZone() {
return Zone.OFFLINE_BANNER;
}
private void onStateChange(ApplicationState state) {
this.lastState = state;
view.setStatus(state);
final Anchor syncAnchor = view.getMenuPanel().getUpdateDatabaseAnchor();
final Anchor fileAnchor = view.getMenuPanel().getTransferFilesAnchor();
switch(state) {
case OFFLINE:
syncAnchor.getElement().getStyle().setDisplay(Style.Display.NONE);
fileAnchor.getElement().getStyle().setDisplay(Style.Display.NONE);
RootPanel.getBodyElement().addClassName("offline");
break;
case READY_TO_SYNCHRONIZE:
fileAnchor.getElement().getStyle().setDisplay(Style.Display.NONE);
syncAnchor.getElement().getStyle().clearDisplay();
syncAnchor.setText(I18N.CONSTANTS.offlineActionSynchronize());
RootPanel.getBodyElement().addClassName("offline");
break;
case ONLINE:
syncAnchor.getElement().getStyle().clearDisplay();
syncAnchor.setText(I18N.CONSTANTS.offlineActionUpdateDatabase());
RootPanel.getBodyElement().removeClassName("offline");
fileAnchor.getElement().getStyle().clearDisplay();
break;
default:
break;
}
}
private void pushAndPull() {
view.setSynchronizeAnchorEnabled(false);
progresses.put(ProgressType.DATABASE, 0.0);
view.setWarningIconVisible(false);
// Show the modal progress popup
view.getSynchronizePopup().setTask(I18N.CONSTANTS.offlineSynchronizePush());
view.getSynchronizePopup().center();
injector.getSynchronizer().push(new AsyncCallback<Void>() {
@Override
public void onFailure(Throwable caught) {
N10N.error(I18N.CONSTANTS.offlineSynchronizePushError());
view.setWarningIconVisible(true);
view.setSynchronizeAnchorEnabled(true);
progresses.remove(ProgressType.DATABASE);
updateProgressBars();
view.getSynchronizePopup().hide();
}
@Override
public void onSuccess(Void result) {
// Success
eventBus.fireEvent(new OfflineEvent(OfflineBannerPresenter.this, ApplicationState.ONLINE));
view.getSynchronizePopup().setProgress(PUSH_VALUE);
view.getSynchronizePopup().setTask(I18N.CONSTANTS.offlineSynchronizePull());
progresses.put(ProgressType.DATABASE, PUSH_VALUE);
injector.getSynchronizer().pull(new SynchroProgressListener() {
@Override
public void onProgress(double progress) {
final double total = PUSH_VALUE + progress * (1.0 - PUSH_VALUE);
view.getSynchronizePopup().setProgress(total);
progresses.put(ProgressType.DATABASE, total);
updateProgressBars();
}
@Override
public void onComplete() {
view.setSynchronizeAnchorEnabled(true);
progresses.remove(ProgressType.DATABASE);
updateProgressBars();
view.getSynchronizePopup().hide();
setDatabaseUpdateDate(new Date());
// Refresh the current page.
eventBus.navigateRequest(injector.getPageManager().getCurrentPageRequest());
pullFilesInvite();
}
@Override
public void onFailure(Throwable caught) {
progresses.remove(ProgressType.DATABASE);
updateProgressBars();
view.getSynchronizePopup().hide();
N10N.error(I18N.CONSTANTS.offlineSynchronizePullError());
view.setWarningIconVisible(true);
view.setSynchronizeAnchorEnabled(true);
}
});
}
});
}
private void pull() {
view.setSynchronizeAnchorEnabled(false);
progresses.put(ProgressType.DATABASE, 0.0);
view.setWarningIconVisible(false);
injector.getSynchronizer().pull(new SynchroProgressListener() {
@Override
public void onProgress(double progress) {
progresses.put(ProgressType.DATABASE, progress);
updateProgressBars();
}
@Override
public void onComplete() {
progresses.remove(ProgressType.DATABASE);
updateProgressBars();
setDatabaseUpdateDate(new Date());
view.setSynchronizeAnchorEnabled(true);
pullFilesInvite();
}
@Override
public void onFailure(Throwable caught) {
N10N.error(I18N.CONSTANTS.offlineSynchronizePullError());
view.setWarningIconVisible(true);
view.setSynchronizeAnchorEnabled(true);
progresses.remove(ProgressType.DATABASE);
updateProgressBars();
}
});
}
private void pullFilesInvite() {
N10N.confirmation(I18N.CONSTANTS.sigmahOfflineAlsoSynchronizeFilesInvite(), new ConfirmCallback() {
@Override
public void onAction() {
eventBus.navigate(Page.OFFLINE_SELECT_FILES);
}
});
}
}