/*******************************************************************************
* 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.ext.git.client.add;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import org.eclipse.che.api.git.gwt.client.GitServiceClient;
import org.eclipse.che.api.git.shared.Status;
import org.eclipse.che.ide.api.app.AppContext;
import org.eclipse.che.ide.api.app.CurrentProject;
import org.eclipse.che.ide.api.notification.NotificationManager;
import org.eclipse.che.ide.api.project.node.HasStorablePath;
import org.eclipse.che.ide.api.selection.Selection;
import org.eclipse.che.ide.ext.git.client.GitLocalizationConstant;
import org.eclipse.che.ide.ext.git.client.outputconsole.GitOutputConsole;
import org.eclipse.che.ide.ext.git.client.outputconsole.GitOutputConsoleFactory;
import org.eclipse.che.ide.extension.machine.client.processes.ConsolesPanelPresenter;
import org.eclipse.che.ide.part.explorer.project.ProjectExplorerPresenter;
import org.eclipse.che.ide.project.node.FolderReferenceNode;
import org.eclipse.che.ide.project.node.ResourceBasedNode;
import org.eclipse.che.ide.rest.AsyncRequestCallback;
import org.eclipse.che.ide.rest.DtoUnmarshallerFactory;
import org.eclipse.che.ide.rest.Unmarshallable;
import org.eclipse.che.ide.websocket.WebSocketException;
import org.eclipse.che.ide.websocket.rest.RequestCallback;
import javax.validation.constraints.NotNull;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import static org.eclipse.che.ide.api.notification.StatusNotification.Status.FAIL;
/**
* Presenter for add changes to Git index.
*
* @author Ann Zhuleva
*/
@Singleton
public class AddToIndexPresenter implements AddToIndexView.ActionDelegate {
public static final String ADD_TO_INDEX_COMMAND_NAME = "Git add to index";
private static final String ROOT_FOLDER = ".";
private final AddToIndexView view;
private final GitServiceClient service;
private final GitLocalizationConstant constant;
private final AppContext appContext;
private final ProjectExplorerPresenter projectExplorer;
private final NotificationManager notificationManager;
private final DtoUnmarshallerFactory dtoUnmarshallerFactory;
private final GitOutputConsoleFactory gitOutputConsoleFactory;
private final ConsolesPanelPresenter consolesPanelPresenter;
private final String workspaceId;
private CurrentProject project;
/**
* Create presenter
*
* @param view
* @param service
* @param constant
* @param appContext
* @param notificationManager
*/
@Inject
public AddToIndexPresenter(AddToIndexView view,
AppContext appContext,
DtoUnmarshallerFactory dtoUnmarshallerFactory,
GitLocalizationConstant constant,
GitOutputConsoleFactory gitOutputConsoleFactory,
ConsolesPanelPresenter consolesPanelPresenter,
GitServiceClient service,
NotificationManager notificationManager,
ProjectExplorerPresenter projectExplorer) {
this.view = view;
this.view.setDelegate(this);
this.service = service;
this.constant = constant;
this.appContext = appContext;
this.projectExplorer = projectExplorer;
this.notificationManager = notificationManager;
this.dtoUnmarshallerFactory = dtoUnmarshallerFactory;
this.gitOutputConsoleFactory = gitOutputConsoleFactory;
this.workspaceId = appContext.getWorkspaceId();
this.consolesPanelPresenter = consolesPanelPresenter;
}
/**
* Show dialog.
*/
public void showDialog() {
project = appContext.getCurrentProject();
if (project == null) {
return;
}
final GitOutputConsole console = gitOutputConsoleFactory.create(ADD_TO_INDEX_COMMAND_NAME);
final Unmarshallable<Status> unmarshall = this.dtoUnmarshallerFactory.newUnmarshaller(Status.class);
service.status(workspaceId, project.getRootProject(),
new AsyncRequestCallback<Status>(unmarshall) {
@Override
protected void onSuccess(final Status result) {
if (!result.isClean()) {
addSelection();
} else {
console.printInfo(constant.nothingAddToIndex());
consolesPanelPresenter.addCommandOutput(appContext.getDevMachineId(), console);
notificationManager.notify(constant.nothingAddToIndex(), project.getRootProject());
}
}
@Override
protected void onFailure(Throwable exception) {
console.printError(exception.getMessage() != null ? exception.getMessage() : constant.statusFailed());
consolesPanelPresenter.addCommandOutput(appContext.getDevMachineId(), console);
notificationManager.notify(constant.statusFailed(), FAIL, true, project.getRootProject());
}
});
}
private void addSelection() {
if (isSelectionEmpty() || isSelectionSingle()) {
final String path = getSingleFilePattern();
this.view.setMessage(formatMessage(path), null);
} else {
final List<String> paths = getMultipleFilePatterns();
this.view.setMessage(constant.addToIndexMultiple(), paths);
}
this.view.setUpdated(false);
this.view.showDialog();
}
/**
* Form the message to display for adding to index, telling the user what is gonna to be added.
*
* @return {@link String} message to display
*/
@NotNull
private String formatMessage(@NotNull final String path) {
String pattern = path;
// Root of the working tree:
if (ROOT_FOLDER.equals(pattern)) {
return constant.addToIndexAllChanges();
}
// Do not display file name longer 50 characters
if (pattern.length() > 50) {
pattern = pattern.substring(0, 50) + "...";
}
if (getExplorerSelection().getHeadElement() instanceof FolderReferenceNode) {
return constant.addToIndexFolder(pattern).asString();
} else {
return constant.addToIndexFile(pattern).asString();
}
}
/**
* {@inheritDoc}
*/
@Override
public void onAddClicked() {
boolean update = view.isUpdated();
final GitOutputConsole console = gitOutputConsoleFactory.create(ADD_TO_INDEX_COMMAND_NAME);
try {
service.add(workspaceId, project.getRootProject(), update, getMultipleFilePatterns(), new RequestCallback<Void>() {
@Override
protected void onSuccess(final Void result) {
console.printInfo(constant.addSuccess());
consolesPanelPresenter.addCommandOutput(appContext.getDevMachineId(), console);
notificationManager.notify(constant.addSuccess(), project.getRootProject());
}
@Override
protected void onFailure(final Throwable exception) {
handleError(exception, console);
}
});
} catch (final WebSocketException e) {
handleError(e, console);
}
view.close();
}
/**
* Returns pattern of the files to be added.
*
* @return pattern of the files to be added
*/
@NotNull
private List<String> getMultipleFilePatterns() {
final Selection<ResourceBasedNode<?>> selection = getExplorerSelection();
if (selection == null || selection.isEmpty()) {
return Collections.singletonList(ROOT_FOLDER);
} else {
final Set<String> paths = new HashSet<>();
final Set<String> directories = new HashSet<>();
for (final ResourceBasedNode<?> node : selection.getAllElements()) {
final String normalized = normalizePath(((HasStorablePath)node).getStorablePath());
if (ROOT_FOLDER.equals(normalized)) {
return Collections.singletonList(ROOT_FOLDER);
}
if (!node.isLeaf()) {
directories.add(normalized);
} else {
paths.add(normalized);
}
}
// filter out 'duplicates'
final List<String> result = new ArrayList<>();
for (final String path : paths) {
boolean found = false;
for (final String directory : directories) {
if (path.startsWith(directory)) {
found = true;
break;
}
}
if (!found) {
result.add(path);
}
}
// add directories to result
result.addAll(directories);
return result;
}
}
private boolean isSelectionSingle() {
final Selection<ResourceBasedNode<?>> selection = getExplorerSelection();
return (selection != null && selection.isSingleSelection());
}
private boolean isSelectionEmpty() {
final Selection<ResourceBasedNode<?>> selection = getExplorerSelection();
return (selection == null || selection.isEmpty());
}
private Selection<ResourceBasedNode<?>> getExplorerSelection() {
final Selection<ResourceBasedNode<?>> selection = (Selection<ResourceBasedNode<?>>)projectExplorer.getSelection();
if (selection == null || selection.isEmpty() || selection.getHeadElement() instanceof HasStorablePath) {
return selection;
} else {
return null;
}
}
private String getSingleFilePattern() {
final Selection<ResourceBasedNode<?>> selection = getExplorerSelection();
final String path;
if (selection == null || selection.isEmpty()) {
return ROOT_FOLDER;
} else {
path = ((HasStorablePath)selection.getHeadElement()).getStorablePath();
}
return normalizePath(path);
}
private String normalizePath(final String path) {
final String projectPath = project.getRootProject().getPath();
String pattern = path;
if (path.startsWith(projectPath)) {
pattern = pattern.replaceFirst(projectPath, "");
}
// no GWT emulation for File
if (pattern.startsWith("/")) {
pattern = pattern.replaceFirst("/", "");
}
if (pattern.length() == 0 || "/".equals(pattern)) {
pattern = ROOT_FOLDER;
}
return pattern;
}
/**
* Handler some action whether some exception happened.
*
* @param e
* exception that happened
* @param console
* console for displaying error
*/
private void handleError(@NotNull final Throwable e, GitOutputConsole console) {
String errorMessage = (e.getMessage() != null && !e.getMessage().isEmpty()) ? e.getMessage() : constant.addFailed();
console.printError(errorMessage);
consolesPanelPresenter.addCommandOutput(appContext.getDevMachineId(), console);
notificationManager.notify(constant.addFailed(), FAIL, true, project.getRootProject());
}
/**
* {@inheritDoc}
*/
@Override
public void onCancelClicked() {
view.close();
}
}