<commit_before>/*******************************************************************************
 * Copyright (c) 2012-2017 <NAME>.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *   <NAME>. - initial API and implementation
 *******************************************************************************/
package org.eclipse.che.ide.api.machine;

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 com.google.web.bindery.event.shared.EventBus;

import org.eclipse.che.api.promises.client.Promise;
import org.eclipse.che.api.promises.client.callback.AsyncPromiseHelper;
import org.eclipse.che.api.workspace.shared.dto.WsAgentHealthStateDto;
import org.eclipse.che.ide.api.app.AppContext;
import org.eclipse.che.ide.api.dialogs.ConfirmCallback;
import org.eclipse.che.ide.api.dialogs.DialogFactory;
import org.eclipse.che.ide.api.machine.events.WsAgentStateEvent;
import org.eclipse.che.ide.api.workspace.model.MachineImpl;
import org.eclipse.che.ide.rest.AsyncRequestFactory;
import org.eclipse.che.ide.ui.loaders.LoaderPresenter;
import org.eclipse.che.ide.websocket.MessageBus;
import org.eclipse.che.ide.websocket.MessageBusProvider;
import org.eclipse.che.ide.websocket.events.ConnectionClosedHandler;
import org.eclipse.che.ide.websocket.events.ConnectionErrorHandler;
import org.eclipse.che.ide.websocket.events.ConnectionOpenedHandler;
import org.eclipse.che.ide.websocket.events.WebSocketClosedEvent;

import java.util.List;

import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.collect.Lists.newArrayList;
import static org.eclipse.che.ide.api.machine.WsAgentState.STARTED;
import static org.eclipse.che.ide.api.machine.WsAgentState.STOPPED;

/**
 * Controls workspace agent's state, defines actions to be perform on different events related to websocket
 * connection (close, open, error, etc.), checks http/websocket connection to control it's state. Currently
 * there are only two states that a workspace agent can be at: {@link WsAgentState#STARTED} or
 * {@link WsAgentState#STOPPED}.
 *
 * @author <NAME>
 * @author <NAME>
 */
@Singleton
public class WsAgentStateController implements ConnectionOpenedHandler, ConnectionClosedHandler, ConnectionErrorHandler {
    private final EventBus               eventBus;
    private final MessageBusProvider     messageBusProvider;
    private final DialogFactory          dialogFactory;
    private final AppContext             appContext;
    private final AsyncRequestFactory    asyncRequestFactory;
    private final LoaderPresenter        loader;
    private       MachineImpl             devMachine;
    private       MessageBus             messageBus;
    private       WsAgentState           state;
    private List<AsyncCallback<MessageBus>> messageBusCallbacks = newArrayList();
    private List<AsyncCallback<MachineImpl>> devMachineCallbacks = newArrayList();

    @Inject
    public WsAgentStateController(EventBus eventBus,
                                  LoaderPresenter loader,
                                  MessageBusProvider messageBusProvider,
                                  AsyncRequestFactory asyncRequestFactory,
                                  DialogFactory dialogFactory,
                                  AppContext appContext) {
        this.loader = loader;
        this.eventBus = eventBus;
        this.messageBusProvider = messageBusProvider;
        this.asyncRequestFactory = asyncRequestFactory;
        this.dialogFactory = dialogFactory;
        this.appContext = appContext;
    }

    public void initialize(MachineImpl devMachine) {
        checkNotNull(devMachine, "Developer machine should not be a null");

        this.devMachine = devMachine;
        this.state = STOPPED;
        loader.show(LoaderPresenter.Phase.STARTING_WORKSPACE_AGENT);
        checkHttpConnection();
    }

    @Override
    public void onClose(WebSocketClosedEvent event) {
        if (STARTED.equals(state)) {
//            checkWsAgentHealth();
        }
    }

    @Override
    public void onError() {
        if (STARTED.equals(state)) {
            state = STOPPED;
            eventBus.fireEvent(WsAgentStateEvent.createWsAgentStoppedEvent());
        }
    }

    @Override
    public void onOpen() {
        messageBus.removeOnOpenHandler(this);
        //need to make sure ready state equals 1 (OPEN) in same situations after opening it still equals 0 (CONNECTING)
        new Timer() {
            @Override
            public void run() {
                if (messageBus.getReadyState().equals(MessageBus.ReadyState.OPEN)) {
                    cancel();
                    started();
                }
            }
        }.scheduleRepeating(300);
    }

    /** Returns state of the ws agent */
    public WsAgentState getState() {
        return state;
    }

