/* * * * Copyright (C) 2016 CS ROMANIA * * * * This program is free software; you can redistribute it and/or modify it * * under the terms of the GNU General Public License as published by the Free * * Software Foundation; either version 3 of the License, or (at your option) * * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * * more details. * * * * You should have received a copy of the GNU General Public License along * * with this program; if not, see http://www.gnu.org/licenses/ * */ package org.esa.snap.utils; import org.esa.snap.core.gpf.OperatorException; import org.esa.snap.core.gpf.descriptor.ToolAdapterOperatorDescriptor; import org.esa.snap.core.gpf.operators.tooladapter.ToolAdapterIO; import org.esa.snap.core.gpf.operators.tooladapter.ToolAdapterRegistry; import org.esa.snap.modules.ModulePackager; import org.openide.modules.Places; import java.io.File; import java.io.IOException; import java.nio.file.*; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.logging.Logger; /** * This singleton class watches for changes (additions/deletions) in the tool adapters folder. * * @author Cosmin Cara */ public enum AdapterWatcher { INSTANCE; private final Logger logger = Logger.getLogger(AdapterWatcher.class.getName()); private WatchService watcher; private final WatchEvent.Kind[] eventTypes = new WatchEvent.Kind[] { StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_DELETE }; private Thread thread; private volatile boolean isRunning; private volatile boolean isSuspended; private Map<Path, String> jarAliases; private Map<Path, WatchKey> monitoredPaths; AdapterWatcher() { try { watcher = FileSystems.getDefault().newWatchService(); monitoredPaths = new HashMap<>(); jarAliases = new HashMap<>(); Path adaptersFolder = ToolAdapterIO.getAdaptersPath(); File userDirectory = Places.getUserDirectory(); Path nbUserModulesPath = Paths.get(userDirectory != null ? userDirectory.getAbsolutePath() : "", "modules"); if (!Files.exists(nbUserModulesPath)) { Files.createDirectory(nbUserModulesPath); } readMap(); monitorPath(adaptersFolder); monitorPath(nbUserModulesPath); handleUninstalledModules(); thread = new Thread(() -> { while (isRunning) { WatchKey key; try { key = watcher.take(); } catch (InterruptedException ex) { return; } key.pollEvents().forEach(event -> { WatchEvent.Kind<?> kind = event.kind(); @SuppressWarnings("unchecked") WatchEvent<Path> ev = (WatchEvent<Path>) event; Path fileName = ev.context(); boolean isJar = fileName.toString().endsWith(".jar"); if (!isSuspended) { if (kind == StandardWatchEventKinds.ENTRY_CREATE) { if (!isJar) { folderAdded(adaptersFolder.resolve(fileName)); } else { jarAdded(nbUserModulesPath.resolve(fileName)); } } else if (kind == StandardWatchEventKinds.ENTRY_DELETE) { if (!isJar) { folderDeleted(adaptersFolder.resolve(fileName)); } else { jarDeleted(nbUserModulesPath.resolve(fileName)); } } } }); boolean valid = key.reset(); if (!valid) { break; } } }); } catch (IOException ignored) { } } /** * Enables this watch service to monitor the registered paths */ public void startMonitor() { isRunning = true; isSuspended = false; thread.start(); } /** * Stops this watch service to monitor the registered paths */ public void stopMonitor() { isRunning = false; } /** * Pauses monitoring on the registered paths */ public void suspend() { isSuspended = true; } /** * Resumes monitoring on the registered paths */ public void resume() { isSuspended = false; } /** * Registers the watch service for the given path to monitor changes. * * @param path The path to monitor */ public void monitorPath(Path path) throws IOException { if (path != null && Files.isDirectory(path)) { WatchKey key = path.register(watcher, eventTypes); monitoredPaths.put(path, key); logger.fine(String.format("Registered %s for watching", path.toString())); } } /** * Unregisters the watch service from the given path. * * @param path The path to unregister for */ public void unmonitorPath(Path path) { if (path != null && Files.isDirectory(path)) { WatchKey key = monitoredPaths.remove(path); if (key != null) { key.cancel(); logger.fine(String.format("Unregistered %s for watching", path.toString())); } } } private void handleUninstalledModules() { Path[] paths = new Path[jarAliases.size()]; jarAliases.keySet().toArray(paths); for (Path path : paths) { if (!Files.exists(path)) { jarDeleted(path); } } } private void folderAdded(Path folder) { try { Thread.sleep(500); ToolAdapterIO.registerAdapter(folder); }catch (InterruptedException | OperatorException ex){ logger.warning("Could not load adapter for folder added in repository: " + folder.toString() + " (error:" + ex.getMessage()); } } private void folderDeleted(Path folder) { if (folder != null) { String alias = folder.toFile().getName(); ToolAdapterOperatorDescriptor operatorDescriptor = ToolAdapterRegistry.INSTANCE.findByAlias(alias); if (operatorDescriptor != null) { ToolAdapterIO.removeOperator(operatorDescriptor); } } } private void jarAdded(Path jarFile) { Path unpackLocation = processJarFile(jarFile); if (unpackLocation != null) { suspend(); folderAdded(unpackLocation); saveMap(); resume(); } else { logger.warning(String.format("Jar %s has not been unpacked.", jarFile.toString())); } } private void jarDeleted(Path jarFile) { String alias = jarAliases.get(jarFile); if (alias == null) { String fileName = jarFile.getFileName().toString().replace(".jar", ""); int idx = fileName.lastIndexOf("."); if (idx > 0) { alias = fileName.substring(idx + 1); } else { alias = fileName; } } ToolAdapterOperatorDescriptor operatorDescriptor = ToolAdapterRegistry.INSTANCE.findByAlias(alias); if (operatorDescriptor != null) { suspend(); ToolAdapterIO.removeOperator(operatorDescriptor); jarAliases.remove(jarFile); saveMap(); resume(); } else { logger.warning(String.format("Cannot find adapter for %s", jarFile.toString())); } } private Path processJarFile(Path jarFile) { Path destination = null; String aliasOrName = null; try { aliasOrName = ModulePackager.getAdapterAlias(jarFile.toFile()); } catch (IOException ignored) { } if (aliasOrName != null) { jarAliases.put(jarFile, aliasOrName); destination = ToolAdapterIO.getAdaptersPath().resolve(aliasOrName); try { if (!Files.exists(destination)) { ModulePackager.unpackAdapterJar(jarFile.toFile(), destination.toFile()); } else { Path versionFile = destination.resolve("version.txt"); if (Files.exists(versionFile)) { String versionText = new String(Files.readAllBytes(versionFile)); String jarVersion = ModulePackager.getAdapterVersion(jarFile.toFile()); if (jarVersion != null && !versionText.equals(jarVersion)) { ModulePackager.unpackAdapterJar(jarFile.toFile(), destination.toFile()); logger.fine(String.format("The adapter with the name %s and version %s was replaced by version %s", aliasOrName, versionText, jarVersion)); } else { logger.fine(String.format("An adapter with the name %s and version %s already exists", aliasOrName, versionText)); } } else { ModulePackager.unpackAdapterJar(jarFile.toFile(), destination.toFile()); } } } catch (Exception e) { logger.severe(e.getMessage()); } } return destination; } private void saveMap() { Path path = ToolAdapterIO.getAdaptersPath().resolve("installed.dat"); StringBuilder builder = new StringBuilder(); for (Map.Entry<Path, String> entry : jarAliases.entrySet()) { builder.append(entry.getKey().toString()) .append(",") .append(entry.getValue()) .append("\n"); } try { Files.write(path, builder.toString().getBytes()); } catch (IOException e) { logger.severe(e.getMessage()); } } private void readMap() { Path path = ToolAdapterIO.getAdaptersPath().resolve("installed.dat"); jarAliases.clear(); try { if (Files.exists(path)) { List<String> lines = Files.readAllLines(path); for (String line : lines) { String[] tokens = line.split(","); jarAliases.put(Paths.get(tokens[0]), tokens[1]); } } } catch (IOException e) { logger.severe(e.getMessage()); } } }