* Bug 11852 fixed: now filebrowser updates (again)
[scilab.git] / scilab / modules / ui_data / src / java / org / scilab / modules / ui_data / filebrowser / ScilabFileBrowserModel.java
1 /*
2  * Scilab ( http://www.scilab.org/ ) - This file is part of Scilab
3  * Copyright (C) 2011 - DIGITEO - Calixte DENIZET
4  * Copyright (C) 2020 - ESI Group - Clement DAVID
5  *
6  * Copyright (C) 2012 - 2016 - Scilab Enterprises
7  *
8  * This file is hereby licensed under the terms of the GNU GPL v2.0,
9  * pursuant to article 5.3.4 of the CeCILL v.2.1.
10  * This file was originally licensed under the terms of the CeCILL v2.1,
11  * and continues to be available under such terms.
12  * For more information, see the COPYING file which you should have received
13  * along with this program.
14  *
15  */
16 package org.scilab.modules.ui_data.filebrowser;
17
18 import java.io.File;
19 import java.io.IOException;
20 import java.nio.file.FileSystems;
21 import java.nio.file.Path;
22 import java.nio.file.StandardWatchEventKinds;
23 import java.nio.file.WatchEvent;
24 import java.nio.file.WatchKey;
25 import java.nio.file.WatchService;
26 import java.nio.file.Watchable;
27 import java.text.DecimalFormat;
28 import java.util.ArrayList;
29 import java.util.Date;
30 import java.util.Iterator;
31 import java.util.List;
32 import java.util.logging.Level;
33 import java.util.logging.Logger;
34 import java.util.regex.Pattern;
35 import java.util.stream.Collectors;
36 import javax.swing.SwingWorker;
37 import org.scilab.modules.ui_data.utils.UiDataMessages;
38
39 /**
40  * The tree table model abstract implementation
41  *
42  * @author Calixte DENIZET
43  */
44 public class ScilabFileBrowserModel extends AbstractScilabTreeTableModel
45     implements ScilabTreeTableModel {
46
47     private static final String[] names = {
48         UiDataMessages.NAME_COLUMN, UiDataMessages.SIZE_COLUMN,
49         UiDataMessages.TYPE_COLUMN, UiDataMessages.LASTMODIF_COLUMN
50     };
51
52     private static final Class[] types = {
53         ScilabTreeTableModel.class, FileSize.class, String.class, Date.class
54     };
55
56     private static final FileSize MINUSONE = new FileSize(-1);
57
58     /** Will trigger a model update on file creation/deletion */
59     private final class DirWatcher extends SwingWorker<Void, Object[]> {
60
61         public DirWatcher(FileNode root) {
62             watchDirectories(new Object[] {root});
63         }
64
65         @Override
66         protected Void doInBackground() throws Exception {
67             for (; ; ) {
68                 WatchKey key = watcher.take();
69                 List<FileNode> treePath = new ArrayList<>();
70                 FileNode fn = null;
71
72                 // identify the associated file and publish for update on EDT
73                 Watchable wa = key.watchable();
74                 if (wa instanceof Path) {
75                     Path p = (Path) wa;
76                     Path r = ((FileNode) root).file.toPath().relativize(p);
77
78                     // reconstruct a TreePath
79                     treePath.add((FileNode) root);
80                     Iterator<Path> it = r.iterator();
81                     while (it.hasNext()) {
82                         Path name = it.next();
83                         FileNode[] children = (FileNode[]) treePath.get(treePath.size() - 1).getChildren();
84                         for (FileNode node : children) {
85                             if (node.name.equals(name.toString())) {
86                                 treePath.add(node);
87                                 break;
88                             }
89                         }
90                     }
91
92                     // in case of directory deletion, only reset the hierarchy up to an
93                     // existing directory
94                     treePath = treePath.stream().filter(n -> n.file.exists()).collect(Collectors.toList());
95                     if (treePath.isEmpty()) {
96                         // if the root is remove then cancel the watcher
97                         watcher.close();
98                         watcher = null;
99                         return null;
100                     }
101
102                     // retrieve the corresponding FileNode
103                     fn = treePath.get(treePath.size() - 1);
104
105                     // reset the children of the FileNode
106                     fn.resetChildren();
107                     Object[] children = fn.getChildren();
108
109                     // on directory creation, add them to the watch list
110                     watchDirectories(children);
111                 }
112
113                 // trigger a refresh on the EDT for the full treepath
114                 publish(new Object[] {key, treePath.toArray()});
115             }
116         }
117
118         @Override
119         protected void process(List<Object[]> chunks) {
120             for (Object[] o : chunks) {
121                 WatchKey key = (WatchKey) o[0];
122                 Object[] path = (Object[]) o[1];
123
124                 List<WatchEvent<?>> events = key.pollEvents();
125                 if (events.isEmpty()) {
126                     continue;
127                 }
128
129                 // reinstall a watch on the directory
130                 key.reset();
131
132                 // reload part of the model
133                 ScilabFileBrowserModel.this.fireTreeStructureChanged(this, path, null, null);
134             }
135         }
136
137         /**
138          * append directories to be watched
139          *
140          * @param children file list
141          */
142         public void watchDirectories(Object[] children) {
143             for (Object o : children) {
144                 if (!(o instanceof FileNode)) {
145                     continue;
146                 }
147                 FileNode fn = (FileNode) o;
148                 if (fn.isFile) {
149                     continue;
150                 }
151                 Path p = fn.file.toPath();
152                 try {
153                     p.register(
154                         watcher, StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_DELETE);
155                 } catch (IOException ex) {
156                     ex.printStackTrace();
157                     Logger.getLogger(SwingScilabTreeTable.class.getName()).log(Level.SEVERE, null, ex);
158                     cancel(false);
159                 }
160             }
161         }
162     }
163
164     private int order = 1;
165     private String baseDir = "";
166     WatchService watcher;
167     private DirWatcher dirWatcher;
168
169     /** Default constructor */
170     public ScilabFileBrowserModel() {
171         super();
172
173         try {
174             // Setup watchers
175             watcher = FileSystems.getDefault().newWatchService();
176         } catch (IOException ex) {
177             Logger.getLogger(SwingScilabTreeTable.class.getName()).log(Level.SEVERE, null, ex);
178             watcher = null;
179         }
180     }
181
182     /**
183      * Set the base directory
184      *
185      * @param baseDir the base directory
186      * @param stt the associated treetable component
187      */
188     public void setBaseDir(final String baseDir, final SwingScilabTreeTable stt) {
189         this.baseDir = baseDir;
190         SwingWorker worker =
191         new SwingWorker<Void, Void>() {
192             protected Void doInBackground() throws Exception {
193                 File f = new File(baseDir);
194                 setRoot(new FileNode(f, -1));
195                 File parentFile = f.getParentFile();
196                 if (parentFile != null) {
197                     parent = new ParentNode(parentFile);
198                 } else {
199                     parent = null;
200                 }
201
202                 return null;
203             }
204
205             protected void done() {
206                 if (!isCancelled()) {
207                     stt.reload(ScilabFileBrowserModel.this);
208                     stt.setDirRefresher(null, null);
209                 }
210             }
211         };
212         stt.setDirRefresher(worker, ScilabFileBrowserModel.this);
213         worker.execute();
214     }
215
216     public void setRoot(Object root) {
217         super.setRoot(root);
218
219         // watch for changes
220         if (watcher != null) {
221             if (dirWatcher != null) {
222                 dirWatcher.cancel(true);
223             }
224             dirWatcher = new DirWatcher((FileNode) root);
225             dirWatcher.execute();
226         }
227
228         // Force the root to load its children in the SwingWorker thread rather than in
229         // EDT
230         watchDirectories(((FileNode) root).getChildren());
231     }
232
233     Object[] watchDirectories(Object[] objects) {
234         if (watcher != null && dirWatcher != null && objects != null) {
235             dirWatcher.watchDirectories(objects);
236         }
237         return objects;
238     }
239
240     /** @return the base directory of this model */
241     public String getBaseDir() {
242         return baseDir;
243     }
244
245     /**
246      * Set the filter pattern
247      *
248      * @pat the pattern
249      */
250     public void setFilter(Pattern pat) {
251         ((FileNode) root).setFilter(pat);
252     }
253
254     /**
255      * @param node the node
256      * @return the file associated with the node
257      */
258     protected File getFile(Object node) {
259         FileNode fileNode = (FileNode) node;
260         return fileNode.getFile();
261     }
262
263     /**
264      * @param node the node
265      * @return the children of this node
266      */
267     protected Object[] getChildren(Object node) {
268         FileNode fileNode = (FileNode) node;
269         FileNode[] raw = fileNode.getRawChildren();
270         if (raw == null) {
271             return watchDirectories(fileNode.getChildren());
272         } else {
273             return fileNode.getChildren();
274         }
275     }
276
277     /**
278      * @param node the node
279      * @return the number of children of this node
280      */
281     public int getChildCount(Object node) {
282         Object[] children = getChildren(node);
283         int count = 0;
284         if (children != null) {
285             count = children.length;
286         }
287
288         if (parent == null || node != getRoot()) {
289             return count;
290         }
291         return count + 1;
292     }
293
294     /**
295      * @param node the node
296      * @param i the child number
297      * @return the child at position i
298      */
299     public Object getChild(Object node, int i) {
300         Object ret;
301         if (node == getRoot()) {
302             if (parent == null) {
303                 ret = getChildren(node)[i];
304             } else {
305                 if (i == 0) {
306                     ret = parent;
307                 } else {
308                     ret = getChildren(node)[i - 1];
309                 }
310             }
311         } else {
312             ret = getChildren(node)[i];
313         }
314
315         return ret;
316     }
317
318     /**
319      * @param node the node
320      * @return true is this node is a leaf
321      */
322     public boolean isLeaf(Object node) {
323         return node != getRoot() && ((FileNode) node).isLeaf();
324     }
325
326     /** {@inheritDoc} */
327     public int getColumnCount() {
328         // TODO : remove the comment and let the choice to the user to remove or not the
329         // columns
330         return 1; // names.length;
331     }
332
333     /** {@inheritDoc} */
334     public String getColumnName(int column) {
335         return names[column];
336     }
337
338     /** {@inheritDoc} */
339     public Class getColumnClass(int column) {
340         return types[column];
341     }
342
343     /** {@inheritDoc} */
344     public Object getValueAt(Object node, int column) {
345         File file = getFile(node);
346         try {
347             switch (column) {
348                 case 0:
349                     return file.getName();
350                 case 1:
351                     return file.isFile() ? new FileSize((int) file.length()) : MINUSONE;
352                 case 2:
353                     if (file.isFile()) {
354                         String ext = FileUtils.getFileExtension(file);
355                         if (ext.isEmpty()) {
356                             return UiDataMessages.FILE;
357                         } else {
358                             return String.format(UiDataMessages.FILETYPE, FileUtils.getFileExtension(file));
359                         }
360                     } else {
361                         return UiDataMessages.DIRECTORY;
362                     }
363                 case 3:
364                     return new Date(file.lastModified());
365             }
366         } catch (SecurityException se) {
367         }
368
369         return null;
370     }
371
372     /** Inner class to represent the parent node of a file node */
373     public static class ParentNode extends FileNode {
374
375         /** {@inheritDoc} */
376         public ParentNode(File f) {
377             super(f, -1);
378         }
379
380         /** {@inheritDoc} */
381         public boolean isLeaf() {
382             return true;
383         }
384
385         /** {@inheritDoc} */
386         public String toString() {
387             return "..";
388         }
389     }
390
391     /** Inner class to represent the size of file */
392     public static class FileSize {
393
394         int size;
395
396         FileSize(int size) {
397             this.size = size;
398         }
399
400         public String toString() {
401             if (size < 0) {
402                 return "";
403             }
404
405             if (size >= 0 && size < 1000) {
406                 return size + " B";
407             }
408
409             DecimalFormat df = new DecimalFormat("#.#");
410             if (size >= 1000 && size < 1000000) {
411                 return df.format(((float) size) / 1000f).toString() + " KB";
412             }
413
414             if (size >= 1000000 && size < 1000000000) {
415                 return df.format(((float) size) / 1000000f).toString() + " MB";
416             }
417
418             return df.format(((float) size) / 1000000000f).toString() + " GB";
419         }
420     }
421 }