    public Promise<MessageBus> getMessageBus() {
        return AsyncPromiseHelper.createFromAsyncRequest(callback -> {
            if (messageBus != null) {
                callback.onSuccess(messageBus);
            } else {
                WsAgentStateController.this.messageBusCallbacks.add(callback);
            }
        });
    }

    /**
     * Goto checking HTTP connection via getting all registered REST Services
     */
    private void checkHttpConnection() {
        //here we add trailing slash because {@link org.eclipse.che.api.core.rest.ApiInfoService} mapped in this way
        String url = appContext.getDevAgentEndpoint() + '/';
        asyncRequestFactory.createGetRequest(url).send().then(ignored -> {
            checkWsConnection();
        }).catchError(ignored -> {
            // FIXME: spi ide
            new Timer() {
                @Override
                public void run() {
                    checkHttpConnection();
                }
            }.schedule(1000);
//            checkWsAgentHealth();
        });
    }

    private void started() {
        state = STARTED;
        loader.setSuccess(LoaderPresenter.Phase.STARTING_WORKSPACE_AGENT);

        for (AsyncCallback<MessageBus> callback : messageBusCallbacks) {
            callback.onSuccess(messageBus);
        }
        messageBusCallbacks.clear();

        for (AsyncCallback<MachineImpl> callback : devMachineCallbacks) {
            callback.onSuccess(devMachine);
        }
        devMachineCallbacks.clear();

        eventBus.fireEvent(WsAgentStateEvent.createWsAgentStartedEvent());
    }

    private void checkStateOfWsAgent(WsAgentHealthStateDto agentHealthStateDto) {
        final int statusCode = agentHealthStateDto.getCode();
        final String infoWindowTitle = "Workspace Agent Not Responding";
        final boolean reloadPage = true;
        final boolean createSnapshot = true;
        final ConfirmCallback stopCallback = new StopCallback(!reloadPage, createSnapshot);
        final ConfirmCallback stopAndReloadCallback = new StopCallback(reloadPage, !createSnapshot);

        if (statusCode == 200) {
            dialogFactory.createChoiceDialog(infoWindowTitle,
                                             "Workspace agent is no longer responding. To fix the problem, verify you have a" +
                                             " good network connection and restart the workspace.",
                                             "Restart",
                                             "Close",
                                             stopAndReloadCallback,
                                             stopCallback).show();
        } else {
            dialogFactory.createChoiceDialog(infoWindowTitle,
                                             "Workspace agent is no longer responding. To fix the problem, restart the workspace.",
                                             "Restart",
                                             "Close",
                                             stopAndReloadCallback,
                                             stopCallback).show();
        }
    }

