/*******************************************************************************
* 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.api.vfs.impl.file.event.detectors;
import org.eclipse.che.api.core.jsonrpc.commons.RequestTransmitter;
import org.eclipse.che.api.core.jsonrpc.commons.RequestHandlerConfigurator;
import org.eclipse.che.api.project.shared.dto.event.ProjectTreeStateUpdateDto;
import org.eclipse.che.api.project.shared.dto.event.ProjectTreeTrackingOperationDto;
import org.eclipse.che.api.project.shared.dto.event.ProjectTreeTrackingOperationDto.Type;
import org.eclipse.che.api.vfs.watcher.FileWatcherManager;
import org.slf4j.Logger;
import javax.inject.Inject;
import javax.inject.Singleton;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Predicate;
import static com.google.common.collect.Sets.newConcurrentHashSet;
import static java.util.stream.Collectors.toSet;
import static org.eclipse.che.api.project.shared.dto.event.FileWatcherEventType.CREATED;
import static org.eclipse.che.api.project.shared.dto.event.FileWatcherEventType.DELETED;
import static org.eclipse.che.api.vfs.watcher.FileWatcherManager.EMPTY_CONSUMER;
import static org.eclipse.che.dto.server.DtoFactory.newDto;
import static org.slf4j.LoggerFactory.getLogger;
@Singleton
public class ProjectTreeTracker {
private static final Logger LOG = getLogger(ProjectTreeTracker.class);
private static final String OUTGOING_METHOD = "event:project-tree-state-changed";
private static final String INCOMING_METHOD = "track:project-tree";
private final Map<String, Integer> watchIdRegistry = new HashMap<>();
private final Set<String> timers = newConcurrentHashSet();
private final RequestTransmitter transmitter;
private final FileWatcherManager fileWatcherManager;
@Inject
public ProjectTreeTracker(FileWatcherManager fileWatcherManager, RequestTransmitter transmitter) {
this.fileWatcherManager = fileWatcherManager;
this.transmitter = transmitter;
}
@Inject
public void configureHandler(RequestHandlerConfigurator configurator) {
configurator.newConfiguration()
.methodName(INCOMING_METHOD)
.paramsAsDto(ProjectTreeTrackingOperationDto.class)
.noResult()
.withConsumer(getProjectTreeTrackingOperationConsumer());
}
private BiConsumer<String, ProjectTreeTrackingOperationDto> getProjectTreeTrackingOperationConsumer() {
return (String endpointId, ProjectTreeTrackingOperationDto operation) -> {
final Type type = operation.getType();
final String path = operation.getPath();
switch (type) {
case START: {
LOG.debug("Received project tree tracking operation START trigger.");
int pathRegistrationId = fileWatcherManager.registerByPath(path,
getCreateOperation(endpointId),
getModifyConsumer(endpointId),
getDeleteOperation(endpointId));
watchIdRegistry.put(path + endpointId, pathRegistrationId);
break;
}
case STOP: {
LOG.debug("Received project tree tracking operation STOP trigger.");
Predicate<Entry<String, Integer>> isSubPath = it -> it.getKey().startsWith(path) && it.getKey().endsWith(endpointId);
watchIdRegistry.entrySet()
.stream()
.filter(isSubPath)
.map(Entry::getKey)
.collect(toSet())
.stream()
.map(watchIdRegistry::remove)
.forEach(fileWatcherManager::unRegisterByPath);
break;
}
case SUSPEND: {
LOG.debug("Received project tree tracking operation SUSPEND trigger.");
fileWatcherManager.suspend();
break;
}
case RESUME: {
LOG.debug("Received project tree tracking operation RESUME trigger.");
fileWatcherManager.resume();
break;
}
default: {
LOG.error("Received file tracking operation UNKNOWN trigger.");
break;
}
}
};
}
private Consumer<String> getCreateOperation(String endpointId) {
return it -> {
if (timers.contains(it)) {
timers.remove(it);
} else {
ProjectTreeStateUpdateDto params = newDto(ProjectTreeStateUpdateDto.class).withPath(it).withType(CREATED);
transmitter.newRequest()
.endpointId(endpointId)
.methodName(OUTGOING_METHOD)
.paramsAsDto(params)
.sendAndSkipResult();
}
};
}
private Consumer<String> getModifyConsumer(String endpointId) {
return EMPTY_CONSUMER;
}
private Consumer<String> getDeleteOperation(String endpointId) {
return it -> {
timers.add(it);
new Timer().schedule(new TimerTask() {
@Override
public void run() {
if (timers.contains(it)) {
timers.remove(it);
ProjectTreeStateUpdateDto params = newDto(ProjectTreeStateUpdateDto.class).withPath(it).withType(DELETED);
transmitter.newRequest()
.endpointId(endpointId)
.methodName(OUTGOING_METHOD)
.paramsAsDto(params)
.sendAndSkipResult();
}
}
}, 1_000L);
};
}
}