/** * OLAT - Online Learning and Training<br> * http://www.olat.org * <p> * Licensed under the Apache License, Version 2.0 (the "License"); <br> * you may not use this file except in compliance with the License.<br> * You may obtain a copy of the License at * <p> * http://www.apache.org/licenses/LICENSE-2.0 * <p> * Unless required by applicable law or agreed to in writing,<br> * software distributed under the License is distributed on an "AS IS" BASIS, <br> * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> * See the License for the specific language governing permissions and <br> * limitations under the License. * <p> * Copyright (c) since 2004 at Multimedia- & E-Learning Services (MELS),<br> * University of Zurich, Switzerland. * <hr> * <a href="http://www.openolat.org"> * OpenOLAT - Online Learning and Training</a><br> * This file has been modified by the OpenOLAT community. Changes are licensed * under the Apache 2.0 license as the original file. * <p> */ package org.olat.core.commons.modules.bc.components; import java.text.Collator; import java.text.DateFormat; import java.util.Collections; import java.util.Comparator; import java.util.Iterator; import java.util.List; import java.util.Locale; import org.olat.core.commons.controllers.linkchooser.CustomLinkTreeModel; import org.olat.core.commons.modules.bc.FolderLoggingAction; import org.olat.core.commons.modules.bc.FolderRunController; import org.olat.core.commons.modules.bc.commands.FolderCommandFactory; import org.olat.core.commons.modules.bc.meta.tagged.LockComparator; import org.olat.core.gui.UserRequest; import org.olat.core.gui.components.AbstractComponent; import org.olat.core.gui.components.ComponentRenderer; import org.olat.core.gui.control.Event; import org.olat.core.gui.translator.Translator; import org.olat.core.id.IdentityEnvironment; import org.olat.core.logging.OLog; import org.olat.core.logging.Tracing; import org.olat.core.logging.activity.CoreLoggingResourceable; import org.olat.core.logging.activity.ThreadLocalUserActivityLogger; import org.olat.core.util.Util; import org.olat.core.util.vfs.VFSContainer; import org.olat.core.util.vfs.VFSItem; import org.olat.core.util.vfs.VFSLeaf; import org.olat.core.util.vfs.filters.VFSItemExcludePrefixFilter; import org.olat.core.util.vfs.filters.VFSItemFilter; import org.olat.core.util.vfs.version.Versionable; /** * Initial Date: Feb 11, 2004 * @author Mike Stock */ public class FolderComponent extends AbstractComponent { private static final OLog log = Tracing.createLoggerFor(FolderComponent.class); private static final ComponentRenderer RENDERER = new FolderComponentRenderer(); public static final String SORT_NAME = "name"; public static final String SORT_SIZE = "size"; public static final String SORT_DATE = "date"; public static final String SORT_REV = "revision"; public static final String SORT_LOCK = "lock"; // see MessagesEditController // see OLAT-4182/OLAT-4219 and OLAT-4259 // the filtering of .nfs is sort of temporary until we make sure that we no longer reference // attached files anywhere at the time of deleting it // likely to be resolved after user logs out, caches get cleared - and if not the server // restart overnight definitely removes those .nfs files. // fxdiff: FXOLAT-333 hide all shadow-files per default public static final String[] ATTACHMENT_EXCLUDE_PREFIXES = new String[]{"."}; protected boolean sortAsc = true; // asc or desc? protected String sortCol = ""; // column to sort protected boolean canMail = false; private IdentityEnvironment identityEnv; private VFSContainer rootContainer; private VFSContainer currentContainer; private String currentContainerPath; private String currentSortOrder; // need to know our children in advance in order to be able to identify them later... private List<VFSItem> currentContainerChildren; private final Collator collator; private Comparator<VFSItem> comparator; protected Translator translator; private VFSItemFilter filter; private final DateFormat dateTimeFormat; private VFSItemExcludePrefixFilter exclFilter; private CustomLinkTreeModel customLinkTreeModel; private final VFSContainer externContainerForCopy; /** * Wraps the folder module as a component. * * @param ureq * The user request * @param name * The component name * @param rootContainer * The base container of this component * @param filter * A file filter or NULL to not use a filter * @param customLinkTreeModel * A custom link tree model used in the HTML editor or NULL to * not use this feature. */ public FolderComponent(UserRequest ureq, String name, VFSContainer rootContainer, VFSItemFilter filter, CustomLinkTreeModel customLinkTreeModel) { this(ureq, name, rootContainer, filter, customLinkTreeModel, null); } public FolderComponent(UserRequest ureq, String name, VFSContainer rootContainer, VFSItemFilter filter, CustomLinkTreeModel customLinkTreeModel, VFSContainer externContainerForCopy) { super(name); this.identityEnv = ureq.getUserSession().getIdentityEnvironment(); this.filter = filter; this.customLinkTreeModel = customLinkTreeModel; this.externContainerForCopy = externContainerForCopy; exclFilter = new VFSItemExcludePrefixFilter(ATTACHMENT_EXCLUDE_PREFIXES); Locale locale = ureq.getLocale(); collator = Collator.getInstance(locale); translator = Util.createPackageTranslator(FolderRunController.class, locale); sort(SORT_NAME); this.rootContainer = rootContainer; setCurrentContainerPath("/"); dateTimeFormat = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, locale); } /** * @see org.olat.core.gui.components.Component#dispatchRequest(org.olat.core.gui.UserRequest) */ protected void doDispatchRequest(UserRequest ureq) { if (ureq.getParameter(ListRenderer.PARAM_EDTID) != null) { fireEvent(ureq, new Event(FolderCommandFactory.COMMAND_EDIT)); return; } else if (ureq.getParameter(ListRenderer.PARAM_CONTENTEDITID) != null) { fireEvent(ureq, new Event(FolderCommandFactory.COMMAND_EDIT_CONTENT)); return; } else if (ureq.getParameter(ListRenderer.PARAM_SERV) != null) { // this is a link on a file... deliver it fireEvent(ureq, new Event(FolderCommandFactory.COMMAND_SERV)); // don't redraw the file listing when serving a resource -> timestamp not consumed setDirty(false); return; } else if (ureq.getParameter(ListRenderer.PARAM_SORTID) != null) { // user clicked on table header for sorting column setSortAsc(ureq.getParameter(ListRenderer.PARAM_SORTID)); sort(ureq.getParameter(ListRenderer.PARAM_SORTID)); // just pass selected column return; } else if (ureq.getParameter("cid") != null) { // user clicked add layer... fireEvent(ureq, new Event(ureq.getParameter("cid"))); return; } else if (ureq.getParameter(ListRenderer.PARAM_VERID) != null) { fireEvent(ureq, new Event(FolderCommandFactory.COMMAND_VIEW_VERSION)); return; } else if (ureq.getParameter(ListRenderer.PARAM_EPORT) != null) { fireEvent(ureq, new Event(FolderCommandFactory.COMMAND_ADD_EPORTFOLIO)); return; } else if (ureq.getParameter(ListRenderer.PARAM_SERV_THUMBNAIL) != null) { // this is a link on a file... deliver it fireEvent(ureq, new Event(FolderCommandFactory.COMMAND_SERV_THUMBNAIL)); // don't redraw the file listing when serving a resource -> timestamp not consumed setDirty(false); return; } // regular browsing, set current container setCurrentContainerPath(ureq.getModuleURI()); // do logging ThreadLocalUserActivityLogger.log(FolderLoggingAction.BC_FOLDER_READ, getClass(), CoreLoggingResourceable.wrapBCFile(getCurrentContainerPath())); fireEvent(ureq, new Event(FolderCommandFactory.COMMAND_BROWSE)); } private void setSortAsc(String col) { if (col == null) col = SORT_NAME; // "clicked" column not existent if (!sortCol.equals(col)) { // if not same col as before, change sort col and sort asc sortCol = col; sortAsc = true; } else { // if same col as before, just change sorting to desc sortAsc = !sortAsc; } } public boolean isCanMail() { return canMail; } public void setCanMail(boolean canMail) { this.canMail = canMail; } /** * Sorts the bc folder components table * * @param col The column to sort */ private void sort(String col) { currentSortOrder = col; if (col.equals(SORT_NAME)) { // sort after file name? comparator = new Comparator<VFSItem>() { public int compare(VFSItem o1, VFSItem o2) { if (sortAsc) { if ((o1 instanceof VFSLeaf && o2 instanceof VFSLeaf) || (!(o1 instanceof VFSLeaf) && !(o2 instanceof VFSLeaf))) { return collator.compare(o1.getName(), o2.getName()); } else { if (!(o1 instanceof VFSLeaf)) { return -1; } else { return 1; } } } else { if ((o1 instanceof VFSLeaf && o2 instanceof VFSLeaf) || (!(o1 instanceof VFSLeaf) && !(o2 instanceof VFSLeaf))) { return collator.compare(o2.getName(), o1.getName()); } else { if (!(o1 instanceof VFSLeaf)) { return -1; } else { return 1; } } } } }; } else if (col.equals(SORT_DATE)) { // sort after modification date (if same, then name) comparator = new Comparator<VFSItem>() { public int compare(VFSItem o1, VFSItem o2) { if (o1.getLastModified() < o2.getLastModified()) return ((sortAsc) ? -1 : 1); else if (o1.getLastModified() > o2.getLastModified()) return ((sortAsc) ? 1 : -1); else { if (sortAsc) return collator.compare(o1.getName(), o2.getName()); else return collator.compare(o2.getName(), o1.getName()); } } }; } else if (col.equals(SORT_SIZE)) { // sort after file size, folders always on top comparator = new Comparator<VFSItem>() { public int compare(VFSItem o1, VFSItem o2) { VFSLeaf leaf1 = null; if (o1 instanceof VFSLeaf) { leaf1 = (VFSLeaf)o1; } VFSLeaf leaf2 = null; if (o2 instanceof VFSLeaf) { leaf2 = (VFSLeaf)o2; } if (leaf1 == null && leaf2 != null) return -1; // folders are always smaller else if (leaf1 != null && leaf2 == null) return 1; // folders are always smaller else if (leaf1 == null && leaf2 == null) // if two folders, sort after name if (sortAsc) return collator.compare(o1.getName(), o2.getName()); else return collator.compare(o2.getName(), o1.getName()); else // if two leafes, sort after size if (sortAsc) return ((leaf1.getSize() < leaf2.getSize()) ? -1 : 1); else return ((leaf1.getSize() < leaf2.getSize()) ? 1 : -1); } }; } else if (col.equals(SORT_REV)) { // sort after revision number, folders always on top comparator = new Comparator<VFSItem>() { public int compare(VFSItem o1, VFSItem o2) { Versionable v1 = null; Versionable v2 = null; if (o1 instanceof Versionable) { v1 = (Versionable)o1; } if (o2 instanceof Versionable) { v2 = (Versionable)o2; } if(v1 == null) { return -1; } else if (v2 == null) { return 1; } String r1 = v1.getVersions().getRevisionNr(); String r2 = v2.getVersions().getRevisionNr(); if(r1 == null) { return -1; } else if (r2 == null) { return 1; } return (sortAsc) ? collator.compare(r1, r2) : collator.compare(r2, r1); } }; } else if (col.equals(SORT_LOCK)) { // sort after modification date (if same, then name) comparator = new LockComparator(sortAsc, collator); } if (currentContainerChildren != null) updateChildren(); // if not empty the update list } /** * @return VFSContainer */ public VFSContainer getRootContainer() { return rootContainer; } /** * @return VFSContainer */ public VFSContainer getCurrentContainer() { return currentContainer; } public String getCurrentContainerPath() { return currentContainerPath; } /** * Return the children of the folder of this FolderComponent. * The children are already alphabetically sorted. * @return */ public List<VFSItem> getCurrentContainerChildren() { return currentContainerChildren; } /** * @return the sort order, one of the SORT_* static variables */ public String getCurrentSortOrder() { return currentSortOrder; } /** * @return true: sorted ascending; false: sorted descending */ public boolean isCurrentSortAsc() { return sortAsc; } public void updateChildren() { setDirty(true); //check if the container is still up-to-date, if not -> return to root if(!currentContainer.exists()) { currentContainer = rootContainer; currentContainerPath = "/"; } // get the children and sort them alphabetically List<VFSItem> children; if (filter != null) { children = currentContainer.getItems(filter); } else { children = currentContainer.getItems(); } // OLAT-5256: filter .nfs files for(Iterator<VFSItem> it = children.iterator(); it.hasNext(); ) { if (!exclFilter.accept(it.next())) { it.remove(); } } try { Collections.sort(children, comparator); } catch (Exception e) { log.error("", e); } currentContainerChildren = children; } /** * @param relPath */ public boolean setCurrentContainerPath(String relPath) { // get the container setDirty(true); if (relPath == null) relPath = "/"; if (!(relPath.charAt(0) == '/')) relPath = "/" + relPath; VFSItem vfsItem = rootContainer.resolve(relPath); if (vfsItem == null || !(vfsItem instanceof VFSContainer)) { // unknown path, reset to root contaner... currentContainer = rootContainer; relPath = ""; return false; } this.currentContainer = (VFSContainer)vfsItem; this.currentContainerPath = relPath; updateChildren(); return true; } /** * Set a custom link tree model that is used in the HTML editor to create * links * * @param customLinkTreeModel * The link tree model or NULL to not use this feature in the * editor */ public void setCustomLinkTreeModel(CustomLinkTreeModel customLinkTreeModel) { this.customLinkTreeModel = customLinkTreeModel; } /** * Get the custom link tree model to build links in the editor * * @return The custom link tree model or NULL if no such model is used. */ public CustomLinkTreeModel getCustomLinkTreeModel() { return this.customLinkTreeModel; } public VFSContainer getExternContainerForCopy() { return externContainerForCopy; } public ComponentRenderer getHTMLRendererSingleton() { return RENDERER; } public IdentityEnvironment getIdentityEnvironnement() { return identityEnv; } public DateFormat getDateTimeFormat() { return dateTimeFormat; } }