/******************************************************************************* * Copyright (c) 2012-2017 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.resources.impl; import com.google.common.annotations.Beta; 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.Function; import org.eclipse.che.api.promises.client.Promise; import org.eclipse.che.api.promises.client.PromiseProvider; import org.eclipse.che.api.promises.client.callback.AsyncPromiseHelper.RequestCall; import org.eclipse.che.ide.api.dialogs.ConfirmCallback; import org.eclipse.che.ide.api.dialogs.DialogFactory; import org.eclipse.che.ide.api.notification.NotificationManager; import org.eclipse.che.ide.api.resources.Resource; import org.eclipse.che.ide.api.resources.ResourceChangedEvent; import org.eclipse.che.ide.api.resources.ResourceChangedEvent.ResourceChangedHandler; import org.eclipse.che.ide.api.resources.ResourceDelta; import org.eclipse.che.ide.api.resources.modification.ClipboardManager; import org.eclipse.che.ide.api.resources.modification.CutResourceMarker; import org.eclipse.che.ide.resource.Path; import org.eclipse.che.ide.resources.reveal.RevealResourceEvent; import org.eclipse.che.ide.util.loging.Log; import static java.util.Arrays.copyOf; import static org.eclipse.che.api.promises.client.callback.AsyncPromiseHelper.createFromAsyncRequest; 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.resources.ResourceDelta.REMOVED; /** * Serves {@link ClipboardManager}, providing common mechanism for support cut/copy/paste operations based on the * input resources. Acts as intermediate level between resources and UI components. * <p/> * Note, that this class is not intended to be used directly by third-party components. * * @author Vlad Zhukovskiy * @see ClipboardManager * @since 4.4.0 */ @Beta @Singleton class CopyPasteManager implements ResourceChangedHandler { private final PromiseProvider promises; private final DialogFactory dialogFactory; private final NotificationManager notificationManager; private final EventBus eventBus; private Resource[] resources; private boolean move; @Inject public CopyPasteManager(PromiseProvider promises, DialogFactory dialogFactory, NotificationManager notificationManager, EventBus eventBus) { this.promises = promises; this.dialogFactory = dialogFactory; this.notificationManager = notificationManager; this.eventBus = eventBus; eventBus.addHandler(ResourceChangedEvent.getType(), this); } protected Resource[] getResources() { return resources; } protected void setResources(Resource[] resources, boolean move) { if (this.resources != null) { for (Resource resource : this.resources) { resource.deleteMarker(CutResourceMarker.ID); } } this.resources = resources; this.move = move; if (move) { for (Resource resource : resources) { resource.addMarker(new CutResourceMarker()); } } } protected void paste(Path destination) { if (resources == null || resources.length == 0) { Log.debug(getClass(), "Resources to process was not found"); return; } final Resource[] resourcesToProcess = copyOf(resources, resources.length); final Path lastCopiedResource = destination.append(resourcesToProcess[resourcesToProcess.length - 1].getName()); pasteSuccessively(promises.resolve(null), resourcesToProcess, 0, destination).then(ignored -> { eventBus.fireEvent(new RevealResourceEvent(lastCopiedResource)); }); } private Promise<Void> pasteSuccessively(Promise<Void> promise, Resource[] resources, int position, final Path destination) { if (position == resources.length) { return promise; } final Resource resource = resources[position]; final Promise<Void> derivedPromise; if (move) { derivedPromise = promise.thenPromise(ignored -> moveResource(resource, destination.append(resource.getName()))); } else { derivedPromise = promise.thenPromise(ignored -> copyResource(resource, destination.append(resource.getName()))); } return pasteSuccessively(derivedPromise, resources, ++position, destination); } private Promise<Void> moveResource(final Resource resource, final Path destination) { //simple move without overwriting return resource.move(destination) .thenPromise((Function<Resource, Promise<Void>>)ignored -> promises.resolve(null)) .catchErrorPromise(error -> { //resource may already exists if (error.getMessage().contains("exists")) { //create dialog with overwriting option return createFromAsyncRequest((RequestCall<Void>)callback -> { //handle overwrite operation final ConfirmCallback overwrite = () -> { //copy with overwriting resource.move(destination, true).then(ignored -> { callback.onSuccess(null); }).catchError(error1 -> { callback.onFailure(error1.getCause()); }); }; //skip this resource final ConfirmCallback skip = () -> callback.onSuccess(null); //change destination name final ConfirmCallback rename = () -> dialogFactory.createInputDialog("Enter new name", "Enter new name", value -> { final Path newPath = destination.parent().append(value); moveResource(resource, newPath) .then(callback::onSuccess) .catchError(error1 -> { callback.onFailure(error1.getCause()); }); }, null).show(); dialogFactory.createChoiceDialog("Error", error.getMessage(), "Overwrite", "Skip", "Change Name", overwrite, skip, rename).show(); }); } else { //notify user about failed copying notificationManager.notify("Error moving resource", error.getMessage(), FAIL, FLOAT_MODE); return promises.resolve(null); } }); } private Promise<Void> copyResource(final Resource resource, final Path destination) { //simple copy without overwriting return resource.copy(destination) .thenPromise((Function<Resource, Promise<Void>>)resource1 -> promises.resolve(null)) .catchErrorPromise(error -> { //resource may already exists if (error.getMessage().contains("exists")) { //create dialog with overwriting option return createFromAsyncRequest((RequestCall<Void>)callback -> { //handle overwrite operation final ConfirmCallback overwrite = () -> { //copy with overwriting resource.copy(destination, true).then(ignored -> { callback.onSuccess(null); }).catchError(error1 -> { callback.onFailure(error1.getCause()); }); }; //skip this resource final ConfirmCallback skip = () -> callback.onSuccess(null); //change destination name final ConfirmCallback rename = () -> dialogFactory.createInputDialog("Enter new name", "Enter new name", value -> { final Path newPath = destination.parent().append(value); copyResource(resource, newPath) .then(callback::onSuccess) .catchError(error1 -> { callback.onFailure(error1.getCause()); }); }, null).show(); dialogFactory.createChoiceDialog("Error", error.getMessage(), "Overwrite", "Skip", "Change Name", overwrite, skip, rename).show(); }); } else { //notify user about failed copying notificationManager.notify("Error copying resource", error.getMessage(), FAIL, FLOAT_MODE); return promises.resolve(null); } }); } @Override public void onResourceChanged(ResourceChangedEvent event) { final ResourceDelta delta = event.getDelta(); //delta should be removed and resources is not null if (delta.getKind() != REMOVED || resources == null) { return; } for (int i = 0; i < resources.length; i++) { final Resource resource = resources[i]; if (delta.getResource().getLocation().isPrefixOf(resource.getLocation())) { int size = resources.length; int numMoved = resources.length - i - 1; if (numMoved > 0) { System.arraycopy(resources, i + 1, resources, i, numMoved); } resources = copyOf(resources, --size); } } } }