Skip to content

Commit

Permalink
Sequential restore of the persistence components
Browse files Browse the repository at this point in the history
Signed-off-by: Vladyslav Zhukovskyi <vzhukovs@redhat.com>
  • Loading branch information
vzhukovs committed Aug 4, 2017
1 parent b51a2b9 commit 530aa44
Show file tree
Hide file tree
Showing 9 changed files with 275 additions and 146 deletions.
Expand Up @@ -12,6 +12,8 @@

import elemental.json.JsonObject;

import org.eclipse.che.api.promises.client.Promise;

import javax.validation.constraints.NotNull;

/**
Expand All @@ -20,14 +22,48 @@
* a multibinder in order to be picked-up on IDE start-up:
* <p>
* <code>
* GinMapBinder<String, StateComponent> stateComponents = GinMapBinder.newMapBinder(binder(), String.class, StateComponent.class);
* stateComponents.addBinding("foo").to(Foo.class);
* GinMultibinder<StateComponent> stateComponents = GinMultibinder.newSetBinder(binder(), StateComponent.class);
* stateComponents.addBinding().to(Foo.class);
* </code>
* </p>
*
* @author Evgen Vidolob
* @author Vlad Zhukovskyi
*/
public interface StateComponent {

/**
* The minimum priority that state component can have.
*
* @see #getPriority()
* @since 5.16.0
*/
int MIN_PRIORITY = 1;

/**
* The default priority that is assigned to a state component.
*
* @see #getPriority()
* @since 5.16.0
*/
int DEFAULT_PRIORITY = 5;

/**
* The maximum priority that state component can have.
*
* @see #getPriority()
* @since 5.16.0
*/
int MAX_PRIORITY = 10;

/**
* Identifier of the component which may have persistent state. Usually uses to identify from the raw json.
*
* @return component id, any string value, non-null and non-empty. If null occurred, then component is not take a part in serialization
* @since 5.16.0
*/
String getId();

/**
* Called when component should store his state.
*
Expand All @@ -39,8 +75,23 @@ public interface StateComponent {
/**
* Called when component should restore his state.
*
* @param state the component state object
* @param state
* the component state object
*/
Promise<Void> loadState(@NotNull JsonObject state);

/**
* Priority of the execution. Each component may be prioritized to execute one self.
* Values should be from 1 (the last one to execute) to 10 (should be executed as first). Default value is 5.
*
* @return priority for the interceptor in which it should be run
* @see #MIN_PRIORITY
* @see #DEFAULT_PRIORITY
* @see #MAX_PRIORITY
* @since 5.16.0
*/
void loadState(@NotNull JsonObject state);
default int getPriority() {
return DEFAULT_PRIORITY;
}

}
Expand Up @@ -462,31 +462,32 @@ private JsonArray storeEditors(EditorPartStack partStack) {

@Override
@SuppressWarnings("unchecked")
public void loadState(@NotNull final JsonObject state) {
public Promise<Void> loadState(@NotNull final JsonObject state) {
if (state.hasKey("FILES")) {
JsonObject files = state.getObject("FILES");
EditorPartStack partStack = editorMultiPartStack.createRootPartStack();
final Map<EditorPartPresenter, EditorPartStack> activeEditors = new HashMap<>();
List<Promise<Void>> restore = restore(files, partStack, activeEditors);
Promise<ArrayOf<?>> promise = promiseProvider.all2(restore.toArray(new Promise[restore.size()]));
promise.then(new Operation() {
@Override
public void apply(Object arg) throws OperationException {
String activeFile = "";
if (state.hasKey("ACTIVE_EDITOR")) {
activeFile = state.getString("ACTIVE_EDITOR");
}
EditorPartPresenter activeEditorPart = null;
for (Map.Entry<EditorPartPresenter, EditorPartStack> entry : activeEditors.entrySet()) {
entry.getValue().setActivePart(entry.getKey());
if (activeFile.equals(entry.getKey().getEditorInput().getFile().getLocation().toString())) {
activeEditorPart = entry.getKey();
}
promise.then((Operation)ignored -> {
String activeFile = "";
if (state.hasKey("ACTIVE_EDITOR")) {
activeFile = state.getString("ACTIVE_EDITOR");
}
EditorPartPresenter activeEditorPart = null;
for (Map.Entry<EditorPartPresenter, EditorPartStack> entry : activeEditors.entrySet()) {
entry.getValue().setActivePart(entry.getKey());
if (activeFile.equals(entry.getKey().getEditorInput().getFile().getLocation().toString())) {
activeEditorPart = entry.getKey();
}
workspaceAgent.setActivePart(activeEditorPart);
}
workspaceAgent.setActivePart(activeEditorPart);
});

return promise.thenPromise(ignored -> promiseProvider.resolve(null));
}

return promiseProvider.resolve(null);
}

private List<Promise<Void>> restore(JsonObject files, EditorPartStack editorPartStack,
Expand Down Expand Up @@ -689,4 +690,14 @@ public void onEditorActivated(EditorPartPresenter editor) {
}
}
}

@Override
public int getPriority() {
return MIN_PRIORITY;
}

@Override
public String getId() {
return "editor";
}
}
Expand Up @@ -13,7 +13,10 @@
import elemental.json.Json;
import elemental.json.JsonArray;
import elemental.json.JsonObject;
import org.eclipse.che.api.promises.client.*;

import org.eclipse.che.api.promises.client.Promise;
import org.eclipse.che.api.promises.client.PromiseError;
import org.eclipse.che.api.promises.client.PromiseProvider;
import org.eclipse.che.ide.api.component.StateComponent;
import org.eclipse.che.ide.api.data.tree.Node;
import org.eclipse.che.ide.resource.Path;
Expand All @@ -25,58 +28,53 @@
import javax.inject.Inject;
import javax.inject.Singleton;
import javax.validation.constraints.NotNull;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

/**
* Persists and restore state of the project explorer presenter, like expanded nodes and showing hidden files
*
* @author Vlad Zhukovskyi
*/
@Singleton
public class ProjectExplorerStateComponent implements StateComponent {
private static final String PATH_PARAM_ID = "revealPath";
private static final String PATH_PARAM_ID = "revealPath";
private static final String SHOW_HIDDEN_FILES = "showHiddenFiles";

private final ProjectExplorerPresenter projectExplorer;
private final TreeResourceRevealer revealer;
private final LoaderFactory loaderFactory;
private final TreeResourceRevealer revealer;
private final LoaderFactory loaderFactory;
private final PromiseProvider promises;

@Inject
public ProjectExplorerStateComponent(ProjectExplorerPresenter projectExplorer, TreeResourceRevealer revealer, LoaderFactory loaderFactory) {
public ProjectExplorerStateComponent(ProjectExplorerPresenter projectExplorer,
TreeResourceRevealer revealer,
LoaderFactory loaderFactory,
PromiseProvider promises) {
this.projectExplorer = projectExplorer;
this.revealer = revealer;
this.loaderFactory = loaderFactory;
this.promises = promises;
}

@Override
public JsonObject getState() {
final List<Path> paths = new ArrayList<>();

/*
The main idea is to look up all expanded nodes in project tree and gather the last one's children.
Then check if child is resource, then we store the path in user preference.
*/

outer:
for (Node node : projectExplorer.getTree().getNodeStorage().getAll()) {
if (projectExplorer.getTree().isExpanded(node) && node instanceof ResourceNode) {

final List<Node> childrenToStore = projectExplorer.getTree().getNodeStorage().getChildren(node);

for (Node children : childrenToStore) {
if (children instanceof ResourceNode) {
paths.add(((ResourceNode) children).getData().getLocation());
continue outer;
}
}
}
}

JsonObject state = Json.createObject();
JsonArray array = Json.createArray();
state.put(PATH_PARAM_ID, array);

List<String> rawPaths = projectExplorer.getTree()
.getNodeStorage()
.getAll()
.stream()
.filter(node -> projectExplorer.getTree().isExpanded(node))
.filter(node -> node instanceof ResourceNode)
.map(node -> ((ResourceNode)node).getData().getLocation().toString())
.collect(Collectors.toList());

int i = 0;
for (Path path : paths) {
array.set(i++, path.toString());
for (String path : rawPaths) {
array.set(i++, path);
}

state.put(SHOW_HIDDEN_FILES, projectExplorer.isShowHiddenFiles());
Expand All @@ -85,69 +83,69 @@ public JsonObject getState() {
}

@Override
public void loadState(@NotNull JsonObject state) {
public Promise<Void> loadState(@NotNull JsonObject state) {
if (state.hasKey(SHOW_HIDDEN_FILES)) {
projectExplorer.showHiddenFiles(state.getBoolean(SHOW_HIDDEN_FILES));
}

JsonArray paths = state.hasKey(PATH_PARAM_ID) ? state.getArray(PATH_PARAM_ID) : Json.createArray();

if (paths.length() == 0) {
return;
return promises.resolve(null);
}

Promise<Node> revealPromise = null;

final MessageLoader loader = loaderFactory.newLoader("Restoring project structure...");
MessageLoader loader = loaderFactory.newLoader("Restoring project structure...");
loader.show();

for (int i = 0; i < paths.length(); i++) {
final String path = paths.getString(i);
String path = paths.getString(i);
if (revealPromise == null) {
revealPromise = revealer.reveal(Path.valueOf(path), false).thenPromise(new Function<Node, Promise<Node>>() {
@Override
public Promise<Node> apply(Node node) throws FunctionException {
if (node != null) {
projectExplorer.getTree().setExpanded(node, true, false);
}

return revealer.reveal(Path.valueOf(path), false);
}
});
revealPromise = revealer.reveal(Path.valueOf(path), false)
.thenPromise(this::doExpand);
continue;
}

revealPromise.thenPromise(new Function<Node, Promise<Node>>() {
@Override
public Promise<Node> apply(Node node) throws FunctionException {
if (node != null) {
projectExplorer.getTree().setExpanded(node, true, false);
}

return revealer.reveal(Path.valueOf(path), false);
}
}).catchError(new Function<PromiseError, Node>() {
@Override
public Node apply(PromiseError error) throws FunctionException {
Log.info(getClass(), error.getMessage());

return null;
}
});
revealPromise.thenPromise(node -> revealer.reveal(Path.valueOf(path), false))
.thenPromise(this::doExpand)
.catchError(this::logError);
}

if (revealPromise != null) {
revealPromise.then(new Operation<Node>() {
@Override
public void apply(Node node) throws OperationException {
loader.hide();
}
}).catchError(new Operation<PromiseError>() {
@Override
public void apply(PromiseError error) throws OperationException {
loader.hide();
}
revealPromise.then(node -> {
loader.hide();
}).catchError(error -> {
loader.hide();
});
}

if (revealPromise == null) {
return promises.resolve(null);
}

return revealPromise.thenPromise(ignored -> promises.resolve(null));
}

private Promise<Node> doExpand(Node node) {
projectExplorer.getTree().setExpanded(node, true);

return promises.resolve(null);
}

private Promise<Node> logError(PromiseError error) {
Log.info(getClass(), error.getMessage());

return promises.resolve(null);
}

@Override
public int getPriority() {
return MAX_PRIORITY;
}

@Override
public String getId() {
return "projectExplorer";
}
}

0 comments on commit 530aa44

Please sign in to comment.