X-Git-Url: https://gerrit.simantics.org/r/gitweb?p=simantics%2Fplatform.git;a=blobdiff_plain;f=bundles%2Forg.simantics.document.ui%2Fsrc%2Forg%2Fsimantics%2Fdocument%2Fui%2Fgraphfile%2FExternalEditorAdapter.java;h=07ed6432771cced743a3a1ac5e17f3c7478c913c;hp=010237fef15695aacd0bcf219a9f95f13aaa0e1a;hb=c8e675ae59eebb045a095a07e54462d0fe87f5cb;hpb=969bd23cab98a79ca9101af33334000879fb60c5 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 010237fef..07ed64327 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 org.eclipse.swt.widgets.Display; -import org.eclipse.ui.PartInitException; -import org.simantics.db.ReadGraph; -import org.simantics.db.Resource; -import org.simantics.db.common.request.ReadRequest; -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.ui.workbench.editor.AbstractResourceEditorAdapter; -import org.simantics.ui.workbench.editor.EditorAdapter; -import org.simantics.utils.ui.ExceptionUtils; - -public class ExternalEditorAdapter extends AbstractResourceEditorAdapter implements EditorAdapter { - - public ExternalEditorAdapter() { - 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; - } - - @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); - } - } - } - }); - } - }); - - - } - -} +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 tempFiles = new ConcurrentHashMap<>(); + private static ExternalFileWatcher fileWatcher; + + public ExternalEditorAdapter() { + super(Messages.ExternalEditorAdapter_ExternalEditor, Activator.imageDescriptorFromPlugin("com.famfamfam.silk", "icons/page.png")); //$NON-NLS-2$ //$NON-NLS-3$ + } + + @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 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("_"); //$NON-NLS-1$ + } + 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); //$NON-NLS-1$ + } + return input; + }); + LOGGER.info("Adding tempFile {} with resource {}", filePath, input); //$NON-NLS-1$ + return filePath; + } + }); + } + try { + Editors.openExternalEditor(path.toFile()); + } catch (PartInitException e) { + ExceptionUtils.logAndShowError(e); + } + } + + public static String generateSHA1(String message) { + return hashString(message, "SHA-1"); //$NON-NLS-1$ + } + + private static String hashString(String message, String algorithm) { + try { + MessageDigest digest = MessageDigest.getInstance(algorithm); + byte[] hashedBytes = digest.digest(message.getBytes("UTF-8")); //$NON-NLS-1$ + + return convertByteArrayToHexString(hashedBytes); + } catch (NoSuchAlgorithmException | UnsupportedEncodingException ex) { + // Should not happen + LOGGER.error("Could not generate hash", ex); //$NON-NLS-1$ + } + return ""; //$NON-NLS-1$ + } + + 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); //$NON-NLS-1$ + Resource resource = tempFiles.get(newPath); + if (resource != null) { + GraphFileUtil.writeDataToGraph(newPath.toFile(), resource); + } else { + LOGGER.warn("No resource found for {}", newPath.toAbsolutePath()); //$NON-NLS-1$ + } + } else if (ENTRY_DELETE == kind) { + System.out.println("New path deleted: " + newPath); //$NON-NLS-1$ + } + } + if (!key.reset()) { + keys.remove(key); +// break; // loop + } + } catch (InterruptedException e) { + if (!stopped.get()) + LOGGER.error("Could not stop", e); //$NON-NLS-1$ + } catch (Throwable t) { + LOGGER.error("An error occured", t); //$NON-NLS-1$ + } + } + }); + } + + public void stop() { + stopped.set(true); + } + + private void register(Path path) throws IOException { + if (Files.isDirectory(path)) { + LOGGER.info("Registering path {}", path); //$NON-NLS-1$ + 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; + } + } +}