// Copyright 2015 Eivind Vegsundvåg // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package ninja.eivind.hotsreplayuploader.files; import javafx.application.Platform; import ninja.eivind.hotsreplayuploader.models.ReplayFile; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.File; import java.io.IOException; import java.nio.file.*; import java.util.ArrayList; import java.util.List; import static java.nio.file.StandardWatchEventKinds.*; /** * Service object that manages a single {@link WatchService}. This handler manages new {@link ReplayFile}s as they are * detected as {@link File}s, mapping them into correct models and notifying * {@link ninja.eivind.hotsreplayuploader.services.UploaderService} */ public class WatchHandler implements Runnable { private static final Logger LOG = LoggerFactory.getLogger(WatchHandler.class); private final List<FileListener> fileListeners; private final Path path; public WatchHandler(final Path path) throws IOException { fileListeners = new ArrayList<>(); this.path = path; } @Override public void run() { try (WatchService watchService = FileSystems.getDefault().newWatchService()) { path.register(watchService, ENTRY_CREATE); while (true) { WatchKey key = watchService.take(); key.pollEvents().forEach(this::handleEvent); if(!key.reset()) { break; } } } catch (IOException e) { LOG.error("Failed to read", e); } catch (InterruptedException e) { LOG.info("{}-{} was interrupted. Winding down thread.", getClass().getSimpleName(), path); } } @SuppressWarnings("unchecked") private boolean handleEvent(WatchEvent<?> watchEvent) { final WatchEvent.Kind<?> kind = watchEvent.kind(); if (kind == OVERFLOW) { return false; } final WatchEvent<Path> event = (WatchEvent<Path>) watchEvent; final Path fileName = event.context(); LOG.info("Received " + kind + " for path " + fileName); final File file = new File(path.toFile(), fileName.toString()); if (!file.getName().endsWith(".StormReplay")) { return false; } final ReplayFile replayFile = getReplayFileForEvent(kind, file); Platform.runLater(() -> { fileListeners.forEach(fileListener -> fileListener.handle(replayFile)); LOG.info("File " + replayFile + " registered with listeners."); }); return true; } private ReplayFile getReplayFileForEvent(final WatchEvent.Kind<?> kind, final File file) { final Handler handler; if (kind == ENTRY_MODIFY) { handler = new ModificationHandler(file); } else { handler = new CreationHandler(file); } return handler.getFile(); } public void addListener(final FileListener fileListener) { fileListeners.add(fileListener); } private abstract class Handler { private final File target; private Handler(File target) { this.target = target; } protected File getTarget() { return target; } public abstract ReplayFile getFile(); } private class CreationHandler extends Handler { private CreationHandler(final File file) { super(file); } @Override public ReplayFile getFile() { try { Thread.sleep(5000L); } catch (InterruptedException e) { LOG.warn("Thread interrupted while awaiting file stabilization.", e); } final File file = new File(getTarget().toString()); return new ReplayFile(file); } } private class ModificationHandler extends Handler { private ModificationHandler(File target) { super(target); } @Override public ReplayFile getFile() { try { do { Thread.sleep(10000L); } while (getTarget().lastModified() > System.currentTimeMillis() - 20000L); return new ReplayFile(new File(getTarget().toString())); } catch (InterruptedException e) { LOG.warn("Thread interrupted while awaiting file modification check.", e); return null; } } } }