-package org.simantics.document.ui.graphfile;\r
-\r
-import java.io.File;\r
-\r
-import org.eclipse.swt.widgets.Display;\r
-import org.eclipse.ui.PartInitException;\r
-import org.simantics.db.ReadGraph;\r
-import org.simantics.db.Resource;\r
-import org.simantics.db.common.request.ReadRequest;\r
-import org.simantics.db.exception.DatabaseException;\r
-import org.simantics.document.DocumentResource;\r
-import org.simantics.document.ui.Activator;\r
-import org.simantics.editors.Editors;\r
-import org.simantics.graphfile.ontology.GraphFileResource;\r
-import org.simantics.graphfile.util.GraphFileUtil;\r
-import org.simantics.ui.SimanticsUI;\r
-import org.simantics.ui.workbench.editor.AbstractResourceEditorAdapter;\r
-import org.simantics.ui.workbench.editor.EditorAdapter;\r
-import org.simantics.utils.ui.ExceptionUtils;\r
-\r
-public class ExternalEditorAdapter extends AbstractResourceEditorAdapter implements EditorAdapter {\r
- \r
- public ExternalEditorAdapter() {\r
- super("External Editor",Activator.imageDescriptorFromPlugin("com.famfamfam.silk", "icons/page.png"));\r
- }\r
-\r
- \r
- @Override\r
- public boolean canHandle(ReadGraph g, Resource r) throws DatabaseException {\r
- GraphFileResource gf = GraphFileResource.getInstance(g);\r
- DocumentResource doc = DocumentResource.getInstance(g);\r
- if (!g.isInstanceOf(r, doc.FileDocument))\r
- return false;\r
- String filename = g.getPossibleRelatedValue(r, gf.HasResourceName);\r
- return filename != null;\r
- }\r
- \r
- @Override\r
- protected void openEditor(final Resource input) throws Exception {\r
- SimanticsUI.getSession().asyncRequest(new ReadRequest() {\r
- \r
- @Override\r
- public void run(ReadGraph graph) throws DatabaseException {\r
- final File file = GraphFileUtil.toTempFile(graph, input);\r
- file.deleteOnExit();\r
- Display.getDefault().asyncExec(new Runnable() {\r
- \r
- @Override\r
- public void run() {\r
- \r
- if (file.exists() && file.canRead()) {\r
- try {\r
- Editors.openExternalEditor(file);\r
- } catch (PartInitException e) {\r
- ExceptionUtils.logAndShowError(e);\r
- }\r
- }\r
- }\r
- });\r
- }\r
- });\r
- \r
-\r
- }\r
-\r
-}\r
+package org.simantics.document.ui.graphfile;
+
+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.ui.PartInitException;
+import org.simantics.Simantics;
+import org.simantics.db.ReadGraph;
+import org.simantics.db.Resource;
+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.util.GraphFileUtil;
+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<Path, Resource> tempFiles = new ConcurrentHashMap<>();
+ private static ExternalFileWatcher fileWatcher;
+
+ public ExternalEditorAdapter() {
+ super("External Editor", Activator.imageDescriptorFromPlugin("com.famfamfam.silk", "icons/page.png"));
+ }
+
+ @Override
+ public boolean canHandle(ReadGraph g, Resource r) throws DatabaseException {
+ DocumentResource doc = DocumentResource.getInstance(g);
+ if (!g.isInstanceOf(r, doc.FileDocument))
+ return false;
+ return true;
+ }
+
+ @Override
+ protected void openEditor(final Resource input) throws Exception {
+ // ensure watching
+ watch();
+
+ Path path = null;
+ for (Entry<Path, Resource> entry : tempFiles.entrySet()) {
+ Resource value = entry.getValue();
+ if (input.equals(value)) {
+ path = entry.getKey();
+ }
+ }
+ if (path == null) {
+ path = Simantics.getSession().syncRequest(new UniqueRead<Path>() {
+
+ @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<WatchKey, Path> 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<Path> pathEvent = (WatchEvent<Path>) watchEvent;
+ Kind<Path> 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;
+ }
+ }
+}