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<Path, Resource> 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<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;
+ }
+ }
}