1 package org.simantics.document.ui.graphfile;
3 import static java.nio.file.StandardWatchEventKinds.ENTRY_DELETE;
4 import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY;
5 import static java.nio.file.StandardWatchEventKinds.OVERFLOW;
7 import java.io.IOException;
8 import java.io.UnsupportedEncodingException;
9 import java.nio.file.FileSystems;
10 import java.nio.file.Files;
11 import java.nio.file.Path;
12 import java.nio.file.WatchEvent;
13 import java.nio.file.WatchEvent.Kind;
14 import java.nio.file.WatchKey;
15 import java.nio.file.WatchService;
16 import java.security.MessageDigest;
17 import java.security.NoSuchAlgorithmException;
19 import java.util.Map.Entry;
20 import java.util.concurrent.ConcurrentHashMap;
21 import java.util.concurrent.ExecutorService;
22 import java.util.concurrent.Executors;
23 import java.util.concurrent.atomic.AtomicBoolean;
25 import org.eclipse.ui.PartInitException;
26 import org.simantics.Simantics;
27 import org.simantics.db.ReadGraph;
28 import org.simantics.db.Resource;
29 import org.simantics.db.common.request.UniqueRead;
30 import org.simantics.db.exception.DatabaseException;
31 import org.simantics.document.DocumentResource;
32 import org.simantics.document.ui.Activator;
33 import org.simantics.editors.Editors;
34 import org.simantics.graphfile.util.GraphFileUtil;
35 import org.simantics.layer0.Layer0;
36 import org.simantics.ui.workbench.editor.AbstractResourceEditorAdapter;
37 import org.simantics.ui.workbench.editor.EditorAdapter;
38 import org.simantics.utils.ui.ExceptionUtils;
39 import org.slf4j.Logger;
40 import org.slf4j.LoggerFactory;
42 public class ExternalEditorAdapter extends AbstractResourceEditorAdapter implements EditorAdapter {
44 private static final Logger LOGGER = LoggerFactory.getLogger(ExternalEditorAdapter.class);
46 private static final Map<Path, Resource> tempFiles = new ConcurrentHashMap<>();
47 private static ExternalFileWatcher fileWatcher;
49 public ExternalEditorAdapter() {
50 super("External Editor", Activator.imageDescriptorFromPlugin("com.famfamfam.silk", "icons/page.png"));
54 public boolean canHandle(ReadGraph g, Resource r) throws DatabaseException {
55 DocumentResource doc = DocumentResource.getInstance(g);
56 if (!g.isInstanceOf(r, doc.FileDocument))
62 protected void openEditor(final Resource input) throws Exception {
67 for (Entry<Path, Resource> entry : tempFiles.entrySet()) {
68 Resource value = entry.getValue();
69 if (input.equals(value)) {
70 path = entry.getKey();
74 path = Simantics.getSession().syncRequest(new UniqueRead<Path>() {
77 public Path perform(ReadGraph graph) throws DatabaseException {
78 StringBuilder sb = new StringBuilder();
79 String uri = graph.getPossibleURI(input);
81 sb.append(generateSHA1(uri)).append("_");
83 sb.append(graph.getRelatedValue(input, Layer0.getInstance(graph).HasName).toString());
84 Path filePath = Activator.getInstanceLocation().resolve(sb.toString());
85 tempFiles.computeIfAbsent(filePath, t -> {
87 GraphFileUtil.writeDataToFile(graph, input, filePath.toFile());
88 } catch (IOException | DatabaseException e) {
89 LOGGER.error("Could not write data to file ", e);
93 LOGGER.info("Adding tempFile {} with resource {}", filePath, input);
99 Editors.openExternalEditor(path.toFile());
100 } catch (PartInitException e) {
101 ExceptionUtils.logAndShowError(e);
105 public static String generateSHA1(String message) {
106 return hashString(message, "SHA-1");
109 private static String hashString(String message, String algorithm) {
111 MessageDigest digest = MessageDigest.getInstance(algorithm);
112 byte[] hashedBytes = digest.digest(message.getBytes("UTF-8"));
114 return convertByteArrayToHexString(hashedBytes);
115 } catch (NoSuchAlgorithmException | UnsupportedEncodingException ex) {
117 LOGGER.error("Could not generate hash", ex);
122 private static String convertByteArrayToHexString(byte[] arrayBytes) {
123 StringBuffer stringBuffer = new StringBuffer();
124 for (int i = 0; i < arrayBytes.length; i++) {
125 stringBuffer.append(Integer.toString((arrayBytes[i] & 0xff) + 0x100, 16)
128 return stringBuffer.toString();
131 private static void watch() throws IOException {
132 if (fileWatcher == null) {
133 synchronized (ExternalEditorAdapter.class) {
134 if (fileWatcher == null) {
135 fileWatcher = new ExternalFileWatcher();
141 static class ExternalFileWatcher{
143 private ExecutorService service = Executors.newFixedThreadPool(1, r -> {
144 Thread t = new Thread(r);
149 private final WatchService ws;
150 private final AtomicBoolean stopped = new AtomicBoolean(true);
151 private ConcurrentHashMap<WatchKey, Path> keys = new ConcurrentHashMap<>();
153 public ExternalFileWatcher() throws IOException {
154 ws = FileSystems.getDefault().newWatchService();
156 register(Activator.getInstanceLocation());
158 service.submit(() -> {
161 while (!stopped.get()) {
163 WatchKey key = ws.take();
164 for (WatchEvent<?> watchEvent : key.pollEvents()) {
165 if (OVERFLOW == watchEvent.kind())
168 @SuppressWarnings("unchecked")
169 WatchEvent<Path> pathEvent = (WatchEvent<Path>) watchEvent;
170 Kind<Path> kind = pathEvent.kind();
171 Path parent = keys.get(key);
172 Path newPath = parent.resolve(pathEvent.context());
173 if (ENTRY_MODIFY == kind) {
174 LOGGER.info("New path modified: " + newPath);
175 Resource resource = tempFiles.get(newPath);
176 if (resource != null) {
177 GraphFileUtil.writeDataToGraph(newPath.toFile(), resource);
179 LOGGER.warn("No resource found for {}", newPath.toAbsolutePath());
181 } else if (ENTRY_DELETE == kind) {
182 System.out.println("New path deleted: " + newPath);
189 } catch (InterruptedException e) {
191 LOGGER.error("Could not stop", e);
192 } catch (Throwable t) {
193 LOGGER.error("An error occured", t);
203 private void register(Path path) throws IOException {
204 if (Files.isDirectory(path)) {
205 LOGGER.info("Registering path {}", path);
206 WatchKey key = path.toAbsolutePath().register(ws, ENTRY_DELETE, ENTRY_MODIFY);
212 public static void stopFileWatcher() {
214 if (fileWatcher != null) {