    /**
     * Try to connect via WebSocket connection
     */
    private void checkWsConnection() {
        if (messageBus != null) {
            messageBus.cancelReconnection();
        }
        // FIXME: spi ide
        final String wsAgentWebSocketURL = appContext.getDevAgentEndpoint().replaceFirst("http", "ws") + "/ws";
        messageBus = messageBusProvider.createMachineMessageBus(wsAgentWebSocketURL);
        // TODO: need to remove all handlers when ws-agent stopped
        messageBus.addOnCloseHandler(this);
        messageBus.addOnErrorHandler(this);
        messageBus.addOnOpenHandler(this);
    }

//    private void checkWsAgentHealth() {
//        workspaceServiceClient.getWsAgentState(appContext.getWorkspaceId(), devMachine.getName()).then(agentHealthState -> {
//            if (RUNNING.equals(agentHealthState.getWorkspaceStatus())) {
//                checkStateOfWsAgent(agentHealthState);
//            }
//        }).catchError(new Operation<PromiseError>() {
//            @Override
//            public void apply(PromiseError arg) throws OperationException {
//                if (arg.getCause() instanceof ServerDisconnectedException) {<commit_msg>Do not block working of IDE when can't establish WebSocket connection to the ws-agent<commit_after>/*******************************************************************************
 * Copyright (c) 2012-2017 <NAME>.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *   <NAME>. - initial API and implementation
 *******************************************************************************/
package org.eclipse.che.ide.api.machine;

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 com.google.web.bindery.event.shared.EventBus;

import org.eclipse.che.api.promises.client.Promise;
import org.eclipse.che.api.promises.client.callback.AsyncPromiseHelper;
import org.eclipse.che.api.workspace.shared.dto.WsAgentHealthStateDto;
import org.eclipse.che.ide.api.app.AppContext;
import org.eclipse.che.ide.api.dialogs.ConfirmCallback;
import org.eclipse.che.ide.api.dialogs.DialogFactory;
import org.eclipse.che.ide.api.machine.events.WsAgentStateEvent;
import org.eclipse.che.ide.api.notification.NotificationManager;
import org.eclipse.che.ide.api.workspace.model.MachineImpl;
import org.eclipse.che.ide.rest.AsyncRequestFactory;
import org.eclipse.che.ide.ui.loaders.LoaderPresenter;
import org.eclipse.che.ide.websocket.MessageBus;
import org.eclipse.che.ide.websocket.MessageBusProvider;
import org.eclipse.che.ide.websocket.events.ConnectionClosedHandler;
import org.eclipse.che.ide.websocket.events.ConnectionErrorHandler;
import org.eclipse.che.ide.websocket.events.ConnectionOpenedHandler;
import org.eclipse.che.ide.websocket.events.WebSocketClosedEvent;

import java.util.List;

import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.collect.Lists.newArrayList;
import static org.eclipse.che.ide.api.machine.WsAgentState.STARTED;
import static org.eclipse.che.ide.api.machine.WsAgentState.STOPPED;
import static org.eclipse.che.ide.api.notification.StatusNotification.DisplayMode.FLOAT_MODE;
import static org.eclipse.che.ide.api.notification.StatusNotification.Status.FAIL;
import static org.eclipse.che.ide.api.notification.StatusNotification.Status.WARNING;

/**
 * Controls workspace agent's state, defines actions to be perform on different events related to websocket
 * connection (close, open, error, etc.), checks http/websocket connection to control it's state. Currently
 * there are only two states that a workspace agent can be at: {@link WsAgentState#STARTED} or
 * {@link WsAgentState#STOPPED}.
 *
 * @author <NAME>
 * @author <NAME>
 */
@Singleton
public class WsAgentStateController implements ConnectionOpenedHandler, ConnectionClosedHandler, ConnectionErrorHandler {
    private final EventBus            eventBus;
    private final MessageBusProvider  messageBusProvider;
    private final DialogFactory       dialogFactory;
    private final AppContext          appContext;
    private final NotificationManager notificationManager;
    private final AsyncRequestFactory asyncRequestFactory;
    private final LoaderPresenter     loader;
    private       MachineImpl         devMachine;
    private       MessageBus          messageBus;
    private       WsAgentState        state;
    private List<AsyncCallback<MessageBus>>  messageBusCallbacks = newArrayList();
    private List<AsyncCallback<MachineImpl>> devMachineCallbacks = newArrayList();

    @Inject
    public WsAgentStateController(EventBus eventBus,
                                  LoaderPresenter loader,
                                  MessageBusProvider messageBusProvider,
                                  AsyncRequestFactory asyncRequestFactory,
                                  DialogFactory dialogFactory,
                                  AppContext appContext,
                                  NotificationManager notificationManager) {
        this.loader = loader;
        this.eventBus = eventBus;
        this.messageBusProvider = messageBusProvider;
        this.asyncRequestFactory = asyncRequestFactory;
        this.dialogFactory = dialogFactory;
        this.appContext = appContext;
        this.notificationManager = notificationManager;
    }

    public void initialize(MachineImpl devMachine) {
        checkNotNull(devMachine, "Developer machine should not be a null");

        this.devMachine = devMachine;
        this.state = STOPPED;
        loader.show(LoaderPresenter.Phase.STARTING_WORKSPACE_AGENT);
        checkHttpConnection();
    }

    @Override
    public void onClose(WebSocketClosedEvent event) {
        notificationManager.notify("ws-agent",
                                   "Can not establish WebSocket connection to ws-agent",
                                   WARNING,
                                   FLOAT_MODE);

        if (STARTED.equals(state)) {
//            checkWsAgentHealth();
        }
    }

    @Override
    public void onError() {
        notificationManager.notify("ws-agent",
                                   "Can not establish WebSocket connection to ws-agent",
                                   FAIL,
                                   FLOAT_MODE);

        if (STARTED.equals(state)) {
            state = STOPPED;
            eventBus.fireEvent(WsAgentStateEvent.createWsAgentStoppedEvent());
        }
    }

    @Override
    public void onOpen() {
        messageBus.removeOnOpenHandler(this);
        //need to make sure ready state equals 1 (OPEN) in same situations after opening it still equals 0 (CONNECTING)
        new Timer() {
            @Override
            public void run() {
                if (messageBus.getReadyState().equals(MessageBus.ReadyState.OPEN)) {
                    cancel();
//                    started();
                    for (AsyncCallback<MessageBus> callback : messageBusCallbacks) {
                        callback.onSuccess(messageBus);
                    }
                    messageBusCallbacks.clear();
                }
            }
        }.scheduleRepeating(300);
    }

