]> gerrit.simantics Code Review - simantics/platform.git/blob
42190d87829283cf165d4ede9a49422abf645fae
[simantics/platform.git] /
1 package org.simantics.document.ui.graphfile;
2
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;
6
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;
18 import java.util.Map;
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;
24
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;
41
42 public class ExternalEditorAdapter extends AbstractResourceEditorAdapter implements EditorAdapter {
43
44     private static final Logger LOGGER = LoggerFactory.getLogger(ExternalEditorAdapter.class);
45     
46     private static final Map<Path, Resource> tempFiles = new ConcurrentHashMap<>();
47     private static ExternalFileWatcher fileWatcher;
48
49         public ExternalEditorAdapter() {
50                 super("External Editor", Activator.imageDescriptorFromPlugin("com.famfamfam.silk", "icons/page.png"));
51         }
52
53         @Override
54         public boolean canHandle(ReadGraph g, Resource r) throws DatabaseException {
55                 DocumentResource doc = DocumentResource.getInstance(g);
56                 if (!g.isInstanceOf(r, doc.FileDocument))
57                         return false;
58                 return true;
59         }
60         
61     @Override
62     protected void openEditor(final Resource input) throws Exception {
63         // ensure watching
64         watch();
65         
66         Path path = null;
67         for (Entry<Path, Resource> entry : tempFiles.entrySet()) {
68             Resource value = entry.getValue();
69             if (input.equals(value)) {
70                 path = entry.getKey();
71             }
72         }
73         if (path == null) {
74             path = Simantics.getSession().syncRequest(new UniqueRead<Path>() {
75
76                 @Override
77                 public Path perform(ReadGraph graph) throws DatabaseException {
78                     StringBuilder sb = new StringBuilder();
79                     String uri = graph.getPossibleURI(input);
80                     if (uri != null) {
81                         sb.append(generateSHA1(uri)).append("_");
82                     }
83                     sb.append(graph.getRelatedValue(input, Layer0.getInstance(graph).HasName).toString());
84                     Path filePath = Activator.getInstanceLocation().resolve(sb.toString());
85                     tempFiles.computeIfAbsent(filePath, t -> {
86                         try {
87                             GraphFileUtil.writeDataToFile(graph, input, filePath.toFile());
88                         } catch (IOException | DatabaseException e) {
89                             LOGGER.error("Could not write data to file ", e);
90                         }
91                         return input;
92                     });
93                     LOGGER.info("Adding tempFile {} with resource {}", filePath, input);
94                     return filePath;
95                 }
96             });
97         }
98         try {
99             Editors.openExternalEditor(path.toFile());
100         } catch (PartInitException e) {
101             ExceptionUtils.logAndShowError(e);
102         }
103     }
104
105     public static String generateSHA1(String message) {
106         return hashString(message, "SHA-1");
107     }
108
109     private static String hashString(String message, String algorithm) {
110         try {
111             MessageDigest digest = MessageDigest.getInstance(algorithm);
112             byte[] hashedBytes = digest.digest(message.getBytes("UTF-8"));
113      
114             return convertByteArrayToHexString(hashedBytes);
115         } catch (NoSuchAlgorithmException | UnsupportedEncodingException ex) {
116             // Should not happen
117             LOGGER.error("Could not generate hash", ex);
118         }
119         return "";
120     }
121
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)
126                     .substring(1));
127         }
128         return stringBuffer.toString();
129     }
130
131     private static void watch() throws IOException {
132         if (fileWatcher == null) {
133             synchronized (ExternalEditorAdapter.class) {
134                 if (fileWatcher == null) {
135                     fileWatcher = new ExternalFileWatcher();
136                 }
137             }
138         }
139     }
140
141     static class ExternalFileWatcher{
142
143         private ExecutorService service = Executors.newFixedThreadPool(1, r -> {
144             Thread t = new Thread(r);
145             t.setDaemon(true);
146             return t;
147         });
148         
149         private final WatchService ws;
150         private final AtomicBoolean stopped = new AtomicBoolean(true);
151         private ConcurrentHashMap<WatchKey, Path> keys = new ConcurrentHashMap<>();
152         
153         public ExternalFileWatcher() throws IOException {
154             ws = FileSystems.getDefault().newWatchService();
155             
156             register(Activator.getInstanceLocation());
157             
158             service.submit(() -> {
159                 stopped.set(false);
160
161                 while (!stopped.get()) {
162                     try {
163                         WatchKey key = ws.take();
164                         for (WatchEvent<?> watchEvent : key.pollEvents()) {
165                             if (OVERFLOW == watchEvent.kind())
166                                 continue; // loop
167                             
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);
178                                 } else {
179                                     LOGGER.warn("No resource found for {}", newPath.toAbsolutePath());
180                                 }
181                             } else if (ENTRY_DELETE == kind) {
182                                 System.out.println("New path deleted: " + newPath);
183                             }
184                         }
185                         if (!key.reset()) {
186                             keys.remove(key);
187 //                            break; // loop
188                         }
189                     } catch (InterruptedException e) {
190                         if (!stopped.get())
191                             LOGGER.error("Could not stop", e);
192                     } catch (Throwable t) {
193                         LOGGER.error("An error occured", t);
194                     }
195                 }
196             });
197         }
198         
199         public void stop() {
200             stopped.set(true);
201         }
202         
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);
207                 keys.put(key, path);
208             }
209         }
210     }
211
212     public static void stopFileWatcher() {
213         tempFiles.clear();
214         if (fileWatcher != null) {
215             fileWatcher.stop();
216             fileWatcher = null;
217         }
218     }
219 }