ddb9dabae71478a184ef9845af5debd7097327a8
[simantics/platform.git] / bundles / org.simantics.fileimport / src / org / simantics / fileimport / FileImportService.java
1 package org.simantics.fileimport;
2
3 import java.io.IOException;
4 import java.io.InputStream;
5 import java.io.OutputStream;
6 import java.nio.file.Files;
7 import java.nio.file.Path;
8 import java.nio.file.Paths;
9 import java.util.ArrayList;
10 import java.util.Collections;
11 import java.util.HashMap;
12 import java.util.List;
13 import java.util.Map;
14 import java.util.Optional;
15 import java.util.Properties;
16 import java.util.function.Consumer;
17
18 import org.osgi.framework.InvalidSyntaxException;
19 import org.osgi.framework.ServiceReference;
20 import org.simantics.databoard.util.Base64;
21 import org.simantics.fileimport.dropins.FileImportDropins;
22 import org.simantics.utils.FileUtils;
23 import org.slf4j.Logger;
24 import org.slf4j.LoggerFactory;
25
26 /**
27  * Utility class for Simantics File import functions
28  * 
29  * @author Jani Simomaa
30  *
31  */
32 public class FileImportService {
33
34     private static final Logger LOGGER = LoggerFactory.getLogger(FileImportService.class);
35     
36     private FileImportService() {}
37     
38     public static final String DB_FILE = ".simanticsdb";
39
40     private static List<IGenericFileImport> getFileImportServices() {
41         ServiceReference<?>[] serviceReferences = new ServiceReference<?>[0];
42         try {
43             serviceReferences = Activator.getContext().getAllServiceReferences(IGenericFileImport.class.getName(),
44                     null);
45         } catch (InvalidSyntaxException e) {
46             LOGGER.error("Could not get service references for IGenericFileImport!", e);
47         }
48         if (serviceReferences.length == 0)
49             return Collections.emptyList();
50
51         List<IGenericFileImport> services = new ArrayList<>(serviceReferences.length);
52         for (ServiceReference<?> reference : serviceReferences) {
53             IGenericFileImport service = (IGenericFileImport) Activator.getContext().getService(reference);
54             services.add(service);
55         }
56         return services;
57     }
58
59     /**
60      * Lists all supported file extensions which have a registered service for handling the import
61      * 
62      * @return Map containing the extension and the description of the extension in that order
63      */
64     public static Map<String, String> supportedExtensionsWithFilters() {
65         List<IGenericFileImport> services = getFileImportServices();
66         Map<String, String> extensionsWithFilters = new HashMap<>();
67         for (IGenericFileImport service : services)
68             extensionsWithFilters.putAll(service.allowedExtensionsWithFilters());
69
70         return extensionsWithFilters;
71     }
72     
73     private static class ConsumerHolder implements Consumer<Throwable> {
74
75         private Throwable throwable;
76         
77         @Override
78         public void accept(Throwable t) {
79             throwable = t;
80         }
81         
82         public Throwable getThrowable() {
83             return throwable;
84         }
85         
86     }
87     
88     public static String performFileImport(String base64, String name) throws Throwable {
89         byte[] bytes = Base64.decode(base64);
90         Path file = Activator.getModelsFolder().resolve(name);
91         Files.write(file, bytes);
92         
93         ConsumerHolder holder = new ConsumerHolder();
94         String result = performFileImport(file, Optional.of(holder));
95         if (holder.getThrowable() != null)
96             throw holder.getThrowable();
97         return result;
98     }
99
100     /**
101      * Method that performs the import of the given file. This method is called when e.g. {@link FileImportDropins} watcher detects {@link java.nio.file.StandardWatchEventKinds.ENTRY_CREATE} operation
102      * 
103      * @param file Path file to be imported
104      * @param callback Optional callback which can be used to catch Throwables thrown in the import process
105      */
106     public static String performFileImport(Path file, Optional<Consumer<Throwable>> callback) {
107         if (file.getFileName().toString().equals(DB_FILE)) {
108             return null;
109         }
110         String result = "Import failed";
111         Optional<IGenericFileImport> serviceOp = findServiceForFileExtension(file);
112         if (serviceOp.isPresent()) {
113             IGenericFileImport service = serviceOp.get();
114             try {
115                 Optional<String> resource = service.perform(file);
116                 saveResourceForPath(file, resource);
117                 result = resource.get();
118             } catch (Throwable t) {
119                 if (callback.isPresent()) {
120                     callback.get().accept(t);
121                 } else {
122                     LOGGER.error("Could not import file " + file, t);
123                 }
124             }
125         } else {
126             LOGGER.warn("Could not find service for importing file " + file);
127             if (callback.isPresent())
128                 callback.get().accept(new Exception("Could not find IGenericFileImport service for file " + file));
129         }
130         return result;
131     }
132
133     
134     /**
135      * Remove the entity that matches the file. This method is called when e.g. the {@link FileImportDropins} watcher detects {@link java.nio.file.StandardWatchEventKinds.ENTRY_DELETE} operation
136      * 
137      * @param file Path file that was deleted
138      * @param callback Optional callback to catch Throwables thrown during the deletion process
139      */
140     public static void removeResourceForFile(Path file, Optional<Consumer<Throwable>> callback) {
141         Optional<IGenericFileImport> serviceOp = findServiceForFileExtension(file);
142         serviceOp.ifPresent(service -> {
143             try {
144                 Optional<String> resource = getResourceForPath(file);
145                 if (!resource.isPresent())
146                     return;
147                 service.remove(resource.get());
148                 removeResourceForPath(file);
149             } catch (Throwable t) {
150                 if (callback.isPresent()) {
151                     callback.get().accept(t);
152                 } else {
153                     t.printStackTrace();
154                 }
155             }
156         });
157     }
158     
159     public static void removeFileForResource(long id, Optional<Consumer<Throwable>> callback) {
160         Optional<Path> fileOp;
161         try {
162             fileOp = findPathForId(id);
163         } catch (IOException e) {
164             e.printStackTrace();
165             return;
166         }
167         if (!fileOp.isPresent())
168             return;
169         Path file = fileOp.get();
170         Optional<IGenericFileImport> serviceOp = findServiceForFileExtension(file);
171         serviceOp.ifPresent(service -> {
172             try {
173                 Optional<String> resource = getResourceForPath(file);
174                 if (!resource.isPresent())
175                     return;
176                 service.remove(resource.get());
177                 removeResourceForPath(file);
178                 try {
179                     Files.delete(file);
180                 } catch (IOException e) {
181                     Files.delete(file);
182                 }
183             } catch (Throwable t) {
184                 if (callback.isPresent()) {
185                     callback.get().accept(t);
186                 } else {
187                     t.printStackTrace();
188                 }
189             }
190         });
191     }
192
193     private static Optional<Path> findPathForId(long id) throws IOException {
194         Path db = Activator.getDropinsFolder().resolve(DB_FILE);
195         if (!Files.exists(db))
196             Files.createFile(db);
197         Properties props = new Properties();
198         try (InputStream stream = Files.newInputStream(db)) {
199             props.load(stream);
200         }
201         for (Map.Entry<Object, Object> entry : props.entrySet()) {
202             Long value = Long.valueOf(entry.getValue().toString());
203             if (value.longValue() == id) {
204                 String key = (String) entry.getKey();
205                 return Optional.of(Paths.get(key));
206             }
207         }
208         return Optional.empty();
209     }
210
211     static final String FOLDER = "_folder_";
212     
213     /**
214      * Method for finding a File Import service for the given file based on the file extension
215      * 
216      * @param file Path file for which the import service is looked for
217      * @return Optiona IGenerigFileImport service which is able to handle the import of this type of file
218      */
219     public static Optional<IGenericFileImport> findServiceForFileExtension(Path file) {
220         String extension = "";
221
222         int i = file.getFileName().toString().lastIndexOf('.');
223         if (i > 0) {
224             extension = file.getFileName().toString().substring(i);
225         } else {
226             // Handle case that file is actually a directory
227             if (Files.isDirectory(file) || !Files.isRegularFile(file)) {
228                 extension = FOLDER;
229             }
230         }
231
232         List<IGenericFileImport> services = getFileImportServices();
233         for (IGenericFileImport service : services) {
234             for (Map.Entry<String, String> entry : service.allowedExtensionsWithFilters().entrySet()) {
235                 String possibleExtensions = entry.getKey();
236                 if (possibleExtensions.startsWith("*"))
237                     possibleExtensions = possibleExtensions.substring(1);
238                 if (possibleExtensions.equals(extension) || possibleExtensions.isEmpty()) {
239                     if (extension.equals(FOLDER) && possibleExtensions.equals(FOLDER)) {
240                         return Optional.of(service);
241                     } else if (!extension.isEmpty() && !extension.equals(FOLDER)){
242                         return Optional.of(service);
243                     }
244                 }
245             }
246         }
247         return Optional.empty();
248     }
249     
250     /**
251      * Method for listing all current paths and their corresponding identifiers in Simantics database
252      * 
253      * @return Map containing 
254      */
255     public static Map<String, String> getPathsAndResources() {
256         try {
257             Path db = Activator.getDropinsFolder().resolve(DB_FILE);
258             if (!Files.exists(db))
259                 Files.createFile(db);
260             Properties props = new Properties();
261             try (InputStream stream = Files.newInputStream(db)) {
262                 props.load(stream);
263             }
264             Map<String, String> map = new HashMap<>();
265             for (Map.Entry<Object, Object> entry : props.entrySet()) {
266                 String value = (String) entry.getValue();
267                 String key = (String) entry.getKey();
268                 map.put(key, value);
269             }
270             return map;
271         } catch (IOException e) {
272             e.printStackTrace();
273             return Collections.emptyMap();
274         }
275     }
276
277     private static void saveResourceForPath(Path file, Optional<String> resource) {
278         resource.ifPresent(res -> {
279             try {
280                 Path db = Activator.getDropinsFolder().resolve(DB_FILE);
281                 if (!Files.exists(db))
282                     Files.createFile(db);
283                 Properties props = new Properties();
284                 try (InputStream stream = Files.newInputStream(db)) {
285                     props.load(stream);
286                 }
287                 props.put(file.getFileName().toString(), resource.get());
288                 try (OutputStream stream = Files.newOutputStream(db)) {
289                     props.store(stream, null);
290                 }
291             } catch (IOException e) {
292                 e.printStackTrace();
293             }
294         });
295     }
296
297     private static void removeResourceForPath(Path file) throws IOException {
298         Path db = Activator.getDropinsFolder().resolve(DB_FILE);
299         if (!Files.exists(db))
300             Files.createFile(db);
301         Properties props = new Properties();
302         try (InputStream stream = Files.newInputStream(db)) {
303             props.load(stream);
304         }
305         props.remove(file.getFileName().toString());
306         try (OutputStream stream = Files.newOutputStream(db)) {
307             props.store(stream, null);
308         }
309     }
310     
311     private static Optional<String> getResourceForPath(Path file) throws IOException {
312         Path db = Activator.getDropinsFolder().resolve(DB_FILE);
313         if (!Files.exists(db))
314             Files.createFile(db);
315         Properties props = new Properties();
316         try (InputStream stream = Files.newInputStream(db)) {
317             props.load(stream);
318         }
319         String value = props.getProperty(file.getFileName().toString());
320         if (value == null)
321             return Optional.empty();
322         return Optional.of(value);
323     }
324 }