    /** Returns state of the ws agent */
    public WsAgentState getState() {
        return state;
    }

    public Promise<MessageBus> getMessageBus() {
        return AsyncPromiseHelper.createFromAsyncRequest(callback -> {
            if (messageBus != null) {
                callback.onSuccess(messageBus);
            } else {
                WsAgentStateController.this.messageBusCallbacks.add(callback);
            }
        });
    }

    /**
     * Goto checking HTTP connection via getting all registered REST Services
     */
    private void checkHttpConnection() {
        //here we add trailing slash because {@link org.eclipse.che.api.core.rest.ApiInfoService} mapped in this way
        String url = appContext.getDevAgentEndpoint() + '/';
        asyncRequestFactory.createGetRequest(url).send().then(ignored -> {
            try {
                started();
            } catch (Exception ignore) {

            }

            checkWsConnection();
        }).catchError(ignored -> {
            // FIXME: spi ide
            new Timer() {
                @Override
                public void run() {
                    checkHttpConnection();
                }
            }.schedule(1000);
//            checkWsAgentHealth();
        });
    }

    private void started() {
        state = STARTED;
        loader.setSuccess(LoaderPresenter.Phase.STARTING_WORKSPACE_AGENT);

//        for (AsyncCallback<MessageBus> callback : messageBusCallbacks) {
//            callback.onSuccess(messageBus);
//        }
//        messageBusCallbacks.clear();
//
//        for (AsyncCallback<MachineImpl> callback : devMachineCallbacks) {
//            callback.onSuccess(devMachine);
//        }
//        devMachineCallbacks.clear();

        eventBus.fireEvent(WsAgentStateEvent.createWsAgentStartedEvent());
    }

    private void checkStateOfWsAgent(WsAgentHealthStateDto agentHealthStateDto) {
        final int statusCode = agentHealthStateDto.getCode();
        final String infoWindowTitle = "Workspace Agent Not Responding";
        final boolean reloadPage = true;
        final boolean createSnapshot = true;
        final ConfirmCallback stopCallback = new StopCallback(!reloadPage, createSnapshot);
        final ConfirmCallback stopAndReloadCallback = new StopCallback(reloadPage, !createSnapshot);

        if (statusCode == 200) {
            dialogFactory.createChoiceDialog(infoWindowTitle,
                                             "Workspace agent is no longer responding. To fix the problem, verify you have a" +
                                             " good network connection and restart the workspace.",
                                             "Restart",
                                             "Close",
                                             stopAndReloadCallback,
                                             stopCallback).show();
        } else {
            dialogFactory.createChoiceDialog(infoWindowTitle,
                                             "Workspace agent is no longer responding. To fix the problem, restart the workspace.",
                                             "Restart",
                                             "Close",
                                             stopAndReloadCallback,
                                             stopCallback).show();
        }
    }

    /**
     * Try to connect via WebSocket connection
     */
    private void checkWsConnection() {
        if (messageBus != null) {
            messageBus.cancelReconnection();
        }
        // FIXME: spi ide
        final String wsAgentWebSocketURL = appContext.getDevAgentEndpoint().replaceFirst("http", "ws") + "/ws1";

        messageBus = messageBusProvider.createMachineMessageBus(wsAgentWebSocketURL);
        // TODO: need to remove all handlers when ws-agent stopped
        messageBus.addOnCloseHandler(this);
        messageBus.addOnErrorHandler(this);
        messageBus.addOnOpenHandler(this);
    }

//    private void checkWsAgentHealth() {
//        workspaceServiceClient.getWsAgentState(appContext.getWorkspaceId(), devMachine.getName()).then(agentHealthState -> {
//            if (RUNNING.equals(agentHealthState.getWorkspaceStatus())) {
//                checkStateOfWsAgent(agentHealthState);
//            }
//        }).catchError(new Operation<PromiseError>() {
//            @Override
//            public void apply(PromiseError arg) throws OperationException {
//                if (arg.getCause() instanceof ServerDisconnectedException) {
{'fraction_non_alphanumeric': 0.09990142927550517, 'fraction_numerical': 0.0024149827501232135, 'mean_word_length': 2.2632679318108715, 'pattern_counts': {'":': 0, '<': 28, '<?xml version=': 0, '>': 36, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 63, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4446509', 'n_tokens_mistral': 4984, 'n_tokens_neox': 4765, 'n_words': 1097}