View Javadoc
1   package org.argeo.cms.ui.fs;
2   
3   import java.io.IOException;
4   import java.net.URI;
5   import java.net.URISyntaxException;
6   import java.nio.file.DirectoryStream;
7   import java.nio.file.FileSystem;
8   import java.nio.file.Files;
9   import java.nio.file.Path;
10  import java.nio.file.Paths;
11  import java.nio.file.spi.FileSystemProvider;
12  import java.security.PrivilegedActionException;
13  import java.security.PrivilegedExceptionAction;
14  import java.util.ArrayList;
15  import java.util.List;
16  
17  import javax.jcr.Node;
18  import javax.jcr.Repository;
19  import javax.jcr.Session;
20  
21  import org.argeo.api.NodeUtils;
22  import org.argeo.cms.CmsException;
23  import org.argeo.cms.auth.CurrentUser;
24  import org.argeo.cms.ui.util.CmsUiUtils;
25  import org.argeo.eclipse.ui.ColumnDefinition;
26  import org.argeo.eclipse.ui.EclipseUiUtils;
27  import org.argeo.eclipse.ui.fs.FileIconNameLabelProvider;
28  import org.argeo.eclipse.ui.fs.FsTableViewer;
29  import org.argeo.eclipse.ui.fs.FsUiConstants;
30  import org.argeo.eclipse.ui.fs.FsUiUtils;
31  import org.argeo.eclipse.ui.fs.NioFileLabelProvider;
32  import org.argeo.jcr.JcrUtils;
33  import org.eclipse.jface.viewers.DoubleClickEvent;
34  import org.eclipse.jface.viewers.IDoubleClickListener;
35  import org.eclipse.jface.viewers.ISelectionChangedListener;
36  import org.eclipse.jface.viewers.IStructuredSelection;
37  import org.eclipse.jface.viewers.SelectionChangedEvent;
38  import org.eclipse.jface.viewers.Viewer;
39  import org.eclipse.swt.SWT;
40  import org.eclipse.swt.custom.SashForm;
41  import org.eclipse.swt.events.KeyEvent;
42  import org.eclipse.swt.events.KeyListener;
43  import org.eclipse.swt.events.ModifyEvent;
44  import org.eclipse.swt.events.ModifyListener;
45  import org.eclipse.swt.events.MouseAdapter;
46  import org.eclipse.swt.events.MouseEvent;
47  import org.eclipse.swt.events.SelectionAdapter;
48  import org.eclipse.swt.events.SelectionEvent;
49  import org.eclipse.swt.graphics.Point;
50  import org.eclipse.swt.layout.GridData;
51  import org.eclipse.swt.layout.GridLayout;
52  import org.eclipse.swt.layout.RowData;
53  import org.eclipse.swt.layout.RowLayout;
54  import org.eclipse.swt.widgets.Button;
55  import org.eclipse.swt.widgets.Composite;
56  import org.eclipse.swt.widgets.Control;
57  import org.eclipse.swt.widgets.Label;
58  import org.eclipse.swt.widgets.Table;
59  import org.eclipse.swt.widgets.Text;
60  
61  /**
62   * Default CMS browser composite: a sashForm layout with bookmarks at the left
63   * hand side, a simple table in the middle and an overview at right hand side.
64   */
65  public class CmsFsBrowser extends Composite {
66  	// private final static Log log = LogFactory.getLog(CmsFsBrowser.class);
67  	private static final long serialVersionUID = -40347919096946585L;
68  
69  	private final FileSystemProvider nodeFileSystemProvider;
70  	private final Node currentBaseContext;
71  
72  	// UI Parts for the browser
73  	private Composite leftPannelCmp;
74  	private Composite filterCmp;
75  	private Text filterTxt;
76  	private FsTableViewer directoryDisplayViewer;
77  	private Composite rightPannelCmp;
78  
79  	private FsContextMenu contextMenu;
80  
81  	// Local context (this composite is state full)
82  	private Path initialPath;
83  	private Path currDisplayedFolder;
84  	private Path currSelected;
85  
86  	// local variables (to be cleaned)
87  	private int bookmarkColWith = 500;
88  
89  	/*
90  	 * WARNING: unfinalised implementation of the mechanism to retrieve base
91  	 * paths
92  	 */
93  
94  	private final static String NODE_PREFIX = "node://";
95  
96  	private String getCurrentHomePath() {
97  		Session session = null;
98  		try {
99  			Repository repo = currentBaseContext.getSession().getRepository();
100 			session = CurrentUser.tryAs(() -> repo.login());
101 			String homepath = NodeUtils.getUserHome(session).getPath();
102 			return homepath;
103 		} catch (Exception e) {
104 			throw new CmsException("Cannot retrieve Current User Home Path", e);
105 		} finally {
106 			JcrUtils.logoutQuietly(session);
107 		}
108 	}
109 
110 	protected Path[] getMyFilesPath() {
111 		// return Paths.get(System.getProperty("user.dir"));
112 		String currHomeUriStr = NODE_PREFIX + getCurrentHomePath();
113 		try {
114 			URI uri = new URI(currHomeUriStr);
115 			FileSystem fileSystem = nodeFileSystemProvider.getFileSystem(uri);
116 			if (fileSystem == null) {
117 				PrivilegedExceptionAction<FileSystem> pea = new PrivilegedExceptionAction<FileSystem>() {
118 					@Override
119 					public FileSystem run() throws Exception {
120 						return nodeFileSystemProvider.newFileSystem(uri, null);
121 					}
122 
123 				};
124 				fileSystem = CurrentUser.tryAs(pea);
125 			}
126 			Path[] paths = { fileSystem.getPath(getCurrentHomePath()), fileSystem.getPath("/") };
127 			return paths;
128 		} catch (URISyntaxException | PrivilegedActionException e) {
129 			throw new RuntimeException("unable to initialise home file system for " + currHomeUriStr, e);
130 		}
131 	}
132 
133 	private Path[] getMyGroupsFilesPath() {
134 		// TODO
135 		Path[] paths = { Paths.get(System.getProperty("user.dir")), Paths.get("/tmp") };
136 		return paths;
137 	}
138 
139 	private Path[] getMyBookmarks() {
140 		// TODO
141 		Path[] paths = { Paths.get(System.getProperty("user.dir")), Paths.get("/tmp"), Paths.get("/opt") };
142 		return paths;
143 	}
144 
145 	/* End of warning */
146 
147 	public CmsFsBrowser(Composite parent, int style, Node context, FileSystemProvider fileSystemProvider) {
148 		super(parent, style);
149 		this.nodeFileSystemProvider = fileSystemProvider;
150 		this.currentBaseContext = context;
151 
152 		this.setLayout(EclipseUiUtils.noSpaceGridLayout());
153 
154 		SashForm form = new SashForm(this, SWT.HORIZONTAL);
155 
156 		leftPannelCmp = new Composite(form, SWT.NO_FOCUS);
157 		// Bookmarks are still static
158 		populateBookmarks(leftPannelCmp);
159 
160 		Composite centerCmp = new Composite(form, SWT.BORDER | SWT.NO_FOCUS);
161 		createDisplay(centerCmp);
162 
163 		rightPannelCmp = new Composite(form, SWT.NO_FOCUS);
164 
165 		form.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
166 		form.setWeights(new int[] { 15, 40, 20 });
167 	}
168 
169 	void refresh() {
170 		modifyFilter(false);
171 		// also refresh bookmarks and groups
172 	}
173 
174 	private void createDisplay(final Composite parent) {
175 		parent.setLayout(EclipseUiUtils.noSpaceGridLayout());
176 
177 		// top filter
178 		filterCmp = new Composite(parent, SWT.NO_FOCUS);
179 		filterCmp.setLayoutData(EclipseUiUtils.fillWidth());
180 		addFilterPanel(filterCmp);
181 
182 		// Main display
183 		directoryDisplayViewer = new FsTableViewer(parent, SWT.MULTI);
184 		List<ColumnDefinition> colDefs = new ArrayList<>();
185 		colDefs.add(new ColumnDefinition(new FileIconNameLabelProvider(), "Name", 250));
186 		colDefs.add(new ColumnDefinition(new NioFileLabelProvider(FsUiConstants.PROPERTY_SIZE), "Size", 100));
187 		colDefs.add(new ColumnDefinition(new NioFileLabelProvider(FsUiConstants.PROPERTY_TYPE), "Type", 150));
188 		colDefs.add(new ColumnDefinition(new NioFileLabelProvider(FsUiConstants.PROPERTY_LAST_MODIFIED),
189 				"Last modified", 400));
190 		final Table table = directoryDisplayViewer.configureDefaultTable(colDefs);
191 		table.setLayoutData(EclipseUiUtils.fillAll());
192 
193 		// table.addKeyListener(new KeyListener() {
194 		// private static final long serialVersionUID = -8083424284436715709L;
195 		//
196 		// @Override
197 		// public void keyReleased(KeyEvent e) {
198 		// }
199 		//
200 		// @Override
201 		// public void keyPressed(KeyEvent e) {
202 		// if (log.isDebugEnabled())
203 		// log.debug("Key event received: " + e.keyCode);
204 		// IStructuredSelection selection = (IStructuredSelection)
205 		// directoryDisplayViewer.getSelection();
206 		// Path selected = null;
207 		// if (!selection.isEmpty())
208 		// selected = ((Path) selection.getFirstElement());
209 		// if (e.keyCode == SWT.CR) {
210 		// if (!Files.isDirectory(selected))
211 		// return;
212 		// if (selected != null) {
213 		// currDisplayedFolder = selected;
214 		// directoryDisplayViewer.setInput(currDisplayedFolder, "*");
215 		// }
216 		// } else if (e.keyCode == SWT.BS) {
217 		// currDisplayedFolder = currDisplayedFolder.getParent();
218 		// directoryDisplayViewer.setInput(currDisplayedFolder, "*");
219 		// directoryDisplayViewer.getTable().setFocus();
220 		// }
221 		// }
222 		// });
223 
224 		directoryDisplayViewer.addSelectionChangedListener(new ISelectionChangedListener() {
225 
226 			@Override
227 			public void selectionChanged(SelectionChangedEvent event) {
228 				IStructuredSelection selection = (IStructuredSelection) directoryDisplayViewer.getSelection();
229 				Path selected = null;
230 				if (selection.isEmpty())
231 					setSelected(null);
232 				else
233 					selected = ((Path) selection.getFirstElement());
234 				if (selected != null) {
235 					// TODO manage multiple selection
236 					setSelected(selected);
237 				}
238 			}
239 		});
240 
241 		directoryDisplayViewer.addDoubleClickListener(new IDoubleClickListener() {
242 			@Override
243 			public void doubleClick(DoubleClickEvent event) {
244 				IStructuredSelection selection = (IStructuredSelection) directoryDisplayViewer.getSelection();
245 				Path selected = null;
246 				if (!selection.isEmpty())
247 					selected = ((Path) selection.getFirstElement());
248 				if (selected != null) {
249 					if (!Files.isDirectory(selected))
250 						return;
251 					setInput(selected);
252 				}
253 			}
254 		});
255 
256 		// The context menu
257 		contextMenu = new FsContextMenu(this);
258 
259 		table.addMouseListener(new MouseAdapter() {
260 			private static final long serialVersionUID = 6737579410648595940L;
261 
262 			@Override
263 			public void mouseDown(MouseEvent e) {
264 				if (e.button == 3) {
265 					// contextMenu.setCurrFolderPath(currDisplayedFolder);
266 					contextMenu.show(table, new Point(e.x, e.y), currDisplayedFolder);
267 				}
268 			}
269 		});
270 	}
271 
272 	private void addPathElementBtn(Path path) {
273 		Button elemBtn = new Button(filterCmp, SWT.PUSH);
274 		String nameStr;
275 		if (path.toString().equals("/"))
276 			nameStr = "[jcr:root]";
277 		else
278 			nameStr = path.getFileName().toString();
279 		elemBtn.setText(nameStr + " >> ");
280 		CmsUiUtils.style(elemBtn, FsStyles.BREAD_CRUMB_BTN);
281 		elemBtn.addSelectionListener(new SelectionAdapter() {
282 			private static final long serialVersionUID = -4103695476023480651L;
283 
284 			@Override
285 			public void widgetSelected(SelectionEvent e) {
286 				setInput(path);
287 			}
288 		});
289 	}
290 
291 	public void setInput(Path path) {
292 		if (path.equals(currDisplayedFolder))
293 			return;
294 		currDisplayedFolder = path;
295 
296 		Path diff = initialPath.relativize(currDisplayedFolder);
297 
298 		for (Control child : filterCmp.getChildren())
299 			if (!child.equals(filterTxt))
300 				child.dispose();
301 
302 		addPathElementBtn(initialPath);
303 		Path currTarget = initialPath;
304 		if (!diff.toString().equals(""))
305 			for (Path pathElem : diff) {
306 				currTarget = currTarget.resolve(pathElem);
307 				addPathElementBtn(currTarget);
308 			}
309 
310 		filterTxt.setText("");
311 		filterTxt.moveBelow(null);
312 		setSelected(null);
313 		filterCmp.getParent().layout(true, true);
314 	}
315 
316 	private void setSelected(Path path) {
317 		currSelected = path;
318 		setOverviewInput(path);
319 	}
320 
321 	public Viewer getViewer() {
322 		return directoryDisplayViewer;
323 	}
324 
325 	private void populateBookmarks(Composite parent) {
326 		CmsUiUtils.clear(parent);
327 		parent.setLayout(new GridLayout());
328 		ISelectionChangedListener selList = new BookmarksSelChangeListener();
329 
330 		FsTableViewer homeViewer = new FsTableViewer(parent, SWT.SINGLE | SWT.NO_SCROLL);
331 		Table table = homeViewer.configureDefaultSingleColumnTable(bookmarkColWith);
332 		GridData gd = EclipseUiUtils.fillWidth();
333 		gd.horizontalIndent = 10;
334 		table.setLayoutData(gd);
335 		homeViewer.addSelectionChangedListener(selList);
336 		homeViewer.setPathsInput(getMyFilesPath());
337 
338 		appendTitle(parent, "Shared files");
339 		FsTableViewer groupsViewer = new FsTableViewer(parent, SWT.SINGLE | SWT.NO_SCROLL);
340 		table = groupsViewer.configureDefaultSingleColumnTable(bookmarkColWith);
341 		gd = EclipseUiUtils.fillWidth();
342 		gd.horizontalIndent = 10;
343 		table.setLayoutData(gd);
344 		groupsViewer.addSelectionChangedListener(selList);
345 		groupsViewer.setPathsInput(getMyGroupsFilesPath());
346 
347 		appendTitle(parent, "My bookmarks");
348 		FsTableViewer bookmarksViewer = new FsTableViewer(parent, SWT.SINGLE | SWT.NO_SCROLL);
349 		table = bookmarksViewer.configureDefaultSingleColumnTable(bookmarkColWith);
350 		gd = EclipseUiUtils.fillWidth();
351 		gd.horizontalIndent = 10;
352 		table.setLayoutData(gd);
353 		bookmarksViewer.addSelectionChangedListener(selList);
354 		bookmarksViewer.setPathsInput(getMyBookmarks());
355 	}
356 
357 	/**
358 	 * Recreates the content of the box that displays information about the
359 	 * current selected Path.
360 	 */
361 	private void setOverviewInput(Path path) {
362 		try {
363 			EclipseUiUtils.clear(rightPannelCmp);
364 			rightPannelCmp.setLayout(new GridLayout());
365 			if (path != null) {
366 				// if (isImg(context)) {
367 				// EditableImage image = new Img(parent, RIGHT, context,
368 				// imageWidth);
369 				// image.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER,
370 				// true, false,
371 				// 2, 1));
372 				// }
373 
374 				Label contextL = new Label(rightPannelCmp, SWT.NONE);
375 				contextL.setText(path.getFileName().toString());
376 				contextL.setFont(EclipseUiUtils.getBoldFont(rightPannelCmp));
377 				addProperty(rightPannelCmp, "Last modified", Files.getLastModifiedTime(path).toString());
378 				// addProperty(rightPannelCmp, "Owner",
379 				// Files.getOwner(path).getName());
380 				if (Files.isDirectory(path)) {
381 					addProperty(rightPannelCmp, "Type", "Folder");
382 				} else {
383 					String mimeType = Files.probeContentType(path);
384 					if (EclipseUiUtils.isEmpty(mimeType))
385 						mimeType = "<i>Unknown</i>";
386 					addProperty(rightPannelCmp, "Type", mimeType);
387 					addProperty(rightPannelCmp, "Size", FsUiUtils.humanReadableByteCount(Files.size(path), false));
388 				}
389 			}
390 			rightPannelCmp.layout(true, true);
391 		} catch (IOException e) {
392 			throw new CmsException("Cannot display details for " + path.toString(), e);
393 		}
394 	}
395 
396 	private void addFilterPanel(Composite parent) {
397 		RowLayout rl = new RowLayout(SWT.HORIZONTAL);
398 		rl.wrap = true;
399 		parent.setLayout(rl);
400 		// parent.setLayout(EclipseUiUtils.noSpaceGridLayout(new GridLayout(2,
401 		// false)));
402 
403 		filterTxt = new Text(parent, SWT.SEARCH | SWT.ICON_CANCEL);
404 		filterTxt.setMessage("Search current folder");
405 		filterTxt.setLayoutData(new RowData(250, SWT.DEFAULT));
406 		filterTxt.addModifyListener(new ModifyListener() {
407 			private static final long serialVersionUID = 1L;
408 
409 			public void modifyText(ModifyEvent event) {
410 				modifyFilter(false);
411 			}
412 		});
413 		filterTxt.addKeyListener(new KeyListener() {
414 			private static final long serialVersionUID = 2533535233583035527L;
415 
416 			@Override
417 			public void keyReleased(KeyEvent e) {
418 			}
419 
420 			@Override
421 			public void keyPressed(KeyEvent e) {
422 				// boolean shiftPressed = (e.stateMask & SWT.SHIFT) != 0;
423 				// // boolean altPressed = (e.stateMask & SWT.ALT) != 0;
424 				// FilterEntitiesVirtualTable currTable = null;
425 				// if (currEdited != null) {
426 				// FilterEntitiesVirtualTable table =
427 				// browserCols.get(currEdited);
428 				// if (table != null && !table.isDisposed())
429 				// currTable = table;
430 				// }
431 				//
432 				// if (e.keyCode == SWT.ARROW_DOWN)
433 				// currTable.setFocus();
434 				// else if (e.keyCode == SWT.BS) {
435 				// if (filterTxt.getText().equals("")
436 				// && !(currEdited.getNameCount() == 1 ||
437 				// currEdited.equals(initialPath))) {
438 				// Path oldEdited = currEdited;
439 				// Path parentPath = currEdited.getParent();
440 				// setEdited(parentPath);
441 				// if (browserCols.containsKey(parentPath))
442 				// browserCols.get(parentPath).setSelected(oldEdited);
443 				// filterTxt.setFocus();
444 				// e.doit = false;
445 				// }
446 				// } else if (e.keyCode == SWT.TAB && !shiftPressed) {
447 				// Path uniqueChild = getOnlyChild(currEdited,
448 				// filterTxt.getText());
449 				// if (uniqueChild != null) {
450 				// // Highlight the unique chosen child
451 				// currTable.setSelected(uniqueChild);
452 				// setEdited(uniqueChild);
453 				// }
454 				// filterTxt.setFocus();
455 				// e.doit = false;
456 				// }
457 			}
458 		});
459 	}
460 
461 	private Path getOnlyChild(Path parent, String filter) {
462 		try (DirectoryStream<Path> stream = Files.newDirectoryStream(currDisplayedFolder, filter + "*")) {
463 			Path uniqueChild = null;
464 			boolean moreThanOne = false;
465 			loop: for (Path entry : stream) {
466 				if (uniqueChild == null) {
467 					uniqueChild = entry;
468 				} else {
469 					moreThanOne = true;
470 					break loop;
471 				}
472 			}
473 			if (!moreThanOne)
474 				return uniqueChild;
475 			return null;
476 		} catch (IOException ioe) {
477 			throw new CmsException(
478 					"Unable to determine unique child existence and get it under " + parent + " with filter " + filter,
479 					ioe);
480 		}
481 	}
482 
483 	private void modifyFilter(boolean fromOutside) {
484 		if (!fromOutside)
485 			if (currDisplayedFolder != null) {
486 				String filter = filterTxt.getText() + "*";
487 				directoryDisplayViewer.setInput(currDisplayedFolder, filter);
488 			}
489 	}
490 
491 	private class BookmarksSelChangeListener implements ISelectionChangedListener {
492 
493 		@Override
494 		public void selectionChanged(SelectionChangedEvent event) {
495 			IStructuredSelection selection = (IStructuredSelection) event.getSelection();
496 			if (selection.isEmpty())
497 				return;
498 			else {
499 				Path newSelected = (Path) selection.getFirstElement();
500 				if (newSelected.equals(currDisplayedFolder) && newSelected.equals(initialPath))
501 					return;
502 				initialPath = newSelected;
503 				setInput(newSelected);
504 			}
505 		}
506 	}
507 
508 	// Simplify UI implementation
509 	private void addProperty(Composite parent, String propName, String value) {
510 		Label contextL = new Label(parent, SWT.NONE);
511 		contextL.setText(propName + ": " + value);
512 	}
513 
514 	private Label appendTitle(Composite parent, String value) {
515 		Label titleLbl = new Label(parent, SWT.NONE);
516 		titleLbl.setText(value);
517 		titleLbl.setFont(EclipseUiUtils.getBoldFont(parent));
518 		GridData gd = EclipseUiUtils.fillWidth();
519 		gd.horizontalIndent = 5;
520 		gd.verticalIndent = 5;
521 		titleLbl.setLayoutData(gd);
522 		return titleLbl;
523 	}
524 }