/*******************************************************************************
* Copyright (c) 2012-2016 Codenvy, S.A.
* 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:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.ide.extension.machine.client.command.edit;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import org.eclipse.che.api.machine.shared.dto.CommandDto;
import org.eclipse.che.api.promises.client.Function;
import org.eclipse.che.api.promises.client.FunctionException;
import org.eclipse.che.api.promises.client.Operation;
import org.eclipse.che.api.promises.client.OperationException;
import org.eclipse.che.api.promises.client.Promise;
import org.eclipse.che.api.promises.client.PromiseError;
import org.eclipse.che.api.workspace.gwt.client.WorkspaceServiceClient;
import org.eclipse.che.api.workspace.shared.dto.UsersWorkspaceDto;
import org.eclipse.che.ide.CoreLocalizationConstant;
import org.eclipse.che.ide.api.app.AppContext;
import org.eclipse.che.ide.dto.DtoFactory;
import org.eclipse.che.ide.extension.machine.client.MachineLocalizationConstant;
import org.eclipse.che.ide.extension.machine.client.actions.SelectCommandComboBoxReady;
import org.eclipse.che.ide.extension.machine.client.command.CommandConfiguration;
import org.eclipse.che.ide.extension.machine.client.command.CommandConfigurationPage;
import org.eclipse.che.ide.extension.machine.client.command.CommandConfigurationPage.DirtyStateListener;
import org.eclipse.che.ide.extension.machine.client.command.CommandManager;
import org.eclipse.che.ide.extension.machine.client.command.CommandType;
import org.eclipse.che.ide.extension.machine.client.command.CommandTypeRegistry;
import org.eclipse.che.ide.ui.dialogs.ConfirmCallback;
import org.eclipse.che.ide.ui.dialogs.DialogFactory;
import org.eclipse.che.ide.ui.dialogs.choice.ChoiceDialog;
import org.eclipse.che.ide.ui.dialogs.confirm.ConfirmDialog;
import org.eclipse.che.ide.util.loging.Log;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Presenter for managing commands.
*
* @author Artem Zatsarynnyi
* @author Oleksii Orel
*/
@Singleton
public class EditCommandsPresenter implements EditCommandsView.ActionDelegate {
public static final String PREVIEW_URL_ATTR = "previewUrl";
private final EditCommandsView view;
private final WorkspaceServiceClient workspaceServiceClient;
private final CommandManager commandManager;
private final String workspaceId;
private final DtoFactory dtoFactory;
private final CommandTypeRegistry commandTypeRegistry;
private final DialogFactory dialogFactory;
private final MachineLocalizationConstant machineLocale;
private final CoreLocalizationConstant coreLocale;
private final Provider<SelectCommandComboBoxReady> selectCommandActionProvider;
private final Set<ConfigurationChangedListener> configurationChangedListeners;
/** Set of the existing command names. */
private final Set<String> commandNames;
private CommandConfigurationPage<CommandConfiguration> editedPage;
/** Command that being edited. */
private CommandConfiguration editedCommand;
/** Name of the edited command before editing. */
private String editedCommandOriginName;
private String editedCommandOriginPreviewUrl;
@Inject
protected EditCommandsPresenter(EditCommandsView view,
WorkspaceServiceClient workspaceServiceClient,
CommandTypeRegistry commandTypeRegistry,
DialogFactory dialogFactory,
MachineLocalizationConstant machineLocale,
CoreLocalizationConstant coreLocale,
Provider<SelectCommandComboBoxReady> selectCommandActionProvider,
CommandManager commandManager,
AppContext appContext,
DtoFactory dtoFactory) {
this.view = view;
this.workspaceServiceClient = workspaceServiceClient;
this.commandManager = commandManager;
this.workspaceId = appContext.getWorkspace().getId();
this.dtoFactory = dtoFactory;
this.commandTypeRegistry = commandTypeRegistry;
this.dialogFactory = dialogFactory;
this.machineLocale = machineLocale;
this.coreLocale = coreLocale;
this.selectCommandActionProvider = selectCommandActionProvider;
this.view.setDelegate(this);
configurationChangedListeners = new HashSet<>();
commandNames = new HashSet<>();
}
@Override
public void onCloseClicked() {
final CommandConfiguration selectedConfiguration = view.getSelectedConfiguration();
onNameChanged();
if (selectedConfiguration != null && isViewModified()) {
onConfigurationSelected(selectedConfiguration);
}
view.close();
}
private void selectCommandOnToolbar(CommandConfiguration commandToSelect) {
selectCommandActionProvider.get().setSelectedCommand(commandToSelect);
}
@Override
public void onSaveClicked() {
final CommandConfiguration selectedConfiguration;
if (view.getSelectedConfiguration() == null) {
return;
}
onNameChanged();
selectedConfiguration = view.getSelectedConfiguration();
updateCommand(selectedConfiguration).then(new Operation<UsersWorkspaceDto>() {
@Override
public void apply(UsersWorkspaceDto arg) throws OperationException {
fetchCommands();
fireConfigurationUpdated(selectedConfiguration);
}
});
}
private Promise<UsersWorkspaceDto> updateCommand(final CommandConfiguration selectedConfiguration) {
final CommandDto commandDto = dtoFactory.createDto(CommandDto.class)
.withName(selectedConfiguration.getName())
.withCommandLine(selectedConfiguration.toCommandLine())
.withType(selectedConfiguration.getType().getId())
.withAttributes(selectedConfiguration.getAttributes());
if (editedCommandOriginName.trim().equals(selectedConfiguration.getName())) {
return workspaceServiceClient.updateCommand(workspaceId, commandDto);
} else {
onNameChanged();
//generate a new unique name if input one already exists
final String newName = getUniqueCommandName(selectedConfiguration.getType(), selectedConfiguration.getName());
if (selectedConfiguration.equals(view.getSelectedConfiguration())) {
//update selected configuration name
view.getSelectedConfiguration().setName(newName);
}
return workspaceServiceClient.deleteCommand(workspaceId, editedCommandOriginName)
.thenPromise(new Function<UsersWorkspaceDto, Promise<UsersWorkspaceDto>>() {
@Override
public Promise<UsersWorkspaceDto> apply(UsersWorkspaceDto arg) throws FunctionException {
commandDto.setName(newName);
return workspaceServiceClient.addCommand(workspaceId, commandDto);
}
});
}
}
@Override
public void onCancelClicked() {
fetchCommands();
}
@Override
public void onDuplicateClicked() {
final CommandConfiguration selectedConfiguration = view.getSelectedConfiguration();
if (selectedConfiguration != null) {
createNewCommand(selectedConfiguration.getType(), selectedConfiguration.toCommandLine(), selectedConfiguration.getName(),
selectedConfiguration.getAttributes());
}
}
@Override
public void onAddClicked() {
final CommandType selectedType = view.getSelectedCommandType();
if (selectedType != null) {
createNewCommand(selectedType, null, null, null);
}
}
private void createNewCommand(final CommandType type, final String customCommand, final String customName, final Map<String, String> attributes) {
if (!isViewModified()) {
reset();
createCommand(type, customCommand, customName, attributes);
return;
}
final ConfirmCallback saveCallback = new ConfirmCallback() {
@Override
public void accepted() {
updateCommand(editedCommand).then(new Operation<UsersWorkspaceDto>() {
@Override
public void apply(UsersWorkspaceDto arg) throws OperationException {
reset();
createCommand(type, customCommand, customName, attributes);
}
});
}
};
final ConfirmCallback discardCallback = new ConfirmCallback() {
@Override
public void accepted() {
fetchCommands();
reset();
createCommand(type, customCommand, customName, attributes);
}
};
final ChoiceDialog dialog = dialogFactory.createChoiceDialog(
machineLocale.editCommandsSaveChangesTitle(),
machineLocale.editCommandsSaveChangesConfirmation(editedCommand.getName()),
coreLocale.save(),
machineLocale.editCommandsSaveChangesDiscard(),
saveCallback,
discardCallback);
dialog.show();
}
private String getUniqueCommandName(CommandType customType, String customName) {
final String newCommandName;
if (customName == null || customName.isEmpty()) {
newCommandName = "new" + customType.getDisplayName();
} else {
if (!commandNames.contains(customName)) {
return customName;
}
newCommandName = customName + " copy";
}
if (!commandNames.contains(newCommandName)) {
return newCommandName;
}
for (int count = 1; count < 1000; count++) {
if (!commandNames.contains(newCommandName + "-" + count)) {
return newCommandName + "-" + count;
}
}
return newCommandName;
}
private void createCommand(CommandType type) {
createCommand(type, null, null, null);
}
private void createCommand(CommandType type, String customCommand, String customName, Map<String, String> attributes) {
Map<String, String> attributesToUpdate = (attributes != null) ? attributes : new HashMap<String, String>();
attributesToUpdate.put(PREVIEW_URL_ATTR, type.getPreviewUrlTemplate());
final CommandDto commandDto = dtoFactory.createDto(CommandDto.class)
.withName(getUniqueCommandName(type, customName))
.withCommandLine(customCommand != null ? customCommand : type.getCommandTemplate())
.withAttributes(attributesToUpdate)
.withType(type.getId());
workspaceServiceClient.addCommand(workspaceId, commandDto).then(new Operation<UsersWorkspaceDto>() {
@Override
public void apply(UsersWorkspaceDto arg) throws OperationException {
fetchCommands();
final CommandType type = commandTypeRegistry.getCommandTypeById(commandDto.getType());
final CommandConfiguration command = type.getConfigurationFactory().createFromDto(commandDto);
fireConfigurationAdded(command);
view.setSelectedConfiguration(command);
}
});
}
@Override
public void onRemoveClicked(final CommandConfiguration selectedConfiguration) {
if (selectedConfiguration == null) {
return;
}
final ConfirmCallback confirmCallback = new ConfirmCallback() {
@Override
public void accepted() {
workspaceServiceClient.deleteCommand(workspaceId, selectedConfiguration.getName()).then(new Operation<UsersWorkspaceDto>() {
@Override
public void apply(UsersWorkspaceDto arg) throws OperationException {
view.selectNextItem();
fetchCommands();
fireConfigurationRemoved(selectedConfiguration);
}
});
}
};
final ConfirmDialog confirmDialog = dialogFactory.createConfirmDialog(
machineLocale.editCommandsViewRemoveTitle(),
machineLocale.editCommandsRemoveConfirmation(selectedConfiguration.getName()),
confirmCallback,
null);
confirmDialog.show();
}
@Override
public void onExecuteClicked() {
final CommandConfiguration selectedConfiguration = view.getSelectedConfiguration();
if (selectedConfiguration == null) {
return;
}
if (isViewModified()) {
dialogFactory.createMessageDialog("", machineLocale.editCommandsExecuteMessage(), null).show();
return;
}
commandManager.execute(selectedConfiguration);
view.close();
}
private void reset() {
editedCommand = null;
editedCommandOriginName = null;
editedCommandOriginPreviewUrl = null;
editedPage = null;
view.setConfigurationName("");
view.setConfigurationPreviewUrl("");
view.clearCommandConfigurationsContainer();
}
@Override
public void onConfigurationSelected(final CommandConfiguration configuration) {
if (!isViewModified()) {
handleCommandSelection(configuration);
return;
}
final ConfirmCallback saveCallback = new ConfirmCallback() {
@Override
public void accepted() {
updateCommand(editedCommand).then(new Operation<UsersWorkspaceDto>() {
@Override
public void apply(UsersWorkspaceDto arg) throws OperationException {
fetchCommands();
fireConfigurationUpdated(editedCommand);
handleCommandSelection(configuration);
}
});
}
};
final ConfirmCallback discardCallback = new ConfirmCallback() {
@Override
public void accepted() {
reset();
fetchCommands();
handleCommandSelection(configuration);
}
};
final ChoiceDialog dialog = dialogFactory.createChoiceDialog(
machineLocale.editCommandsSaveChangesTitle(),
machineLocale.editCommandsSaveChangesConfirmation(editedCommand.getName()),
coreLocale.save(),
machineLocale.editCommandsSaveChangesDiscard(),
saveCallback,
discardCallback);
dialog.show();
}
private String getPreviewUrlOrNull(CommandConfiguration configuration) {
if (configuration.getAttributes() != null) {
return configuration.getAttributes().get(PREVIEW_URL_ATTR);
}
return null;
}
private void handleCommandSelection(CommandConfiguration configuration) {
editedCommand = configuration;
editedCommandOriginName = configuration.getName();
editedCommandOriginPreviewUrl = getPreviewUrlOrNull(configuration);
view.setConfigurationName(configuration.getName());
view.setConfigurationPreviewUrl(getPreviewUrlOrNull(configuration));
final Collection<CommandConfigurationPage<? extends CommandConfiguration>> pages = configuration.getType().getConfigurationPages();
for (CommandConfigurationPage<? extends CommandConfiguration> page : pages) {
final CommandConfigurationPage<CommandConfiguration> p = ((CommandConfigurationPage<CommandConfiguration>)page);
editedPage = p;
p.setDirtyStateListener(new DirtyStateListener() {
@Override
public void onDirtyStateChanged() {
view.setCancelButtonState(isViewModified());
view.setSaveButtonState(isViewModified());
}
});
p.resetFrom(configuration);
p.go(view.getCommandConfigurationsContainer());
// TODO: for now only the 1'st page is showing but need to show all the pages
break;
}
}
@Override
public void onNameChanged() {
CommandConfiguration selectedConfiguration = view.getSelectedConfiguration();
if (selectedConfiguration == null || !selectedConfiguration.equals(editedCommand)) {
return;
}
selectedConfiguration.setName(view.getConfigurationName());
view.setCancelButtonState(isViewModified());
view.setSaveButtonState(isViewModified());
}
@Override
public void onPreviewUrlChanged() {
CommandConfiguration selectedConfiguration = view.getSelectedConfiguration();
if (selectedConfiguration == null || !selectedConfiguration.equals(editedCommand)) {
return;
}
selectedConfiguration.getAttributes().put(PREVIEW_URL_ATTR, view.getConfigurationPreviewUrl());
view.setCancelButtonState(isViewModified());
view.setSaveButtonState(isViewModified());
}
/** Show dialog. */
public void show() {
fetchCommands();
view.show();
}
/**
* Fetch commands from server and update view.
*/
private void fetchCommands() {
final String originName = editedCommandOriginName;
reset();
view.setCancelButtonState(false);
view.setSaveButtonState(false);
workspaceServiceClient.getCommands(workspaceId).then(new Function<List<CommandDto>, List<CommandConfiguration>>() {
@Override
public List<CommandConfiguration> apply(List<CommandDto> arg) throws FunctionException {
final List<CommandConfiguration> configurationList = new ArrayList<>();
for (CommandDto descriptor : arg) {
final CommandType type = commandTypeRegistry.getCommandTypeById(descriptor.getType());
// skip command if it's type isn't registered
if (type != null) {
try {
configurationList.add(type.getConfigurationFactory().createFromDto(descriptor));
} catch (IllegalArgumentException e) {
Log.warn(EditCommandsPresenter.class, e.getMessage());
}
}
}
return configurationList;
}
}).then(new Operation<List<CommandConfiguration>>() {
@Override
public void apply(List<CommandConfiguration> commandConfigurations) throws OperationException {
commandNames.clear();
final Map<CommandType, List<CommandConfiguration>> categories = new HashMap<>();
for (CommandType type : commandTypeRegistry.getCommandTypes()) {
final List<CommandConfiguration> settingsCategory = new ArrayList<>();
for (CommandConfiguration configuration : commandConfigurations) {
if (type.getId().equals(configuration.getType().getId())) {
settingsCategory.add(configuration);
commandNames.add(configuration.getName());
if (configuration.getName().equals(originName)) {
view.setSelectedConfiguration(configuration);
}
}
}
Collections.sort(settingsCategory, new Comparator<CommandConfiguration>() {
@Override
public int compare(CommandConfiguration o1, CommandConfiguration o2) {
return o1.getName().compareTo(o2.getName());
}
});
categories.put(type, settingsCategory);
}
view.setData(categories);
view.setFilterState(!commandConfigurations.isEmpty());
}
}).catchError(new Operation<PromiseError>() {
@Override
public void apply(PromiseError arg) throws OperationException {
dialogFactory.createMessageDialog("Error", arg.toString(), null).show();
}
});
}
private boolean isViewModified() {
if (editedCommand == null || editedPage == null) {
return false;
}
return editedPage.isDirty()
|| !editedCommandOriginName.equals(view.getConfigurationName())
|| !editedCommandOriginPreviewUrl.equals(view.getConfigurationPreviewUrl());
}
private void fireConfigurationAdded(CommandConfiguration command) {
for (ConfigurationChangedListener listener : configurationChangedListeners) {
listener.onConfigurationAdded(command);
}
}
private void fireConfigurationRemoved(CommandConfiguration command) {
for (ConfigurationChangedListener listener : configurationChangedListeners) {
listener.onConfigurationRemoved(command);
}
}
private void fireConfigurationUpdated(CommandConfiguration command) {
for (ConfigurationChangedListener listener : configurationChangedListeners) {
listener.onConfigurationsUpdated(command);
}
}
public void addConfigurationsChangedListener(ConfigurationChangedListener listener) {
configurationChangedListeners.add(listener);
}
public void removeConfigurationsChangedListener(ConfigurationChangedListener listener) {
configurationChangedListeners.remove(listener);
}
/** Listener that will be called when command configuration changed. */
public interface ConfigurationChangedListener {
void onConfigurationAdded(CommandConfiguration command);
void onConfigurationRemoved(CommandConfiguration command);
void onConfigurationsUpdated(CommandConfiguration command);
}
}