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
6 * Copyright (C) 2012 - 2016 - Scilab Enterprises
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.
16 package org.scilab.modules.ui_data.filebrowser;
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;
40 * The tree table model abstract implementation
42 * @author Calixte DENIZET
44 public class ScilabFileBrowserModel extends AbstractScilabTreeTableModel
45 implements ScilabTreeTableModel {
47 private static final String[] names = {
48 UiDataMessages.NAME_COLUMN, UiDataMessages.SIZE_COLUMN,
49 UiDataMessages.TYPE_COLUMN, UiDataMessages.LASTMODIF_COLUMN
52 private static final Class[] types = {
53 ScilabTreeTableModel.class, FileSize.class, String.class, Date.class
56 private static final FileSize MINUSONE = new FileSize(-1);
58 /** Will trigger a model update on file creation/deletion */
59 private final class DirWatcher extends SwingWorker<Void, Object[]> {
61 public DirWatcher(FileNode root) {
62 watchDirectories(new Object[] {root});
66 protected Void doInBackground() throws Exception {
68 WatchKey key = watcher.take();
69 List<FileNode> treePath = new ArrayList<>();
72 // identify the associated file and publish for update on EDT
73 Watchable wa = key.watchable();
74 if (wa instanceof Path) {
76 Path r = ((FileNode) root).file.toPath().relativize(p);
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())) {
92 // in case of directory deletion, only reset the hierarchy up to an
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
102 // retrieve the corresponding FileNode
103 fn = treePath.get(treePath.size() - 1);
105 // reset the children of the FileNode
107 Object[] children = fn.getChildren();
109 // on directory creation, add them to the watch list
110 watchDirectories(children);
113 // trigger a refresh on the EDT for the full treepath
114 publish(new Object[] {key, treePath.toArray()});
119 protected void process(List<Object[]> chunks) {
120 for (Object[] o : chunks) {
121 WatchKey key = (WatchKey) o[0];
122 Object[] path = (Object[]) o[1];
124 List<WatchEvent<?>> events = key.pollEvents();
125 if (events.isEmpty()) {
129 // reinstall a watch on the directory
132 // reload part of the model
133 ScilabFileBrowserModel.this.fireTreeStructureChanged(this, path, null, null);
138 * append directories to be watched
140 * @param children file list
142 public void watchDirectories(Object[] children) {
143 for (Object o : children) {
144 if (!(o instanceof FileNode)) {
147 FileNode fn = (FileNode) o;
151 Path p = fn.file.toPath();
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);
164 private int order = 1;
165 private String baseDir = "";
166 WatchService watcher;
167 private DirWatcher dirWatcher;
169 /** Default constructor */
170 public ScilabFileBrowserModel() {
175 watcher = FileSystems.getDefault().newWatchService();
176 } catch (IOException ex) {
177 Logger.getLogger(SwingScilabTreeTable.class.getName()).log(Level.SEVERE, null, ex);
183 * Set the base directory
185 * @param baseDir the base directory
186 * @param stt the associated treetable component
188 public void setBaseDir(final String baseDir, final SwingScilabTreeTable stt) {
189 this.baseDir = baseDir;
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);
205 protected void done() {
206 if (!isCancelled()) {
207 stt.reload(ScilabFileBrowserModel.this);
208 stt.setDirRefresher(null, null);
212 stt.setDirRefresher(worker, ScilabFileBrowserModel.this);
216 public void setRoot(Object root) {
220 if (watcher != null) {
221 if (dirWatcher != null) {
222 dirWatcher.cancel(true);
224 dirWatcher = new DirWatcher((FileNode) root);
225 dirWatcher.execute();
228 // Force the root to load its children in the SwingWorker thread rather than in
230 watchDirectories(((FileNode) root).getChildren());
233 Object[] watchDirectories(Object[] objects) {
234 if (watcher != null && dirWatcher != null && objects != null) {
235 dirWatcher.watchDirectories(objects);
240 /** @return the base directory of this model */
241 public String getBaseDir() {
246 * Set the filter pattern
250 public void setFilter(Pattern pat) {
251 ((FileNode) root).setFilter(pat);
255 * @param node the node
256 * @return the file associated with the node
258 protected File getFile(Object node) {
259 FileNode fileNode = (FileNode) node;
260 return fileNode.getFile();
264 * @param node the node
265 * @return the children of this node
267 protected Object[] getChildren(Object node) {
268 FileNode fileNode = (FileNode) node;
269 FileNode[] raw = fileNode.getRawChildren();
271 return watchDirectories(fileNode.getChildren());
273 return fileNode.getChildren();
278 * @param node the node
279 * @return the number of children of this node
281 public int getChildCount(Object node) {
282 Object[] children = getChildren(node);
284 if (children != null) {
285 count = children.length;
288 if (parent == null || node != getRoot()) {
295 * @param node the node
296 * @param i the child number
297 * @return the child at position i
299 public Object getChild(Object node, int i) {
301 if (node == getRoot()) {
302 if (parent == null) {
303 ret = getChildren(node)[i];
308 ret = getChildren(node)[i - 1];
312 ret = getChildren(node)[i];
319 * @param node the node
320 * @return true is this node is a leaf
322 public boolean isLeaf(Object node) {
323 return node != getRoot() && ((FileNode) node).isLeaf();
327 public int getColumnCount() {
328 // TODO : remove the comment and let the choice to the user to remove or not the
330 return 1; // names.length;
334 public String getColumnName(int column) {
335 return names[column];
339 public Class getColumnClass(int column) {
340 return types[column];
344 public Object getValueAt(Object node, int column) {
345 File file = getFile(node);
349 return file.getName();
351 return file.isFile() ? new FileSize((int) file.length()) : MINUSONE;
354 String ext = FileUtils.getFileExtension(file);
356 return UiDataMessages.FILE;
358 return String.format(UiDataMessages.FILETYPE, FileUtils.getFileExtension(file));
361 return UiDataMessages.DIRECTORY;
364 return new Date(file.lastModified());
366 } catch (SecurityException se) {
372 /** Inner class to represent the parent node of a file node */
373 public static class ParentNode extends FileNode {
376 public ParentNode(File f) {
381 public boolean isLeaf() {
386 public String toString() {
391 /** Inner class to represent the size of file */
392 public static class FileSize {
400 public String toString() {
405 if (size >= 0 && size < 1000) {
409 DecimalFormat df = new DecimalFormat("#.#");
410 if (size >= 1000 && size < 1000000) {
411 return df.format(((float) size) / 1000f).toString() + " KB";
414 if (size >= 1000000 && size < 1000000000) {
415 return df.format(((float) size) / 1000000f).toString() + " MB";
418 return df.format(((float) size) / 1000000000f).toString() + " GB";