/*==========================================================================*\ | $Id: FileBrowserRow.java,v 1.3 2010/11/01 17:04:05 aallowat Exp $ |*-------------------------------------------------------------------------*| | Copyright (C) 2006-2008 Virginia Tech | | This file is part of Web-CAT. | | Web-CAT is free software; you can redistribute it and/or modify | it under the terms of the GNU Affero General Public License as published | by the Free Software Foundation; either version 3 of the License, or | (at your option) any later version. | | Web-CAT is distributed in the hope that it will be useful, | but WITHOUT ANY WARRANTY; without even the implied warranty of | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | GNU General Public License for more details. | | You should have received a copy of the GNU Affero General Public License | along with Web-CAT; if not, see <http://www.gnu.org/licenses/>. \*==========================================================================*/ package org.webcat.core; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FilenameFilter; import java.util.zip.ZipOutputStream; import org.apache.log4j.Logger; import org.webcat.ui.generators.JavascriptGenerator; import com.webobjects.appserver.WOActionResults; import com.webobjects.appserver.WOComponent; import com.webobjects.appserver.WOContext; import com.webobjects.appserver.WOResponse; import com.webobjects.foundation.NSArray; import com.webobjects.foundation.NSData; import com.webobjects.foundation.NSMutableArray; import com.webobjects.foundation.NSTimestamp; import er.extensions.foundation.ERXFileUtilities; //------------------------------------------------------------------------- /** * One row in a directory contents table. * * @author Stephen Edwards * @author Last changed by $Author: aallowat $ * @version $Revision: 1.3 $, $Date: 2010/11/01 17:04:05 $ */ public class FileBrowserRow extends WOComponent { //~ Constructors .......................................................... // ---------------------------------------------------------- /** * Creates a new FileBrowserRow object. * * @param context the context for this component instance */ public FileBrowserRow( WOContext context ) { super( context ); } //~ KVC Attributes (must be public) ....................................... public Boolean isExpanded = null; public boolean isEditable = false; public boolean allowSelection = false; public boolean isLast = true; public File file; public File baseFile; public int depth = 0; public int myRowNumber = -1; public int index; public Integer initialExpansionDepth; public boolean allowSelectDir = false; public NSArray<String> allowSelectExtensions = null; public boolean applyChangesOnMod = false; public String currentSelection; public String paneId; public String formId; public String buttonGroup; public String alsoRefresh; public NSArray<String> focusedFiles; // For the spacer repetition public int spacerIndex; public NSMutableArray<IsLastEntryAtThisLevel> isLastEntry; public IsLastEntryAtThisLevel spacerWalker; // For the sub-file repetition public int subFileIndex; public NSArray<File> contents = emptyContents; public File subFileWalker; public FileBrowser.FileSelectionListener fileSelectionListener = null; public EditFilePage.FileEditListener fileEditListener = null; //~ Methods ............................................................... private static class NotDotOrDotDot implements FilenameFilter { public boolean accept(File file, String name) { return !name.equals(".") && !name.equals(".."); } } private static final FilenameFilter notDotOrDotDot = new NotDotOrDotDot(); private static final NSArray<File> emptyContents = new NSArray<File>(); // ---------------------------------------------------------- /** * Adds to the response of the page * * @param response The response being built * @param context The context of the request */ public void appendToResponse(WOResponse response, WOContext context) { contents = emptyContents; myRowNumber = index; // Check to make sure we expand if we contain the current selection if (isExpanded == null && currentSelection != null) { String prunedPath = pruneBaseFrom(file, true); if (log.isDebugEnabled()) { log.debug("checking row: " + prunedPath + " against current selection: " + currentSelection); } if (currentSelection.startsWith(prunedPath)) { isExpanded = true; } } // Otherwise, expand if we contain any of the other focused files if ((isExpanded == null || !isExpanded) && focusedFiles != null && focusedFiles.size() > 0) { String prunedPath = pruneBaseFrom(file, false); for (String oneFocusedFile : focusedFiles) { if (log.isDebugEnabled()) { log.debug("checking row: " + prunedPath + " against focused file: " + oneFocusedFile); } if (oneFocusedFile.startsWith(prunedPath)) { isExpanded = true; if (oneFocusedFile.equals(prunedPath)) { focusedFiles.remove(oneFocusedFile); } break; } } } // Otherwise, check the expansion depth setting if (isExpanded == null) { if (initialExpansionDepth != null) { log.debug("initialExpansionDepth = " + initialExpansionDepth); log.debug("my depth = " + depth ); isExpanded = (initialExpansionDepth.intValue() >= depth); log.debug("isExpanded = " + isExpanded); } else { // isExpanded = !isArchive(); isExpanded = false; } } if (isLastEntry == null) { isLastEntry = new NSMutableArray<IsLastEntryAtThisLevel>(); } while (isLastEntry.count() <= depth) { isLastEntry.addObject(new IsLastEntryAtThisLevel()); } isLastEntry.get(depth).last = isLast; if (isExpanded) { if (file.isDirectory()) { contents = new NSArray<File>(file.listFiles(notDotOrDotDot)); } else { // Don't handle zip/jar archives yet! } } else { contents = emptyContents; } super.appendToResponse(response, context); while (isLastEntry.count() > depth) { isLastEntry.removeLastObject(); } } // ---------------------------------------------------------- public int innerDepth() { return depth + 1; } // ---------------------------------------------------------- public boolean currentEntryIsLastAtItsLevel() { return (subFileIndex == contents.count() - 1); } // ---------------------------------------------------------- /** * A mutable boolean value holder for storing in the * isLastEntry array. */ public class IsLastEntryAtThisLevel { public boolean last = false; } // ---------------------------------------------------------- /** * Return this row's number. * @return this row's number */ public int rowNumber() { return myRowNumber; } // ---------------------------------------------------------- /** * Return true on the final spacer rep. * @return true for the last spacer in a sequence */ public boolean isFinalSpacerRep() { return spacerIndex == depth; } // ---------------------------------------------------------- /** * Return true if this file represents an archive file. * @return true if this file is a zip or jar archive */ public boolean isArchive() { String name = file.getName().toLowerCase(); return file.isFile() && (name.endsWith(".zip") || name.endsWith(".jar")); } // ---------------------------------------------------------- /** * Return true if this file represents an archive or a directory. * @return true if this file is a directory or archive file */ public boolean isDirectory() { return file.isDirectory(); // || isArchive(); } // ---------------------------------------------------------- /** * Determine if a file can be viewed inline. * @return true if the file can be viewed in the browser */ public boolean isViewable() { return FileUtilities.showInline(file); } // ---------------------------------------------------------- /** * Determine if a file can be edited in a browser window. * @return true if this file can be edited as a text file */ public boolean canEdit() { return isEditable && FileUtilities.isEditable(file); } // ---------------------------------------------------------- /** * Determine if a file can be deleted. * @return true if this file can be deleted */ public boolean canDelete() { return isEditable; } // ---------------------------------------------------------- /** * View the selected file. * @return a view page for the selected file */ public WOComponent viewFile() { DeliverFile nextPage = (DeliverFile)pageWithName( DeliverFile.class.getName()); nextPage.setFileName(file); nextPage.setContentType(FileUtilities.mimeType(file)); nextPage.setStartDownload(!FileUtilities.showInline(file)); return nextPage; } // ---------------------------------------------------------- /** * Edit the selected file. * @return an edit page for the selected file */ public WOComponent editFile() { if (applyChangesOnMod) { // TODO: how should the call to commitLocalChanges() be fixed? ((Session)session()).commitSessionChanges(); } EditFilePage nextPage = (EditFilePage)pageWithName( EditFilePage.class.getName()); nextPage.file = file; nextPage.baseFile = baseFile; nextPage.nextPage = (WCComponent)context().page(); nextPage.fileEditListener = fileEditListener; return nextPage; } // ---------------------------------------------------------- /** * Delete the selected file. * @return the current page, which will be reloaded */ public WOActionResults deleteFile() { return new ConfirmingAction(this, true) { @Override protected String confirmationTitle() { return "Delete " + file.getName() + "?"; } @Override protected String confirmationMessage() { String message = "Are you sure you want to delete "; if (isArchive()) { message += "the archive file " + file.getName() + " and all its contents?"; } else if (file.isDirectory()) { message += "the folder " + file.getName() + " and all its contents?"; } else { message += "the file " + file.getName() + "?"; } message += " This action cannot be undone."; return message; } @Override protected WOActionResults actionWasConfirmed() { if (applyChangesOnMod) { // TODO: how should commitLocalChanges() call be fixed? ((Session)session()).commitSessionChanges(); } log.debug("delete: " + file.getPath()); if (file.isDirectory()) { ERXFileUtilities.deleteDirectory(file); } else { file.delete(); } return new JavascriptGenerator().refresh(paneId); } // Can't get ajax-based action to work! // @Override // protected void actionWasConfirmed(JavascriptGenerator page) // { // if (applyChangesOnMod) // { // // TODO: how should commitLocalChanges() call be fixed? // ((Session)session()).commitSessionChanges(); // } // log.debug("delete: " + file.getPath()); // if (file.isDirectory()) // { // ERXFileUtilities.deleteDirectory(file); // } // else // { // file.delete(); // } // page.refresh(paneId); // } }; } // ---------------------------------------------------------- public String deleteFileTitle() { if (isArchive()) { return "Delete this archive and its contents"; } else if (file.isDirectory()) { return "Delete this directory and its contents"; } else { return "Delete this file"; } } // ---------------------------------------------------------- public NSTimestamp lastModified() { if (lastModified == null) { lastModified = new NSTimestamp(file.lastModified()); } return lastModified; } // ---------------------------------------------------------- /** * View or download the selected file. * @return a download page for the selected file * @throws java.io.IOException if an error occurs reading the file */ public WOComponent downloadFile() throws java.io.IOException { DeliverFile nextPage = (DeliverFile)pageWithName( DeliverFile.class.getName()); if (file.isDirectory()) { File zipFile = new File(file.getName() + ".zip"); nextPage.setFileName(zipFile); nextPage.setContentType(FileUtilities.mimeType(zipFile)); ByteArrayOutputStream boas = new ByteArrayOutputStream(); ZipOutputStream zos = new ZipOutputStream(boas); FileUtilities.appendToZip( file, zos, file.getCanonicalPath().length()); zos.close(); nextPage.setFileData(new NSData(boas.toByteArray())); boas.close(); } else { nextPage.setFileName(file); nextPage.setContentType(FileUtilities.mimeType(file)); } nextPage.setStartDownload(true); return nextPage; } // ---------------------------------------------------------- /** * Return the URL for the download icon to use for this file. * @return the Core framework file name for the desired download * icon, based on whether or not this file is a directory */ public String downloadIcon() { if (file.isDirectory()) { return "icons/archive.gif"; } else { return "icons/download.gif"; } } // ---------------------------------------------------------- /** * Toggle this entry between open and closed, if it is a directory. * @return this page, so that it reloads */ public WOActionResults toggleExpansion() { isExpanded = (!isExpanded.booleanValue() && !isArchive()); log.debug("toggleExpansion(): now isExpanded = " + isExpanded); JavascriptGenerator page = new JavascriptGenerator(); page.refresh(paneId); if (alsoRefresh != null) { page.refresh(alsoRefresh); } return page; } // ---------------------------------------------------------- /** * Return the URL for the icon representing this file's type. * @return the icon file URL */ public String iconURL() { String result = FileUtilities.iconURL(file); log.debug("iconURL(" + file + ") = " + result); return result; } // ---------------------------------------------------------- public WOComponent select() { WOComponent result = null; log.debug("select = " + file.getPath()); if (fileSelectionListener != null) { log.debug("notifying fileSelectionListener"); result = fileSelectionListener.selectFile( pruneBaseFrom(file, false)); } else { log.debug("no fileSelectionListener registered"); } return result; } // ---------------------------------------------------------- public boolean isSelected() { boolean result = false; if (currentSelection != null) { String myPath = file.getPath().replaceAll("\\\\", "/"); result = myPath.endsWith(currentSelection); log.debug("comparing " + myPath + " with " + currentSelection + " = " + result); } else { log.debug("isSelected(): current selection is null"); } return result; } // ---------------------------------------------------------- public boolean canSelectThis() { boolean result = false; if (allowSelection) // && !isSelected()) { if (file.isDirectory()) { result = allowSelectDir; } else if (allowSelectExtensions == null) { result = true; } else { result = allowSelectExtensions.contains( FileUtilities.extensionOf(file).toLowerCase()); } } return result; } // ---------------------------------------------------------- public String pruneBaseFrom(File aFile, boolean trimParent) { String path = aFile.getPath(); if (baseFile != null) { String parent = trimParent ? baseFile.getParentFile().getParent() : baseFile.getParent(); if (parent != null && parent.length() > 0) { path = path.substring(parent.length() + 1); } } path = path.replaceAll("\\\\", "/"); return path; } //~ Instance/static variables ............................................. private NSTimestamp lastModified; static Logger log = Logger.getLogger(FileBrowserRow.class); }