/******************************************************************************* * 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.watcher; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; import java.io.File; import java.nio.file.Path; import java.nio.file.WatchEvent; import java.util.Map; import java.util.Map.Entry; import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; import java.util.function.Predicate; import static com.google.common.collect.Sets.newHashSet; import static java.nio.file.Files.isDirectory; import static org.eclipse.che.api.vfs.watcher.FileWatcherUtils.toInternalPath; @Singleton public class FileWatcherEventHandler { private static final Logger LOG = LoggerFactory.getLogger(FileWatcherManager.class); private final AtomicInteger idCounter = new AtomicInteger(); private final Map<Path, Set<FileWatcherOperation>> operations = new ConcurrentHashMap<>(); private final File root; @Inject public FileWatcherEventHandler(@Named("che.user.workspaces.storage") File root) { this.root = root; } /** * Registers create, modify and delete operations when item defined by path * parameter is correspondingly being created, modified or deleted. If path * parameter denotes directory than listed operations are considered to * correspond any directory entry related event. Return value corresponds to * identifier that is used to distinguish different operation sets registered * for the same path and can be later used to unregister those operations. * * @param path * path * @param create * consumer for entry create event * @param modify * consumer for entry modify event * @param delete * consumer for entry delete event * * @return number identifier of operations set */ int register(Path path, Consumer<String> create, Consumer<String> modify, Consumer<String> delete) { LOG.debug("Registering operations for path '{}'"); int id = idCounter.incrementAndGet(); FileWatcherOperation operation = new FileWatcherOperation(id, create, modify, delete); operations.putIfAbsent(path, newHashSet()); operations.get(path).add(operation); return id; } /** * Cancels registration of operations identified by parameter. Identifier * is unique and generated during registration phase. If there left no * operation sets registered for a path it is also removed. * * @param id * identifier * * @return path that corresponds to operations set identified by parameter */ Path unRegister(int id) { Path dir = null; for (Entry<Path, Set<FileWatcherOperation>> entry : operations.entrySet()) { Path path = entry.getKey(); Set<FileWatcherOperation> operationsSet = entry.getValue(); Predicate<FileWatcherOperation> predicate = it -> Objects.equals(id, it.getId()); if (operationsSet.removeIf(predicate)) { dir = isDirectory(path) ? path : path.getParent(); } if (operationsSet.isEmpty()) { operations.remove(path); } } return dir; } /** * Handles event passed form file watcher system. Path parameter is expected * to be passed in a normal operation system file system form and is * transformed into internal virtual file system format before further * processing. * * @param path * path that the event is originated from * @param kind * kind of event (e.g. created, modified, removed) */ void handle(Path path, WatchEvent.Kind<?> kind) { Path dir = path.getParent(); String internalPath = toInternalPath(root.toPath(), path); Set<FileWatcherOperation> dirOperations = operations.get(dir); Set<FileWatcherOperation> itemOperations = operations.get(path); if (dirOperations != null) { dirOperations.stream() .map(it -> it.get(kind)) .filter(Optional::isPresent) .map(Optional::get) .forEach(it -> it.accept(internalPath)); } if (itemOperations != null) { itemOperations.stream() .map(it -> it.get(kind)) .filter(Optional::isPresent) .map(Optional::get) .forEach(it -> it.accept(internalPath)); } } }