From e140d8f611f3ebcee3f36370aa0b50344d896953 Mon Sep 17 00:00:00 2001 From: jsimomaa Date: Fri, 29 Dec 2017 13:21:36 +0200 Subject: [PATCH] Fall back to HasName if HasResourceName not available for GraphFile Also adding watching of externally opened files and writeback for data changed by the external editor Now with hashed URI based filename refs #7699 Change-Id: I2eadb539d599b0e55840e0fd7f141e44004833fc --- .../org/simantics/document/ui/Activator.java | 14 ++ .../ui/graphfile/ExternalEditorAdapter.java | 227 +++++++++++++++--- .../graphfile/util/GraphFileUtil.java | 4 +- 3 files changed, 207 insertions(+), 38 deletions(-) diff --git a/bundles/org.simantics.document.ui/src/org/simantics/document/ui/Activator.java b/bundles/org.simantics.document.ui/src/org/simantics/document/ui/Activator.java index a4db0a578..5a25c5b6b 100644 --- a/bundles/org.simantics.document.ui/src/org/simantics/document/ui/Activator.java +++ b/bundles/org.simantics.document.ui/src/org/simantics/document/ui/Activator.java @@ -11,10 +11,16 @@ *******************************************************************************/ package org.simantics.document.ui; +import java.nio.file.Path; +import java.nio.file.Paths; + +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.Platform; import org.eclipse.jface.resource.ImageDescriptor; import org.eclipse.ui.plugin.AbstractUIPlugin; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; +import org.simantics.document.ui.graphfile.ExternalEditorAdapter; /** * The activator class controls the plug-in life cycle @@ -31,6 +37,8 @@ public class Activator extends AbstractUIPlugin { public ImageDescriptor cross; public ImageDescriptor clock_red; + + private static Path location; /** * The constructor @@ -54,6 +62,8 @@ public class Activator extends AbstractUIPlugin { cross = ImageDescriptor.createFromURL(bundle.getResource("icons/silk_small/cross.png")); clock_red = ImageDescriptor.createFromURL(bundle.getResource("icons/silk_small/clock_red.png")); + IPath ipath = Platform.getStateLocation(bundle); + location = Paths.get(ipath.toOSString()); } /* @@ -61,6 +71,7 @@ public class Activator extends AbstractUIPlugin { * @see org.eclipse.ui.plugin.AbstractUIPlugin#stop(org.osgi.framework.BundleContext) */ public void stop(BundleContext context) throws Exception { + ExternalEditorAdapter.stopFileWatcher(); plugin = null; super.stop(context); } @@ -74,4 +85,7 @@ public class Activator extends AbstractUIPlugin { return plugin; } + public static Path getInstanceLocation() { + return location; + } } diff --git a/bundles/org.simantics.document.ui/src/org/simantics/document/ui/graphfile/ExternalEditorAdapter.java b/bundles/org.simantics.document.ui/src/org/simantics/document/ui/graphfile/ExternalEditorAdapter.java index 03b256b28..42190d878 100644 --- a/bundles/org.simantics.document.ui/src/org/simantics/document/ui/graphfile/ExternalEditorAdapter.java +++ b/bundles/org.simantics.document.ui/src/org/simantics/document/ui/graphfile/ExternalEditorAdapter.java @@ -1,66 +1,219 @@ package org.simantics.document.ui.graphfile; -import java.io.File; +import static java.nio.file.StandardWatchEventKinds.ENTRY_DELETE; +import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY; +import static java.nio.file.StandardWatchEventKinds.OVERFLOW; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.WatchEvent; +import java.nio.file.WatchEvent.Kind; +import java.nio.file.WatchKey; +import java.nio.file.WatchService; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicBoolean; -import org.eclipse.swt.widgets.Display; import org.eclipse.ui.PartInitException; +import org.simantics.Simantics; import org.simantics.db.ReadGraph; import org.simantics.db.Resource; -import org.simantics.db.common.request.ReadRequest; +import org.simantics.db.common.request.UniqueRead; import org.simantics.db.exception.DatabaseException; import org.simantics.document.DocumentResource; import org.simantics.document.ui.Activator; import org.simantics.editors.Editors; -import org.simantics.graphfile.ontology.GraphFileResource; import org.simantics.graphfile.util.GraphFileUtil; -import org.simantics.ui.SimanticsUI; +import org.simantics.layer0.Layer0; import org.simantics.ui.workbench.editor.AbstractResourceEditorAdapter; import org.simantics.ui.workbench.editor.EditorAdapter; import org.simantics.utils.ui.ExceptionUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class ExternalEditorAdapter extends AbstractResourceEditorAdapter implements EditorAdapter { - + + private static final Logger LOGGER = LoggerFactory.getLogger(ExternalEditorAdapter.class); + + private static final Map tempFiles = new ConcurrentHashMap<>(); + private static ExternalFileWatcher fileWatcher; + public ExternalEditorAdapter() { - super("External Editor",Activator.imageDescriptorFromPlugin("com.famfamfam.silk", "icons/page.png")); + super("External Editor", Activator.imageDescriptorFromPlugin("com.famfamfam.silk", "icons/page.png")); } - @Override public boolean canHandle(ReadGraph g, Resource r) throws DatabaseException { - GraphFileResource gf = GraphFileResource.getInstance(g); DocumentResource doc = DocumentResource.getInstance(g); if (!g.isInstanceOf(r, doc.FileDocument)) return false; - String filename = g.getPossibleRelatedValue(r, gf.HasResourceName); - return filename != null; + return true; } - @Override - protected void openEditor(final Resource input) throws Exception { - SimanticsUI.getSession().asyncRequest(new ReadRequest() { - - @Override - public void run(ReadGraph graph) throws DatabaseException { - final File file = GraphFileUtil.toTempFile(graph, input); - file.deleteOnExit(); - Display.getDefault().asyncExec(new Runnable() { - - @Override - public void run() { - - if (file.exists() && file.canRead()) { - try { - Editors.openExternalEditor(file); - } catch (PartInitException e) { - ExceptionUtils.logAndShowError(e); - } - } - } - }); - } - }); - + @Override + protected void openEditor(final Resource input) throws Exception { + // ensure watching + watch(); + + Path path = null; + for (Entry entry : tempFiles.entrySet()) { + Resource value = entry.getValue(); + if (input.equals(value)) { + path = entry.getKey(); + } + } + if (path == null) { + path = Simantics.getSession().syncRequest(new UniqueRead() { - } + @Override + public Path perform(ReadGraph graph) throws DatabaseException { + StringBuilder sb = new StringBuilder(); + String uri = graph.getPossibleURI(input); + if (uri != null) { + sb.append(generateSHA1(uri)).append("_"); + } + sb.append(graph.getRelatedValue(input, Layer0.getInstance(graph).HasName).toString()); + Path filePath = Activator.getInstanceLocation().resolve(sb.toString()); + tempFiles.computeIfAbsent(filePath, t -> { + try { + GraphFileUtil.writeDataToFile(graph, input, filePath.toFile()); + } catch (IOException | DatabaseException e) { + LOGGER.error("Could not write data to file ", e); + } + return input; + }); + LOGGER.info("Adding tempFile {} with resource {}", filePath, input); + return filePath; + } + }); + } + try { + Editors.openExternalEditor(path.toFile()); + } catch (PartInitException e) { + ExceptionUtils.logAndShowError(e); + } + } + + public static String generateSHA1(String message) { + return hashString(message, "SHA-1"); + } + + private static String hashString(String message, String algorithm) { + try { + MessageDigest digest = MessageDigest.getInstance(algorithm); + byte[] hashedBytes = digest.digest(message.getBytes("UTF-8")); + + return convertByteArrayToHexString(hashedBytes); + } catch (NoSuchAlgorithmException | UnsupportedEncodingException ex) { + // Should not happen + LOGGER.error("Could not generate hash", ex); + } + return ""; + } + + private static String convertByteArrayToHexString(byte[] arrayBytes) { + StringBuffer stringBuffer = new StringBuffer(); + for (int i = 0; i < arrayBytes.length; i++) { + stringBuffer.append(Integer.toString((arrayBytes[i] & 0xff) + 0x100, 16) + .substring(1)); + } + return stringBuffer.toString(); + } + + private static void watch() throws IOException { + if (fileWatcher == null) { + synchronized (ExternalEditorAdapter.class) { + if (fileWatcher == null) { + fileWatcher = new ExternalFileWatcher(); + } + } + } + } + + static class ExternalFileWatcher{ + + private ExecutorService service = Executors.newFixedThreadPool(1, r -> { + Thread t = new Thread(r); + t.setDaemon(true); + return t; + }); + + private final WatchService ws; + private final AtomicBoolean stopped = new AtomicBoolean(true); + private ConcurrentHashMap keys = new ConcurrentHashMap<>(); + + public ExternalFileWatcher() throws IOException { + ws = FileSystems.getDefault().newWatchService(); + + register(Activator.getInstanceLocation()); + + service.submit(() -> { + stopped.set(false); + + while (!stopped.get()) { + try { + WatchKey key = ws.take(); + for (WatchEvent watchEvent : key.pollEvents()) { + if (OVERFLOW == watchEvent.kind()) + continue; // loop + + @SuppressWarnings("unchecked") + WatchEvent pathEvent = (WatchEvent) watchEvent; + Kind kind = pathEvent.kind(); + Path parent = keys.get(key); + Path newPath = parent.resolve(pathEvent.context()); + if (ENTRY_MODIFY == kind) { + LOGGER.info("New path modified: " + newPath); + Resource resource = tempFiles.get(newPath); + if (resource != null) { + GraphFileUtil.writeDataToGraph(newPath.toFile(), resource); + } else { + LOGGER.warn("No resource found for {}", newPath.toAbsolutePath()); + } + } else if (ENTRY_DELETE == kind) { + System.out.println("New path deleted: " + newPath); + } + } + if (!key.reset()) { + keys.remove(key); +// break; // loop + } + } catch (InterruptedException e) { + if (!stopped.get()) + LOGGER.error("Could not stop", e); + } catch (Throwable t) { + LOGGER.error("An error occured", t); + } + } + }); + } + + public void stop() { + stopped.set(true); + } + + private void register(Path path) throws IOException { + if (Files.isDirectory(path)) { + LOGGER.info("Registering path {}", path); + WatchKey key = path.toAbsolutePath().register(ws, ENTRY_DELETE, ENTRY_MODIFY); + keys.put(key, path); + } + } + } + public static void stopFileWatcher() { + tempFiles.clear(); + if (fileWatcher != null) { + fileWatcher.stop(); + fileWatcher = null; + } + } } diff --git a/bundles/org.simantics.graphfile/src/org/simantics/graphfile/util/GraphFileUtil.java b/bundles/org.simantics.graphfile/src/org/simantics/graphfile/util/GraphFileUtil.java index 76003e023..fbfcc3a14 100644 --- a/bundles/org.simantics.graphfile/src/org/simantics/graphfile/util/GraphFileUtil.java +++ b/bundles/org.simantics.graphfile/src/org/simantics/graphfile/util/GraphFileUtil.java @@ -76,7 +76,9 @@ public class GraphFileUtil { public static File toTempFile(ReadGraph graph, Resource res)throws DatabaseException { GraphFileResource gf = GraphFileResource.getInstance(graph); - String filename = graph.getRelatedValue(res, gf.HasResourceName); + String filename = graph.getPossibleRelatedValue(res, gf.HasResourceName); + if (filename == null) + filename = graph.getRelatedValue(res, Layer0.getInstance(graph).HasName); int index = filename.lastIndexOf("."); String name = ""; -- 2.47.1